From 6d49341ba97c0365c1eddd44c29010fab0288a50 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Tue, 10 Sep 2019 12:46:52 -0700 Subject: [PATCH 1/2] Cleanup processing of plugin docs * Mostly separate the steps of processing plugin docs 1) Acquire source data 2) Transform and calculate additonal data 3) Format data for output 4) Output data format_plugin_doc() is still mixing transformation and formatting but that should be fixed in a devel-only change * Raise exceptions in _get_plugin_doc() on errors. * Remove check to exclude on blacklisted extensions. We already request only .py files * If there is no DOCUMENTATION entry in the plugin, raise an exception from _get_plugin_doc(). Everywhere we use _get_plugin_doc(), this is treated as an error --- lib/ansible/cli/doc.py | 138 ++++++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 57 deletions(-) diff --git a/lib/ansible/cli/doc.py b/lib/ansible/cli/doc.py index f3977fc9582..0c68490c41f 100644 --- a/lib/ansible/cli/doc.py +++ b/lib/ansible/cli/doc.py @@ -36,6 +36,14 @@ def jdump(text): display.display(json.dumps(text, sort_keys=True, indent=4)) +class RemovedPlugin(Exception): + pass + + +class PluginNotFound(Exception): + pass + + class DocCLI(CLI): ''' displays information on modules installed in Ansible libraries. It displays a terse listing of plugins and their short descriptions, @@ -180,28 +188,52 @@ class DocCLI(CLI): if len(context.CLIARGS['args']) == 0: raise AnsibleOptionsError("Incorrect options passed") - # process command line list + # get the docs for plugins in the command line list + plugin_docs = {} + for plugin in context.CLIARGS['args']: + try: + doc, plainexamples, returndocs, metadata = DocCLI._get_plugin_doc(plugin, loader, search_paths) + except PluginNotFound: + display.warning("%s %s not found in:\n%s\n" % (plugin_type, plugin, search_paths)) + continue + except RemovedPlugin: + display.warning("%s %s has been removed\n" % (plugin_type, plugin)) + continue + except Exception as e: + display.vvv(traceback.format_exc()) + raise AnsibleError("%s %s missing documentation (or could not parse" + " documentation): %s\n" % + (plugin_type, plugin, to_native(e))) + + if not doc: + # The doc section existed but was empty + continue + + plugin_docs[plugin] = {'doc': doc, 'examples': plainexamples, + 'return': returndocs, 'metadata': metadata} + if do_json: - dump = {} - for plugin in context.CLIARGS['args']: - doc, plainexamples, returndocs, metadata = DocCLI._get_plugin_doc(plugin, loader, plugin_type, search_paths) + # Some changes to how json docs are formatted + for plugin, doc_data in plugin_docs.items(): try: - returndocs = yaml.load(returndocs) + doc_data['return'] = yaml.load(doc_data['return']) except Exception: pass - if doc: - dump[plugin] = {'doc': doc, 'examples': plainexamples, 'return': returndocs, 'metadata': metadata} - jdump(dump) - else: - text = '' - for plugin in context.CLIARGS['args']: - textret = DocCLI.format_plugin_doc(plugin, loader, plugin_type, search_paths) + jdump(plugin_docs) + + else: + # Some changes to how plain text docs are formatted + text = [] + for plugin, doc_data in plugin_docs.items(): + textret = DocCLI.format_plugin_doc(plugin, plugin_type, + doc_data['doc'], doc_data['examples'], + doc_data['return'], doc_data['metadata']) if textret: - text += textret + text.append(textret) if text: - DocCLI.pager(text) + DocCLI.pager(''.join(text)) return 0 @@ -261,58 +293,50 @@ class DocCLI(CLI): return clean_ns @staticmethod - def _get_plugin_doc(plugin, loader, plugin_type, search_paths): + def _get_plugin_doc(plugin, loader, search_paths): + # if the plugin lives in a non-python file (eg, win_X.ps1), require the corresponding python file for docs + filename = loader.find_plugin(plugin, mod_type='.py', ignore_deprecated=True, check_aliases=True) + if filename is None: + raise PluginNotFound('%s was not found in %s' % (plugin, search_paths)) - doc = plainexamples = returndocs = metadata = {} - try: - # if the plugin lives in a non-python file (eg, win_X.ps1), require the corresponding python file for docs - filename = loader.find_plugin(plugin, mod_type='.py', ignore_deprecated=True, check_aliases=True) - if filename is None: - display.warning("%s %s not found in:\n%s\n" % (plugin_type, plugin, search_paths)) - return + doc, plainexamples, returndocs, metadata = get_docstring(filename, fragment_loader, verbose=(context.CLIARGS['verbosity'] > 0)) - if not any(filename.endswith(x) for x in C.BLACKLIST_EXTS): - doc, plainexamples, returndocs, metadata = get_docstring(filename, fragment_loader, verbose=(context.CLIARGS['verbosity'] > 0)) + # doc may be None when the module has been removed. Calling code may choose to + # handle that but we can't. + if 'removed' in metadata['status']: + raise RemovedPlugin('%s has been removed' % plugin) - if doc: - # doc may be None, such as when the module has been removed - doc['filename'] = filename + # If the plugin existed but did not have a DOCUMENTATION element and was not removed, it's + # an error + if doc is None: + raise ValueError('%s did not contain a DOCUMENTATION attribute' % plugin) - except Exception as e: - display.vvv(traceback.format_exc()) - raise AnsibleError("%s %s missing documentation (or could not parse documentation): %s\n" % (plugin_type, plugin, to_native(e))) + doc['filename'] = filename return doc, plainexamples, returndocs, metadata @staticmethod - def format_plugin_doc(plugin, loader, plugin_type, search_paths): - text = '' - - doc, plainexamples, returndocs, metadata = DocCLI._get_plugin_doc(plugin, loader, plugin_type, search_paths) - if doc is not None: - - # assign from other sections - doc['plainexamples'] = plainexamples - doc['returndocs'] = returndocs - doc['metadata'] = metadata - - # generate extra data - if plugin_type == 'module': - # is there corresponding action plugin? - if plugin in action_loader: - doc['action'] = True - else: - doc['action'] = False - doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') - if 'docuri' in doc: - doc['docuri'] = doc[plugin_type].replace('_', '-') - - if context.CLIARGS['show_snippet'] and plugin_type == 'module': - text += DocCLI.get_snippet_text(doc) + def format_plugin_doc(plugin, plugin_type, doc, plainexamples, returndocs, metadata): + # assign from other sections + doc['plainexamples'] = plainexamples + doc['returndocs'] = returndocs + doc['metadata'] = metadata + + # generate extra data + if plugin_type == 'module': + # is there corresponding action plugin? + if plugin in action_loader: + doc['action'] = True else: - text += DocCLI.get_man_text(doc) + doc['action'] = False - elif 'removed' in metadata['status']: - display.warning("%s %s has been removed\n" % (plugin_type, plugin)) + doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') + if 'docuri' in doc: + doc['docuri'] = doc[plugin_type].replace('_', '-') + + if context.CLIARGS['show_snippet'] and plugin_type == 'module': + text = DocCLI.get_snippet_text(doc) + else: + text = DocCLI.get_man_text(doc) return text -- 2.20.1