123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
- #
- # See COPYING file distributed along with the DataLad package for the
- # copyright and license terms.
- #
- # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
- import argparse
- import datetime
- import re
- class ManPageFormatter(argparse.HelpFormatter):
- # This code was originally distributed
- # under the same License of Python
- # Copyright (c) 2014 Oz Nahum Tiram <nahumoz@gmail.com>
- def __init__(self,
- prog,
- indent_increment=2,
- max_help_position=4,
- width=1000000,
- section=1,
- ext_sections=None,
- authors=None,
- version=None
- ):
- super(ManPageFormatter, self).__init__(
- prog,
- indent_increment=indent_increment,
- max_help_position=max_help_position,
- width=width)
- self._prog = prog
- self._section = 1
- self._today = datetime.date.today().strftime('%Y\\-%m\\-%d')
- self._ext_sections = ext_sections
- self._version = version
- def _get_formatter(self, **kwargs):
- return self.formatter_class(prog=self.prog, **kwargs)
- def _markup(self, txt):
- return txt.replace('-', '\\-')
- def _underline(self, string):
- return "\\fI\\s-1" + string + "\\s0\\fR"
- def _bold(self, string):
- if not string.strip().startswith('\\fB'):
- string = '\\fB' + string
- if not string.strip().endswith('\\fR'):
- string = string + '\\fR'
- return string
- def _mk_synopsis(self, parser):
- self.add_usage(parser.usage, parser._actions,
- parser._mutually_exclusive_groups, prefix='')
- usage = self._format_usage(None, parser._actions,
- parser._mutually_exclusive_groups, '')
- # replace too long list of commands with a single placeholder
- usage = re.sub(r'{[^]]*?create,.*?}', ' COMMAND ', usage, flags=re.MULTILINE)
- # take care of proper wrapping
- usage = re.sub(r'\[([-a-zA-Z0-9]*)\s([a-zA-Z0-9{}|_]*)\]', r'[\1\~\2]', usage)
- usage = usage.replace('%s ' % self._prog, '')
- usage = '.SH SYNOPSIS\n.nh\n.HP\n\\fB%s\\fR %s\n.hy\n' % (self._markup(self._prog),
- usage)
- return usage
- def _mk_title(self, prog):
- name_version = "{0} {1}".format(prog, self._version)
- return '.TH "{0}" "{1}" "{2}" "{3}"\n'.format(
- prog, self._section, self._today, name_version)
- def _mk_name(self, prog, desc):
- """
- this method is in consitent with others ... it relies on
- distribution
- """
- desc = desc.splitlines()[0] if desc else 'it is in the name'
- # ensure starting lower case
- desc = desc[0].lower() + desc[1:]
- return '.SH NAME\n%s \\- %s\n' % (self._bold(prog), desc)
- def _mk_description(self, parser):
- desc = parser.description
- desc = '\n'.join(desc.splitlines()[1:])
- if not desc:
- return ''
- desc = desc.replace('\n\n', '\n.PP\n')
- # sub-section headings
- desc = re.sub(r'^\*(.*)\*$', r'.SS \1', desc, flags=re.MULTILINE)
- # italic commands
- desc = re.sub(r'^ ([-a-z]*)$', r'.TP\n\\fI\1\\fR', desc, flags=re.MULTILINE)
- # deindent body text, leave to troff viewer
- desc = re.sub(r'^ (\S.*)\n', '\\1\n', desc, flags=re.MULTILINE)
- # format NOTEs as indented paragraphs
- desc = re.sub(r'^NOTE\n', '.TP\nNOTE\n', desc, flags=re.MULTILINE)
- # deindent indented paragraphs after heading setup
- desc = re.sub(r'^ (.*)$', '\\1', desc, flags=re.MULTILINE)
- return '.SH DESCRIPTION\n%s\n' % self._markup(desc)
- def _mk_footer(self, sections):
- if not hasattr(sections, '__iter__'):
- return ''
- footer = []
- for section, value in sections.items():
- part = ".SH {}\n {}".format(section.upper(), value)
- footer.append(part)
- return '\n'.join(footer)
- def format_man_page(self, parser):
- page = []
- page.append(self._mk_title(self._prog))
- page.append(self._mk_name(self._prog, parser.description))
- page.append(self._mk_synopsis(parser))
- page.append(self._mk_description(parser))
- page.append(self._mk_options(parser))
- page.append(self._mk_footer(self._ext_sections))
- return ''.join(page)
- def _mk_options(self, parser):
- formatter = parser._get_formatter()
- # positionals, optionals and user-defined groups
- for action_group in parser._action_groups:
- formatter.start_section(None)
- formatter.add_text(None)
- formatter.add_arguments(action_group._group_actions)
- formatter.end_section()
- # epilog
- formatter.add_text(parser.epilog)
- # determine help from format above
- help = formatter.format_help()
- # add spaces after comma delimiters for easier reformatting
- help = re.sub(r'([a-z]),([a-z])', '\\1, \\2', help)
- # get proper indentation for argument items
- help = re.sub(r'^ (\S.*)\n', '.TP\n\\1\n', help, flags=re.MULTILINE)
- # deindent body text, leave to troff viewer
- help = re.sub(r'^ (\S.*)\n', '\\1\n', help, flags=re.MULTILINE)
- return '.SH OPTIONS\n' + help
- def _format_action_invocation(self, action, doubledash='--'):
- if not action.option_strings:
- metavar, = self._metavar_formatter(action, action.dest)(1)
- return metavar
- else:
- parts = []
- # if the Optional doesn't take a value, format is:
- # -s, --long
- if action.nargs == 0:
- parts.extend([self._bold(action_str) for action_str in
- action.option_strings])
- # if the Optional takes a value, format is:
- # -s ARGS, --long ARGS
- else:
- default = self._underline(action.dest.upper())
- args_string = self._format_args(action, default)
- for option_string in action.option_strings:
- parts.append('%s %s' % (self._bold(option_string),
- args_string))
- return ', '.join(p.replace('--', doubledash) for p in parts)
- class RSTManPageFormatter(ManPageFormatter):
- def _get_formatter(self, **kwargs):
- return self.formatter_class(prog=self.prog, **kwargs)
- def _markup(self, txt):
- # put general tune-ups here
- return txt
- def _underline(self, string):
- return "*{0}*".format(string)
- def _bold(self, string):
- return "**{0}**".format(string)
- def _mk_synopsis(self, parser):
- self.add_usage(parser.usage, parser._actions,
- parser._mutually_exclusive_groups, prefix='')
- usage = self._format_usage(None, parser._actions,
- parser._mutually_exclusive_groups, '')
- usage = usage.replace('%s ' % self._prog, '')
- usage = 'Synopsis\n--------\n::\n\n %s %s\n' \
- % (self._markup(self._prog), usage)
- return usage
- def _mk_title(self, prog):
- # and an easy to use reference point
- title = ".. _man_%s:\n\n" % prog.replace(' ', '-')
- title += "{0}".format(prog)
- title += '\n{0}\n\n'.format('=' * len(prog))
- return title
- def _mk_name(self, prog, desc):
- return ''
- def _mk_description(self, parser):
- desc = parser.description
- if not desc:
- return ''
- return 'Description\n-----------\n%s\n' % self._markup(desc)
- def _mk_footer(self, sections):
- if not hasattr(sections, '__iter__'):
- return ''
- footer = []
- for section, value in sections.items():
- part = "\n{0}\n{1}\n{2}\n".format(
- section,
- '-' * len(section),
- value)
- footer.append(part)
- return '\n'.join(footer)
- def _mk_options(self, parser):
- # this non-obvious maneuver is really necessary!
- formatter = self.__class__(self._prog)
- # positionals, optionals and user-defined groups
- for action_group in parser._action_groups:
- formatter.start_section(None)
- formatter.add_text(None)
- formatter.add_arguments(action_group._group_actions)
- formatter.end_section()
- # epilog
- formatter.add_text(parser.epilog)
- # determine help from format above
- option_sec = formatter.format_help()
- return '\n\nOptions\n-------\n{0}'.format(option_sec)
- def _format_action(self, action):
- # determine the required width and the entry label
- action_header = self._format_action_invocation(action)
- if action.help:
- help_text = self._expand_help(action)
- help_lines = self._split_lines(help_text, 80)
- help = ' '.join(help_lines)
- else:
- help = ''
- # return a single string
- return '{0}\n{1}\n{2}\n\n'.format(
- action_header,
- '~' * len(action_header),
- help)
- def cmdline_example_to_rst(src, out=None, ref=None):
- if out is None:
- from io import StringIO
- out = StringIO()
- # place header
- out.write('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n')
- if ref:
- # place cross-ref target
- out.write('.. {0}:\n\n'.format(ref))
- # parser status vars
- inexample = False
- incodeblock = False
- for line in src:
- if line.startswith('#% EXAMPLE START'):
- inexample = True
- incodeblock = False
- continue
- if not inexample:
- continue
- if line.startswith('#% EXAMPLE END'):
- break
- if not inexample:
- continue
- if line.startswith('#%'):
- incodeblock = not incodeblock
- if incodeblock:
- out.write('\n.. code-block:: sh\n\n')
- continue
- if not incodeblock and line.startswith('#'):
- out.write(line[(min(2, len(line) - 1)):])
- continue
- if incodeblock:
- if not line.rstrip().endswith('#% SKIP'):
- out.write(' %s' % line)
- continue
- if not len(line.strip()):
- continue
- else:
- raise RuntimeError("this should not happen")
- return out
|