From 2c6672b47b04082865528e6028dd1fd3645c7058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Tue, 28 Aug 2018 17:30:55 +0200 Subject: [PATCH] Handle multiple file input/output, fix some templates vars, format with black --- src/shellman/__init__.py | 9 +- src/shellman/cli.py | 195 +++++++++++++----- src/shellman/reader.py | 60 +++--- src/shellman/tags.py | 186 +++++++++-------- src/shellman/templates/__init__.py | 95 +++++---- src/shellman/templates/filters.py | 139 +++++++------ src/shellman/templates/helptext/function.txt | 8 +- src/shellman/templates/helptext/helptext.txt | 48 +++-- src/shellman/templates/manpage/function.groff | 8 +- src/shellman/templates/manpage/function.md | 4 +- src/shellman/templates/manpage/manpage.1 | 1 + src/shellman/templates/manpage/manpage.3 | 1 + src/shellman/templates/manpage/manpage.groff | 34 ++- src/shellman/templates/manpage/manpage.md | 23 ++- src/shellman/templates/wikipage/function.md | 4 +- src/shellman/templates/wikipage/wikipage.md | 30 ++- 16 files changed, 526 insertions(+), 319 deletions(-) create mode 120000 src/shellman/templates/manpage/manpage.1 create mode 120000 src/shellman/templates/manpage/manpage.3 diff --git a/src/shellman/__init__.py b/src/shellman/__init__.py index 073e93c..658a46e 100644 --- a/src/shellman/__init__.py +++ b/src/shellman/__init__.py @@ -11,9 +11,14 @@ write this documentation as text, man, or markdown format on stdout. """ -__version__ = '0.2.2' +__version__ = "0.2.2" from .reader import DocFile, DocStream -__all__ = ['DocFile', 'DocStream'] +__all__ = ["DocFile", "DocStream"] + +# TODO: context injection from command-line / environment variables +# TODO: re-implement --check option with warnings +# TODO: plugin architecture +# TODO: documentation diff --git a/src/shellman/cli.py b/src/shellman/cli.py index 90ad862..1a4594e 100644 --- a/src/shellman/cli.py +++ b/src/shellman/cli.py @@ -20,9 +20,8 @@ import argparse import os import sys -from datetime import date -from .reader import DocFile, DocStream +from .reader import DocFile, DocStream, merge from . import templates from . import __version__ @@ -43,41 +42,87 @@ def valid_file(value): if not value: raise argparse.ArgumentTypeError("'' is not a valid file path") elif not os.path.exists(value): - raise argparse.ArgumentTypeError("%s is not a valid file path" % - value) + raise argparse.ArgumentTypeError("%s is not a valid file path" % value) elif os.path.isdir(value): raise argparse.ArgumentTypeError( - "%s is a directory, not a regular file" % value) + "%s is a directory, not a regular file" % value + ) return value def get_parser(): """Return a parser for the command line arguments.""" parser = argparse.ArgumentParser() + + mxg = parser.add_mutually_exclusive_group() + parser.add_argument( - '-0', '-n', '--nice', action='store_true', dest='nice', - help='be nice: return 0 even if warnings (false)') - parser.add_argument( - '-c', '--check', action='store_true', dest='check', - help='check if the documentation is correct (false)') + "-0", + "-n", + "--nice", + action="store_true", + dest="nice", + help="be nice: return 0 even if warnings (default: false)", + ) + mxg.add_argument( + "-c", + "--check", + action="store_true", + dest="check", + help="only check if the documentation is correct, no output (default: false)", + ) parser.add_argument( - '-f', '--format', dest='format', default='', - help='template format to choose (different for each template)') + "-f", + "--format", + dest="format", + default="", + help="template format to choose (different for each template)", + ) parser.add_argument( - '-t', '--template', choices=templates.parser_choices(), - default='helptext', dest='template', - help='The Jinja2 template to use. Prefix with "path:" to specify the path ' - 'to a directory containing a file named "index". ' - 'Available templates: %s' % ', '.join(templates.names())) + "-t", + "--template", + choices=templates.parser_choices(), + default="helptext", + dest="template", + help='the Jinja2 template to use. Prefix with "path:" to specify the path ' + 'to a directory containing a file named "index". ' + "Available templates: %s" % ", ".join(templates.names()), + ) parser.add_argument( - '-o', '--output', action='store', dest='output', + "-m", + "--merge", + action="store_true", + dest="merge", + help="when multiple files as input, merge their sections in the output (default: false)", + ) + mxg.add_argument( + "-o", + "--output", + action="store", + dest="output", + default=None, + help="file to write to (default: stdout)", + ) + mxg.add_argument( + "-O", + "--multiple-output", + action="store", + dest="multiple_output", default=None, - help='file to write to (stdout by default)') + help="output file path formatted for each input file. " + "You can use the following variables: " + "{filename} and {format} (default: not used)", + ) + parser.add_argument( + "-w", + "--warn", + action="store_true", + dest="warn", + help="actually display the warnings (default: false)", + ) parser.add_argument( - '-w', '--warn', action='store_true', dest='warn', - help='actually display the warnings (false)') - parser.add_argument('FILE', type=valid_file, nargs='*', - help='path to the file(s) to read') + "FILE", type=valid_file, nargs="*", help="path to the file(s) to read" + ) return parser @@ -99,50 +144,94 @@ def main(argv=None): args = parser.parse_args(argv) success = True - doc = None + docs = [] + + if len(args.FILE) > 1 and args.multiple_output and args.merge: + print( + "shellman: error: cannot merge multiple files and output in multiple files.\n" + " Please use --output instead, or remove --merge." + ) + return 2 if args.FILE: for file in args.FILE: - # TODO: actually support multiple files at once. - # Their sections should be merged. - # What about the filename? - doc = DocFile(file) + docs.append(DocFile(file)) # if args.warn: # doc.warn() # success &= bool(doc) else: + print("shellman: reading on standard input -", file=sys.stderr) try: - doc = DocStream(sys.stdin) + docs.append(DocStream(sys.stdin, name=args.output or "")) # if args.warn: # doc.warn() # success &= bool(doc) except KeyboardInterrupt: pass - if doc is None: - return 1 - - if args.format and not args.format.startswith('.'): - args.format = '.' + args.format - template = templates.templates[args.template].get(args.format) - - indent = 4 - rendered = template.render( - doc=doc, - context=dict( - indent=indent, - indent_str=indent * " " - ), - shellman_version=__version__, - now=date.today() - ) - - rendered = rendered.rstrip('\n') - - if args.output is not None: - with open(args.output, 'w') as write_stream: - print(rendered, file=write_stream) + template = templates.templates[args.template] + + if len(docs) == 1: + doc = docs[0] + contents = get_contents(template, args.format, doc) + if args.output: + write(contents, args.output) + elif args.multiple_output: + write( + contents, + args.multiple_output.format(filename=doc.filename, format=args.format), + ) + else: + print(contents) else: - print(rendered) + if args.output or not args.multiple_output: + if args.merge: + doc = merge( + docs, os.path.basename(args.output or common_ancestor(docs)) + ) + contents = get_contents(template, args.format, doc) + else: + contents = "\n\n\n".join( + get_contents(template, args.format, doc) for doc in docs + ) + if args.output: + write(contents, args.output) + else: + print(contents) + elif args.multiple_output: + for doc in docs: + contents = get_contents(template, args.format, doc) + write( + contents, + args.multiple_output.format( + filename=doc.filename, format=args.format + ), + ) return 0 if args.nice or success else 1 + + +def get_contents(template, format, doc): + return template.render(format, doc=doc, shellman_version=__version__) + + +def write(contents, filepath): + with open(filepath, "w") as write_stream: + print(contents, file=write_stream) + + +def common_ancestor(docs): + splits = [os.path.split(doc.filepath) for doc in docs] + vertical = [] + depth = 1 + while True: + if not all(len(s) >= depth for s in splits): + break + vertical.append([s[depth - 1] for s in splits]) + depth += 1 + common = "" + for v in vertical: + if v.count(v[0]) != len(v): + break + common = v[0] + return common or "" diff --git a/src/shellman/reader.py b/src/shellman/reader.py index e78df87..837e04e 100644 --- a/src/shellman/reader.py +++ b/src/shellman/reader.py @@ -16,19 +16,18 @@ from .tags import TAGS -tag_value_regexp = re.compile(r'^\s*[\\@]([_a-zA-Z][\w-]*)\s+(.+)$') -tag_no_value_regexp = re.compile(r'^\s*[\\@]([_a-zA-Z][\w-]*)\s*$') +tag_value_regexp = re.compile(r"^\s*[\\@]([_a-zA-Z][\w-]*)\s+(.+)$") +tag_no_value_regexp = re.compile(r"^\s*[\\@]([_a-zA-Z][\w-]*)\s*$") class DocType: - TAG = 'T' - TAG_VALUE = 'TV' - VALUE = 'V' - INVALID = 'I' + TAG = "T" + TAG_VALUE = "TV" + VALUE = "V" + INVALID = "I" class DocLine: - def __init__(self, path, lineno, tag, value): self.path = path self.lineno = lineno @@ -44,8 +43,8 @@ def __str__(self): elif doc_type == DocType.VALUE: s = '"%s"' % self.value else: - s = 'invalid' - return '%s:%s: %s: %s' % (self.path, self.lineno, doc_type, s) + s = "invalid" + return "%s:%s: %s: %s" % (self.path, self.lineno, doc_type, s) def doc_type(self): if self.tag: @@ -67,7 +66,7 @@ def __bool__(self): return bool(self.lines) def __str__(self): - return '\n'.join([str(line) for line in self.lines]) + return "\n".join([str(line) for line in self.lines]) def append(self, line): self.lines.append(line) @@ -106,11 +105,10 @@ def values(self): class DocStream: - def __init__(self, stream, name=''): + def __init__(self, stream, name=""): self.filepath = None self.filename = name or stream.name - self.sections = process_blocks( - preprocess_lines(preprocess_stream(stream))) + self.sections = process_blocks(preprocess_lines(preprocess_stream(stream))) class DocFile: @@ -120,43 +118,41 @@ def __init__(self, path): with open(path) as stream: try: self.sections = process_blocks( - preprocess_lines(preprocess_stream(stream))) + preprocess_lines(preprocess_stream(stream)) + ) except UnicodeDecodeError: - print('Cannot read file %s' % path) + print("Cannot read file %s" % path) self.sections = [] def preprocess_stream(stream): for lineno, line in enumerate(stream, 1): - line = line.lstrip(' \t').rstrip('\n') - if line.startswith('##'): + line = line.lstrip(" \t").rstrip("\n") + if line.startswith("##"): yield stream.name, lineno, line def preprocess_lines(lines): current_block = DocBlock() for path, lineno, line in lines: - line = line.lstrip('#') + line = line.lstrip("#") res = tag_value_regexp.search(line) if res: tag, value = res.groups() - if current_block and not tag.startswith(current_block.tag + '-'): + if current_block and not tag.startswith(current_block.tag + "-"): yield current_block current_block = DocBlock() - current_block.append(DocLine( - path, lineno, tag, value)) + current_block.append(DocLine(path, lineno, tag, value)) else: res = tag_no_value_regexp.search(line) if res: tag = res.groups()[0] - if current_block and not tag.startswith(current_block.tag + '-'): + if current_block and not tag.startswith(current_block.tag + "-"): yield current_block current_block = DocBlock() - current_block.append(DocLine( - path, lineno, tag, '')) + current_block.append(DocLine(path, lineno, tag, "")) else: - current_block.append(DocLine( - path, lineno, None, line[1:])) + current_block.append(DocLine(path, lineno, None, line[1:])) if current_block: yield current_block @@ -167,3 +163,15 @@ def process_blocks(blocks): tag_class = TAGS.get(block.tag, TAGS[None]) sections[block.tag].append(tag_class.from_lines(block.lines)) return dict(sections) + + +def merge(docs, filename): + stream = object() + stream.name = filename + final_doc = DocStream(stream=stream) + for doc in docs: + for section, values in doc.sections.items(): + if section not in final_doc.sections: + final_doc.sections[section] = [] + final_doc.sections[section].extend(values) + return final_doc diff --git a/src/shellman/tags.py b/src/shellman/tags.py index 8c54ebd..19a906d 100644 --- a/src/shellman/tags.py +++ b/src/shellman/tags.py @@ -15,7 +15,7 @@ def __init__(self, text): @classmethod def from_lines(cls, lines): - return cls(text='\n'.join(l.value for l in lines)) + return cls(text="\n".join(l.value for l in lines)) class AuthorTag(Tag): @@ -53,10 +53,10 @@ def __init__(self, name, description): @classmethod def from_lines(cls, lines): - name, description = '', [] + name, description = "", [] for line in lines: - if line.tag == 'env': - split = line.value.split(' ', 1) + if line.tag == "env": + split = line.value.split(" ", 1) if len(split) > 1: name = split[0] description.append(split[1]) @@ -64,7 +64,7 @@ def from_lines(cls, lines): name = split[0] else: description.append(line.value) - return EnvTag(name=name, description='\n'.join(description)) + return EnvTag(name=name, description="\n".join(description)) class ErrorTag(Tag): @@ -82,30 +82,31 @@ def from_lines(cls, lines): title, code, explanation = [], [], [] current = None for line in lines: - if line.tag == 'example': + if line.tag == "example": if line.value: title.append(line.value) - current = 'title' - elif line.tag == 'example-code': + current = "title" + elif line.tag == "example-code": if line.value: code.append(line.value) - current = 'code' - elif line.tag == 'example-explanation': + current = "code" + elif line.tag == "example-explanation": if line.value: explanation.append(line.value) - current = 'explanation' + current = "explanation" elif not line.tag and line.value: - if current == 'title': + if current == "title": title.append(line.value) - elif current == 'code': + elif current == "code": code.append(line.value) - elif current == 'explanation': + elif current == "explanation": explanation.append(line.value) return ExampleTag( - title='\n'.join(title), - code='\n'.join(code), - explanation='\n'.join(explanation)) + title="\n".join(title), + code="\n".join(code), + explanation="\n".join(explanation), + ) class ExitTag: @@ -115,10 +116,10 @@ def __init__(self, code, description): @classmethod def from_lines(cls, lines): - code, description = '', [] + code, description = "", [] for line in lines: - if line.tag == 'exit': - split = line.value.split(' ', 1) + if line.tag == "exit": + split = line.value.split(" ", 1) if len(split) > 1: code = split[0] description.append(split[1]) @@ -126,7 +127,7 @@ def from_lines(cls, lines): code = split[0] else: description.append(line.value) - return ExitTag(code=code, description='\n'.join(description)) + return ExitTag(code=code, description="\n".join(description)) class FileTag: @@ -136,10 +137,10 @@ def __init__(self, name, description): @classmethod def from_lines(cls, lines): - name, description = '', [] + name, description = "", [] for line in lines: - if line.tag == 'file': - split = line.value.split(' ', 1) + if line.tag == "file": + split = line.value.split(" ", 1) if len(split) > 1: name = split[0] description.append(split[1]) @@ -147,12 +148,23 @@ def from_lines(cls, lines): name = split[0] else: description.append(line.value) - return FileTag(name=name, description='\n'.join(description)) + return FileTag(name=name, description="\n".join(description)) class FunctionTag: - def __init__(self, prototype, brief, description, arguments, preconditions, - return_codes, seealso, stderr, stdin, stdout): + def __init__( + self, + prototype, + brief, + description, + arguments, + preconditions, + return_codes, + seealso, + stderr, + stdin, + stdout, + ): self.prototype = prototype self.brief = brief self.description = description @@ -166,8 +178,8 @@ def __init__(self, prototype, brief, description, arguments, preconditions, @classmethod def from_lines(cls, lines): - brief = '' - prototype = '' + brief = "" + prototype = "" description = [] arguments = [] return_codes = [] @@ -177,25 +189,25 @@ def from_lines(cls, lines): stdin = [] stdout = [] for line in lines: - if line.tag == 'function': + if line.tag == "function": prototype = line.value - elif line.tag == 'function-brief': + elif line.tag == "function-brief": brief = line.value - elif line.tag == 'function-description': + elif line.tag == "function-description": description.append(line.value) - elif line.tag == 'function-argument': + elif line.tag == "function-argument": arguments.append(line.value) - elif line.tag == 'function-precondition': + elif line.tag == "function-precondition": preconditions.append(line.value) - elif line.tag == 'function-return': + elif line.tag == "function-return": return_codes.append(line.value) - elif line.tag == 'function-seealso': + elif line.tag == "function-seealso": seealso.append(line.value) - elif line.tag == 'function-stderr': + elif line.tag == "function-stderr": stderr.append(line.value) - elif line.tag == 'function-stdin': + elif line.tag == "function-stdin": stdin.append(line.value) - elif line.tag == 'function-stdout': + elif line.tag == "function-stdout": stdout.append(line.value) else: description.append(line.value) @@ -203,14 +215,15 @@ def from_lines(cls, lines): return FunctionTag( prototype=prototype, brief=brief, - description='\n'.join(description), + description="\n".join(description), arguments=arguments, preconditions=preconditions, return_codes=return_codes, seealso=seealso, stderr=stderr, stdin=stdin, - stdout=stdout) + stdout=stdout, + ) class HistoryTag(Tag): @@ -239,17 +252,17 @@ def __init__(self, short, long, positional, default, group, description): def signature(self): if self.__signature is not None: return self.__signature - sign = '' + sign = "" if self.short: sign = self.short if self.long: - sign += ', ' + sign += ", " elif self.positional: - sign += ' ' + sign += " " if self.long: if not self.short: - sign += ' ' - sign += self.long + ' ' + sign += " " + sign += self.long + " " if self.positional: sign += self.positional self.__signature = sign @@ -257,30 +270,37 @@ def signature(self): @classmethod def from_lines(cls, lines): - short, long, positional, default, group = '', '', '', '', '' + short, long, positional, default, group = "", "", "", "", "" description = [] for line in lines: - if line.tag == 'option': + if line.tag == "option": search = re.search( - r'^(?P-\w)?' - r'(?:, )?' - r'(?P--[\w-]+)? ?' - r'(?P.+)?', line.value) + r"^(?P-\w)?" + r"(?:, )?" + r"(?P--[\w-]+)? ?" + r"(?P.+)?", + line.value, + ) if search: - short, long, positional = search.groups(default='') + short, long, positional = search.groups(default="") else: positional = line.value - elif line.tag == 'option-default': + elif line.tag == "option-default": default = line.value - elif line.tag == 'option-group': + elif line.tag == "option-group": group = line.value - elif line.tag == 'option-description' and line.value: + elif line.tag == "option-description" and line.value: description.append(line.value) else: description.append(line.value) return OptionTag( - short=short, long=long, positional=positional, - default=default, group=group, description='\n'.join(description)) + short=short, + long=long, + positional=positional, + default=default, + group=group, + description="\n".join(description), + ) class SeealsoTag(Tag): @@ -307,8 +327,8 @@ def __init__(self, program, command): @classmethod def from_lines(cls, lines): # TODO: only first line kept. Change it? - program, command = '', '' - split = lines[0].value.split(' ', 1) + program, command = "", "" + split = lines[0].value.split(" ", 1) if len(split) > 1: program, command = split else: @@ -328,27 +348,27 @@ def from_lines(cls, lines): TAGS = { None: Tag, - 'author': AuthorTag, - 'bug': BugTag, - 'brief': BriefTag, - 'caveat': CaveatTag, - 'copyright': CopyrightTag, - 'date': DateTag, - 'desc': DescTag, - 'env': EnvTag, - 'error': ErrorTag, - 'example': ExampleTag, - 'exit': ExitTag, - 'file': FileTag, - 'function': FunctionTag, - 'history': HistoryTag, - 'license': LicenseTag, - 'note': NoteTag, - 'option': OptionTag, - 'seealso': SeealsoTag, - 'stderr': StderrTag, - 'stdin': StdinTag, - 'stdout': StdoutTag, - 'usage': UsageTag, - 'version': VersionTag, + "author": AuthorTag, + "bug": BugTag, + "brief": BriefTag, + "caveat": CaveatTag, + "copyright": CopyrightTag, + "date": DateTag, + "desc": DescTag, + "env": EnvTag, + "error": ErrorTag, + "example": ExampleTag, + "exit": ExitTag, + "file": FileTag, + "function": FunctionTag, + "history": HistoryTag, + "license": LicenseTag, + "note": NoteTag, + "option": OptionTag, + "seealso": SeealsoTag, + "stderr": StderrTag, + "stdin": StdinTag, + "stdout": StdoutTag, + "usage": UsageTag, + "version": VersionTag, } diff --git a/src/shellman/templates/__init__.py b/src/shellman/templates/__init__.py index 867ce19..bae3a9e 100644 --- a/src/shellman/templates/__init__.py +++ b/src/shellman/templates/__init__.py @@ -1,4 +1,6 @@ import os +from copy import deepcopy +from datetime import date import pkg_resources from jinja2 import Environment, FileSystemLoader @@ -7,68 +9,68 @@ from .filters import FILTERS -SECTION_ORDER = [ - 'brief', - 'usage', - 'desc', - 'option', - 'env', - 'file', - 'exit', - 'stdin', - 'stdout', - 'stderr', - 'function', - 'example', - 'error', - 'bug', - 'caveat', - 'author', - 'copyright', - 'license', - 'history', - 'note', - 'seealso', -] - - class Template: - def __init__(self, name, directory='', base_template='', default_format=''): + def __init__( + self, name, directory="", base_template="", formats=None, context=None + ): self.name = name self.directory = directory or get_builtin_path(name) self.base_template = base_template or name - self.default_format = default_format + self.formats = formats or [] + self.context = context or {} + + # FIXME: might need to get this out for performance when multiple file inputs/outputs self.env = Environment( loader=FileSystemLoader(self.directory), trim_blocks=True, lstrip_blocks=True, keep_trailing_newline=True, - optimized=True + auto_reload=False, ) self.env.filters.update(FILTERS) - def get(self, format=None): - file_name = self.base_template + (format or self.default_format) + def get(self, format=""): + if format or self.formats: + format = "." + (format or self.formats[0]) + file_name = self.base_template + format return self.env.get_template(file_name) + def get_context(self, format=""): + # Initialize with generic context + format_context = deepcopy(self.context) + for f in self.formats: + if f in format_context: + del format_context[f] + # Update with specific context + if format in self.context: + format_context.update(self.context[format]) + return format_context + + def render(self, format="", **kwargs): + return ( + self.get(format) + .render(context=self.get_context(format), now=date.today(), **kwargs) + .rstrip("\n") + ) + def get_plugin_templates(): plugins = [] - for entry_point in pkg_resources.iter_entry_points(group='shellman'): + for entry_point in pkg_resources.iter_entry_points(group="shellman"): obj = entry_point.load() - if issubclass(obj, Template): + if isinstance(obj, Template): plugins.append(obj) return plugins -def get_builtin_path(subdirectory=''): +def get_builtin_path(subdirectory=""): return os.path.join(os.path.dirname(os.path.abspath(__file__)), subdirectory) def get_custom_template(base_template_path): directory, base_template = os.path.split(base_template_path) try: - return Template('', directory, base_template).get() + return Template("", directory, base_template).get() except TemplateNotFound: raise FileNotFoundError @@ -80,14 +82,29 @@ def names(): def parser_choices(): class TemplateChoice(tuple): def __contains__(self, item): - return super(TemplateChoice, self).__contains__(item) or item.startswith('path:') + return super(TemplateChoice, self).__contains__(item) or item.startswith( + "path:" + ) + return TemplateChoice(names()) builtin_templates = [ - Template(name='helptext', default_format='.txt'), - Template(name='manpage', default_format='.groff'), - Template(name='wikipage', default_format='.md'), + Template( + name="helptext", + formats=["txt"], + context={"indent": 2, "indent_str": " ", "option_padding": 22}, + ), + Template( + name="manpage", + formats=["groff", "1", "3", "md"], + context={"indent": 4, "indent_str": " "}, + ), + Template(name="wikipage", formats=["md"]), ] + plugin_templates = get_plugin_templates() -templates = {t.name: t for t in builtin_templates + plugin_templates} +templates = {t.name: t for t in builtin_templates} + +# Update with plugin templates (they are allowed to override builtin ones) +templates.update({t.name: t for t in plugin_templates}) diff --git a/src/shellman/templates/filters.py b/src/shellman/templates/filters.py index 4497c34..8312111 100644 --- a/src/shellman/templates/filters.py +++ b/src/shellman/templates/filters.py @@ -7,61 +7,62 @@ from jinja2.filters import make_attrgetter, _GroupTuple, environmentfilter -def groff_auto_escape(string): - return string\ - .replace('-', '\\-')\ - .replace("'", "\\'")\ - .replace('"', '\\"')\ - .replace('.', '\\.')\ - .replace('$', '\\f$') +def do_groffautoescape(string): + return ( + string.replace("-", "\\-") + .replace("'", "\\'") + .replace('"', '\\"') + .replace(".", "\\.") + .replace("$", "\\f$") + ) -def groff_strong(string): - return '\\fB' + string + '\\fR' +def do_groffstrong(string): + return "\\fB" + string + "\\fR" -def groff_emphasis(string): - return '\\fI' + string + '\\fR' +def do_groffemphasis(string): + return "\\fI" + string + "\\fR" -def groff_auto_emphasis(string): - return re.sub(r'(\b[A-Z_0-9]{2,}\b)', r'\\fI\1\\fR', string) +def do_groffautoemphasis(string): + return re.sub(r"(\b[A-Z_0-9]{2,}\b)", r"\\fI\1\\fR", string) -def groff_auto_strong(string): - return re.sub(r'(--?[\w-]+=?)', r'\\fB\1\\fR', string) +def do_groffautostrong(string): + return re.sub(r"(--?[\w-]+=?)", r"\\fB\1\\fR", string) -def groff_auto(string, escape=True): - string = groff_auto_emphasis(string) - string = groff_auto_strong(string) +def do_groffauto(string, escape=True): + string = do_groffautoemphasis(string) + string = do_groffautostrong(string) if escape: - string = groff_auto_escape(string) + string = do_groffautoescape(string) return string -def first_word(string, delimiter=' '): - return string.split(delimiter)[0] +def do_firstword(string, delimiters=" "): + # FIXME: maybe use a regex instead: ^[\w_]+ + for i, char in enumerate(string): + if char in delimiters: + return string[:i] + return string -def body(string_or_list, delimiter=' '): +def do_body(string_or_list, delimiter=" "): if isinstance(string_or_list, str): return string_or_list.split(delimiter, 1)[1] elif isinstance(string_or_list, list): - return '\n'.join(string_or_list[1:]) + return "\n".join(string_or_list[1:]) -def first_line(string_or_list): +def do_firstline(string_or_list): if isinstance(string_or_list, str): - return string_or_list.split('\n', 1)[0] + return string_or_list.split("\n", 1)[0] elif isinstance(string_or_list, list): return string_or_list[0] -def trim_join(string_list, join_str=' '): - return join_str.join(s.trim() for s in string_list) - - def console_width(default=80): """ Return current console width. @@ -77,7 +78,7 @@ def console_width(default=80): return shutil.get_terminal_size((default, 20)).columns -def smart_width(text, indent=4, width=None, indentfirst=True): +def do_smartwrap(text, indent=4, width=None, indentfirst=True): if width is None or width < 0: c_width = console_width(default=79) if width is None: @@ -85,12 +86,12 @@ def smart_width(text, indent=4, width=None, indentfirst=True): else: width += c_width - indent_str = indent * ' ' + indent_str = indent * " " to_join = defaultdict(lambda: False) - lines = text.split('\n') + lines = text.split("\n") previous = True for i, line in enumerate(lines): - if not (line == '' or line[0] in (' ', '\t')): + if not (line == "" or line[0] in (" ", "\t")): if previous: to_join[i] = True previous = True @@ -99,57 +100,67 @@ def smart_width(text, indent=4, width=None, indentfirst=True): joined_lines = [lines[0]] for i in range(1, len(lines)): if to_join[i]: - joined_lines.append(' ' + lines[i]) + joined_lines.append(" " + lines[i]) else: - joined_lines.append('\n' + lines[i]) - new_text = ''.join(joined_lines) - new_text_lines = new_text.split('\n') + joined_lines.append("\n" + lines[i]) + new_text = "".join(joined_lines) + new_text_lines = new_text.split("\n") wrap_indented_text_lines = [] for line in new_text_lines: - if not (line == '' or line[0] in (' ', '\t')): + if not (line == "" or line[0] in (" ", "\t")): wrap_indented_text_lines.append( textwrap.fill( line, width, - initial_indent=indent_str if indentfirst else '', - subsequent_indent=indent_str + initial_indent=indent_str if indentfirst else "", + subsequent_indent=indent_str, ) ) else: wrap_indented_text_lines.append(indent_str + line) - return '\n'.join(wrap_indented_text_lines) + return "\n".join(wrap_indented_text_lines) -def format(s, *args, **kwargs): +# Override Jinja2's format filter to use format method instead of % operator +def do_format(s, *args, **kwargs): return s.format(*args, **kwargs) +# Override Jinja2's groupby filter to add un(sort) option @environmentfilter -def groupby_unsorted(environment, value, attribute): +def do_groupby(environment, value, attribute, sort=True): expr = make_attrgetter(environment, attribute) - groups_all = [expr(i) for i in value] - groups_set = set() - groups_unique = [] - for group in groups_all: - if group not in groups_set: - groups_unique.append(group) - groups_set.add(group) + + # Original behavior: groups are sorted + if sort: + return [ + _GroupTuple(key, list(values)) + for key, values in groupby(sorted(value, key=expr), expr) + ] + + # Added behavior: original order of appearance is kept + all_groups = [expr(_) for _ in value] + group_set = set() + unique_groups = [] + for group in all_groups: + if group not in group_set: + unique_groups.append(group) + group_set.add(group) grouped = {k: list(v) for k, v in groupby(sorted(value, key=expr), expr)} - return [_GroupTuple(group, grouped[group]) for group in groups_unique] + return [_GroupTuple(group, grouped[group]) for group in unique_groups] FILTERS = { - 'groff_auto_escape': groff_auto_escape, - 'groff_strong': groff_strong, - 'groff_emphasis': groff_emphasis, - 'groff_auto_strong': groff_auto_strong, - 'groff_auto_emphasis': groff_auto_emphasis, - 'groff_auto': groff_auto, - 'first_word': first_word, - 'first_line': first_line, - 'body': body, - 'trim_join': trim_join, - 'smart_width': smart_width, - 'format': format, - 'groupby_unsorted': groupby_unsorted + "groffstrong": do_groffstrong, + "groffemphasis": do_groffemphasis, + "groffautostrong": do_groffautostrong, + "groffautoemphasis": do_groffautoemphasis, + "groffautoescape": do_groffautoescape, + "groffauto": do_groffauto, + "groupby": do_groupby, + "firstword": do_firstword, + "firstline": do_firstline, + "body": do_body, + "smartwrap": do_smartwrap, + "format": do_format, } diff --git a/src/shellman/templates/helptext/function.txt b/src/shellman/templates/helptext/function.txt index 72c798e..21d29f5 100644 --- a/src/shellman/templates/helptext/function.txt +++ b/src/shellman/templates/helptext/function.txt @@ -2,14 +2,14 @@ {{ context.indent_str * 2 }}{{ function.brief }} {% if function.description %} -{{ function.description|smart_width(context.indent*2) }} +{{ function.description|smartwrap(context.indent*2) }} {% endif %} {% if function.arguments %} {{ context.indent_str * 2 }}Arguments: -{% with longest = function.arguments|map('first_word')|map('length')|max %} +{% with longest = function.arguments|map('firstword')|map('length')|max %} {% for argument in function.arguments %} -{{ context.indent_str * 3 }}{{ "{a:{w}}"|format(a=argument|first_word, w=longest) }} - {{ argument|body }} +{{ context.indent_str * 3 }}{{ "{a:{w}}"|format(a=argument|firstword, w=longest) }} - {{ argument|body }} {% endfor %} {% endwith %} @@ -17,7 +17,7 @@ {% if function.return_codes %} {{ context.indent_str * 2 }}Return codes: {% for return_code in function.return_codes %} -{{ context.indent_str * 3 }}{{ return_code|first_word }} - {{ return_code|body }} +{{ context.indent_str * 3 }}{{ return_code|firstword }} - {{ return_code|body }} {% endfor %} {% endif %} diff --git a/src/shellman/templates/helptext/helptext.txt b/src/shellman/templates/helptext/helptext.txt index 4ef6dce..2ff0a3c 100644 --- a/src/shellman/templates/helptext/helptext.txt +++ b/src/shellman/templates/helptext/helptext.txt @@ -7,20 +7,19 @@ Usage: {{ doc.sections.usage[0].program }} {{ doc.sections.usage[0].command }} {% endif %} {% if doc.sections.desc %} {% for desc in doc.sections.desc %} -{{ desc.text|smart_width(0) }} +{{ desc.text|smartwrap(0) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} {% endif %} {% if doc.sections.option %} Options: -{% set width=22 %} -{% for opt_group, opt_list in doc.sections.option|groupby_unsorted('group') %} +{% for opt_group, opt_list in doc.sections.option|groupby('group', sort=False) %} {% if opt_group %} {{ ' ' * (context.indent / 2)|int }}{{ opt_group }}: {% endif %} {% for option in opt_list %} -{{ context.indent_str }}{{ "{sign:{width}}"|format(sign=option.signature, width=width-1-context.indent) }} {% if option.signature|length > width-2-context.indent %}{{ '\n' + option.description|smart_width(context.indent + width-context.indent) + '\n' }}{% else %}{{ option.description|smart_width(context.indent + width-context.indent, indentfirst=False) + '\n' }}{% endif %} +{{ context.indent_str }}{{ "{sign:{padding}}"|format(sign=option.signature, padding=context.option_padding-1) }} {% if option.signature|length > context.option_padding-2 %}{{ '\n' + option.description|smartwrap(context.indent + context.option_padding) + '\n' }}{% else %}{{ option.description|smartwrap(context.indent + context.option_padding, indentfirst=False) + '\n' }}{% endif %} {% endfor %} {% if not loop.last %}{{ '\n' }}{% endif %} {% endfor %} @@ -30,7 +29,7 @@ Options: Environment Variables: {% for env in doc.sections.env %} {{ context.indent_str }}{{ env.name }} -{{ env.description|smart_width(context.indent*2) }} +{{ env.description|smartwrap(context.indent*2) }} {% endfor %} {% endif %} @@ -38,7 +37,7 @@ Environment Variables: Files: {% for file in doc.sections.file %} {{ context.indent_str }}{{ file.name }} -{{ file.description|smart_width(context.indent*2) }} +{{ file.description|smartwrap(context.indent*2) }} {% endfor %} {% endif %} @@ -46,17 +45,32 @@ Files: Exit Status: {% for exit in doc.sections.exit %} {{ context.indent_str }}{{ exit.code }} -{{ exit.description|smart_width(context.indent*2) }} +{{ exit.description|smartwrap(context.indent*2) }} {% endfor %} {% endif %} {% if doc.sections.stdin %} +Standard Input: +{% for stdin in doc.sections.stdin %} +{{ stdin.text|smartwrap(context.indent) }} +{% if not loop.last %}{{ "\n" }}{% endif %} +{% endfor %} {% endif %} {% if doc.sections.stdout %} +Standard Output: +{% for stdout in doc.sections.stdout %} +{{ stdout.text|smartwrap(context.indent) }} +{% if not loop.last %}{{ "\n" }}{% endif %} +{% endfor %} {% endif %} {% if doc.sections.stderr %} +Standard Error: +{% for stderr in doc.sections.stderr %} +{{ stderr.text|smartwrap(context.indent) }} +{% if not loop.last %}{{ "\n" }}{% endif %} +{% endfor %} {% endif %} {% if doc.sections.function %} @@ -70,9 +84,9 @@ Functions: Examples: {% for example in doc.sections.example %} {{ context.indent_str }}{{ example.title }} -{% if example.code %}{{ "\n" + example.code|smart_width(context.indent*2) + "\n\n" }}{% endif %} +{% if example.code %}{{ "\n" + example.code|smartwrap(context.indent*2) + "\n\n" }}{% endif %} {% if example.explanation %} -{{ example.explanation|smart_width(context.indent*2) }} +{{ example.explanation|smartwrap(context.indent*2) }} {% endif %} {% endfor %} {% endif %} @@ -80,7 +94,7 @@ Examples: {% if doc.sections.error %} Errors: {% for error in doc.sections.error %} -{{ error.description|smart_width(context.indent) }} +{{ error.text|smartwrap(context.indent) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -88,7 +102,7 @@ Errors: {% if doc.sections.bug %} Bugs: {% for bug in doc.sections.bug %} -{{ bug.description|smart_width(context.indent) }} +{{ bug.text|smartwrap(context.indent) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -96,7 +110,7 @@ Bugs: {% if doc.sections.caveat %} Caveats: {% for caveat in doc.sections.caveat %} -{{ caveat.description|smart_width(context.indent) }} +{{ caveat.text|smartwrap(context.indent) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -112,7 +126,7 @@ Authors: {% if doc.sections.copyright %} Copyright: {% for copyright in doc.sections.copyright %} -{{ copyright.text|smart_width(context.indent) }} +{{ copyright.text|smartwrap(context.indent) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -120,7 +134,7 @@ Copyright: {% if doc.sections.license %} License: {% for license in doc.sections.license %} -{{ license.text|smart_width(context.indent) }} +{{ license.text|smartwrap(context.indent) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -128,7 +142,7 @@ License: {% if doc.sections.history %} History: {% for history in doc.sections.history %} -{{ history.text|smart_width(context.indent) }} +{{ history.text|smartwrap(context.indent) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -136,7 +150,7 @@ History: {% if doc.sections.note %} Notes: {% for note in doc.sections.note %} -{{ note.text|smart_width(context.indent) }} +{{ note.text|smartwrap(context.indent) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -144,7 +158,7 @@ Notes: {% if doc.sections.seealso %} See Also: {% for seealso in doc.sections.seealso %} -{{ seealso.text|smart_width(context.indent) }} +{{ seealso.text|smartwrap(context.indent) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} {% endif %} diff --git a/src/shellman/templates/manpage/function.groff b/src/shellman/templates/manpage/function.groff index daa5061..452339f 100644 --- a/src/shellman/templates/manpage/function.groff +++ b/src/shellman/templates/manpage/function.groff @@ -1,4 +1,4 @@ -.IP "{{ function.prototype|groff_auto_escape|groff_strong }}" {{ context.indent }} +.IP "{{ function.prototype|groffautoescape|groffstrong }}" {{ context.indent }} {{ function.brief }} {% if function.description %} @@ -7,9 +7,9 @@ {% endif %} {% if function.arguments %} .I Arguments -{% with longest = function.arguments|map('first_word')|map('length')|max %} +{% with longest = function.arguments|map('firstword')|map('length')|max %} {% for argument in function.arguments %} -{{ context.indent_str }}{{ "{a:{w}}"|format(a=argument|first_word, w=longest)|groff_strong }} - {{ argument|body }} +{{ context.indent_str }}{{ "{a:{w}}"|format(a=argument|firstword, w=longest)|groffstrong }} - {{ argument|body }} {% endfor %} {% endwith %} @@ -17,7 +17,7 @@ {% if function.return_codes %} .I Return codes {% for return_code in function.return_codes %} -{{ context.indent_str }}{{ return_code|first_word|groff_strong }} - {{ return_code|body }} +{{ context.indent_str }}{{ return_code|firstword|groffstrong }} - {{ return_code|body }} {% endfor %} {% endif %} diff --git a/src/shellman/templates/manpage/function.md b/src/shellman/templates/manpage/function.md index bfbb43b..0835b85 100644 --- a/src/shellman/templates/manpage/function.md +++ b/src/shellman/templates/manpage/function.md @@ -8,14 +8,14 @@ {% if function.arguments %} *Arguments* {% for argument in function.arguments %} -**{{ argument|first_word }}** - {{ argument|body }} +**{{ argument|firstword }}** - {{ argument|body }} {% endfor %} {% endif %} {% if function.return_codes %} *Return codes* {% for return_code in function.return_codes %} -**{{ return_code|first_word|e }}** - {{ return_code|body|e }} +**{{ return_code|firstword|e }}** - {{ return_code|body|e }} {% endfor %} {% endif %} diff --git a/src/shellman/templates/manpage/manpage.1 b/src/shellman/templates/manpage/manpage.1 new file mode 120000 index 0000000..3cfa8be --- /dev/null +++ b/src/shellman/templates/manpage/manpage.1 @@ -0,0 +1 @@ +manpage.groff \ No newline at end of file diff --git a/src/shellman/templates/manpage/manpage.3 b/src/shellman/templates/manpage/manpage.3 new file mode 120000 index 0000000..3cfa8be --- /dev/null +++ b/src/shellman/templates/manpage/manpage.3 @@ -0,0 +1 @@ +manpage.groff \ No newline at end of file diff --git a/src/shellman/templates/manpage/manpage.groff b/src/shellman/templates/manpage/manpage.groff index e967d75..d3204d0 100644 --- a/src/shellman/templates/manpage/manpage.groff +++ b/src/shellman/templates/manpage/manpage.groff @@ -13,7 +13,7 @@ Version {{ doc.sections.version[0].text }} - {% endif -%} {% if doc.sections.usage %} .SH "SYNOPSIS" {% for usage in doc.sections.usage %} -{{ usage.program|groff_strong }} {{ usage.command|groff_auto }} +{{ usage.program|groffstrong }} {{ usage.command|groffauto }} {% if not loop.last %} .br {% endif %} @@ -30,19 +30,18 @@ Version {{ doc.sections.version[0].text }} - {% endif -%} {% endif %} {% if doc.sections.option %} .SH "OPTIONS" -{% set width=context.indent*2 %} -{% for opt_group, opt_list in doc.sections.option|groupby_unsorted('group') %} +{% for opt_group, opt_list in doc.sections.option|groupby('group', sort=False) %} {% if opt_group %} .SS "{{ opt_group }}" {% endif %} {% for option in opt_list %} {% with sign = option.signature.lstrip() %} -{% if sign|length > width-2-context.indent %} -.IP "{{ sign|groff_auto }}" {{ context.indent }} +{% if sign|length > context.indent-2 %} +.IP "{{ sign|groffauto }}" {{ context.indent }} {{ option.description + '\n' }} {% else %} .TP -{{ "{sign:{width}}"|format(sign=sign, width=width-1-context.indent)|groff_auto }} {{ option.description }} +{{ "{sign:{padding}}"|format(sign=sign, padding=context.indent-1)|groffauto }} {{ option.description }} {% endif %} {% endwith %} {% endfor %} @@ -77,12 +76,27 @@ Version {{ doc.sections.version[0].text }} - {% endif -%} {% endif %} {% if doc.sections.stdin %} +.SH "STANDARD INPUT" +{% for stdin in doc.sections.stdin %} +{{ stdin.text }} +{% if not loop.last %}{{ "\n" }}{% endif %} +{% endfor %} {% endif %} {% if doc.sections.stdout %} +.SH "STANDARD OUTPUT" +{% for stdout in doc.sections.stdout %} +{{ stdout.text }} +{% if not loop.last %}{{ "\n" }}{% endif %} +{% endfor %} {% endif %} {% if doc.sections.stderr %} +.SH "STANDARD ERROR" +{% for stderr in doc.sections.stderr %} +{{ stderr.text }} +{% if not loop.last %}{{ "\n" }}{% endif %} +{% endfor %} {% endif %} {% if doc.sections.function %} @@ -95,7 +109,7 @@ Version {{ doc.sections.version[0].text }} - {% endif -%} {% if doc.sections.example %} .SH "EXAMPLES" {% for example in doc.sections.example %} -.IP "{{ example.title|groff_strong }}" {{ context.indent }} +.IP "{{ example.title|groffstrong }}" {{ context.indent }} {% if example.code %}{{ "\n" + example.code + "\n\n" }}{% endif %} {% if example.explanation %}{{ example.explanation + "\n" }}{% endif %} {% if not loop.last %}{{ "\n" }}{% endif %} @@ -105,7 +119,7 @@ Version {{ doc.sections.version[0].text }} - {% endif -%} {% if doc.sections.error %} .SH "ERRORS" {% for error in doc.sections.error %} -{{ error.description }} +{{ error.text }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -113,7 +127,7 @@ Version {{ doc.sections.version[0].text }} - {% endif -%} {% if doc.sections.bug %} .SH "BUGS" {% for bug in doc.sections.bug %} -{{ bug.description }} +{{ bug.text }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -121,7 +135,7 @@ Version {{ doc.sections.version[0].text }} - {% endif -%} {% if doc.sections.caveat %} .SH "CAVEATS" {% for caveat in doc.sections.caveat %} -{{ caveat.description }} +{{ caveat.text }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} diff --git a/src/shellman/templates/manpage/manpage.md b/src/shellman/templates/manpage/manpage.md index 5764bc1..90d744e 100644 --- a/src/shellman/templates/manpage/manpage.md +++ b/src/shellman/templates/manpage/manpage.md @@ -21,7 +21,7 @@ {% endif %} {% if doc.sections.option %} **OPTIONS** -{% for opt_group, opt_list in doc.sections.option|groupby_unsorted('group') %} +{% for opt_group, opt_list in doc.sections.option|groupby('group', sort=False) %} {% if opt_group %} *{{ opt_group }}* {% endif %} @@ -61,12 +61,27 @@ {% endif %} {% if doc.sections.stdin %} +**STANDARD INPUT** +{% for stdin in doc.sections.stdin %} +{{ stdin.text|e }} +{% if not loop.last %}{{ "\n" }}{% endif %} +{% endfor %} {% endif %} {% if doc.sections.stdout %} +**STANDARD OUTPUT** +{% for stdout in doc.sections.stdout %} +{{ stdout.text|e }} +{% if not loop.last %}{{ "\n" }}{% endif %} +{% endfor %} {% endif %} {% if doc.sections.stderr %} +**STANDARD ERROR** +{% for stderr in doc.sections.stderr %} +{{ stderr.text|e }} +{% if not loop.last %}{{ "\n" }}{% endif %} +{% endfor %} {% endif %} {% if doc.sections.function %} @@ -95,7 +110,7 @@ {% if doc.sections.error %} **ERRORS** {% for error in doc.sections.error %} -{{ error.description|e }} +{{ error.text|e }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -103,7 +118,7 @@ {% if doc.sections.bug %} **BUGS** {% for bug in doc.sections.bug %} -{{ bug.description|e }} +{{ bug.text|e }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -111,7 +126,7 @@ {% if doc.sections.caveat %} **CAVEATS** {% for caveat in doc.sections.caveat %} -{{ caveat.description|e }} +{{ caveat.text|e }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} diff --git a/src/shellman/templates/wikipage/function.md b/src/shellman/templates/wikipage/function.md index 7374f05..990c1f9 100644 --- a/src/shellman/templates/wikipage/function.md +++ b/src/shellman/templates/wikipage/function.md @@ -8,14 +8,14 @@ {% if function.arguments %} #### Arguments {% for argument in function.arguments %} -- **`{{ argument|first_word }}`**: {{ argument|body }} +- **`{{ argument|firstword }}`**: {{ argument|body }} {% endfor %} {% endif %} {% if function.return_codes %} #### Return codes {% for return_code in function.return_codes %} -- **`{{ return_code|first_word }}`**: {{ return_code|body }} +- **`{{ return_code|firstword }}`**: {{ return_code|body }} {% endfor %} {% endif %} diff --git a/src/shellman/templates/wikipage/wikipage.md b/src/shellman/templates/wikipage/wikipage.md index 4be78e7..fda2676 100644 --- a/src/shellman/templates/wikipage/wikipage.md +++ b/src/shellman/templates/wikipage/wikipage.md @@ -22,13 +22,13 @@ {% endif %} {% if doc.sections.option %} ## Options -{% for opt_group, opt_list in doc.sections.option|groupby_unsorted('group') %} +{% for opt_group, opt_list in doc.sections.option|groupby('group', sort=False) %} {% if opt_group %} ### {{ opt_group }} {% endif %} {% for option in opt_list %} - {% if option.short %}**`{{ option.short }}`**{% if option.long %},{% endif %} {% endif %}{% if option.long %}**`{{ option.long }}`**{% if option.positional %} {% endif %}{% endif %}{% if option.positional %}*`{{ option.positional }}`*{% endif %}: - {{ option.description|e|indent(context.indent) }} + {{ option.description|e|indent(2) }} {% endfor %} {% if not loop.last %}{{ '\n' }}{% endif %} {% endfor %} @@ -38,7 +38,7 @@ ## Environment Variables {% for env in doc.sections.env %} - *`{{ env.name }}`*: - {{ env.description|e|indent(context.indent) }} + {{ env.description|e|indent(2) }} {% endfor %} {% endif %} @@ -46,7 +46,7 @@ ## Files {% for file in doc.sections.file %} - *`{{ file.name }}`*: - {{ file.description|e|indent(context.indent) }} + {{ file.description|e|indent(2) }} {% endfor %} {% endif %} @@ -54,17 +54,29 @@ ## Exit Status {% for exit in doc.sections.exit %} - **`{{ exit.code }}`**: - {{ exit.description|e|indent(context.indent) }} + {{ exit.description|e|indent(2) }} {% endfor %} {% endif %} {% if doc.sections.stdin %} +## Standard Input +{% for stdin in doc.sections.stdin %} +{{ stdin.text|e }} +{% if not loop.last %}{{ "\n" }}{% endif %} {% endif %} {% if doc.sections.stdout %} +## Standard Output +{% for stdout in doc.sections.stdout %} +{{ stdout.text|e }} +{% if not loop.last %}{{ "\n" }}{% endif %} {% endif %} {% if doc.sections.stderr %} +## Standard Error +{% for stderr in doc.sections.stderr %} +{{ stderr.text|e }} +{% if not loop.last %}{{ "\n" }}{% endif %} {% endif %} {% if doc.sections.function %} @@ -95,7 +107,7 @@ {% if doc.sections.error %} ## Errors {% for error in doc.sections.error %} -- {{ error.description|e|indent(context.indent) }} +- {{ error.text|e|indent(2) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -103,7 +115,7 @@ {% if doc.sections.bug %} ## Bugs {% for bug in doc.sections.bug %} -- {{ bug.description|e|indent(context.indent) }} +- {{ bug.text|e|indent(2) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -111,7 +123,7 @@ {% if doc.sections.caveat %} ## Caveats {% for caveat in doc.sections.caveat %} -- {{ caveat.description|e|indent(context.indent) }} +- {{ caveat.text|e|indent(2) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %} @@ -119,7 +131,7 @@ {% if doc.sections.author %} ## Authors {% for author in doc.sections.author %} -- {{ author.text|indent(context.indent) }} +- {{ author.text|indent(2) }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endfor %}