diff --git a/doc/toolchain/bin/help2adoc b/doc/toolchain/bin/help2adoc new file mode 100755 index 0000000000..feeb5ebb62 --- /dev/null +++ b/doc/toolchain/bin/help2adoc @@ -0,0 +1,5 @@ +#!/bin/bash +set -e +SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") +export PYTHONPATH=$(readlink -f "${SCRIPT_DIR}"/../lib) +python3 -m help2adoc.main "$@" diff --git a/doc/toolchain/lib/help2adoc/__init__.py b/doc/toolchain/lib/help2adoc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/doc/toolchain/lib/help2adoc/generator.py b/doc/toolchain/lib/help2adoc/generator.py new file mode 100644 index 0000000000..a30becc5b3 --- /dev/null +++ b/doc/toolchain/lib/help2adoc/generator.py @@ -0,0 +1,51 @@ +from .parser import Parser + +import typing +import re + + +class AsciiDocGenerator(Parser): + USAGE_RE = re.compile('^usage:\\s*') + + def __init__(self, output: typing.Callable[[str], None]): + self.output = output + + def on_usage(self, text: str): + usage = text[self.USAGE_RE.match(text).end():] + self.output('Usage:\n\n ') + self.output(usage) + self.output('\n\n') + + def on_paragraph(self, text: str): + self.output(self.escape(text)) + self.output('\n\n') + + def enter_options(self): + self.output('Options:\n\n') + + def exit_options(self): + self.output('\n') + + def on_option(self, option: str, help: str): + self.output('* `+++') + self.output(option) + self.output('+++`: ') + self.output(self.escape(help)) + self.output('\n\n') + + def enter_option_group(self, name: str): + self.output(name) + self.output(':\n\n') + + def exit_option_group(self, name: str): + self.output('\n') + + def enter_description(self): + pass + + def exit_description(self): + pass + + def escape(self, text: str): + # TODO + return text diff --git a/doc/toolchain/lib/help2adoc/main.py b/doc/toolchain/lib/help2adoc/main.py new file mode 100644 index 0000000000..ca4526c597 --- /dev/null +++ b/doc/toolchain/lib/help2adoc/main.py @@ -0,0 +1,28 @@ +from .parser import lexer, LookAheadIterator +from .generator import AsciiDocGenerator + +from argparse import ArgumentParser +import sys + +def main(): + ap = ArgumentParser('help2adoc') + ap.add_argument('file') + args = ap.parse_args() + with open(args.file, 'r') as f: + tokens = LookAheadIterator(lexer(f)) + AsciiDocGenerator(sys.stdout.write).parse_help(tokens) + token = tokens.lookahead() + if token is None: + return + epilog_start = token.lineno + f.seek(0) + for i in range(epilog_start): + next(f) + print('....') + for line in f: + print(line, end='') + print('....') + + +if __name__ == '__main__': + main() diff --git a/doc/toolchain/lib/help2adoc/parser.py b/doc/toolchain/lib/help2adoc/parser.py new file mode 100644 index 0000000000..d152417c2e --- /dev/null +++ b/doc/toolchain/lib/help2adoc/parser.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python3 +import typing +import re +import io + + +class Token: + def __init__(self, lineno: int, tpe: str, text: str): + self.tpe = tpe + self.lineno = lineno + self.text = text + + def __str__(self): + return f'Token(tpe={self.tpe}, text={self.text})' + + +class EmptyLineToken(Token): + def __init__(self, lineno: int): + super().__init__(lineno, 'emptyline', '') + + +class TextToken(Token): + def __init__(self, lineno: int, indent: int, text: str): + super().__init__(lineno, 'text', text) + self.indent = indent + + +class LogToken(Token): + def __init__(self, lineno: int, text: str): + super().__init__(lineno, 'log', text) + + +class UsageToken(Token): + def __init__(self, lineno: int, text: str): + super().__init__(lineno, 'usage', text) + + +class OptionsToken(Token): + def __init__(self, lineno: int, text: str): + super().__init__(lineno, 'options', text) + + +class OptionToken(Token): + def __init__(self, lineno: int, text: str): + super().__init__(lineno, 'option', text) + + +def lexer(lines: typing.Iterator[str]): + for lineno, line in enumerate(lines): + line = line.rstrip() + if not line: + yield EmptyLineToken(lineno) + elif re.match('(?:\033\\[\\d+m)?(?:INFO|ERROR)(?:\033\\[0m)?: ', line): + pass + elif re.match('usage: ', line): + yield UsageToken(lineno, line) + elif re.match('options:', line): + yield OptionsToken(lineno, line) + elif re.match('\\s+-', line): + yield OptionToken(lineno, line) + else: + indent = re.search('\\S', line).start() + yield TextToken(lineno, indent, line) + + +T = typing.TypeVar('T') + + +class LookAheadIterator(typing.Generic[T]): + def __init__(self, it: typing.Iterator[T]): + self._it = it + self._lookahead_buffer = list() + self._lookahead_index = -1 + + def lookahead(self): + if self._lookahead_index + 1 == len(self._lookahead_buffer): + try: + ret = next(self._it) + self._lookahead_index += 1 + self._lookahead_buffer.append(ret) + return ret + except StopIteration: + return None + else: + self._lookahead_index += 1 + return self._lookahead_buffer[self._lookahead_index] + + def consume(self): + self._lookahead_buffer = self._lookahead_buffer[self._lookahead_index+1:] + self._lookahead_index = -1 + + def rollback(self): + self._lookahead_index -= 1 + + def reset(self): + self._lookahead_index = -1 + + +class SyntaxErrorException(Exception): + pass + + +class Parser: + def on_usage(self, text: str): + print('') + + def enter_description(self): + print('') + + def exit_description(self): + print('') + + def on_paragraph(self, text: str): + print(f'{text}') + + def enter_options(self): + print('') + + def exit_options(self): + print('') + + def enter_option_group(self, name: str): + print(f'') + + def exit_option_group(self, name: str): + print(f'') + + def on_option(self, option: str, help: str): + print(f'