#import re


#class CronTab(object):
#    pass


#class ParseException(Exception):
#    """Raised by crontab_parser when the input can't be parsed."""


## https://github.com/celery/celery/blob/master/celery/schedules.py
#class CrontabParser(object):
#    """Parser for crontab expressions. Any expression of the form 'groups'
#    (see BNF grammar below) is accepted and expanded to a set of numbers.
#    These numbers represent the units of time that the crontab needs to
#    run on::
#        digit   :: '0'..'9'
#        dow     :: 'a'..'z'
#        number  :: digit+ | dow+
#        steps   :: number
#        range   :: number ( '-' number ) ?
#        numspec :: '*' | range
#        expr    :: numspec ( '/' steps ) ?
#        groups  :: expr ( ',' expr ) *
#    The parser is a general purpose one, useful for parsing hours, minutes and
#    day_of_week expressions.  Example usage::
#        >>> minutes = crontab_parser(60).parse('*/15')
#        [0, 15, 30, 45]
#        >>> hours = crontab_parser(24).parse('*/4')
#        [0, 4, 8, 12, 16, 20]
#        >>> day_of_week = crontab_parser(7).parse('*')
#        [0, 1, 2, 3, 4, 5, 6]
#    It can also parse day_of_month and month_of_year expressions if initialized
#    with an minimum of 1.  Example usage::
#        >>> days_of_month = crontab_parser(31, 1).parse('*/3')
#        [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31]
#        >>> months_of_year = crontab_parser(12, 1).parse('*/2')
#        [1, 3, 5, 7, 9, 11]
#        >>> months_of_year = crontab_parser(12, 1).parse('2-12/2')
#        [2, 4, 6, 8, 10, 12]
#    The maximum possible expanded value returned is found by the formula::
#        max_ + min_ - 1
#    """
#    ParseException = ParseException

#    _range = r'(\w+?)-(\w+)'
#    _steps = r'/(\w+)?'
#    _star = r'\*'

#    def __init__(self, max_=60, min_=0):
#        self.max_ = max_
#        self.min_ = min_
#        self.pats = (
#            (re.compile(self._range + self._steps), self._range_steps),
#            (re.compile(self._range), self._expand_range),
#            (re.compile(self._star + self._steps), self._star_steps),
#            (re.compile('^' + self._star + '$'), self._expand_star),
#        )

#    def parse(self, spec):
#        acc = set()
#        for part in spec.split(','):
#            if not part:
#                raise self.ParseException('empty part')
#            acc |= set(self._parse_part(part))
#        return acc

#    def _parse_part(self, part):
#        for regex, handler in self.pats:
#            m = regex.match(part)
#            if m:
#                return handler(m.groups())
#        return self._expand_range((part, ))

#    def _expand_range(self, toks):
#        fr = self._expand_number(toks[0])
#        if len(toks) > 1:
#            to = self._expand_number(toks[1])
#            if to < fr:  # Wrap around max_ if necessary
#                return (list(range(fr, self.min_ + self.max_)) +
#                        list(range(self.min_, to + 1)))
#            return list(range(fr, to + 1))
#        return [fr]

#    def _range_steps(self, toks):
#        if len(toks) != 3 or not toks[2]:
#            raise self.ParseException('empty filter')
#        return self._expand_range(toks[:2])[::int(toks[2])]

#    def _star_steps(self, toks):
#        if not toks or not toks[0]:
#            raise self.ParseException('empty filter')
#        return self._expand_star()[::int(toks[0])]

#    def _expand_star(self, *args):
#        return list(range(self.min_, self.max_ + self.min_))

#    def _expand_number(self, s):
#        if isinstance(s, str) and s[0] == '-':
#            raise self.ParseException('negative numbers not supported')
#        try:
#            i = int(s)
#        except ValueError:
#            try:
#                i = weekday(s)
#            except KeyError:
#                raise ValueError('Invalid weekday literal {0!r}.'.format(s))

#        max_val = self.min_ + self.max_ - 1
#        if i > max_val:
#            raise ValueError(
#                'Invalid end range: {0} > {1}.'.format(i, max_val))
#        if i < self.min_:
#            raise ValueError(
#                'Invalid beginning range: {0} < {1}.'.format(i, self.min_))

#        return i