class Atom(object):

    flag_or_upper_re = re.compile(r'^(?P<hyphen>-{0,2})'
                                  r'($|[\da-zA-Z_][\da-zA-Z_\-]*$)')
    angular_bracket_re = re.compile(r'^<.*?>$')
    options_re = re.compile(r'\[(?P<title>[^\s\]]*)options\]', re.IGNORECASE)

    def __init__(self, *names, **kwargs):
        self.names = set(names)
        self.default = kwargs.get('default', None)
        self.value = None

    def _cache(get_class):
        cache_dic = {}

        def wrapped(cls, atom):
            if atom not in cache_dic:
                result = get_class(cls, atom)
                cache_dic[atom] = result
            return cache_dic[atom]

        return wrapped

    @classmethod
    @_cache
    def get_class(cls, atom):
        if atom in ('-', '--'):
            return Command, None
        elif atom == '-?':
            return Option, None

        opt = cls.options_re.match(atom)
        if opt is not None:
            title = opt.groupdict()['title']
            return OptionsShortcut, title

        m = cls.flag_or_upper_re.match(atom)
        if m:
            match = m.groupdict()
            if match['hyphen']:
                return Option, None
            elif atom.isupper():
                return Argument, None
            else:
                return Command, None
        elif cls.angular_bracket_re.match(atom):
            return Argument, None
        else:
            logger.debug('I guess %s is a Command' % atom)
            return Command, None

    def arg_range(self):
        return [1]

    def fix(self):
        return self
    fix_nest = fix
    fix_empty = fix

    @property
    def repeat(self):
        return False

    def dump_value(self):
        return self.value

    def load_value(self, value):
        self.value = value

    def expand(self):
        return [self]

    @classmethod
    def convert_2_dict(cls, obj):
        return {
            '__class__': obj.__class__.__name__,
            'names': tuple(obj.names),
            'default': obj.default,
        }

    @classmethod
    def convert_2_object(cls, dic, options, namedoptions):
        names = dic['names']
        cls_name = dic['__class__']
        assert cls_name in ('Option', 'Command', 'Argument')
        if cls_name == 'Option':
            # raise NotImplementedError('call Option.convert_2_object instead')
            return Option.convert_2_object(dic, options, namedoptions)
        elif cls_name == 'Command':
            cls = Command
        elif cls_name == 'Argument':
            cls = Argument
        default = dic['default']
        # Not work on py2.6
        # return cls(*names, default=default)
        return cls(*names, **{'default': default})

    def copy(self):
        return self.__class__(*self.names)

    def __str__(self):
        return '/'.join(self.names)

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, ', '.join(self.names))

    def __eq__(self, other):
        if type(self) is not type(other):
            return False
        if not self.names.intersection(other.names):
            return False
        return True
 class Option(Atom):
    long_re = re.compile(r'(?P<name>--[0-9a-zA-Z]*)(?P<eq>=?)(?P<value>.*)')

    def __init__(self, *names, **kwargs):
        # assert all(x.startswith('-') for x in names)
        super(Option, self).__init__(*names, **kwargs)
        self.ref = kwargs.get('ref', None)    # a instance, not a list
        # self.countable = False

    def get_value(self, appeared_only, in_repeat):
        # default = self.default
        names = self.names
        value = self.value
        # if appeared_only and value is None:
        #     return {}
        ref = self.ref
        logger.debug('%s in repeat: %s', self, in_repeat)

        multi = (in_repeat or (value is not True and
                               value is not False and
                               not isinstance(value, StrType)))

        if ref is not None:
            max_value_num = -1 if in_repeat else max(ref.arg_range())
            multi = multi or (max_value_num > 1)
        else:
            max_value_num = 0

        if not value:  # False/None/0
            if max_value_num == 0:
                if value is None:
                    value = False
            elif max_value_num == 1:
                value = None
            else:
                value = []

            result = {}
            if appeared_only:
                value = -1  # means never appeared

            for name in names:
                result[name] = value
            logger.debug('%s returns its value %s', self, result)
            return result

        # has value, has ref
        if ref is not None:
            if max_value_num != 0:
                value = ref.get_flat_list_value()
                if max_value_num == 1:
                    assert len(value) < 2, value
                    value = value[0] if value else None

        # has value, no ref
        elif multi:
            if value is None:
                value = 0
            value = int(value)

        logger.debug('%s returns its value %s', self, value)
        # Not work on py2.6
        # return {name: value for name in names}
        result = {}
        for name in names:
            result[name] = value
        return result

    def get_sys_default_value(self, appeared_only, in_repeat):
        if appeared_only and self.value is None:
            return {}

        if in_repeat:
            if self.ref is not None:
                value = []
            else:
                value = 0
        elif self.ref is not None:
            arg_range = self.ref.arg_range()
            max_arg_num = max(arg_range)
            if max_arg_num == 0:
                value = False
            elif max_arg_num == 1:
                value = None
            else:
                value = []
        else:
            value = False
        # Not work on py2.6
        # return {name: value for name in self.names}
        result = {}
        for name in self.names:
            result[name] = value
        return result

    def match(self, argv, repeat_match):

        if not repeat_match and self.value:
            logger.debug('%s already has a value', self)
            return True

        current = argv.current()
        if current is None:
            logger.debug('no argv left')
            return False

        # self_value = self.dump_value()
        argv_value = argv.dump_value()
        # saver.save(self, argv)

        find_it, attached_value, index, _from = \
            argv.break_for_option(self.names)

        if find_it is None:
            logger.debug('not found matching %s in %s', self, argv)
            argv.load_value(argv_value)
            # saver.rollback(self, argv)
            return False

        if repeat_match:
            if self.value is None:
                self.value = 1
            else:
                self.value += 1
        else:
            self.value = True

        if self.ref is None:
            if attached_value is not None:
                if (argv.stdopt
                        and argv.attachopt
                        and not find_it.startswith('--')):
                    logger.debug("%s put -%s pack to argv %s",
                                 self, attached_value, argv)
                    argv.insert(index, '-' + attached_value, _from)
                    logger.debug('%s matched %s / %s', self, self.value, argv)
                    return True
                logger.debug("%s doesn't have ref but argv is %s",
                             self, attached_value)
                # need restore?
                # self.load_value(self_value)
                # argv.load_value(argv_value)
                raise ExceptNoArgumentExit(
                    '%s must not have argument(s)' % '/'.join(self.names),
                    option=self.names,
                    hit=attached_value
                )
            logger.debug('%s matched %s / %s', self, self.value, argv)
            return True

        # option expects a value(e.g. `--flag=<sth>`), but the argv looks like
        # --flag -- sth
        # then it should fail
        if (attached_value is None and
                argv.current(index) == '--' and
                argv.auto_dashes and
                min(self.ref.arg_range()) > 0):
            logger.debug(
                '%s ref must fully match but failed because `--`', self)
            raise ExpectArgumentHitDoubleDashesExit(
                ('/'.join(self.names) +
                 ' requires argument(s) but hits "--".'),
                option=self.names
            )

        to_match_ref_argv = argv.clone()
        if attached_value is None:
            # --force=[<val>] <arg>
            # --force -- value
            if argv.current(index) == '--' and argv.auto_dashes:
                del to_match_ref_argv[:]
            else:
                to_match_ref_argv[:] = argv[index:]
                del argv[index:]
        else:
            to_match_ref_argv[:] = [attached_value]

        to_match_ref_argv.auto_dashes = False
        result = self.ref.match(to_match_ref_argv, repeat_match)

        if attached_value is not None and to_match_ref_argv:
            logger.debug('%s ref must fully match but failed for %s',
                         self, to_match_ref_argv)
            raise ExpectArgumentExit(
                '%s requires argument(s).' % ('/'.join(self.names)),
                option=self.names,
                hit=to_match_ref_argv[0] if to_match_ref_argv else None)

        if not result:
            logger.debug('%s ref match failed %s', self, to_match_ref_argv)

            raise ExpectArgumentExit(
                '%s requires argument(s).' % ('/'.join(self.names)),
                option=self.names,
                hit=to_match_ref_argv[0] if to_match_ref_argv else None)

            # saver.rollback(self, argv)
            # self.load_value(self_value)
            # argv.load_value(argv_value)
            # return False
        # merge argv
        if attached_value is None:
            argv.extend(to_match_ref_argv)
        logger.debug('%s matched %s / %s', self, self.value, argv)
        return True

    def reset(self):
        # value = self.value
        # if value is not None:
        #     if value is True or value is False:
        #         self.value = False
        #     else:
        #         self.value = 0
        self.value = None

        if self.ref is not None:
            self.ref.reset()

    # Note: this is kind of tricky:
    # make it different from Unit(Command, Unit())
    def dump_value(self):
        ref = self.ref
        return {'self': self.value,
                'ref': None if ref is None else ref.dump_value()}

    def load_value(self, value):
        self.value = value['self']
        if self.ref is not None:
            self.ref.load_value(value['ref'])

    def merge_value(self, values):
        opt_values = [each['self'] for each in values]
        ref_values = [each['ref'] for each in values]

        if len(opt_values) == 1:
            assert len(ref_values) == 1
            result = {'self': opt_values[0], 'ref': ref_values[0]}
        else:
            opt_value = sum(filter(None, opt_values))
            if all(x is None for x in ref_values):
                ref_value = None
            else:
                ref_value = self.ref.merge_value(ref_values)
            result = {'self': opt_value, 'ref': ref_value}
        return result

    def matched(self):
        return self.value in ([], None, -1, 0)

    def copy(self):
        return self.__class__(*self.names, **{'ref': self.ref})

    @classmethod
    def convert_2_dict(cls, obj):
        ref = obj.ref
        result = super(Option, cls).convert_2_dict(obj)
        result['ref'] = (ref.convert_2_dict(ref) if ref is not None else None)
        return result

    @classmethod
    def convert_2_object(cls, dic, options, namedoptions):
        names = dic['names']
        cls_name = dic['__class__']
        assert cls_name == 'Option'
        default = dic['default']
        ref_value = dic['ref']
        ref = (None
               if ref_value is None
               else Unit.convert_2_object(ref_value, options, namedoptions))
        # Not work on py2.6
        # return cls(*names, default=default, ref=ref)
        return cls(*names, **{'default': default, 'ref': ref})

    def __str__(self):
        result = super(Option, self).__str__()
        if self.ref:
            return '`%s %s`' % (result, self.ref)
        return '`%s`' % result

    def __repr__(self):
        return 'Option(%s, ref=%r)' % (', '.join(self.names), self.ref)

    def __eq__(self, other):
        if not isinstance(other, Option):
            return False

        equal = self.names.intersection(other.names)

        if equal:
            equal = (self.ref == other.ref)

        return equal
 class Command(Atom):

    def __init__(self, *names, **kwargs):
        # assert all(self.type(x) == self.COMMAND for x in names)
        super(Command, self).__init__(*names, **kwargs)
        self.value = False

    def reset(self):
        value = self.value
        if isinstance(value, bool):
            self.value = False
        else:
            self.value = 0
        # self.value = False

    def match(self, argv, repeat_match):

        if not repeat_match and self.value:
            logger.debug('%s already has a value %s', self, self.value)
            return True

        current = argv.current()
        if current is None:
            logger.debug('argv ran out before matching %s', self)
            return False

        if argv.option_only:
            logger.debug('option only, %s skipped.', self)
            return False

        skip = 0
        if current == '--':
            argv.check_dash()
            if argv.auto_dashes and argv.dashes:
                current = argv.current(1)
                skip = 1
            else:
                logger.debug('%s matching %s failed', self, current)
                return False

        if current not in self.names or Atom.get_class(current)[0] is Option:
            logger.debug('%s matching %s failed', self, current)
            return False

        if repeat_match:
            self.value += 1
        else:
            self.value = True

        # saver.save(self, argv)
        argv.next(skip)
        logger.debug('%s matched %s/%s', self, self.value, argv)
        return True

    def merge_value(self, values):
        if len(values) == 1:
            return values[0]
        else:
            return sum(filter(None, values))

    def get_value(self, appeared_only, in_repeat):
        if in_repeat:
            value = int(self.value)
            if appeared_only and value == 0:
                return {}
        else:
            value = self.value

        # Not work on py2.6
        # return {name: value for name in self.names}
        result = {}
        for name in self.names:
            result[name] = value
        return result

    def get_sys_default_value(self, appeared_only, in_repeat):
        if in_repeat:
            value = 0
        else:
            value = False
        # Not work on py2.6
        # return {name: value for name in self.names}
        result = {}
        for name in self.names:
            result[name] = value
        return result

    def matched(self):
        return self.value
 class Argument(Atom):

    def __init__(self, *names, **kwargs):
        super(Argument, self).__init__(*names, **kwargs)
        self.value = None

    def reset(self):
        if isinstance(self.value, list):
            del self.value[:]
        else:
            self.value = None

    def dump_value(self):
        value = self.value
        if isinstance(value, list):
            return list(value)
        return value

    def match(self, argv, repeat_match):

        if not repeat_match and (self.value is not None and self.value != []):
            logger.debug('%s already has a value %s', self, self.value)
            return True

        if argv.option_only:
            logger.debug('option only, %s skipped.', self)
            return False

        current = argv.current()
        if current is None:
            logger.debug('argv ran out when matching %s', self)
            return False

        if current == '--':
            argv.check_dash()
            if argv.auto_dashes and argv.dashes:
                current = argv.current(1)
                # nothing left
                if current is None:
                    logger.debug('argv %s ran out when matching %s',
                                 argv, self)
                    return False
                # force to be command/arg
                if repeat_match:
                    if self.value is None:
                        self.value = [current]
                    else:
                        self.value.append(current)

                else:
                    self.value = current

                # saver.save(self, argv)
                argv.next(1)
                logger.debug('%s matched %s/%s', self, self.value, argv)
                return True
            else:
                logger.debug('%s matching %s failed', self, current)
                return False

        # check if it's `--flag=sth`

        if current.startswith('--') and '=' in current:
            opt, value = current.split('=', 1)
            if Atom.get_class(opt)[0] is Option:
                logger.debug('%s matching %s failed', self, current)
                return False

        if Atom.get_class(current)[0] is Option:
            logger.debug('%s matching %s failed', self, current)
            return False

        if repeat_match:
            if self.value is None:
                self.value = [current]
            else:
                self.value.append(current)

        else:
            self.value = current

        # saver.save(self, argv)
        argv.next()
        logger.debug('%s matched %s/%s', self, self.value, argv)
        return True

    def get_value(self, appeared_only, in_repeat):
        value = self.value
        if in_repeat:
            if value is None:
                value = []
            elif not isinstance(value, list):
                value = [value]
        # Not work on py2.6
        # return {name: value for name in self.names}
        result = {}
        if appeared_only and self.value is None:
            pass
        else:
            for name in self.names:
                result[name] = value
        return result

    def get_sys_default_value(self, appeared_only, in_repeat):
        if in_repeat:
            value = []
        else:
            value = None
        # Not work on py2.6
        # return {name: value for name in self.names}
        result = {}
        for name in self.names:
            result[name] = value
        return result

    def merge_value(self, values):
        if len(values) == 1:
            return values[0]
        else:
            result = []
            for each in values:
                if each is None:
                    pass
                elif isinstance(each, list):
                    result.extend(each)
                else:
                    result.append(each)
            return result

    def matched(self):
        return self.value

    def __eq__(self, other):
        if isinstance(other, Argument):
            return True
        return False
 class Optional(Unit):

    def arg_range(self):
        this_range = super(Optional, self).arg_range()
        if 0 not in this_range:
            this_range.append(0)
        return this_range

    def match_oneline(self, argv):
        # saver.save(self, argv)
        self._match_oneline(argv)
        return True

    def match(self, argv, repeat_match):
        repeat = repeat_match or self.repeat
        logger.debug('matching %s with %s%s',
                     self, argv, ', repeatedly' if repeat else '')
        func = (self.match_repeat if repeat else self.match_oneline)

        func(argv)
        return True

    def __eq__(self, other):
        if not isinstance(other, Optional):
            return False

        return super(Optional, self).__eq__(other)

    def __str__(self):
        if len(self) == 1:
            if isinstance(self[0], OptionsShortcut):
                if self.repeat:
                    return '%s...' % self[0]
                return str(self[0])

        return '[%s]%s' % \
               (' '.join(map(str, self)), '...' if self.repeat else '')
 class Required(Unit):

    def match(self, argv, repeat_match):

        if not (repeat_match or self.repeat):
            logger.debug('try to match %s once, %s', self, argv)
            result = self.match_oneline(argv)
            logger.debug('%s matching status: %s', self, result)
            return result

        logger.debug('try to match %s repeatedly', self)
        return self.match_repeat(argv)

    def match_oneline(self, argv):
        # saver.save(self, argv)
        self_value = self.dump_value()
        argv_value = argv.dump_value()

        matched_status = self._match_oneline(argv)

        if all(matched_status):
            logger.debug('%s matched', self)
            return True
        logger.debug('%s matching failed %s / %s', self, matched_status, argv)
        if self.balance_value_for_ellipsis_args():
            logger.debug('%s balace value succeed', self)
            return True

        self.load_value(self_value)
        argv.load_value(argv_value)
        # saver.rollback(self, argv)
        return False

    def __eq__(self, other):
        if not isinstance(other, Required):
            return False

        return super(Required, self).__eq__(other)

    def __str__(self):
        return '(%s)%s' % (' '.join(map(str, self)),
                           '...' if self.repeat else '')
 class OptionsShortcut(object):
    error = None

    def __init__(self, name, options):
        self._hide = set()
        self.name = name
        self.options = options

    def copy(self):
        ins = OptionsShortcut(
            self.name,
            self.options
        )
        ins.set_hide(self._hide)
        return ins

    def matched(self):
        return True

    def set_hide(self, names):
        self._hide.update(names)

    def get_hide(self):
        return self._hide

    def need_hide(self, *name):
        return self._hide.intersection(name)

    def arg_range(self):
        logger.debug('fixme: Unexpected call')
        return [0]

    def match(self, argv, repeat_match):
        options = self.options

        hide = self._hide
        logger.debug('[options]/%s/%s try matching %s', options, hide, argv)
        for each in filter(
                lambda x: not hide.intersection(x[0].names), options):

            if not argv:
                logger.debug('argv run out before matching [options] %s(-%s)',
                             options, self._hide)
                return True
            logger.debug('[options] try %s matching %s', each, argv)
            each.match(argv, repeat_match)

        return True

    def get_value(self, appeared_only, in_repeat):
        options = self.options
        hide = self._hide
        result = {}  # developer should ensure no same options in [options]
        for each in filter(
                lambda x: not hide.intersection(x[0].names), options):
            result.update(each.get_value(appeared_only, in_repeat))
        return result

    def get_sys_default_value(self, appeared_only, in_repeat):
        options = self.options
        hide = self._hide
        result = {}
        for each in filter(
                lambda x: not hide.intersection(x[0].names), options):
            result.update(each.get_sys_default_value(appeared_only, in_repeat))
        return result

    def reset(self):
        for each in self.options:
            each.reset()

    @property
    def repeat(self):
        return False

    def fix(self):
        if all(self.need_hide(*x[0].names) for x in self.options):
            return None
        return self

    def dump_value(self):
        return [x.dump_value() for x in self.options]

    def load_value(self, value):
        for ins, val in zip(self.options, value):
            ins.load_value(val)

    def merge_value(self, values):
        options = self.options
        result = []
        for index, value in enumerate(zip(values)):
            ins = options[index]
            result.append(ins.merge_value(value))

        return result

    def expand(self):
        # return [self]
        real = []
        for each in self.options:
            if self.need_hide(*each[0].names):
                continue
            real.append(each)
        return [Optional(*real)]

    @classmethod
    def convert_2_dict(cls, obj):
        return {
            '__class__': obj.__class__.__name__,
            'name': obj.name,
            # 'ref': [x.convert_2_dict(x) for x in obj.ref],
            'hide': tuple(obj._hide),
        }

    formal_title_re = re.compile('[\\-_]')

    @classmethod
    def convert_2_object(cls, dic, options, namedoptions):
        assert dic['__class__'] == 'OptionsShortcut'

        if namedoptions:
            name = dic['name']
            formal_name = cls.formal_title_re.sub(' ', name.lower()).strip()
            for title, opts in options.items():
                if (formal_title_re.sub(' ', title.lower()).strip() ==
                        formal_name):
                    break
            else:
                raise AttributeError('Unexpected Error: %s not found' % name)
        else:
            opts = sum(options.values(), [])

        ins = OptionsShortcut(dic['name'], opts)
        ins.set_hide(dic['hide'])
        return ins

    # TODO: better behavior
    def __eq__(self, other):
        if isinstance(other, OptionsShortcut):
            return True
        return False

    def __str__(self):
        return '`[options]`'

    def __repr__(self):
        return 'OptionsShortcut()'
 class Either(list):
    error = None

    def __init__(self, *branch):
        assert(all(isinstance(x, Unit) for x in branch))
        super(Either, self).__init__(branch)
        self.matched_branch = -1

    def fix(self):
        if not self:
            return None
        result = []
        for each in self:
            fixed = each.fix()
            if fixed is not None:
                result.append(fixed)
        if not result:
            return None
        self[:] = result
        return self.fix_argument_only()

    def fix_argument_only(self):
        '''
        fix_argument_only() -> Either or Unit(Argument)
        `<arg> | ARG | <arg3>` ->
            `Required(Argument('<arg>', 'ARG', '<arg3>'))`
        `[<arg>] | [ARG] | [<arg3>]` ->
            `Optional(Argument('<arg>', 'ARG', '<arg3>'))`
        `(<arg>) | [ARG]` -> not change, return self
        `-a | --better` -> not change
        '''
        # for idx, branch in enumerate(self):
        #     if isinstance(branch[0], Either):
        #         self[idx] = branch.fix()
        first_type = type(self[0])
        if first_type not in (Required, Optional):
            return self
        for branch in self:
            if not (len(branch) == 1 and
                    isinstance(branch, first_type) and
                    isinstance(branch[0], Argument)):
                logger.debug('fix %r not change', self)
                return self
        else:
            first = self[0][0]
            for each in self:
                first.names.update(each[0].names)
            result = first_type(first)
            logger.debug('fix %r -> %r', self, result)
            return result

    def arg_range(self):
        result = set()
        for each in self:
            result.update(each.arg_range())
        return list(result)

    def __repr__(self):
        return 'Either(%s)' % (', '.join(repr(x) for x in self))
 class Token(list):
    _brackets = {'(': ')', '[': ']'}  # , '{': '}', '<': '>'}

    def next(self):
        return self.pop(0) if self else None

    def current(self):
        return self[0] if self else None

    def check_ellipsis_and_drop(self):
        if self and self[0] == '...':
            self.pop(0)
            return True
        return False

    def till_end_bracket(self, start):
        # start bracket should not in self
        end = self._brackets[start]
        count = dict.fromkeys(self._brackets, 0)
        count[start] = 1
        element = []
        while self:
            this = self.pop(0)
            for each_start, each_end in self._brackets.items():
                count[each_start] += (this == each_start)
                count[each_start] -= (this == each_end)
            if this == end and all(x == 0 for x in count.values()):
                if any(x < 0 for x in count.values()):
                    raise DocpieError("brackets not in pair")
                return element
            element.append(this)
        else:
            raise DocpieError("brackets not in pair") class DocpieError(Exception, DocpieException):
    """Error in construction of usage-message by developer."""
from docpie.element import Atom, Option, Command, Argument
from docpie.element import Optional, Required, OptionsShortcut
from docpie.element import Either
from docpie.tokens import Token
from docpie.error import DocpieError
import logging
import re
import textwrap
import warnings

                    raw_content[prefix] += '\n%s%s' % (title, section)
                else:
                    raw_content[prefix] = title + section

        if formal_collect:
            for each_title, values in formal_collect.items():
                value = '\n'.join(map(textwrap.dedent, values))
                formal_collect[each_title] = value

        self.formal_content = formal_collect

    def parse_names_and_default(self):
        """parse for `parse_content`
        {title: [('-a, --all=STH', 'default'), ...]}"""
        result = {}
        for title, text in self.formal_content.items():

            if not text:
                result[title] = []
                continue

            logger.debug('\n' + text)

            collect = []
            to_list = text.splitlines()

            # parse first line. Should NEVER failed.
            # this will ensure in `[default: xxx]`,
            # the `xxx`(e.g: `\t`, `,`) will not be changed by _format_line
            previous_line = to_list.pop(0)
            collect.append(self.parse_line_option_indent(previous_line))

            for line in to_list:
                indent_match = self.indent_re.match(line)
                this_indent = len(indent_match.groupdict()['indent'])

                if this_indent >= collect[-1]['indent']:
                    # A multi line description
                    previous_line = line
                    continue

                # new option line
                # deal the default for previous option
                collect[-1]['default'] = self.parse_default(previous_line)
                # deal this option
                collect.append(self.parse_line_option_indent(line))
                logger.debug(collect[-1])
                previous_line = line
            else:
                collect[-1]['default'] = self.parse_default(previous_line)

            result[title] = [
                (each['option'], each['default']) for each in collect]

        return result

    spaces_re = re.compile(r'(\ \ \s*|\t\s*)')

    @classmethod
    def cut_first_spaces_outside_bracket(cls, string):
        right = cls.spaces_re.split(string)
        left = []
        if right and right[0] == '':    # re matches the start of the string
            right.pop(0)
        if right and not right[0].strip():    # it is indent
            left.append(right.pop(0))
        brackets = {'(': 0, '[': 0, '<': 0}
        close_brancket = {'(': ')', '[': ']', '<': '>'}
        cutted = ''

        while right:
            this = right.pop(0)
            for open_b in brackets:
                brackets[open_b] += this.count(open_b)
                brackets[open_b] -= this.count(close_brancket[open_b])
            if sum(brackets.values()):
                left.append(this)
            elif (not this.strip() and
                    len(this.expandtabs()) >= 2):
                cutted = this
                break
            else:
                left.append(this)
        return ''.join(left), cutted, ''.join(right)

    @classmethod
    def parse_line_option_indent(cls, line):
        opt_str, separater, description_str = \
                cls.cut_first_spaces_outside_bracket(line)

        logger.debug('%(line)s -> %(opt_str)r, '
                     '%(separater)r, '
                     '%(description_str)r' % locals())
        if description_str.strip():
            indent = len(opt_str.expandtabs()) + len(separater.expandtabs())
            logger.debug('indent: %s', indent)
        else:
            indent = 2 + len(cls.indent_re.match(
                                 opt_str.expandtabs()
                            ).groupdict()['indent'])
            logger.debug('indent: %s', indent)
        return {'option': opt_str.strip(), 'indent': indent}

    @classmethod
    def parse_default(cls, line):
        m = cls.default_re.search(line)
        if m is None:
            return None
        return m.groupdict()['default']

    def parse_to_instance(self, title_of_name_and_default):
        """{title: [Option(), ...]}"""
        result = {}
        for title, name_and_default in title_of_name_and_default.items():
            logger.debug((title, name_and_default))
            result[title] = opts = []
            for opt_str, default in name_and_default:
                logger.debug('%s:%r' % (opt_str, default))
                opt, repeat = self.parse_opt_str(opt_str)
                opt.default = default

                opt_ins = Optional(opt, repeat=repeat)
