diff options
Diffstat (limited to 'docs/source/powerline_automan.py')
-rw-r--r-- | docs/source/powerline_automan.py | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/docs/source/powerline_automan.py b/docs/source/powerline_automan.py new file mode 100644 index 0000000..85d241c --- /dev/null +++ b/docs/source/powerline_automan.py @@ -0,0 +1,408 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import re +import codecs + +from collections import namedtuple +from argparse import REMAINDER + +from functools import reduce + +from docutils.parsers.rst import Directive +from docutils.parsers.rst.directives import unchanged_required +from docutils import nodes + +from powerline.lib.unicode import u + + +AUTHOR_LINE_START = '* `' +GLYPHS_AUTHOR_LINE_START = '* The glyphs in the font patcher are created by ' + + +def get_authors(): + credits_file = os.path.join(os.path.dirname(__file__), 'license-and-credits.rst') + authors = [] + glyphs_author = None + with codecs.open(credits_file, encoding='utf-8') as CF: + section = None + prev_line = None + for line in CF: + line = line[:-1] + if line and not line.replace('-', ''): + section = prev_line + elif section == 'Authors': + if line.startswith(AUTHOR_LINE_START): + authors.append(line[len(AUTHOR_LINE_START):line.index('<')].strip()) + elif section == 'Contributors': + if line.startswith(GLYPHS_AUTHOR_LINE_START): + assert(not glyphs_author) + glyphs_author = line[len(GLYPHS_AUTHOR_LINE_START):line.index(',')].strip() + prev_line = line + return { + 'authors': ', '.join(authors), + 'glyphs_author': glyphs_author, + } + + +class AutoManSubparsers(object): + def __init__(self): + self.parsers = [] + + def add_parser(self, command, *args, **kwargs): + self.parsers.append((command, AutoManParser(*args, **kwargs))) + return self.parsers[-1][1] + + +Argument = namedtuple('Argument', ('names', 'help', 'choices', 'metavar', 'required', 'nargs', 'is_option', 'is_long_option', 'is_short_option', 'multi', 'can_be_joined')) + + +def parse_argument(*args, **kwargs): + is_option = args[0].startswith('-') + is_long_option = args[0].startswith('--') + is_short_option = is_option and not is_long_option + action = kwargs.get('action', 'store') + multi = kwargs.get('action') in ('append',) or kwargs.get('nargs') is REMAINDER + nargs = kwargs.get('nargs', (1 if action in ('append', 'store') else 0)) + return Argument( + names=args, + help=u(kwargs.get('help', '')), + choices=[str(choice) for choice in kwargs.get('choices', [])], + metavar=kwargs.get('metavar') or args[-1].lstrip('-').replace('-', '_').upper(), + required=kwargs.get('required', False) if is_option else ( + kwargs.get('nargs') not in ('?',)), + nargs=nargs, + multi=multi, + is_option=is_option, + is_long_option=is_long_option, + is_short_option=is_short_option, + can_be_joined=(is_short_option and not multi and not nargs) + ) + + +class AutoManGroup(object): + is_short_option = False + is_option = False + is_long_option = False + can_be_joined = False + + def __init__(self): + self.arguments = [] + self.required = False + + def add_argument(self, *args, **kwargs): + self.arguments.append(parse_argument(*args, **kwargs)) + + def add_argument_group(self, *args, **kwargs): + self.arguments.append(AutoManGroup()) + return self.arguments[-1] + + +class SurroundWith(): + def __init__(self, ret, condition, start='[', end=']'): + self.ret = ret + self.condition = condition + self.start = start + self.end = end + + def __enter__(self, *args): + if self.condition: + self.ret.append(nodes.Text(self.start)) + + def __exit__(self, *args): + if self.condition: + self.ret.append(nodes.Text(self.end)) + + +def insert_separators(ret, sep): + for i in range(len(ret) - 1, 0, -1): + ret.insert(i, nodes.Text(sep)) + return ret + + +def format_usage_arguments(arguments, base_length=None): + line = [] + prev_argument = None + arg_indexes = [0] + arguments = arguments[:] + while arguments: + argument = arguments.pop(0) + if isinstance(argument, nodes.Text): + line += [argument] + continue + can_join_arguments = ( + argument.is_short_option + and prev_argument + and prev_argument.can_be_joined + and prev_argument.required == argument.required + ) + if ( + prev_argument + and not prev_argument.required + and prev_argument.can_be_joined + and not can_join_arguments + ): + line.append(nodes.Text(']')) + arg_indexes.append(len(line)) + if isinstance(argument, AutoManGroup): + arguments = ( + [nodes.Text(' (')] + + insert_separators(argument.arguments[:], nodes.Text(' |')) + + [nodes.Text(' )')] + + arguments + ) + else: + if not can_join_arguments: + line.append(nodes.Text(' ')) + with SurroundWith(line, not argument.required and not argument.can_be_joined): + if argument.can_be_joined and not can_join_arguments and not argument.required: + line.append(nodes.Text('[')) + if argument.is_option: + line.append(nodes.strong()) + name = argument.names[0] + if can_join_arguments: + name = name[1:] + # `--` is automatically transformed into – (EN DASH) + # when parsing into HTML. We do not need this. + line[-1] += [nodes.Text(char) for char in name] + elif argument.nargs is REMAINDER: + line.append(nodes.Text('[')) + line.append(nodes.strong()) + line[-1] += [nodes.Text(char) for char in '--'] + line.append(nodes.Text('] ')) + if argument.nargs: + assert(argument.nargs in (1, '?', REMAINDER)) + with SurroundWith( + line, ( + True + if argument.nargs is REMAINDER + else (argument.nargs == '?' and argument.is_option) + ) + ): + if argument.is_long_option: + line.append(nodes.Text('=')) + line.append(nodes.emphasis(text=argument.metavar)) + elif not argument.is_option: + line.append(nodes.strong(text=argument.metavar)) + if argument.multi: + line.append(nodes.Text('…')) + prev_argument = argument + if ( + prev_argument + and prev_argument.can_be_joined + and not prev_argument.required + ): + line.append(nodes.Text(']')) + arg_indexes.append(len(line)) + ret = [] + if base_length is None: + ret = line + else: + length = base_length + prev_arg_idx = arg_indexes.pop(0) + while arg_indexes: + next_arg_idx = arg_indexes.pop(0) + arg_length = sum((len(element.astext()) for element in line[prev_arg_idx:next_arg_idx])) + if length + arg_length > 68: + ret.append(nodes.Text('\n' + (' ' * base_length))) + length = base_length + ret += line[prev_arg_idx:next_arg_idx] + length += arg_length + prev_arg_idx = next_arg_idx + return ret + + +LITERAL_RE = re.compile(r"`(.*?)'") + + +def parse_argparse_text(text): + rst_text = LITERAL_RE.subn(r'``\1``', text)[0] + ret = [] + for i, text in enumerate(rst_text.split('``')): + if i % 2 == 0: + ret.append(nodes.Text(text)) + else: + ret.append(nodes.literal(text=text)) + return ret + + +def flatten_groups(arguments): + for argument in arguments: + if isinstance(argument, AutoManGroup): + for group_argument in flatten_groups(argument.arguments): + yield group_argument + else: + yield argument + + +def format_arguments(arguments): + return [nodes.definition_list( + '', *[ + nodes.definition_list_item( + '', + nodes.term( + # node.Text('') is required because otherwise for some + # reason first name node is seen in HTML output as + # `<strong>abc</strong>`. + '', *([nodes.Text('')] + ( + insert_separators([ + nodes.strong('', '', *[nodes.Text(ch) for ch in name]) + for name in argument.names + ], ', ') + if argument.is_option else + # Unless node.Text('') is here metavar is written in + # bold in the man page. + [nodes.Text(''), nodes.emphasis(text=argument.metavar)] + ) + ( + [] if not argument.is_option or not argument.nargs else + [nodes.Text(' '), nodes.emphasis('', argument.metavar)] + )) + ), + nodes.definition('', nodes.paragraph('', *parse_argparse_text(argument.help or ''))), + ) + for argument in flatten_groups(arguments) + ] + [ + nodes.definition_list_item( + '', + nodes.term( + '', nodes.Text(''), + nodes.strong(text='-h'), + nodes.Text(', '), + nodes.strong('', '', nodes.Text('-'), nodes.Text('-help')), + ), + nodes.definition('', nodes.paragraph('', nodes.Text('Display help and exit.'))) + ) + ] + )] + + +def format_subcommand_usage(arguments, subcommands, progname, base_length): + return reduce((lambda a, b: a + reduce((lambda c, d: c + d), b, [])), [ + [ + [progname] + + format_usage_arguments(arguments) + + [nodes.Text(' '), nodes.strong(text=subcmd)] + + format_usage_arguments(subparser.arguments) + + [nodes.Text('\n')] + for subcmd, subparser in subparsers.parsers + ] + for subparsers in subcommands + ], []) + + +def format_subcommands(subcommands): + return reduce((lambda a, b: a + reduce((lambda c, d: c + d), b, [])), [ + [ + [ + nodes.section( + '', + nodes.title(text='Arguments specific to ' + subcmd + ' subcommand'), + *format_arguments(subparser.arguments), + ids=['subcmd-' + subcmd] + ) + ] + for subcmd, subparser in subparsers.parsers + ] + for subparsers in subcommands + ], []) + + +class AutoManParser(object): + def __init__(self, description=None, help=None): + self.description = description + self.help = help + self.arguments = [] + self.subcommands = [] + + def add_argument(self, *args, **kwargs): + self.arguments.append(parse_argument(*args, **kwargs)) + + def add_subparsers(self): + self.subcommands.append(AutoManSubparsers()) + return self.subcommands[-1] + + def add_mutually_exclusive_group(self): + self.arguments.append(AutoManGroup()) + return self.arguments[-1] + + def automan_usage(self, prog): + block = nodes.literal_block() + progname = nodes.strong() + progname += [nodes.Text(prog)] + base_length = len(prog) + if self.subcommands: + block += format_subcommand_usage(self.arguments, self.subcommands, progname, base_length) + else: + block += [progname] + block += format_usage_arguments(self.arguments, base_length) + return [block] + + def automan_description(self): + ret = [] + if self.help: + ret += parse_argparse_text(self.help) + ret += format_arguments(self.arguments) + format_subcommands(self.subcommands) + return ret + + +class AutoMan(Directive): + required_arguments = 1 + optional_arguments = 0 + option_spec = dict(prog=unchanged_required, minimal=bool) + has_content = False + + def run(self): + minimal = self.options.get('minimal') + module = self.arguments[0] + template_args = {} + template_args.update(get_authors()) + get_argparser = __import__(str(module), fromlist=[str('get_argparser')]).get_argparser + parser = get_argparser(AutoManParser) + if minimal: + container = nodes.container() + container += parser.automan_usage(self.options['prog']) + container += parser.automan_description() + return [container] + synopsis_section = nodes.section( + '', + nodes.title(text='Synopsis'), + ids=['synopsis-section'], + ) + synopsis_section += parser.automan_usage(self.options['prog']) + description_section = nodes.section( + '', nodes.title(text='Description'), + ids=['description-section'], + ) + description_section += parser.automan_description() + author_section = nodes.section( + '', nodes.title(text='Author'), + nodes.paragraph( + '', + nodes.Text('Written by {authors} and contributors. The glyphs in the font patcher are created by {glyphs_author}.'.format( + **get_authors() + )) + ), + ids=['author-section'] + ) + issues_url = 'https://github.com/powerline/powerline/issues' + reporting_bugs_section = nodes.section( + '', nodes.title(text='Reporting bugs'), + nodes.paragraph( + '', + nodes.Text('Report {prog} bugs to '.format( + prog=self.options['prog'])), + nodes.reference( + issues_url, issues_url, + refuri=issues_url, + internal=False, + ), + nodes.Text('.'), + ), + ids=['reporting-bugs-section'] + ) + return [synopsis_section, description_section, author_section, reporting_bugs_section] + + +def setup(app): + app.add_directive('automan', AutoMan) |