# -*- coding: utf-8 -*-

from __future__ import unicode_literals

from .Consts import *

PIECE_SYMBOLS = ['',   'p',  'l',  'n',  's', 'g',  'b',  'r', 'k',
                      '+p', '+l', '+n', '+s',      '+b', '+r']
PIECE_JAPANESE_SYMBOLS = [
    '',
    '\u6b69', '\u9999', '\u6842', '\u9280', '\u91d1', '\u89d2', '\u98db',
    '\u7389', '\u3068', '\u674f', '\u572d', '\u5168', '\u99ac', '\u9f8d'
]

class Piece(object):
    def __init__(self, piece_type, color):
        if piece_type is None:
            raise ValueError('Piece type must be set')
        if color is None:
            raise ValueError('Color must be set')
        self.piece_type = piece_type
        self.color = color

    def symbol(self):
        '''
        Gets the symbol `p`, `l`, `n`, etc.
        '''
        if self.color == BLACK:
            return PIECE_SYMBOLS[self.piece_type].upper()
        else:
            return PIECE_SYMBOLS[self.piece_type]

    def japanese_symbol(self):
        # no direction
        return PIECE_JAPANESE_SYMBOLS[self.piece_type]

    def japanese_symbol_with_direction(self):
        if self.color == BLACK:
            prefix = ' '
        else:
            prefix = 'v'
        return prefix + PIECE_JAPANESE_SYMBOLS[self.piece_type]

    def is_promoted(self):
        return self.piece_type >= PROM_PAWN

    def __hash__(self):
        return self.piece_type * (self.color + 1)

    def __repr__(self):
        return "Piece.from_symbol('{0}')".format(self.symbol())

    def __str__(self):
        return self.symbol()

    def __eq__(self, other):
        try:
            return self.piece_type == other.piece_type and self.color == other.color
        except AttributeError:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)

    @classmethod
    def from_symbol(cls, symbol):
        '''
        Creates a piece instance from a piece symbol.
        Raises `ValueError` if the symbol is invalid.
        '''
        if symbol.lower() == symbol:
            return cls(PIECE_SYMBOLS.index(symbol), WHITE)
        else:
            return cls(PIECE_SYMBOLS.index(symbol.lower()), BLACK)


# -*- coding: utf-8 -*-

from __future__ import unicode_literals

# NOTE: In chess we use "file - rank" notation like 'a1',
#       in shogi we use a number for file, an alphabet for rank
#       and opposite direction of files and ranks like '9i'.
#       We use chess style notation internally, but exports it with this table.

from .Piece import *

SQUARE_NAMES = [
    '9a', '8a', '7a', '6a', '5a', '4a', '3a', '2a', '1a',
    '9b', '8b', '7b', '6b', '5b', '4b', '3b', '2b', '1b',
    '9c', '8c', '7c', '6c', '5c', '4c', '3c', '2c', '1c',
    '9d', '8d', '7d', '6d', '5d', '4d', '3d', '2d', '1d',
    '9e', '8e', '7e', '6e', '5e', '4e', '3e', '2e', '1e',
    '9f', '8f', '7f', '6f', '5f', '4f', '3f', '2f', '1f',
    '9g', '8g', '7g', '6g', '5g', '4g', '3g', '2g', '1g',
    '9h', '8h', '7h', '6h', '5h', '4h', '3h', '2h', '1h',
    '9i', '8i', '7i', '6i', '5i', '4i', '3i', '2i', '1i',
]

class Move(object):
    '''
    Represents a move from a square to a square and possibly the promotion piece
    type.
    Null moves are supported.
    '''

    def __init__(self, from_square, to_square, promotion=False, drop_piece_type=None):
        # if from_square is None, it's a drop and
        self.from_square = from_square
        self.to_square = to_square
        if from_square is None and to_square is not None:
            if drop_piece_type is None:
                raise ValueError('Drop piece type must be set.')
            if promotion:
                raise ValueError('Cannot set promoted piece.')
            self.promotion = False
            self.drop_piece_type = drop_piece_type
        else:
            self.promotion = promotion
            if drop_piece_type:
                raise ValueError('Drop piece type must not be set.')
            self.drop_piece_type = None

    def usi(self):
        '''
        Gets an USI string for the move.
        For example a move from 7A to 8A would be `7a8a` or `7a8a+` if it is
        a promotion.
        '''
        if self:
            if self.drop_piece_type:
                return '{0}*{1}'.format(PIECE_SYMBOLS[self.drop_piece_type].upper(), SQUARE_NAMES[self.to_square])
            else:
                return SQUARE_NAMES[self.from_square] + SQUARE_NAMES[self.to_square] + \
                       ('+' if self.promotion else '')
        else:
            return '0000'

    def __bool__(self):
        return not bool(self.from_square is None and self.to_square is None)

    def __nonzero__(self):
        return (self.from_square or self.to_square) is not None

    def __eq__(self, other):
        try:
            return self.from_square == other.from_square and self.to_square == other.to_square and \
                   self.promotion == other.promotion and self.drop_piece_type == other.drop_piece_type
        except AttributeError:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)

    def __repr__(self):
        return "Move.from_usi('{0}')".format(self.usi())

    def __str__(self):
        return self.usi()

    def __hash__(self):
        # 7 bit is enough to represent 81 patterns
        if self.drop_piece_type is None:
            return self.to_square | self.from_square << 7 | self.promotion << 14
        else:
            # drop piece
            return self.to_square | (81 + self.drop_piece_type) << 7

    @classmethod
    def from_usi(cls, usi):
        '''
        Parses an USI string.
        Raises `ValueError` if the USI string is invalid.
        '''
        if usi == '0000':
            return cls.null()
        elif len(usi) == 4:
            if usi[1] == '*':
                piece = Piece.from_symbol(usi[0])
                return cls(None, SQUARE_NAMES.index(usi[2:4]), False, piece.piece_type)
            else:
                return cls(SQUARE_NAMES.index(usi[0:2]), SQUARE_NAMES.index(usi[2:4]))
        elif len(usi) == 5 and usi[4] == '+':
            return cls(SQUARE_NAMES.index(usi[0:2]), SQUARE_NAMES.index(usi[2:4]), True)
        else:
            raise ValueError('expected usi string to be of length 4 or 5')

    @classmethod
    def null(cls):
        '''
        Gets a null move.
        A null move just passes the turn to the other side (and possibly
        forfeits en-passant capturing). Null moves evaluate to `False` in
        boolean contexts.
        >>> bool(shogi.Move.null())
        False
        '''
        return cls(None, None, NONE)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Parse git logs.

These parsing functions expect output of the following command:

    git log --pretty=raw --numstat

"""

import re

__author__ = 'Tavish Armstrong'
__email__ = 'tavisharmstrong@gmail.com'
__version__ = '0.2.1'

PAT_COMMIT = r'''
(
commit\ (?P<commit>[a-f0-9]+)\n
tree\ (?P<tree>[a-f0-9]+)\n
(?P<parents>(parent\ [a-f0-9]+\n)*)
(?P<author>author \s+(.+)\s+<(.*)>\s+(\d+)\s+([+\-]\d\d\d\d)\n)
(?P<committer>committer \s+(.+)\s+<(.*)>\s+(\d+)\s+([+\-]\d\d\d\d)\n)\n
(?P<message>
(\ \ \ \ [^\n]*\n)*
)
\n
(?P<numstats>
(^(\d+|-)\s+(\d+|-)\s+(.*)$\n)*
)
)
'''
RE_COMMIT = re.compile(PAT_COMMIT, re.MULTILINE | re.VERBOSE)

# -------------------------------------------------------------------
# Main parsing functions

def parse_commits(data):
    '''Accept a string and parse it into many commits.
    Parse and yield each commit-dictionary.
    This function is a generator.
    '''
    raw_commits = RE_COMMIT.finditer(data)
    for rc in raw_commits:
        full_commit = rc.groups()[0]
        parts = RE_COMMIT.match(full_commit).groupdict()
        parsed_commit = parse_commit(parts)
        yield parsed_commit

def parse_commit(parts):
    '''Accept a parsed single commit. Some of the named groups
    require further processing, so parse those groups.
    Return a dictionary representing the completely parsed
    commit.
    '''
    commit = {}
    commit['commit'] = parts['commit']
    commit['tree'] = parts['tree']
    parent_block = parts['parents']
    commit['parents'] = [
        parse_parent_line(parentline)
        for parentline in
        parent_block.splitlines()
    ]
    commit['author'] = parse_author_line(parts['author'])
    commit['committer'] = parse_committer_line(parts['committer'])
    message_lines = [
        parse_message_line(msgline)
        for msgline in
        parts['message'].split("\n")
    ]
    commit['message'] = "\n".join(
        msgline
        for msgline in
        message_lines
        if msgline is not None
    )
    commit['changes'] = [
        parse_numstat_line(numstat)
        for numstat in
        parts['numstats'].splitlines()
    ]
    return commit

# -------------------------------------------------------------------
# Parsing helper functions

def parse_hash_line(line, name):
    RE_HASH_LINE = name + r' ([abcdef0-9]+)'
    result = re.match(RE_HASH_LINE, line)
    if result is None:
        return result
    else:
        return result.groups()[0]

def parse_commit_line(line):
    return parse_hash_line(line, 'commit')

def parse_parent_line(line):
    return parse_hash_line(line, 'parent')

def parse_tree_line(line):
    return parse_hash_line(line, 'tree')

def parse_person_line(line, name):
    RE_PERSON = name + r' (.+) <(.*)> (\d+) ([+\-]\d\d\d\d)'
    result = re.match(RE_PERSON, line)
    if result is None:
        return result
    else:
        groups = result.groups()
        name = groups[0]
        email = groups[1]
        timestamp = int(groups[2])
        timezone = groups[3]
        d_result = {
            'name': name,
            'email': email,
            'date': timestamp,
            'timezone': timezone,
        }
        return d_result

def parse_committer_line(line):
    return parse_person_line(line, 'committer')

def parse_author_line(line):
    return parse_person_line(line, 'author')

def parse_message_line(line):
    RE_MESSAGE = r'    (.*)'
    result = re.match(RE_MESSAGE, line)
    if result is None:
        return result
    else:
        return result.groups()[0]

def parse_numstat_line(line):
    RE_NUMSTAT = r'(\d+|-)\s+(\d+|-)\s+(.*)'
    result = re.match(RE_NUMSTAT, line)
    if result is None:
        return result
    else:
        (sadd, sdel, fname) = result.groups()
        try:
            return (int(sadd), int(sdel), fname)
        except ValueError:
            return (sadd, sdel, fname)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generate a json log of a git repository.
"""

from __future__ import print_function
import json
import sys
from .parser import parse_commits

__author__ = 'Tavish Armstrong'
__email__ = 'tavisharmstrong@gmail.com'
__version__ = '0.2.3'

# -------------------------------------------------------------------
# Main

def main():
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--git-dir',
        default=None,
        help='Path to the .git/ directory of the repository you are targeting'
    )
    parser.add_argument(
        '--since',
        default=None,
        help=('Show commits more recent than a specific date. If present, '
              'this argument is passed through to "git log" unchecked. ')
    )
    args = parser.parse_args()
    if sys.version_info < (3, 0):
        print(git2json(run_git_log(args.git_dir, args.since)))
    else:
        print(git2jsons(run_git_log(args.git_dir, args.since)))

# -------------------------------------------------------------------
# Main API functions

def git2jsons(s):
    return json.dumps(list(parse_commits(s)), ensure_ascii=False)

def git2json(fil):
    return json.dumps(list(parse_commits(fil.read())), ensure_ascii=False)

# -------------------------------------------------------------------
# Functions for interfacing with git

def run_git_log(git_dir=None, git_since=None):
    '''run_git_log([git_dir]) -> File

    Run `git log --numstat --pretty=raw` on the specified
    git repository and return its stdout as a pseudo-File.'''
    import subprocess
    if git_dir:
        command = [
            'git',
            '--git-dir=' + git_dir,
            'log',
            '--numstat',
            '--pretty=raw'
        ]
    else:
        command = ['git', 'log', '--numstat', '--pretty=raw']
    if git_since is not None:
        command.append('--since=' + git_since)
    raw_git_log = subprocess.Popen(
        command,
        stdout=subprocess.PIPE
    )
    if sys.version_info < (3, 0):
        return raw_git_log.stdout
    else:
        return raw_git_log.stdout.read().decode('utf-8', 'ignore')


import argparse
from typing import List
from contextlib import contextmanager

from goodconf import GoodConf
from .argparse import argparser_add_argument

@contextmanager
def load_config_from_cli(config: GoodConf, argv: List[str]) -> List[str]:
    """Loads config, checking CLI arguments for a config file"""

    # Monkey patch Django's command parser
    from django.core.management.base import BaseCommand
    original_parser = BaseCommand.create_parser

    def patched_parser(self, prog_name, subcommand):
        parser = original_parser(self, prog_name, subcommand)
        argparser_add_argument(parser, config)
        return parser

    BaseCommand.create_parser = patched_parser

    try:
        parser = argparse.ArgumentParser(add_help=False)
        argparser_add_argument(parser, config)

        config_arg, default_args = parser.parse_known_args(argv)
        config.load(config_arg.config)
        yield default_args
    finally:
        # Put that create_parser back where it came from or so help me!
        BaseCommand.create_parser = original_parser

def execute_from_command_line_with_config(config: GoodConf, argv: List[str]):
    """Load's config then runs Django's execute_from_command_line"""
    with load_config_from_cli(config, argv) as args:
        from django.core.management import execute_from_command_line
        execute_from_command_line(args)


import argparse
from .. import GoodConf

def argparser_add_argument(parser: argparse.ArgumentParser, config: GoodConf):
    """Adds argument for config to existing argparser"""
    help = "Config file."
    if config.file_env_var:
        help += (" Can also be configured via the "
                 "environment variable: {}".format(config.file_env_var))
    if config.default_files:
        help += (" Defaults to the first file that exists from "
                 "[{}].".format(', '.join(config.default_files)))
    parser.add_argument('-C', '--config', metavar='FILE', help=help)


"""
Transparently load variables from environment or JSON/YAML file.
"""
import json
import logging
import os
import sys
import errno
from io import StringIO
from typing import List

from goodconf.base import DeclarativeValuesMetaclass
from goodconf.values import Value  # noqa

log = logging.getLogger(__name__)

def _load_config(path: str) -> dict:
    """
    Given a file path, parse it based on its extension (YAML or JSON)
    and return the values as a Python dictionary. JSON is the default if an
    extension can't be determined.
    """
    __, ext = os.path.splitext(path)
    if ext in ['.yaml', '.yml']:
        import ruamel.yaml
        loader = ruamel.yaml.safe_load
    else:
        loader = json.load
    with open(path) as f:
        config = loader(f)
    return config

def _find_file(filename: str, require: bool = True) -> str:
    if not os.path.exists(filename):
        if not require:
            return None
        raise FileNotFoundError(
            errno.ENOENT, os.strerror(errno.ENOENT), filename)
    return os.path.abspath(filename)

class GoodConf(metaclass=DeclarativeValuesMetaclass):
    def __init__(self,
                 file_env_var: str = None,
                 default_files: List[str] = None,
                 load: bool = False):
        """
        :param file_env_var: the name of an environment variable which can be
                             used for the name of the configuration file to
                             load
        :param default_files: if no file is given, try to load a configuration
                              from these files in order
        :param load: load config file on instantiation [default: False].

        A docstring defined on the class should be a plain-text description
        used as a header when generating a configuration file.
        """
        self.file_env_var = file_env_var
        self.config_file = None
        self.default_files = default_files or []
        if load:
            self.load()

    def load(self, filename: str = None):
        """Find config file and set values"""
        if filename:
            self.config_file = _find_file(filename)
        else:
            if self.file_env_var and self.file_env_var in os.environ:
                self.config_file = _find_file(os.environ[self.file_env_var])
            if not self.config_file:
                for filename in self.default_files:
                    self.config_file = _find_file(filename, require=False)
                    if self.config_file:
                        break
        if self.config_file:
            config = _load_config(self.config_file)
            log.info("Loading config from %s", self.config_file)
        else:
            config = {}
            log.info("No config file specified. "
                     "Loading with environment variables.")
        self.set_values(config)

    def set_values(self, config: dict):
        for k in self._values:
            if k in config:
                setattr(self, k, config.get(k))
            else:
                setattr(self, k, self._values[k].value)

    @classmethod
    def get_initial(cls, **override):
        return {k: override.get(k, getattr(cls, k)) for k in cls._values}

    @classmethod
    def generate_yaml(cls, **override):
        """
        Dumps initial config in YAML
        """
        import ruamel.yaml
        yaml = ruamel.yaml.YAML()
        yaml_str = StringIO()
        yaml.dump(cls.get_initial(**override), stream=yaml_str)
        yaml_str.seek(0)
        dict_from_yaml = yaml.load(yaml_str)
        if cls.__doc__:
            dict_from_yaml.yaml_set_start_comment(
                '\n' + cls.__doc__ + '\n\n')
        for k in dict_from_yaml.keys():
            if cls._values[k].help:
                dict_from_yaml.yaml_set_comment_before_after_key(
                    k, before='\n' + cls._values[k].help)
        yaml_str = StringIO()
        yaml.dump(dict_from_yaml, yaml_str)
        yaml_str.seek(0)
        return yaml_str.read()

    @classmethod
    def generate_json(cls, **override):
        """
        Dumps initial config in JSON
        """
        return json.dumps(cls.get_initial(**override), indent=2)

    @classmethod
    def generate_markdown(cls):
        """
        Documents values in markdown
        """
        lines = []
        if cls.__doc__:
            lines.extend(['# {}'.format(cls.__doc__), ''])
        for k, v in cls._values.items():
            lines.append('* **{}**  '.format(k))
            if v.required:
                lines[-1] = lines[-1] + '_REQUIRED_  '
            if v.help:
                lines.append('  {}  '.format(v.help))
            lines.append('  type: `{}`  '.format(v.cast_as.__name__))
            if v.default is not None:
                lines.append('  default: `{}`  '.format(v.default))
        return '\n'.join(lines)

    def django_manage(self, args: List[str] = None):
        args = args or sys.argv
        from .contrib.django import execute_from_command_line_with_config
        execute_from_command_line_with_config(self, args)


import os
from typing import TypeVar, Callable
from decimal import Decimal

from distutils.util import strtobool

CASTS = [int, str, float, list, bool, Decimal]
CastTypes = TypeVar('CastTypes', *CASTS)

class RequiredValueMissing(Exception):
    pass

class Value:
    def __init__(self, key: str=None, default=None,
                 initial: Callable[[], CastTypes] = None,
                 cast_as: CastTypes = None, help: str = ""):
        """
        :param key:      Name of the value used in file or environment
                         variable. Set automatically by the GoodConf metaclass.
        :param default:  Default value if none is provided. If left unset,
                         loading a config that fails to provide this value
                         will raise a RequiredValueMissing exception.
        :param initial:  Initial value to use when generating a config
        :param cast_as:  Python type to cast variable as. Defaults to type of
                         default (if provided) or str.
        :param help:     Plain-text description of the value.
        """
        self.key = key
        self.default = default
        self.initial = initial
        self.help = help
        if cast_as:
            self.cast_as = cast_as
        elif default is not None:
            self.cast_as = type(default)
        else:
            self.cast_as = str

    @property
    def required(self):
        return self.default is None

    @property
    def value(self):
        if self.key in os.environ:
            self._value = self.cast(os.environ[self.key])
        if not hasattr(self, '_value'):
            self._value = self.default
            if callable(self._value):
                self._value = self._value()
        return self._value

    @value.setter
    def value(self, value):
        if value is None:
            if self.required:
                raise RequiredValueMissing(self.key)
            del self.value
        else:
            self._value = value

    @value.deleter
    def value(self):
        if hasattr(self, '_value'):
            del self._value

    @property
    def initial(self):
        if self._initial:
            return self._initial()
        if self.default is not None:
            return self.default
        return ''

    @initial.setter
    def initial(self, value):
        if value and not callable(value):
            raise ValueError("Initial value must be a callable.")
        self._initial = value

    def __get__(self, instance, owner):
        if instance is None:
            # Accessing via the class. Return the initial value (falling
            # back to default).
            return self.initial
        return self.value

    def __set__(self, instance, value):
        self.value = value

    def __delete__(self, instance):
        del self.value

    def cast(self, val: str):
        """converts string to type requested by `cast_as`"""
        try:
            return getattr(self, 'cast_as_{}'.format(
                self.cast_as.__name__.lower()))(val)
        except AttributeError:
            return self.cast_as(val)

    def cast_as_list(self, val: str) -> list:
        """Convert a comma-separated string to a list"""
        return val.split(',')

    def cast_as_bool(self, val: str) -> bool:
        """
        True values are y, yes, t, true, on and 1
        False values are n, no, f, false, off and 0
        Raises ValueError if val is anything else.
        """
        return bool(strtobool(val))


#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import with_statement, print_function, division, unicode_literals

import sys
import os.path as op
from functools import wraps
import datetime
from datetime import timedelta
from collections import defaultdict, namedtuple
from zipfile import ZipFile
from io import BytesIO

# We could have used "six", but like this we have no dependency
if sys.version_info[0] < 3:
    range = xrange
    from itertools import izip as zip
    from urllib2 import urlopen

    def iteritems(d):
        return d.iteritems()

    def itervalues(d):
        return d.itervalues()

else:
    from urllib.request import urlopen

    def iteritems(d):
        return d.items()

    def itervalues(d):
        return d.values()

_DIRNAME = op.realpath(op.dirname(__file__))
CURRENCY_FILE = op.join(_DIRNAME, 'eurofxref-hist.zip')

Bounds = namedtuple('Bounds', 'first_date last_date')

__all__ = ['CurrencyConverter',
           'S3CurrencyConverter',
           'RateNotFoundError', ]

def memoize(function):
    memo = {}

    @wraps(function)
    def wrapper(*args):
        if args not in memo:
            memo[args] = function(*args)
        return memo[args]
    return wrapper

@memoize
def list_dates_between(first_date, last_date):
    """Returns all dates from first to last included."""
    return [first_date + timedelta(days=n)
            for n in range(1 + (last_date - first_date).days)]

@memoize
def parse_date(s):
    """Fast %Y-%m-%d parsing."""
    try:
        return datetime.date(int(s[:4]), int(s[5:7]), int(s[8:10]))
    except ValueError:  # other accepted format used in one-day data set
        return datetime.datetime.strptime(s, '%d %B %Y').date()

def get_lines_from_zip(zip_str):
    zip_file = ZipFile(BytesIO(zip_str))
    for name in zip_file.namelist():
        for line in zip_file.read(name).decode('utf-8').splitlines():
            yield line

class RateNotFoundError(Exception):
    """Custom exception when data is missing in the rates file."""
    pass

class CurrencyConverter(object):
    """
    At init, load the historic currencies (since 1999) from the ECB.
    The rates are EUR foreign exchange reference rates:

    Date,USD,JPY,BGN,CYP,CZK,...
    2014-03-28,1.3759,140.9,1.9558,N/A,27.423,...
    2014-03-27,1.3758,...

    ``_rates`` is a dictionary with:

    - currencies as keys
    - {date: rate, ...} as values.

    ``currencies`` is a set of all available currencies.
    ``bounds`` is a dict if first and last date available per currency.
    """
    def __init__(self,
                 currency_file=CURRENCY_FILE,
                 fallback_on_wrong_date=False,
                 fallback_on_missing_rate=False,
                 ref_currency='EUR',
                 na_values=frozenset(['', 'N/A']),
                 verbose=False):
        """Instantiate a CurrencyConverter.

        :param str currency_file: Path to the source data. Can be a local path,
            or an URL starting with 'http://' or 'https://'. Defaults to the
            European Central Bank historical rates file included in the package.
        :param bool fallback_on_wrong_date: Set to False (default) to raise a
            RateNotFoundError when dates are requested outside the data's range.
            Set to True to extrapolate rates for dates outside the source data's
            range. The extrapolation is done by falling back to the first or
            last data point, for dates before and after the data's range,
            respectively.
        :param bool fallback_on_missing_rate: Set to True to linearly
            interpolate missing rates by their two closest valid rates. This
            only affects dates within the source data's range. Default False.
        :param str ref_currency: Three-letter currency code for the currency
            that the source data is oriented towards. This is EUR for the
            default European Central Bank data, and so the default is 'EUR'.
        :param iterable na_values: What to interpret as missing values in the
            source data.
        :param verbose: Set to True to print what is going on under the hood.
        """
        # Global options
        self.fallback_on_wrong_date = fallback_on_wrong_date
        self.fallback_on_missing_rate = fallback_on_missing_rate
        self.ref_currency = ref_currency  # reference currency of rates
        self.na_values = na_values        # missing values
        self.verbose = verbose

        # Will be filled once the file is loaded
        self._rates = None
        self.bounds = None
        self.currencies = None

        if currency_file is not None:
            self.load_file(currency_file)

    def load_file(self, currency_file):
        """To be subclassed if alternate methods of loading data.
        """
        if currency_file.startswith(('http://', 'https://')):
            content = urlopen(currency_file).read()
        else:
            with open(currency_file, 'rb') as f:
                content = f.read()

        if currency_file.endswith('.zip'):
            self.load_lines(get_lines_from_zip(content))
        else:
            self.load_lines(content.decode('utf-8').splitlines())

    def load_lines(self, lines):
        _rates = self._rates = defaultdict(dict)
        na_values = self.na_values

        lines = iter(lines)
        header = next(lines).strip().split(',')[1:]

        for line in lines:
            line = line.strip().split(',')
            date = parse_date(line[0])
            for currency, rate in zip(header, line[1:]):
                currency = currency.strip()
                if rate not in na_values and currency:  # skip empty currency
                    _rates[currency][date] = float(rate)

        self.currencies = set(self._rates) | set([self.ref_currency])
        self._compute_bounds()

        for currency in sorted(self._rates):
            self._set_missing_to_none(currency)
            if self.fallback_on_missing_rate:
                self._compute_missing_rates(currency)

    def _compute_bounds(self):
        self.bounds = dict((currency, Bounds(min(r), max(r)))
                           for currency, r in iteritems(self._rates))

        self.bounds[self.ref_currency] = Bounds(
            min(b.first_date for b in itervalues(self.bounds)),
            max(b.last_date for b in itervalues(self.bounds)))

    def _set_missing_to_none(self, currency):
        """Fill missing rates of a currency with the closest available ones."""
        rates = self._rates[currency]
        first_date, last_date = self.bounds[currency]

        for date in list_dates_between(first_date, last_date):
            if date not in rates:
                rates[date] = None

        if self.verbose:
            missing = len([r for r in itervalues(rates) if r is None])
            if missing:
                print('{0}: {1} missing rates from {2} to {3} ({4} days)'.format(
                    currency, missing, first_date, last_date,
                    1 + (last_date - first_date).days))

    def _compute_missing_rates(self, currency):
        """Fill missing rates of a currency.

        This is done by linear interpolation of the two closest available rates.

        :param str currency: The currency to fill missing rates for.
        """
        rates = self._rates[currency]

        # tmp will store the closest rates forward and backward
        tmp = defaultdict(lambda: [None, None])

        for date in sorted(rates):
            rate = rates[date]
            if rate is not None:
                closest_rate = rate
                dist = 0
            else:
                dist += 1
                tmp[date][0] = closest_rate, dist

        for date in sorted(rates, reverse=True):
            rate = rates[date]
            if rate is not None:
                closest_rate = rate
                dist = 0
            else:
                dist += 1
                tmp[date][1] = closest_rate, dist

        for date in sorted(tmp):
            (r0, d0), (r1, d1) = tmp[date]
            rates[date] = (r0 * d1 + r1 * d0) / (d0 + d1)
            if self.verbose:
                print(('{0}: filling {1} missing rate using {2} ({3}d old) and '
                       '{4} ({5}d later)').format(currency, date, r0, d0, r1, d1))

    def _get_rate(self, currency, date):
        """Get a rate for a given currency and date.

        :type date: datetime.date

        >>> from datetime import date
        >>> c = CurrencyConverter()
        >>> c._get_rate('USD', date=date(2014, 3, 28))
        1.375...
        >>> c._get_rate('BGN', date=date(2010, 11, 21))
        Traceback (most recent call last):
        RateNotFoundError: BGN has no rate for 2010-11-21
        """
        if currency == self.ref_currency:
            return 1.0

        if date not in self._rates[currency]:
            first_date, last_date = self.bounds[currency]

            if not self.fallback_on_wrong_date:
                raise RateNotFoundError('{0} not in {1} bounds {2}/{3}'.format(
                    date, currency, first_date, last_date))

            if date < first_date:
                fallback_date = first_date
            elif date > last_date:
                fallback_date = last_date
            else:
                raise AssertionError('Should never happen, bug in the code!')

            if self.verbose:
                print(r'/!\ {0} not in {1} bounds {2}/{3}, falling back to {4}'.format(
                    date, currency, first_date, last_date, fallback_date))

            date = fallback_date

        rate = self._rates[currency][date]
        if rate is None:
            raise RateNotFoundError('{0} has no rate for {1}'.format(currency, date))
        return rate

    def convert(self, amount, currency, new_currency='EUR', date=None):
        """Convert amount from a currency to another one.

        :param float amount: The amount of `currency` to convert.
        :param str currency: The currency to convert from.
        :param str new_currency: The currency to convert to.
        :param datetime.date date: Use the conversion rate of this date. If this
            is not given, the most recent rate is used.

        :return: The value of `amount` in `new_currency`.
        :rtype: float

        >>> from datetime import date
        >>> c = CurrencyConverter()
        >>> c.convert(100, 'EUR', 'USD', date=date(2014, 3, 28))
        137.5...
        >>> c.convert(100, 'USD', date=date(2014, 3, 28))
        72.67...
        >>> c.convert(100, 'BGN', date=date(2010, 11, 21))
        Traceback (most recent call last):
        RateNotFoundError: BGN has no rate for 2010-11-21
        """
        for c in currency, new_currency:
            if c not in self.currencies:
                raise ValueError('{0} is not a supported currency'.format(c))

        if date is None:
            date = self.bounds[currency].last_date
        else:
            try:
                date = date.date()  # fallback if input was a datetime object
            except AttributeError:
                pass

        r0 = self._get_rate(currency, date)
        r1 = self._get_rate(new_currency, date)

        return float(amount) / r0 * r1

class S3CurrencyConverter(CurrencyConverter):
    """
    Load the ECB CSV file from an S3 key instead of from a local file.
    The first argument should be an instance of boto.s3.key.Key (or any other
    object that provides a get_contents_as_string() method which returns the
    CSV file as a string).
    """
    def __init__(self, currency_file, **kwargs):
        """Make currency_file a required attribute"""
        super(S3CurrencyConverter, self).__init__(currency_file, **kwargs)

    def load_file(self, currency_file):
        lines = currency_file.get_contents_as_string().splitlines()
        self.load_lines(lines)


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import argparse

from .currency_converter import CurrencyConverter, CURRENCY_FILE, parse_date

if sys.version_info[0] < 3:
    from itertools import izip_longest as zip_longest
else:
    from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    """Group iterable by n elements.

    >>> for t in grouper('abcdefg', 3, fillvalue='x'):
    ...     print(''.join(t))
    abc
    def
    gxx
    """
    return list(zip_longest(*[iter(iterable)] * n, fillvalue=fillvalue))

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('amount', type=float)
    parser.add_argument('currency')

    parser.add_argument(
        '-t', '--to',
        help='target currency, default is %(default)s',
        default='EUR')

    parser.add_argument(
        '-d', '--date',
        help='date of rate, with format %%Y-%%m-%%d',
        default=None)

    parser.add_argument(
        '-v', '--verbose',
        help=('display available currencies, use twice (-vv) to '
              'also display details of missing rates completion'),
        action='count',
        default=0)

    parser.add_argument(
        '-f', '--file',
        help='change currency file used, default is %(default)s',
        default=CURRENCY_FILE)

    args = parser.parse_args()

    c = CurrencyConverter(currency_file=args.file,
                          fallback_on_wrong_date=True,
                          fallback_on_missing_rate=True,
                          verbose=args.verbose > 1)
    currencies = sorted(c.currencies)

    if args.verbose:
        print('{0} available currencies:'.format(len(currencies)))
        for group in grouper(currencies, 10, fillvalue=''):
            print(' '.join(group))
        print('')

        currencies.sort(key=lambda u: c.bounds[u].last_date, reverse=True)
        currencies.sort(key=lambda u: c.bounds[u].first_date)
        for currency in currencies:
            first_date, last_date = c.bounds[currency]
            print('{0}: from {1} to {2} ({3} days)'.format(
                currency, first_date, last_date,
                1 + (last_date - first_date).days))
        print('')

    if args.currency not in c.currencies:
        print(r'/!\ "{0}" is not in available currencies:'.format(args.currency))
        for group in grouper(currencies, 10, fillvalue=''):
            print(' '.join(group))
        exit(1)

    if args.date is not None:
        date = parse_date(args.date)
    else:
        date = c.bounds[args.currency].last_date

    new_amount = c.convert(amount=args.amount,
                           currency=args.currency,
                           new_currency=args.to,
                           date=date)

    print('{0:.3f} {1} = {2:.3f} {3} on {4}'.format(
        args.amount,
        args.currency,
        new_amount,
        args.to,
        date))

if __name__ == '__main__':
    main()


"""Examples demonstrating various spinners.

Attributes
----------
CLEAR_LINE : str
    Ascii to clear the current line
"""
from __future__ import print_function

import os
os.sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import sys
import time
import codecs

try:
    import cursor
except ImportError:
    import subprocess

    if sys.version_info.major == 2:
        subprocess.call(['pip2', 'install', 'cursor'])
    else:
        subprocess.call(['pip3', 'install', 'cursor'])

    import cursor

from spinners import Spinners

CLEAR_LINE = '\033[K'

def decode_utf_8_text(text):
    """Decodes the text from utf-8 format.

    Parameters
    ----------
    text : str
        Text to be decoded

    Returns
    -------
    str
        Decoded text
    """
    try:
        return codecs.decode(text, 'utf-8')
    except:
        return text

def encode_utf_8_text(text):
    """Encodes the text to utf-8 format

    Parameters
    ----------
    text : str
        Text to be encoded

    Returns
    -------
    str
        Encoded text
    """
    try:
        return codecs.encode(text, 'utf-8')
    except:
        return text

if sys.version_info.major == 2:
    get_coded_text = encode_utf_8_text
else:
    get_coded_text = decode_utf_8_text

def animate(frames, interval, name, iterations=2):
    """Animate given frame for set number of iterations.

    Parameters
    ----------
    frames : list
        Frames for animating
    interval : float
        Interval between two frames
    name : str
        Name of animation
    iterations : int, optional
        Number of loops for animations
    """
    for i in range(iterations):
        for frame in frames:
            frame = get_coded_text(frame)
            output = "\r{0} {1}".format(frame, name)
            sys.stdout.write(output)
            sys.stdout.write(CLEAR_LINE)
            sys.stdout.flush()
            time.sleep(0.001 * interval)

try:
    cursor.hide()
    spinners = [spinner for spinner in Spinners] # pylint: disable=not-an-iterable
    sorted_spinners = sorted(spinners, key=lambda spinner: spinner.name)

    for spinner in sorted_spinners:
        frames = spinner.value['frames']
        interval = spinner.value['interval']
        name = spinner.name
        animate(frames, interval, name)

    print('\n')
finally:
    cursor.show()


from __future__ import absolute_import
from satispy import Cnf, Variable
from six.moves import map
from six.moves import range
from six.moves import zip

class DimacsCnf(object):
    def __init__(self):
        self.varname_dict = {}
        self.varobj_dict = {}

    def varname(self, vo):
        return self.varname_dict[vo]

    def varobj(self, v):
        return self.varobj_dict[v]

    def tostring(self, cnf):
        """Convert Cnf object ot Dimacs cnf string

        cnf: Cnf object

        In the converted Cnf there will be only numbers for
        variable names. The conversion guarantees that the
        variables will be numbered alphabetically.
        """
        self.varname_dict = {}
        self.varobj_dict = {}

        varis = set()
        for d in cnf.dis:
            for v in d:
                varis.add(v.name)

        ret = "p cnf %d %d" % (len(varis), len(cnf.dis))

        varis = dict(list(zip(sorted(list(varis)),list(map(str,list(range(1,len(varis)+1)))))))

        for v in varis:
            vo = Variable(v)
            self.varname_dict[vo] = varis[v]
            self.varobj_dict[varis[v]] = vo

        for d in cnf.dis:
            ret += "\n"
            vnamelist = []
            for v in d:
                vnamelist.append(("-" if v.inverted else "") + varis[v.name])
            ret += " ".join(vnamelist) + " 0"

        return ret

    def fromstring(self, s):
        self.varname_dict = {}
        self.varobj_dict = {}

        lines = s.split("\n")
        # Throw away comments and empty lines
        new_lines = []
        for l in lines:
            l = l.strip()
            if l[0] != 'c' and l != "":
                new_lines.append(l)

        _,_,varz,clauses = new_lines[0].split(" ")
        varz, clauses = int(varz), int(clauses)

        c = Cnf()

        lines = new_lines[1:]
        c.dis = frozenset(
            frozenset([Variable("v"+vn.strip(" \t\r\n-"), vn[0] == '-') for vn in line.split(" ")[:-1]])
            for line in lines
        )

        for i in range(1,varz+1):
            stri = str(i)
            vo = Variable('v'+stri)
            self.varname_dict[vo] = stri
            self.varobj_dict[stri] = vo

        return c

    def write(self, cnf, fil):
        f = open(fil, 'w')
        f.write(self.tostring(cnf))
        f.close()

    def read(self, fil):
        f = open(fil, 'r')
        cnf = self.fromstring(f.read())
        f.close()
        return cnf


from __future__ import absolute_import
from itertools import product
from six.moves import map
from functools import total_ordering

cnfClass = None

@total_ordering
class Variable(object):
    def __init__(self, name, inverted=False):
        self.name = name
        self.inverted = inverted

    def __neg__(self):
        v = Variable(self.name)
        v.inverted = not self.inverted
        return v

    def __and__(self, other):
        c = cnfClass.create_from(self)
        return c & other

    def __or__(self, other):
        c = cnfClass.create_from(self)
        return c | other

    def __xor__(self, other):
        c = cnfClass.create_from(self)
        return c ^ other

    def __rshift__(self, other): # implies
        c = cnfClass.create_from(self)
        return -c | other

    def __str__(self):
        return ("-" if self.inverted else "") + str(self.name)

    def __eq__(self, other):
        return self.name == other.name and self.inverted == other.inverted

    def __hash__(self):
        return hash(self.name) ^ hash(self.inverted)

    def __cmp__(self, other):
        if self == other:
            return 0
        if (self.name, self.inverted) < (other.name, other.inverted):
            return -1
        else:
            return 1

    def __lt__(self, other):
        return self.__cmp__(other) == -1

class NaiveCnf(object):
    def __init__(self):
        self.dis = frozenset()

    @classmethod
    def create_from(cls, x):
        if isinstance(x, Variable):
            cnf = NaiveCnf()
            cnf.dis = frozenset([frozenset([x])])
            return cnf
        elif isinstance(x, cls):
            return x
        else:
            raise Exception("Could not create a Cnf object from %s" % str(type(x)))

    def __and__(self, other):
        other = NaiveCnf.create_from(other)
        result = NaiveCnf()
        result.dis = self.dis | other.dis
        return result

    def __or__(self, other):
        other = NaiveCnf.create_from(other)

        if self.dis and other.dis:
            new_dis = frozenset(
                d1 | d2
                for d1, d2 in product(self.dis, other.dis)
            )
        elif other.dis:
            new_dis = other.dis
        else:
            new_dis = self.dis

        c = NaiveCnf()
        c.dis = new_dis
        return c

    def __xor__(self, other):
        return (self | other) & (-self | -other)

    def __neg__(self):
        cnfs = []

        for d in self.dis:
            c = NaiveCnf()
            c.dis = frozenset(
                frozenset([-v])
                for v in d
            )
            cnfs.append(c)

        ret = NaiveCnf()
        for cnf in cnfs:
            ret |= cnf

        return ret

    def __rshift__(self, other): # implies
        return -self | other

    def __str__(self):
        ret = []
        for d in self.dis:
            ret.append(" | ".join(map(str,d)))
        return "(" + ") & (".join(ret) + ")"

    def __eq__(self, other):
        return self.dis == other.dis

    def __hash__(self):
        return hash(self.dis)

def reduceCnf(cnf):
    """
    I just found a remarkably large bug in my SAT solver and found an
    interesting solution.
    Remove all b | -b
    (-b | b) & (b | -a) & (-b | a) & (a | -a)
    becomes
    (b | -a) & (-b | a)

    Remove all (-e) & (-e)
    (-e | a) & (-e | a) & (-e | a) & (-e | a)
    becomes
    (-e | a)
    (-b | b | c) becomes nothing, not (c)
    """
    output = Cnf()
    for x in cnf.dis:
        dont_add = False
        for y in x:
            for z in x:
                if z == -y:
                    dont_add = True
                    break
            if dont_add: break
        if dont_add: continue
        # TODO: Is this necessary anymore? Probably not. Do statistical analysis.
        if x not in output.dis:
            output.dis |= frozenset([x])
    return output
#end def reduceCnf(cnf)

class Cnf(object):
    def __init__(self):
        self.dis = frozenset()

    @classmethod
    def create_from(cls, x):
        if isinstance(x, Variable):
            cnf = Cnf()
            cnf.dis = frozenset([frozenset([x])])
            return cnf
        elif isinstance(x, cls):
            return x
        else:
            raise Exception("Could not create a Cnf object from %s" % str(type(x)))

    def __and__(self, other):
        other = Cnf.create_from(other)
        result = Cnf()
        result.dis = self.dis | other.dis
        return result

    def __or__(self, other):
        other = Cnf.create_from(other)

        if self.dis and other.dis:
            new_dis = frozenset(
                d1 | d2
                for d1, d2 in product(self.dis, other.dis)
            )
        elif other.dis:
            new_dis = other.dis
        else:
            new_dis = self.dis

        c = Cnf()
        c.dis = new_dis
        return reduceCnf(c)

    def __xor__(self, other):
        return reduceCnf((self | other) & (-self | -other))

    def __neg__(self):
        cnfs = []

        for d in self.dis:
            c = Cnf()
            c.dis = frozenset(
                frozenset([-v])
                for v in d
            )
            x = reduceCnf(c)
            if x not in cnfs:
                cnfs.append(x)

        ret = Cnf()
        for cnf in cnfs:
            ret |= cnf

        return ret

    def __rshift__(self, other): # implies
        return -self | other

    def __str__(self):
        ret = []
        for d in self.dis:
            ret.append(" | ".join(map(str,d)))
        return "(" + ") & (".join(ret) + ")"

    def __eq__(self, other):
        return self.dis == other.dis

    def __hash__(self):
        return hash(self.dis)

class CnfFromString(Cnf):
    def __init__(self):
        pass

    @staticmethod
    def create(string):
        output_queue, symbols = CnfFromString.string_to_rpn(string)
        return CnfFromString.execute_rpn(output_queue), symbols

    @staticmethod
    def string_to_rpn(string):
        # Convert string to RPN using the railroad-shunt algorithm

        symbols = {} # list of 'Variable' objects; keys are strings used as names in input
        current_token = ''

        output_queue = []
        stack = []

        for i in range(0, len(string)):
            ch = string[i]
            operator_char = (ch in ["-", "&", "|", "^", ">", " ", "(", ")"])

            # reached end of variable-name token
            if current_token and operator_char:
                if current_token not in symbols.keys():
                    symbols[current_token] = Variable(current_token)

                output_queue.append(symbols[current_token])
                current_token = ''

            elif current_token and i == len(string)-1:

                if not operator_char:
                    current_token += ch

                if current_token not in symbols.keys():
                    symbols[current_token] = Variable(current_token)

                output_queue.append(symbols[current_token])
                current_token = ''

            if ch in ["-", "&", "|", "^", '>']:

                # don't let unary '-' operator pop a binary operator from the stack
                # give parens a high precedence
                if stack and ch != "-" and stack[-1] != '(':
                    output_queue.append(stack.pop())

                    # if popped a minus, pop again?
                    if output_queue[-1] == "-" and stack and stack[-1] != '(':
                        output_queue.append(stack.pop())

                if ch == ">" and string[i+1] != ">":
                    print("Error: single '>' is not allowed")
                    return
                if ch == ">" and string[i+1] == ">":
                    stack.append(ch)
                    i += 1
                else:
                    stack.append(ch)

            if ch == "(":
                stack.append("(")

            if ch == ")":
                while True:
                    next_operator = stack.pop()
                    if next_operator == "(":
                        break
                    output_queue.append(next_operator)

            if  ch not in ["-", "&", "|", "^", '>', "(", ")", " "]:
                current_token += ch

        while stack:
            output_queue.append(stack.pop())

        return output_queue, symbols

    @staticmethod
    def execute_rpn(output_queue):
        result_stack = []
        i = 0
        while i < len(output_queue):

            # n.b. operators were recorded as strings, whislt variables are Variable objects
            if not isinstance(output_queue[i], str):
                # not an operator
                result_stack.append(output_queue[i])

            elif output_queue[i] == "-":
                # negation
                result_stack[-1] = result_stack[-1].__neg__()

            else:
                # other operator
                operator = output_queue[i]
                rarg = result_stack.pop()
                larg = result_stack.pop()

                result_stack.append(CnfFromString.apply_operator(larg, rarg, operator))

            i += 1

        return result_stack[0]

    @staticmethod
    def apply_operator(larg, rarg, operator):
        if operator == "&":
            return larg.__and__(rarg)
        elif operator == "|":
            return larg.__or__(rarg)
        elif operator == "^":
            return larg.__xor__(rarg)
        elif operator == ">>":
            return larg.rshift(rarg)

# Change this to NaiveCnf if you want.
cnfClass = Cnf


"""Compute positions from an ephemeris installed as a Python package.

Note: This entire module is DEPRECATED.  The idea of distributing JPL
ephemerides as Python packages proved to be impractical (they were much
too large for the Python Package Index to easily store and distribute),
and it forced `jplephem` users get their ephemerides from a different
source than mainline astronomers, who use SPICE files.  This package's
documentation now recommends avoiding this old code, and using the
features now build directly into the `SPK` class instead.

"""
import os
import numpy as np

class DateError(ValueError):
    """Date input is outside the range covered by the ephemeris."""

class Ephemeris(object):
    """[DEPRECATED] JPL planetary ephemeris for computing positions on dates."""

    def __init__(self, module):
        self.name = module.__name__.upper()
        self.dirpath = os.path.dirname(module.__file__)
        self.names = tuple(sorted(
            name.split('-')[-1].split('.')[0]
            for name in os.listdir(self.dirpath)
            if not name.startswith('constants') and name.endswith('.npy')
            ))
        path = self.path('constants.npy')
        self.__dict__.update((k.decode('ascii'), v) for k, v in np.load(path))
        self.earth_share = 1.0 / (1.0 + self.EMRAT)
        self.moon_share = self.EMRAT / (1.0 + self.EMRAT)
        self.sets = {}

    def path(self, filename):
        """[DEPRECATED] Compute the path to a particular file in the ephemeris."""
        return os.path.join(self.dirpath, filename)

    def load(self, name):
        """[DEPRECATED] Load the polynomial series for `name` and return it."""
        s = self.sets.get(name)
        if s is None:
            self.sets[name] = s = np.load(self.path('jpl-%s.npy' % name))
        return s

    def position(self, name, tdb, tdb2=0.0):
        """[DEPRECATED] Compute the position of `name` at time ``tdb [+ tdb2]``.

        The position is returned as a NumPy array ``[x y z]``.

        The barycentric dynamical time `tdb` argument should be a float.
        If there are many dates you want computed, then make `tdb` an
        array, which is more efficient than calling this method multiple
        times; the return value will be a two-dimensional array giving a
        row of values for each coordinate.

        For extra precision, the time can be split into two floats; a
        popular choice is to use `tdb` for the integer or half-integer
        date, and `tdb2` to hold the remaining fraction.

        Consult the `names` attribute of this ephemeris for the values
        of `name` it supports, such as ``'mars'`` or ``'earthmoon'``.

        """
        bundle = self.compute_bundle(name, tdb, tdb2)
        return self.position_from_bundle(bundle)

    def position_and_velocity(self, name, tdb, tdb2=0.0):
        """[DEPRECATED] Compute the position and velocity of `name` at ``tdb [+ tdb2]``.

        The position and velocity are returned in a 2-tuple::

            ([x y z], [xdot ydot zdot])

        The barycentric dynamical time `tdb` argument should be a float.
        If there are many dates you want computed, then make `tdb` an
        array, which is more efficient than calling this method multiple
        times; the return values will be two-dimensional arrays giving a
        row of values for each coordinate.

        For extra precision, the time can be split into two floats; a
        popular choice is to use `tdb` for the integer or half-integer
        date, and `tdb2` to hold the remaining fraction.

        Consult the `names` attribute of this ephemeris for the values
        of `name` it supports, such as ``'mars'`` or ``'earthmoon'``.

        """
        bundle = self.compute_bundle(name, tdb, tdb2)
        position = self.position_from_bundle(bundle)
        velocity = self.velocity_from_bundle(bundle)
        return position, velocity

    def compute(self, name, tdb):
        """[DEPRECATED] Legacy routine that concatenates position and velocity vectors.

        This routine is deprecated.  Use the methods `position()` and
        `position_and_velocity()` instead.  This method follows the same
        calling convention, but incurs extra copy operations in order to
        return a single NumPy array::

            [x y z xdot ydot zdot]

        """
        bundle = self.compute_bundle(name, tdb, 0.0)
        position = self.position_from_bundle(bundle)
        velocity = self.velocity_from_bundle(bundle)
        return np.concatenate((position, velocity))

    def compute_bundle(self, name, tdb, tdb2=0.0):
        """[DEPRECATED] Return a tuple of coefficients and parameters for `tdb`.

        The return value is a tuple that bundles together the
        coefficients and other Chebyshev intermediate values that are
        needed for the computation of either the position or velocity.
        The bundle can then be passed to either `position_from_bundle()`
        or `velocity_from_bundle()` to finish the computation.  See the
        package-level documentation for details; most users will simply
        call `position()` or `position_and_velocity()` instead.

        The barycentric dynamical time `tdb` argument should be a float.
        If there are many dates you want computed, then make `tdb` an
        array, which is more efficient than calling this method multiple
        times; the return values will be arrays providing a value for
        each time in `tdb`.

        For extra precision, the time can be split into two floats; a
        popular choice is to use `tdb` for the integer or half-integer
        date, and `tdb2` to hold the remaining fraction.

        Consult the `names` attribute of this ephemeris for the values
        of `name` it supports, such as ``'mars'`` or ``'earthmoon'``.

        """
        input_was_scalar = getattr(tdb, 'shape', ()) == ()
        if input_was_scalar:
            tdb = np.array((tdb,))
        # no need to deal with tdb2; numpy broadcast will add fine below.

        coefficient_sets = self.load(name)
        number_of_sets, axis_count, coefficient_count = coefficient_sets.shape

        jalpha, jomega = self.jalpha, self.jomega
        days_per_set = (jomega - jalpha) / number_of_sets
        # to keep precision, first subtract, then add
        index, offset = divmod((tdb - jalpha) + tdb2, days_per_set)
        index = index.astype(int)

        if (index < 0).any() or (number_of_sets < index).any():
            raise DateError('ephemeris %s only covers dates %.1f through %.1f'
                            % (self.name, jalpha, jomega))

        omegas = (index == number_of_sets)
        index[omegas] -= 1
        offset[omegas] += days_per_set

        coefficients = np.rollaxis(coefficient_sets[index], 1)

        # Chebyshev recurrence:

        T = np.empty((coefficient_count, len(index)))
        T[0] = 1.0
        T[1] = t1 = 2.0 * offset / days_per_set - 1.0
        twot1 = t1 + t1
        for i in range(2, coefficient_count):
            T[i] = twot1 * T[i-1] - T[i-2]

        bundle = coefficients, days_per_set, T, twot1
        return bundle

    def position_from_bundle(self, bundle):
        """[DEPRECATED] Return position, given the `coefficient_bundle()` return value."""

        coefficients, days_per_set, T, twot1 = bundle
        return (T.T * coefficients).sum(axis=2)

    def velocity_from_bundle(self, bundle):
        """[DEPRECATED] Return velocity, given the `coefficient_bundle()` return value."""

        coefficients, days_per_set, T, twot1 = bundle
        coefficient_count = coefficients.shape[2]

        # Chebyshev derivative:

        dT = np.empty_like(T)
        dT[0] = 0.0
        dT[1] = 1.0
        dT[2] = twot1 + twot1
        for i in range(3, coefficient_count):
            dT[i] = twot1 * dT[i-1] - dT[i-2] + T[i-1] + T[i-1]
        dT *= 2.0
        dT /= days_per_set

        return (dT.T * coefficients).sum(axis=2)


"""Access a NASA JPL SPICE Double Precision Array File (DAF).

http://naif.jpl.nasa.gov/pub/naif/toolkit_docs/FORTRAN/req/daf.html

"""
import io
import mmap
import sys
from struct import Struct
from numpy import array as numpy_array, ndarray

FTPSTR = b'FTPSTR:\r:\n:\r\n:\r\x00:\x81:\x10\xce:ENDFTP'  # FTP test string
LOCFMT = {b'BIG-IEEE': '>', b'LTL-IEEE': '<'}
K = 1024

class DAF(object):
    """Access to NASA SPICE Double Precision Array Files (DAF).

    Provide the constructor with a ``file_object`` for full access to
    both the segment summaries and to the numeric arrays.  If you pass a
    ``StringIO`` instead, then you can fetch the summary information but
    not access the arrays.

    """
    def __init__(self, file_object):
        if getattr(file_object, 'encoding', None):
            raise ValueError('file_object must be opened in binary "b" mode')

        self.file = file_object
        self._map = None
        self._array = None

        file_record = self.read_record(1)

        def unpack():
            fmt = self.endian + '8sII60sIII8s603s28s297s'
            self.file_record_struct = Struct(fmt)
            (locidw, self.nd, self.ni, self.locifn, self.fward, self.bward,
             self.free, locfmt, self.prenul, self.ftpstr, self.pstnul
            ) = self.file_record_struct.unpack(file_record)

        self.locidw = file_record[:8].upper().rstrip()

        if self.locidw == b'NAIF/DAF':
            for self.locfmt, self.endian in LOCFMT.items():
                unpack()
                if self.nd == 2:
                    break
            else:
                raise ValueError('neither a big- nor a little-endian scan'
                                 ' of this file produces the expected ND=2')
        elif self.locidw.startswith(b'DAF/'):
            if file_record[500:1000].strip(b'\0') != FTPSTR:
                raise ValueError('this SPK file has been damaged')
            self.locfmt = file_record[88:96]
            self.endian = LOCFMT.get(self.locfmt)
            if self.endian is None:
                raise ValueError('unknown format {0!r}'.format(self.locfmt))
            unpack()
        else:
            raise ValueError('file starts with {0!r}, not "NAIF/DAF" or "DAF/"'
                             .format(self.locidw))

        self.locifn_text = self.locifn.rstrip()

        summary_format = 'd' * self.nd + 'i' * self.ni

        self.summary_control_struct = Struct(self.endian + 'ddd')
        self.summary_struct = struct = Struct(self.endian + summary_format)
        self.summary_length = length = struct.size
        self.summary_step = length + (-length % 8) # pad to 8 bytes
        self.summaries_per_record = (1024 - 8 * 3) // self.summary_step

    def read_record(self, n):
        """Return record `n` as 1,024 bytes; records are indexed from 1."""
        self.file.seek(n * K - K)
        return self.file.read(K)

    def write_record(self, n, data):
        """Write `data` to file record `n`; records are indexed from 1."""
        self.file.seek(n * K - K)
        return self.file.write(data)

    def write_file_record(self):
        data = self.file_record_struct.pack(
            self.locidw.ljust(8, b' '), self.nd, self.ni, self.locifn,
            self.fward, self.bward, self.free, self.locfmt,
            self.prenul, self.ftpstr, self.pstnul,
        )
        self.write_record(1, data)

    def map_words(self, start, end):
        """Return a memory-map of the elements `start` through `end`.

        The memory map will offer the 8-byte double-precision floats
        ("elements") in the file from index `start` through to the index
        `end`, inclusive, both counting the first float as element 1.
        Memory maps must begin on a page boundary, so `skip` returns the
        number of extra bytes at the beginning of the return value.

        """
        i, j = 8 * start - 8, 8 * end
        try:
            fileno = self.file.fileno()
        except (AttributeError, io.UnsupportedOperation):
            fileno = None
        if fileno is None:
            skip = 0
            self.file.seek(i)
            m = self.file.read(j - i)
        else:
            skip = i % mmap.ALLOCATIONGRANULARITY
            r = mmap.ACCESS_READ
            m = mmap.mmap(fileno, length=j-i+skip, access=r, offset=i-skip)
        if sys.version_info > (3,):
            m = memoryview(m)  # so further slicing can return views
        return m, skip

    def comments(self):
        """Return the text inside the comment area of the file."""
        record_numbers = range(2, self.fward)
        if not record_numbers:
            return ''
        data = b''.join(self.read_record(n)[0:1000] for n in record_numbers)
        try:
            return data[:data.find(b'\4')].decode('ascii').replace('\0', '\n')
        except IndexError:
            raise ValueError('DAF file comment area is missing its EOT byte')
        except UnicodeDecodeError:
            raise ValueError('DAF file comment area is not ASCII text')

    def read_array(self, start, end):
        """Return floats from `start` to `end` inclusive, indexed from 1.

        The entire range of floats is immediately read into memory from
        the file, making this efficient for small sequences of floats
        whose values are all needed immediately.

        """
        f = self.file
        f.seek(8 * (start - 1))
        length = 1 + end - start
        data = f.read(8 * length)
        return ndarray(length, self.endian + 'd', data)

    def map_array(self, start, end):
        """Return floats from `start` to `end` inclusive, indexed from 1.

        Instead of pausing to load all of the floats into RAM, this
        routine creates a memory map which will load data from the file
        only as it is accessed, and then will let it expire back out to
        disk later.  This is very efficient for large data sets to which
        you need random access.

        """
        if self._array is None:
            self._map, skip = self.map_words(1, self.free - 1)
            assert skip == 0
            self._array = ndarray(self.free - 1, self.endian + 'd', self._map)
        return self._array[start - 1 : end]

    def summary_records(self):
        """Yield (record_number, n_summaries, record_data) for each record.

        Readers will only use the second two values in each tuple.
        Writers can update the record using the `record_number`.

        """
        record_number = self.fward
        unpack = self.summary_control_struct.unpack
        while record_number:
            data = self.read_record(record_number)
            next_number, previous_number, n_summaries = unpack(data[:24])
            yield record_number, n_summaries, data
            record_number = int(next_number)

    def summaries(self):
        """Yield (name, (value, value, ...)) for each summary in the file."""
        length = self.summary_length
        step = self.summary_step
        for record_number, n_summaries, summary_data in self.summary_records():
            name_data = self.read_record(record_number + 1)
            for i in range(0, int(n_summaries) * step, step):
                j = self.summary_control_struct.size + i
                name = name_data[i:i+step].strip()
                data = summary_data[j:j+length]
                values = self.summary_struct.unpack(data)
                yield name, values

    def map(self, summary_values):
        """Return the array of floats described by a summary.

        Instead of pausing to load all of the floats into RAM, this
        routine creates a memory map which will load data from the file
        only as it is accessed, and then will let it expire back out to
        disk later.  This is very efficient for large data sets to which
        you need random access.

        """
        return self.map_array(summary_values[-2], summary_values[-1])

    def add_array(self, name, values, array):
        """Add a new array to the DAF file.

        The summary will be initialized with the `name` and `values`,
        and will have its start word and end word fields set to point to
        where the `array` of floats has been appended to the file.

        """
        f = self.file
        scs = self.summary_control_struct

        record_number = self.bward
        data = bytearray(self.read_record(record_number))
        next_record, previous_record, n_summaries = scs.unpack(data[:24])

        if n_summaries < self.summaries_per_record:
            summary_record = record_number
            name_record = summary_record + 1
            data[:24] = scs.pack(next_record, previous_record, n_summaries + 1)
            self.write_record(summary_record, data)
        else:
            summary_record = ((self.free - 1) * 8 + 1023) // 1024 + 1
            name_record = summary_record + 1
            free_record = summary_record + 2

            n_summaries = 0
            data[:24] = scs.pack(summary_record, previous_record, n_summaries)
            self.write_record(record_number, data)

            summaries = scs.pack(0, record_number, 1).ljust(1024, b'\0')
            names = b'\0' * 1024
            self.write_record(summary_record, summaries)
            self.write_record(name_record, names)

            self.bward = summary_record
            self.free = (free_record - 1) * 1024 // 8 + 1

        start_word = self.free
        f.seek((start_word - 1) * 8)
        array = numpy_array(array)  # TODO: force correct endian
        f.write(array.view())
        end_word = f.tell() // 8

        self.free = end_word + 1
        self.write_file_record()

        values = values[:self.nd + self.ni - 2] + (start_word, end_word)

        base = 1024 * (summary_record - 1)
        offset = int(n_summaries) * self.summary_step
        f.seek(base + scs.size + offset)
        f.write(self.summary_struct.pack(*values))
        f.seek(base + 1024 + offset)
        f.write(name[:self.summary_length].ljust(self.summary_step, b' '))

NAIF_DAF = DAF  # a separate class supported NAIF/DAF format in jplephem 2.2


"""Compute positions from a NASA SPICE SPK ephemeris kernel file.

http://naif.jpl.nasa.gov/pub/naif/toolkit_docs/FORTRAN/req/spk.html

"""
from numpy import array, empty, empty_like, rollaxis
from .daf import DAF
from .names import target_names

T0 = 2451545.0
S_PER_DAY = 86400.0

def jd(seconds):
    """Convert a number of seconds since J2000 to a Julian Date."""
    return T0 + seconds / S_PER_DAY

class SPK(object):
    """A JPL SPK ephemeris kernel for computing positions and velocities.

    You can load an SPK by specifying its filename::

        kernel = SPK.open('de431.bsp')

    Run ``print(kernel)`` see which segments are inside.  You can also
    loop across all of the segments in the list ``kernel.segments`` or,
    as a convenience, you can select a particular segment by providing a
    center and target integer in square brackets.  So ``kernel[3,399]``
    will select the segment that computes the distance between the
    Earth-Moon barycenter (3) and the Earth itself (399).

    To extract the text comments from the SPK use ``kernel.comments()``.

    """
    def __init__(self, daf):
        self.daf = daf
        self.segments = [Segment(self.daf, *t) for t in self.daf.summaries()]
        self.pairs = dict(((s.center, s.target), s) for s in self.segments)

    @classmethod
    def open(cls, path):
        """Open the file at `path` and return an SPK instance."""
        return cls(DAF(open(path, 'rb')))

    def close(self):
        """Close this SPK file."""
        self.daf.file.close()
        for segment in self.segments:
            if hasattr(segment, '_data'):
                del segment._data
        self.daf._array = None
        self.daf._map = None

    def __str__(self):
        daf = self.daf
        d = lambda b: b.decode('latin-1')
        lines = (str(segment) for segment in self.segments)
        return 'File type {0} and format {1} with {2} segments:\n{3}'.format(
            d(daf.locidw), d(daf.locfmt), len(self.segments), '\n'.join(lines))

    def __getitem__(self, key):
        """Given (center, target) integers, return the last matching segment."""
        return self.pairs[key]

    def comments(self):
        """Return the file comments, as a string."""
        return self.daf.comments()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

class Segment(object):
    """A single segment of an SPK file.

    There are several items of information about each segment that are
    loaded from the underlying SPK file, and made available as object
    attributes:

    segment.source - official ephemeris name, like 'DE-0430LE-0430'
    segment.start_second - initial epoch, as seconds from J2000
    segment.end_second - final epoch, as seconds from J2000
    segment.start_jd - start_second, converted to a Julian Date
    segment.end_jd - end_second, converted to a Julian Date
    segment.center - integer center identifier
    segment.target - integer target identifier
    segment.frame - integer frame identifier
    segment.data_type - integer data type identifier
    segment.start_i - index where segment starts
    segment.end_i - index where segment ends

    """
    def __init__(self, daf, source, descriptor):
        self.daf = daf
        self.source = source
        (self.start_second, self.end_second, self.target, self.center,
         self.frame, self.data_type, self.start_i, self.end_i) = descriptor
        self.start_jd = jd(self.start_second)
        self.end_jd = jd(self.end_second)
        self._data = None

    def __str__(self):
        return self.describe(verbose=False)

    def describe(self, verbose=True):
        """Return a textual description of the segment."""
        center = titlecase(target_names.get(self.center, 'Unknown center'))
        target = titlecase(target_names.get(self.target, 'Unknown target'))
        text = ('{0.start_jd:.2f}..{0.end_jd:.2f}  {1} ({0.center})'
                ' -> {2} ({0.target})'.format(self, center, target))
        if verbose:
            text += ('\n  frame={0.frame} data_type={0.data_type} source={1}'
                     .format(self, self.source.decode('ascii')))
        return text

    def compute(self, tdb, tdb2=0.0):
        """Compute the component values for the time `tdb` plus `tdb2`."""
        for position in self.generate(tdb, tdb2):
            return position

    def compute_and_differentiate(self, tdb, tdb2=0.0):
        """Compute components and differentials for time `tdb` plus `tdb2`."""
        return tuple(self.generate(tdb, tdb2))

    def _load(self):
        """Map the coefficients into memory using a NumPy array.

        """
        if self.data_type == 2:
            component_count = 3
        elif self.data_type == 3:
            component_count = 6
        else:
            raise ValueError('only SPK data types 2 and 3 are supported')

        init, intlen, rsize, n = self.daf.read_array(self.end_i - 3, self.end_i)
        initial_epoch = jd(init)
        interval_length = intlen / S_PER_DAY
        coefficient_count = int(rsize - 2) // component_count
        coefficients = self.daf.map_array(self.start_i, self.end_i - 4)

        coefficients.shape = (int(n), int(rsize))
        coefficients = coefficients[:,2:]  # ignore MID and RADIUS elements
        coefficients.shape = (int(n), component_count, coefficient_count)
        coefficients = rollaxis(coefficients, 1)
        return initial_epoch, interval_length, coefficients

    def load_array(self):
        data = self._data
        if data is None:
            self._data = data = self._load()
        return data

    def generate(self, tdb, tdb2):
        """Generate components and differentials for time `tdb` plus `tdb2`.

        Most uses will simply want to call the `compute()` method or the
        `compute_differentials()` method, for convenience.  But in those
        cases (see Skyfield) where you want to compute a position and
        examine it before deciding whether to proceed with the velocity,
        but without losing all of the work that it took to get to that
        point, this generator lets you get them as two separate steps.

        """
        scalar = not getattr(tdb, 'shape', 0) and not getattr(tdb2, 'shape', 0)
        if scalar:
            tdb = array((tdb,))

        data = self._data
        if data is None:
            self._data = data = self._load()

        initial_epoch, interval_length, coefficients = data
        component_count, n, coefficient_count = coefficients.shape

        # Subtracting tdb before adding tdb2 affords greater precision.
        index, offset = divmod((tdb - initial_epoch) + tdb2, interval_length)
        index = index.astype(int)

        if (index < 0).any() or (index > n).any():
            final_epoch = initial_epoch + interval_length * n
            raise ValueError('segment only covers dates %.1f through %.1f'
                            % (initial_epoch, final_epoch))

        omegas = (index == n)
        index[omegas] -= 1
        offset[omegas] += interval_length

        coefficients = coefficients[:,index]

        # Chebyshev polynomial.

        T = empty((coefficient_count, len(index)))
        T[0] = 1.0
        T[1] = t1 = 2.0 * offset / interval_length - 1.0
        twot1 = t1 + t1
        for i in range(2, coefficient_count):
            T[i] = twot1 * T[i-1] - T[i-2]

        components = (T.T * coefficients).sum(axis=2)
        if scalar:
            components = components[:,0]

        yield components

        # Chebyshev differentiation.

        dT = empty_like(T)
        dT[0] = 0.0
        dT[1] = 1.0
        if coefficient_count > 2:
            dT[2] = twot1 + twot1
            for i in range(3, coefficient_count):
                dT[i] = twot1 * dT[i-1] - dT[i-2] + T[i-1] + T[i-1]
        dT *= 2.0
        dT /= interval_length

        rates = (dT.T * coefficients).sum(axis=2)
        if scalar:
            rates = rates[:,0]

        yield rates

def titlecase(name):
    """Title-case target `name` if it looks safe to do so."""
    return name if name.startswith(('1', 'C/', 'DSS-')) else name.title()


"""Compute things from a NASA SPICE binary PCK kernel file.

ftp://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/pck.html

"""
from numpy import array, empty, empty_like, rollaxis
from .daf import DAF
from .names import target_names

T0 = 2451545.0
S_PER_DAY = 86400.0

def jd(seconds):
    """Convert a number of seconds since J2000 to a Julian Date."""
    return T0 + seconds / S_PER_DAY

class BinaryPCK(object):
    """A JPL binary PCK (extension ``.bcp``) kernel.

    You can load a binary PCK file by specifying its filename::

        kernel = BinaryPCK.open('moon_pa_de421_1900-2050.bpc')

    Run ``print(kernel)`` see which segments are inside and iterate
    across ``kernel.segments`` to access them each in turn.

    To see the text comments, call ``kernel.comments()``.

    """
    def __init__(self, daf):
        self.daf = daf
        self.segments = [Segment(self.daf, source, descriptor)
                         for source, descriptor in self.daf.summaries()]

    @classmethod
    def open(cls, path):
        """Open the file at `path` and return a binary PCK instance."""
        return cls(DAF(open(path, 'rb')))

    def close(self):
        """Close this file."""
        self.daf.file.close()
        for segment in self.segments:
            if hasattr(segment, '_data'):
                del segment._data  # TODO: explicitly close each memory map

    def __str__(self):
        daf = self.daf
        d = lambda b: b.decode('latin-1')
        lines = (str(segment) for segment in self.segments)
        return 'File type {0} and format {1} with {2} segments:\n{3}'.format(
            d(daf.locidw), d(daf.locfmt), len(self.segments), '\n'.join(lines))

    def comments(self):
        """Return the file comments, as a string."""
        return self.daf.comments()

class Segment(object):
    """A single segment of a binary PCK file.

    There are several items of information about each segment that are
    loaded from the underlying PCK file, and made available as object
    attributes:

    segment.source - official ephemeris name, like 'DE-0430LE-0430'
    segment.start_second - initial epoch, as seconds from J2000
    segment.end_second - final epoch, as seconds from J2000
    segment.start_jd - start_second, converted to a Julian Date
    segment.end_jd - end_second, converted to a Julian Date
    segment.body - integer body identifier
    segment.frame - integer frame identifier
    segment.data_type - integer data type identifier
    segment.start_i - index where segment starts
    segment.end_i - index where segment ends

    """
    def __init__(self, daf, source, descriptor):
        self.daf = daf
        self.source = source
        (self.start_second, self.end_second, self.body, self.frame,
         self.data_type, self.start_i, self.end_i) = descriptor
        self.start_jd = jd(self.start_second)
        self.end_jd = jd(self.end_second)
        self._data = None

    def __str__(self):
        return self.describe(verbose=False)

    def describe(self, verbose=True):
        """Return a textual description of the segment."""
        body = titlecase(target_names.get(self.body, 'Unknown body'))
        text = ('{0.start_jd:.2f}..{0.end_jd:.2f} frame={0.frame}'
                '  {1} ({0.body})'.format(self, body))
        if verbose:
            text += ('\n  data_type={0.data_type} source={1}'
                     .format(self, self.source.decode('ascii')))
        return text

    def _load(self):
        """Map the coefficients into memory using a NumPy array.

        """
        if self.data_type == 2:
            component_count = 3
        else:
            raise ValueError('only binary PCK data type 2 is supported')

        init, intlen, rsize, n = self.daf.read_array(self.end_i - 3, self.end_i)
        initial_epoch = jd(init)
        interval_length = intlen / S_PER_DAY
        coefficient_count = int(rsize - 2) // component_count
        coefficients = self.daf.map_array(self.start_i, self.end_i - 4)

        coefficients.shape = (int(n), int(rsize))
        coefficients = coefficients[:,2:]  # ignore MID and RADIUS elements
        coefficients.shape = (int(n), component_count, coefficient_count)
        coefficients = rollaxis(coefficients, 1)
        return initial_epoch, interval_length, coefficients

    def compute(self, tdb, tdb2, derivative=True):
        """Generate angles and derivatives for time `tdb` plus `tdb2`.

        If ``derivative`` is true, return a tuple containing both the
        angle and its derivative; otherwise simply return the angles.

        """
        scalar = not getattr(tdb, 'shape', 0) and not getattr(tdb2, 'shape', 0)
        if scalar:
            tdb = array((tdb,))

        data = self._data
        if data is None:
            self._data = data = self._load()

        initial_epoch, interval_length, coefficients = data
        component_count, n, coefficient_count = coefficients.shape

        # Subtracting tdb before adding tdb2 affords greater precision.
        index, offset = divmod((tdb - initial_epoch) + tdb2, interval_length)
        index = index.astype(int)

        if (index < 0).any() or (index > n).any():
            final_epoch = initial_epoch + interval_length * n
            raise ValueError('segment only covers dates %.1f through %.1f'
                            % (initial_epoch, final_epoch))

        omegas = (index == n)
        index[omegas] -= 1
        offset[omegas] += interval_length

        coefficients = coefficients[:,index]

        # Chebyshev polynomial.

        T = empty((coefficient_count, len(index)))
        T[0] = 1.0
        T[1] = t1 = 2.0 * offset / interval_length - 1.0
        twot1 = t1 + t1
        for i in range(2, coefficient_count):
            T[i] = twot1 * T[i-1] - T[i-2]

        components = (T.T * coefficients).sum(axis=2)
        if scalar:
            components = components[:,0]

        if not derivative:
            return components

        # Chebyshev differentiation.

        dT = empty_like(T)
        dT[0] = 0.0
        dT[1] = 1.0
        if coefficient_count > 2:
            dT[2] = twot1 + twot1
            for i in range(3, coefficient_count):
                dT[i] = twot1 * dT[i-1] - dT[i-2] + T[i-1] + T[i-1]
        dT *= 2.0
        dT /= interval_length

        rates = (dT.T * coefficients).sum(axis=2)
        if scalar:
            rates = rates[:,0]

        return components, rates

def titlecase(name):
    """Title-case body `name` if it looks safe to do so."""
    return name if name.startswith(('1', 'C/', 'DSS-')) else name.title()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# utils.py --- utils for musicbox
# Copyright (c) 2015-2016 omi & Contributors

from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from builtins import str
from future import standard_library
standard_library.install_aliases()
import platform
import os

def utf8_data_to_file(f, data):
    if hasattr(data, 'decode'):
        f.write(data.decode('utf-8'))
    else:
        f.write(data)

def notify_command_osx(msg, msg_type, t=None):
    command = '/usr/bin/osascript -e "display notification \\\"{}\\\" {} with title \\\"musicbox\\\""'
    sound = 'sound name \\\"/System/Library/Sounds/Ping.aiff\\\"' if msg_type else ''
    return command.format(msg, sound)

def notify_command_linux(msg, t=None):
    command = '/usr/bin/notify-send "' + msg + '"'
    if t:
        command += ' -t ' + str(t)
    command += ' -h int:transient:1'
    return command

def notify(msg, msg_type=0, t=None):
    "Show system notification with duration t (ms)"
    if platform.system() == 'Darwin':
        command = notify_command_osx(msg, msg_type, t)
    else:
        command = notify_command_linux(msg, t)
    os.system(command.encode('utf-8'))

if __name__ == "__main__":
    notify("I'm test 1", msg_type=1, t=1000)
    notify("I'm test 2", msg_type=0, t=1000)