From 202b805aaf079504a3c8db0eb2a8b14e43fa5bae Mon Sep 17 00:00:00 2001 From: Kefu Chai Date: Thu, 7 Jan 2021 23:43:26 +0800 Subject: [PATCH] doc: build mon_command_api.rst using a homebrew extension so we can build the command doc using ReadTheDoc infra, without adding the generated rst file to the source repo. before this change, all the commands are ordered alphabetically. after this change, command docs are generated by two directives, and are ordered separately. we could restructure the directives and merge them. but let's leave it for a future change if this is important. for more details on writing sphinx directives, see https://www.sphinx-doc.org/en/master/extdev/markupapi.html and https://docutils.sourceforge.io/docs/howto/rst-directives.html Signed-off-by: Kefu Chai --- admin/build-doc | 5 - admin/doc-python-common-requirements.txt | 2 - admin/doc-requirements.txt | 2 + doc/.gitignore | 1 - doc/_ext/ceph_commands.py | 429 ++++++++++++++++++ doc/api/mon_command_api.rst | 2 + doc/conf.py | 1 + doc/scripts/gen_mon_command_api.py | 206 --------- src/script/gen_static_command_descriptions.py | 169 ------- 9 files changed, 434 insertions(+), 383 deletions(-) create mode 100644 doc/_ext/ceph_commands.py create mode 100644 doc/api/mon_command_api.rst delete mode 100644 doc/scripts/gen_mon_command_api.py delete mode 100644 src/script/gen_static_command_descriptions.py diff --git a/admin/build-doc b/admin/build-doc index 3fdca1a4488af..bb2a46282c8b3 100755 --- a/admin/build-doc +++ b/admin/build-doc @@ -64,11 +64,6 @@ install -d -m0755 \ $TOPDIR/build-doc/output/html \ $TOPDIR/build-doc/output/man -# required by script/gen_static_command_descriptions.py, which imports ceph_argparse -export PYTHONPATH=$TOPDIR/src/pybind - -$vdir/bin/python $TOPDIR/doc/scripts/gen_mon_command_api.py > $TOPDIR/doc/api/mon_command_api.rst - for opt in "$@"; do case $opt in html|man|livehtml) diff --git a/admin/doc-python-common-requirements.txt b/admin/doc-python-common-requirements.txt index e4768e3e4300d..21ca6e85c4ae4 100644 --- a/admin/doc-python-common-requirements.txt +++ b/admin/doc-python-common-requirements.txt @@ -1,3 +1 @@ -pcpp -Jinja2 src/python-common diff --git a/admin/doc-requirements.txt b/admin/doc-requirements.txt index 9e1b4d609ee10..3ad2f6edbfcce 100644 --- a/admin/doc-requirements.txt +++ b/admin/doc-requirements.txt @@ -1,8 +1,10 @@ Sphinx == 3.2.1 git+https://github.com/ceph/sphinx-ditaa.git@py3#egg=sphinx-ditaa breathe >= 4.20.0 +Jinja2 pyyaml >= 5.1.2 Cython +pcpp prettytable sphinx-autodoc-typehints sphinx-prompt diff --git a/doc/.gitignore b/doc/.gitignore index 2999c5a872942..0c7c74746ae94 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,3 +1,2 @@ /overview.png /object_store.png -/api/mon_command_api.rst \ No newline at end of file diff --git a/doc/_ext/ceph_commands.py b/doc/_ext/ceph_commands.py new file mode 100644 index 0000000000000..234c5e5833d27 --- /dev/null +++ b/doc/_ext/ceph_commands.py @@ -0,0 +1,429 @@ +import io +import os +import sys +import contextlib + +from docutils import nodes +from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive +from jinja2 import Template +from pcpp.preprocessor import Preprocessor +from sphinx.util import logging +from sphinx.util.console import bold + +logger = logging.getLogger(__name__) + + +class Flags: + NOFORWARD = (1 << 0) + OBSOLETE = (1 << 1) + DEPRECATED = (1 << 2) + MGR = (1 << 3) + POLL = (1 << 4) + HIDDEN = (1 << 5) + + VALS = { + NOFORWARD: 'no_forward', + OBSOLETE: 'obsolete', + DEPRECATED: 'deprecated', + MGR: 'mgr', + POLL: 'poll', + HIDDEN: 'hidden', + } + + def __init__(self, fs): + self.fs = fs + + def __contains__(self, other): + return other in str(self) + + def __str__(self): + keys = Flags.VALS.keys() + es = {Flags.VALS[k] for k in keys if self.fs & k == k} + return ', '.join(sorted(es)) + + def __bool__(self): + return bool(str(self)) + + +class CmdParam(object): + t = { + 'CephInt': 'int', + 'CephString': 'str', + 'CephChoices': 'str', + 'CephPgid': 'str', + 'CephOsdName': 'str', + 'CephPoolname': 'str', + 'CephObjectname': 'str', + 'CephUUID': 'str', + 'CephEntityAddr': 'str', + 'CephIPAddr': 'str', + 'CephName': 'str', + 'CephBool': 'bool', + 'CephFloat': 'float', + 'CephFilepath': 'str', + } + + bash_example = { + 'CephInt': '1', + 'CephString': 'string', + 'CephChoices': 'choice', + 'CephPgid': '0', + 'CephOsdName': 'osd.0', + 'CephPoolname': 'poolname', + 'CephObjectname': 'objectname', + 'CephUUID': 'uuid', + 'CephEntityAddr': 'entityaddr', + 'CephIPAddr': '0.0.0.0', + 'CephName': 'name', + 'CephBool': 'true', + 'CephFloat': '0.0', + 'CephFilepath': '/path/to/file', + } + + def __init__(self, type, name, who=None, n=None, req=True, range=None, strings=None, + goodchars=None): + self.type = type + self.name = name + self.who = who + self.n = n == 'N' + self.req = req != 'false' + self.range = range.split('|') if range else [] + self.strings = strings.split('|') if strings else [] + self.goodchars = goodchars + + assert who == None + + def help(self): + advanced = [] + if self.type != 'CephString': + advanced.append(self.type + ' ') + if self.range: + advanced.append('range= ``{}`` '.format('..'.join(self.range))) + if self.strings: + advanced.append('strings=({}) '.format(' '.join(self.strings))) + if self.goodchars: + advanced.append('goodchars= ``{}`` '.format(self.goodchars)) + if self.n: + advanced.append('(can be repeated)') + + advanced = advanced or ["(string)"] + return ' '.join(advanced) + + def mk_example_value(self): + if self.type == 'CephChoices' and self.strings: + return self.strings[0] + if self.range: + return self.range[0] + return CmdParam.bash_example[self.type] + + def mk_bash_example(self, simple): + val = self.mk_example_value() + + if self.type == 'CephBool': + return '--' + self.name + if simple: + if self.type == "CephChoices" and self.strings: + return val + elif self.type == "CephString" and self.name != 'who': + return 'my_' + self.name + else: + return CmdParam.bash_example[self.type] + else: + return '--{}={}'.format(self.name, val) + + +class CmdCommand(object): + def __init__(self, sig, desc, module=None, perm=None, flags=0, poll=None): + self.sig = [s for s in sig if isinstance(s, str)] + self.params = sorted([CmdParam(**s) for s in sig if not isinstance(s, str)], + key=lambda p: p.req, reverse=True) + self.help = desc + self.module = module + self.perm = perm + self.flags = Flags(flags) + self.needs_overload = False + + def prefix(self): + return ' '.join(self.sig) + + def is_reasonably_simple(self): + if len(self.params) > 3: + return False + if any(p.n for p in self.params): + return False + return True + + def mk_bash_example(self): + simple = self.is_reasonably_simple() + line = ' '.join(['ceph', self.prefix()] + [p.mk_bash_example(simple) for p in self.params]) + return line + + +class Sig: + @staticmethod + def _param_to_sig(p): + try: + return {kv.split('=')[0]: kv.split('=')[1] for kv in p.split(',')} + except IndexError: + return p + + @staticmethod + def from_cmd(cmd): + sig = cmd.split() + return [Sig._param_to_sig(s) or s for s in sig] + + +TEMPLATE = ''' +.. This file is automatically generated. do not modify + +{% for command in commands %} + +{{ command.prefix() }} +{{ command.prefix() | length * '^' }} + +{{ command.help | wordwrap(70)}} + +Example command: + +.. code-block:: bash + + {{ command.mk_bash_example() }} +{% if command.params %} +Parameters: + +{% for param in command.params %}* **{{param.name}}**: {{ param.help() | wordwrap(70) | indent(2) }} +{% endfor %}{% endif %} +Ceph Module: + +* *{{ command.module }}* + +Required Permissions: + +* *{{ command.perm }}* + +{% if command.flags %}Command Flags: + +* *{{ command.flags }}* +{% endif %} +{% endfor %} + +''' + +class CephMgrCommands(Directive): + """ + extracts commands from specified mgr modules + """ + has_content = True + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + + def _normalize_path(self, dirname): + my_dir = os.path.dirname(os.path.realpath(__file__)) + src_dir = os.path.abspath(os.path.join(my_dir, '../..')) + return os.path.join(src_dir, dirname) + + def _is_mgr_module(self, dirname, name): + if not os.path.isdir(os.path.join(dirname, name)): + return False + if not os.path.isfile(os.path.join(dirname, name, '__init__.py')): + return False + return name not in ['tests'] + + @contextlib.contextmanager + def mocked_modules(self): + # src/pybind/mgr/tests + from tests import mock + mock_imports = ['rados', + 'rbd', + 'cephfs', + 'dateutil', + 'dateutil.parser'] + # make dashboard happy + mock_imports += ['ceph_argparse', + 'OpenSSL', + 'jwt', + 'bcrypt', + 'scipy', + 'jsonpatch', + 'rook.rook_client', + 'rook.rook_client.ceph', + 'cherrypy=3.2.3'] + + # make restful happy + mock_imports += ['pecan', + 'pecan.rest', + 'pecan.hooks', + 'werkzeug', + 'werkzeug.serving'] + + for m in mock_imports: + args = {} + parts = m.split('=', 1) + mocked = parts[0] + if len(parts) > 1: + args['__version__'] = parts[1] + sys.modules[mocked] = mock.Mock(**args) + + try: + yield + finally: + for m in mock_imports: + mocked = m.split('=', 1)[0] + sys.modules.pop(mocked) + + def _collect_module_commands(self, name): + with self.mocked_modules(): + logger.info(bold(f"loading mgr module '{name}'...")) + mgr_mod = __import__(name, globals(), locals(), [], 0) + from tests import M + def subclass(x): + try: + return issubclass(x, M) + except TypeError: + return False + ms = [c for c in mgr_mod.__dict__.values() + if subclass(c) and 'Standby' not in c.__name__] + [m] = ms + assert isinstance(m.COMMANDS, list) + return m.COMMANDS + + def _normalize_command(self, command): + if 'handler' in command: + del command['handler'] + command['sig'] = Sig.from_cmd(command['cmd']) + del command['cmd'] + command['flags'] = (1 << 3) + command['module'] = 'mgr' + return command + + def _render_cmds(self, commands): + rendered = Template(TEMPLATE).render(commands=list(commands)) + lines = rendered.split("\n") + assert lines + source = self.state_machine.input_lines.source(self.lineno - + self.state_machine.input_offset - 1) + self.state_machine.insert_input(lines, source) + + def run(self): + module_path = self._normalize_path(self.arguments[0]) + sys.path.insert(0, module_path) + os.environ['UNITTEST'] = 'true' + modules = [name for name in os.listdir(module_path) + if self._is_mgr_module(module_path, name)] + commands = sum([self._collect_module_commands(name) for name in modules], []) + cmds = [CmdCommand(**self._normalize_command(c)) for c in commands] + cmds = [cmd for cmd in cmds if 'hidden' not in cmd.flags] + cmds = sorted(cmds, key=lambda cmd: cmd.sig) + self._render_cmds(cmds) + return [] + +class MyProcessor(Preprocessor): + def __init__(self): + super().__init__() + self.cmds = [] + self.undef('__DATE__') + self.undef('__TIME__') + self.expand_linemacro = False + self.expand_filemacro = False + self.expand_countermacro = False + self.line_directive = '#line' + self.define("__PCPP_VERSION__ " + '') + self.define("__PCPP_ALWAYS_FALSE__ 0") + self.define("__PCPP_ALWAYS_TRUE__ 1") + + def eval(self, src): + _cmds = [] + + NONE = 0 + NOFORWARD = (1 << 0) + OBSOLETE = (1 << 1) + DEPRECATED = (1 << 2) + MGR = (1 << 3) + POLL = (1 << 4) + HIDDEN = (1 << 5) + TELL = (1 << 6) + + def FLAG(a): + return a + + def COMMAND(cmd, desc, module, perm): + _cmds.append({ + 'cmd': cmd, + 'desc': desc, + 'module': module, + 'perm': perm + }) + + def COMMAND_WITH_FLAG(cmd, desc, module, perm, flag): + _cmds.append({ + 'cmd': cmd, + 'desc': desc, + 'module': module, + 'perm': perm, + 'flags': flag + }) + + self.parse(src) + out = io.StringIO() + self.write(out) + out.seek(0) + s = out.read() + exec(s, globals(), locals()) + return _cmds + + +class CephMonCommands(Directive): + """ + extracts commands from specified header file + """ + has_content = True + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + + + def _src_dir(self): + my_dir = os.path.dirname(os.path.realpath(__file__)) + return os.path.abspath(os.path.join(my_dir, '../..')) + + def _parse_headers(self, headers): + src_dir = self._src_dir() + src = '\n'.join(f'#include "{src_dir}/{header}"' for header in headers) + return MyProcessor().eval(src) + + def _normalize_command(self, command): + if 'handler' in command: + del command['handler'] + command['sig'] = Sig.from_cmd(command['cmd']) + del command['cmd'] + return command + + def _render_cmds(self, commands): + rendered = Template(TEMPLATE).render(commands=list(commands)) + lines = rendered.split("\n") + assert lines + source = self.state_machine.input_lines.source(self.lineno - + self.state_machine.input_offset - 1) + self.state_machine.insert_input(lines, source) + + def run(self): + headers = self.arguments[0].split() + commands = self._parse_headers(headers) + cmds = [CmdCommand(**self._normalize_command(c)) for c in commands] + cmds = [cmd for cmd in cmds if 'hidden' not in cmd.flags] + cmds = sorted(cmds, key=lambda cmd: cmd.sig) + self._render_cmds(cmds) + return [] + + +def setup(app): + app.add_directive("ceph-mgr-commands", CephMgrCommands) + app.add_directive("ceph-mon-commands", CephMonCommands) + + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/doc/api/mon_command_api.rst b/doc/api/mon_command_api.rst new file mode 100644 index 0000000000000..1ef7594c5ce49 --- /dev/null +++ b/doc/api/mon_command_api.rst @@ -0,0 +1,2 @@ +.. ceph-mgr-commands:: src/pybind/mgr +.. ceph-mon-commands:: src/mon/MonCommands.h src/mgr/MgrCommands.h diff --git a/doc/conf.py b/doc/conf.py index cbc05b1ba16fc..8ba08573ae7d2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -102,6 +102,7 @@ extensions = [ 'sphinx_autodoc_typehints', 'sphinx_substitution_extensions', 'breathe', + 'ceph_commands', 'ceph_releases', 'sphinxcontrib.openapi' ] diff --git a/doc/scripts/gen_mon_command_api.py b/doc/scripts/gen_mon_command_api.py deleted file mode 100644 index dc09210bcb680..0000000000000 --- a/doc/scripts/gen_mon_command_api.py +++ /dev/null @@ -1,206 +0,0 @@ -import os -import json -import sys -from subprocess import check_output - -from jinja2 import Template - - -class Flags: - NOFORWARD = (1 << 0) - OBSOLETE = (1 << 1) - DEPRECATED = (1 << 2) - MGR = (1 << 3) - POLL = (1 << 4) - HIDDEN = (1 << 5) - - VALS = { - NOFORWARD: 'no_forward', - OBSOLETE: 'obsolete', - DEPRECATED: 'deprecated', - MGR: 'mgr', - POLL: 'poll', - HIDDEN: 'hidden', - } - - def __init__(self, fs): - self.fs = fs - - def __contains__(self, other): - return other in str(self) - - def __str__(self): - keys = Flags.VALS.keys() - es = {Flags.VALS[k] for k in keys if self.fs & k == k} - return ', '.join(sorted(es)) - - def __bool__(self): - return bool(str(self)) - - -class CmdParam(object): - t = { - 'CephInt': 'int', - 'CephString': 'str', - 'CephChoices': 'str', - 'CephPgid': 'str', - 'CephOsdName': 'str', - 'CephPoolname': 'str', - 'CephObjectname': 'str', - 'CephUUID': 'str', - 'CephEntityAddr': 'str', - 'CephIPAddr': 'str', - 'CephName': 'str', - 'CephBool': 'bool', - 'CephFloat': 'float', - 'CephFilepath': 'str', - } - - bash_example = { - 'CephInt': '1', - 'CephString': 'string', - 'CephChoices': 'choice', - 'CephPgid': '0', - 'CephOsdName': 'osd.0', - 'CephPoolname': 'poolname', - 'CephObjectname': 'objectname', - 'CephUUID': 'uuid', - 'CephEntityAddr': 'entityaddr', - 'CephIPAddr': '0.0.0.0', - 'CephName': 'name', - 'CephBool': 'true', - 'CephFloat': '0.0', - 'CephFilepath': '/path/to/file', - } - - def __init__(self, type, name, who=None, n=None, req=True, range=None, strings=None, - goodchars=None): - self.type = type - self.name = name - self.who = who - self.n = n == 'N' - self.req = req != 'false' - self.range = range.split('|') if range else [] - self.strings = strings.split('|') if strings else [] - self.goodchars = goodchars - - assert who == None - - def help(self): - advanced = [] - if self.type != 'CephString': - advanced.append(self.type + ' ') - if self.range: - advanced.append('range= ``{}`` '.format('..'.join(self.range))) - if self.strings: - advanced.append('strings=({}) '.format(' '.join(self.strings))) - if self.goodchars: - advanced.append('goodchars= ``{}`` '.format(self.goodchars)) - if self.n: - advanced.append('(can be repeated)') - - advanced = advanced or ["(string)"] - return ' '.join(advanced) - - def mk_example_value(self): - if self.type == 'CephChoices' and self.strings: - return self.strings[0] - if self.range: - return self.range[0] - return CmdParam.bash_example[self.type] - - def mk_bash_example(self, simple): - val = self.mk_example_value() - - if self.type == 'CephBool': - return '--' + self.name - if simple: - if self.type == "CephChoices" and self.strings: - return val - elif self.type == "CephString" and self.name != 'who': - return 'my_' + self.name - else: - return CmdParam.bash_example[self.type] - else: - return '--{}={}'.format(self.name, val) - - -class CmdCommand(object): - def __init__(self, sig, desc, module=None, perm=None, flags=0, poll=None): - self.sig = [s for s in sig if isinstance(s, str)] - self.params = sorted([CmdParam(**s) for s in sig if not isinstance(s, str)], - key=lambda p: p.req, reverse=True) - self.help = desc - self.module = module - self.perm = perm - self.flags = Flags(flags) - self.needs_overload = False - - def prefix(self): - return ' '.join(self.sig) - - def is_reasonably_simple(self): - if len(self.params) > 3: - return False - if any(p.n for p in self.params): - return False - return True - - def mk_bash_example(self): - simple = self.is_reasonably_simple() - line = ' '.join(['ceph', self.prefix()] + [p.mk_bash_example(simple) for p in self.params]) - return line - - -tpl = ''' -.. This file is automatically generated. do not modify - -{% for command in commands %} - -{{ command.prefix() }} -{{ command.prefix() | length * '^' }} - -{{ command.help | wordwrap(70)}} - -Example command: - -.. code-block:: bash - - {{ command.mk_bash_example() }} -{% if command.params %} -Parameters: - -{% for param in command.params %}* **{{param.name}}**: {{ param.help() | wordwrap(70) | indent(2) }} -{% endfor %}{% endif %} -Ceph Module: - -* *{{ command.module }}* - -Required Permissions: - -* *{{ command.perm }}* - -{% if command.flags %}Command Flags: - -* *{{ command.flags }}* -{% endif %} -{% endfor %} - -''' - -def mk_sigs(all): - sigs = [CmdCommand(**e) for e in all] - sigs = [s for s in sigs if 'hidden' not in s.flags] - sigs = sorted(sigs, key=lambda f: f.sig) - - - tm = Template(tpl) - msg = tm.render(commands=list(sigs)) - - print(msg) - - -if __name__ == '__main__': - script_dir = os.path.dirname(os.path.realpath(__file__)) - commands = json.loads(check_output([sys.executable, script_dir + '/../../src/script/gen_static_command_descriptions.py'])) - mk_sigs(commands) diff --git a/src/script/gen_static_command_descriptions.py b/src/script/gen_static_command_descriptions.py deleted file mode 100644 index 225f9601fe925..0000000000000 --- a/src/script/gen_static_command_descriptions.py +++ /dev/null @@ -1,169 +0,0 @@ -""" -Prints a statically compiled list of all commands. - -See - -* /admin/doc-requirements.txt -* /doc/scripts/gen_mon_command_api.py - -Rational for putting this file here is to allow others to make use of this output. -""" -import json -import os -import io -import sys - -from pcpp.preprocessor import Preprocessor, OutputDirective, Action - -os.environ['UNITTEST'] = 'true' - -script_dir = os.path.dirname(os.path.realpath(__file__)) - -mgr_dir = os.path.abspath(script_dir + '/../../src/pybind/mgr') - -sys.path.insert(0, mgr_dir) - -from tests import mock, M - - -def param_to_sig(p): - try: - return {kv.split('=')[0]: kv.split('=')[1] for kv in p.split(',')} - except IndexError: - return p - -def cmd_to_sig(cmd): - sig = cmd.split() - return [param_to_sig(s) or s for s in sig] - - -def list_mgr_module(m_name): - sys.modules['rados'] = mock.Mock() - sys.modules['rbd'] = mock.Mock() - sys.modules['cephfs'] = mock.Mock() - sys.modules['dateutil'] = mock.Mock() - sys.modules['dateutil.parser'] = mock.Mock() - - # make dashboard happy - sys.modules['OpenSSL'] = mock.Mock() - sys.modules['jwt'] = mock.Mock() - sys.modules['bcrypt'] = mock.Mock() - - sys.modules['scipy'] = mock.Mock() - sys.modules['jsonpatch'] = mock.Mock() - sys.modules['rook.rook_client'] = mock.Mock() - sys.modules['rook.rook_client.ceph'] = mock.Mock() - - sys.modules['cherrypy'] = mock.Mock(__version__="3.2.3") - - # make restful happy: - sys.modules['pecan'] = mock.Mock() - sys.modules['pecan.rest'] = mock.Mock() - sys.modules['pecan.hooks'] = mock.Mock() - sys.modules['werkzeug'] = mock.Mock() - sys.modules['werkzeug.serving'] = mock.Mock() - - mgr_mod = __import__(m_name, globals(), locals(), [], 0) - - def subclass(x): - try: - return issubclass(x, M) - except TypeError: - return False - - ms = [c for c in mgr_mod.__dict__.values() if subclass(c) and 'Standby' not in c.__name__] - [m] = ms - assert isinstance(m.COMMANDS, list) - return m.COMMANDS - - -def from_mgr_modules(): - names = [name for name in os.listdir(mgr_dir) - if os.path.isdir(os.path.join(mgr_dir, name)) and - os.path.isfile(os.path.join(mgr_dir, name, '__init__.py')) and - name not in ['tests']] - - comms = sum([list_mgr_module(name) for name in names], []) - for c in comms: - if 'handler' in c: - del c['handler'] - c['sig'] = cmd_to_sig(c['cmd']) - del c['cmd'] - c['flags'] = (1 << 3) - c['module'] = 'mgr' - return comms - - -def from_mon_commands_h(): - input_str = """ - #include "{script_dir}/../mon/MonCommands.h" - #include "{script_dir}/../mgr/MgrCommands.h" - """.format(script_dir=script_dir) - - cmds = [] - - class MyProcessor(Preprocessor): - def __init__(self): - super(MyProcessor, self).__init__() - self.undef('__DATE__') - self.undef('__TIME__') - self.expand_linemacro = False - self.expand_filemacro = False - self.expand_countermacro = False - self.line_directive = '#line' - self.define("__PCPP_VERSION__ " + '') - self.define("__PCPP_ALWAYS_FALSE__ 0") - self.define("__PCPP_ALWAYS_TRUE__ 1") - self.parse(input_str) - out = io.StringIO() - self.write(out) - out.seek(0) - s = out.read() - - NONE = 0 - NOFORWARD = (1 << 0) - OBSOLETE = (1 << 1) - DEPRECATED = (1 << 2) - MGR = (1 << 3) - POLL = (1 << 4) - HIDDEN = (1 << 5) - TELL = (1 << 6) - - def FLAG(a): - return a - - def COMMAND(cmd, desc, module, perm): - cmds.append({ - 'cmd': cmd, - 'desc': desc, - 'module': module, - 'perm': perm - }) - - def COMMAND_WITH_FLAG(cmd, desc, module, perm, flag): - cmds.append({ - 'cmd': cmd, - 'desc': desc, - 'module': module, - 'perm': perm, - 'flags': flag - }) - - exec(s, globals(), locals()) - - MyProcessor() - for c in cmds: - if 'handler' in c: - del c['handler'] - c['sig'] = cmd_to_sig(c['cmd']) - del c['cmd'] - return cmds - - -def gen_commands_dicts(): - comms = from_mon_commands_h() + from_mgr_modules() - comms = sorted(comms, key=lambda c: [e for e in c['sig'] if isinstance(e, str)]) - return comms - -if __name__ == '__main__': - print(json.dumps(gen_commands_dicts(), indent=2, sort_keys=True)) -- 2.39.5