Fix structure of generic snippet feature (#74932)

* Fix struture of cli/doc.py snippet code.

A couple releases ago, cli/doc.py was modified to mostly conform to the
data processing pipeline steps.  format_plugin_doc() was the biggest
exception in that refactor.  When the snippet code was made generic
instead of being only for modules, the new code should have conformed to
the data processing pipeline too.

* Move the decision to output a snippet to the run() method alongside
  the decision to output a listing versus plugin_docs.
* Move the test for invalid plugin_types to the run() method as it
  affects all snippets in this run, not just a single snippet. (-t can
  only be specified once)
* Rename get_snippet_text() to format_snippet() as:
  * This is the data formatting step
  * The format_snippet() name matches with its conceptual sibling,
    format_plugin_doc().
* Use ValueError inside of format_snippet() to flag unrecoverable errors
  formatting a single snippet.
* Emit a warning when format_snippet() raises ValueError and continue to
  the next snippet.
* If the yaml(?) or toml inventory plugin is specified for snippet output,
  raise ValueError() so that the user sees a warning instead of simply
  seeing blank output.
* Do not modify arguments passed into format_snippet().  This is the
  formatting step so data should not be modified.
* Change _do_yaml_snippet() and _do_lookup_snippet() to operate side
  effect free.
* Fix raising of exceptions when formatting requred options for snippets.

* Unrelated: Use to_text() instead of to_native when calling
  display.warning(). to_native() is used for raising exceptions.  Not
  for display methods.

* Add a changelog
pull/75078/head
Toshio Kuratomi 3 years ago committed by GitHub
parent 5d3d9ad8be
commit a4021977ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
minor_changes:
- Make the code structure of ansible-doc's generic snippet feature more maintainable.

@ -577,7 +577,7 @@ class DocCLI(CLI, RoleMixin):
data[keyword] = kdata data[keyword] = kdata
except KeyError as e: except KeyError as e:
display.warning("Skipping Invalid keyword '%s' specified: %s" % (keyword, to_native(e))) display.warning("Skipping Invalid keyword '%s' specified: %s" % (keyword, to_text(e)))
return data return data
@ -716,9 +716,23 @@ class DocCLI(CLI, RoleMixin):
if plugin_type in C.DOCUMENTABLE_PLUGINS: if plugin_type in C.DOCUMENTABLE_PLUGINS:
if listing and docs: if listing and docs:
self.display_plugin_list(docs) self.display_plugin_list(docs)
elif context.CLIARGS['show_snippet']:
if plugin_type not in SNIPPETS:
raise AnsibleError('Snippets are only available for the following plugin'
' types: %s' % ', '.join(SNIPPETS))
for plugin, doc_data in docs.items():
try:
textret = DocCLI.format_snippet(plugin, plugin_type, doc_data['doc'])
except ValueError as e:
display.warning("Unable to construct a snippet for"
" '{0}': {1}".format(plugin, to_text(e)))
else:
text.append(textret)
else: else:
# Some changes to how plain text docs are formatted # Some changes to how plain text docs are formatted
for plugin, doc_data in docs.items(): for plugin, doc_data in docs.items():
textret = DocCLI.format_plugin_doc(plugin, plugin_type, textret = DocCLI.format_plugin_doc(plugin, plugin_type,
doc_data['doc'], doc_data['examples'], doc_data['doc'], doc_data['examples'],
doc_data['return'], doc_data['metadata']) doc_data['return'], doc_data['metadata'])
@ -726,11 +740,13 @@ class DocCLI(CLI, RoleMixin):
text.append(textret) text.append(textret)
else: else:
display.warning("No valid documentation was retrieved from '%s'" % plugin) display.warning("No valid documentation was retrieved from '%s'" % plugin)
elif plugin_type == 'role': elif plugin_type == 'role':
if context.CLIARGS['list_dir'] and docs: if context.CLIARGS['list_dir'] and docs:
self._display_available_roles(docs) self._display_available_roles(docs)
elif docs: elif docs:
self._display_role_doc(docs) self._display_role_doc(docs)
elif docs: elif docs:
text = DocCLI._dump_yaml(docs, '') text = DocCLI._dump_yaml(docs, '')
@ -824,6 +840,26 @@ class DocCLI(CLI, RoleMixin):
# return everything as one dictionary # return everything as one dictionary
return {'doc': doc, 'examples': plainexamples, 'return': returndocs, 'metadata': metadata} return {'doc': doc, 'examples': plainexamples, 'return': returndocs, 'metadata': metadata}
@staticmethod
def format_snippet(plugin, plugin_type, doc):
''' return heavily commented plugin use to insert into play '''
if plugin_type == 'inventory' and doc.get('options', {}).get('plugin'):
# these do not take a yaml config that we can write a snippet for
raise ValueError('The {0} inventory plugin does not take YAML type config source'
' that can be used with the "auto" plugin so a snippet cannot be'
' created.'.format(plugin))
text = []
if plugin_type == 'lookup':
text = _do_lookup_snippet(doc)
elif 'options' in doc:
text = _do_yaml_snippet(doc)
text.append('')
return "\n".join(text)
@staticmethod @staticmethod
def format_plugin_doc(plugin, plugin_type, doc, plainexamples, returndocs, metadata): def format_plugin_doc(plugin, plugin_type, doc, plainexamples, returndocs, metadata):
collection_name = doc['collection'] collection_name = doc['collection']
@ -839,15 +875,6 @@ class DocCLI(CLI, RoleMixin):
doc['returndocs'] = returndocs doc['returndocs'] = returndocs
doc['metadata'] = metadata doc['metadata'] = metadata
if context.CLIARGS['show_snippet']:
if plugin_type not in SNIPPETS:
raise AnsibleError("Snippets are only available for the following plugin types: %s" % ', '.join(SNIPPETS))
if plugin_type == 'inventory' and doc.get('options') and not doc['options'].get('plugin'):
# these are 'configurable' but not intended for yaml type inventory sources, like ini or script
# so we cannot use as source for snippets
del doc['options']
text = DocCLI.get_snippet_text(doc, plugin_type)
else:
try: try:
text = DocCLI.get_man_text(doc, collection_name, plugin_type) text = DocCLI.get_man_text(doc, collection_name, plugin_type)
except Exception as e: except Exception as e:
@ -963,21 +990,6 @@ class DocCLI(CLI, RoleMixin):
ret.append(i) ret.append(i)
return os.pathsep.join(ret) return os.pathsep.join(ret)
@staticmethod
def get_snippet_text(doc, ptype='module'):
''' return heavily commented plugin use to insert into play '''
text = []
if ptype == 'lookup':
_do_lookup_snippet(text, doc)
elif 'options' in doc:
_do_yaml_snippet(text, doc)
elif ptype == 'inventory':
display.warning('Snippets are only available to inventory plugins with YAML type sources that can be used with the "auto" plugin.')
text.append('')
return "\n".join(text)
@staticmethod @staticmethod
def _dump_yaml(struct, indent): def _dump_yaml(struct, indent):
return DocCLI.tty_ify('\n'.join([indent + line for line in yaml.dump(struct, default_flow_style=False, Dumper=AnsibleDumper).split('\n')])) return DocCLI.tty_ify('\n'.join([indent + line for line in yaml.dump(struct, default_flow_style=False, Dumper=AnsibleDumper).split('\n')]))
@ -1286,10 +1298,12 @@ class DocCLI(CLI, RoleMixin):
return "\n".join(text) return "\n".join(text)
def _do_yaml_snippet(text, doc): def _do_yaml_snippet(doc):
text = []
mdesc = DocCLI.tty_ify(doc['short_description']) mdesc = DocCLI.tty_ify(doc['short_description'])
module = doc.get('module') module = doc.get('module')
if module: if module:
# this is actually a usable task! # this is actually a usable task!
text.append("- name: %s" % (mdesc)) text.append("- name: %s" % (mdesc))
@ -1311,7 +1325,7 @@ def _do_yaml_snippet(text, doc):
required = opt.get('required', False) required = opt.get('required', False)
if not isinstance(required, bool): if not isinstance(required, bool):
raise("Incorrect value for 'Required', a boolean is needed.: %s" % required) raise ValueError("Incorrect value for 'Required', a boolean is needed: %s" % required)
o = '%s:' % o o = '%s:' % o
if module: if module:
@ -1326,11 +1340,14 @@ def _do_yaml_snippet(text, doc):
text.append("%s %-9s # %s" % (o, default, textwrap.fill(desc, limit, subsequent_indent=subdent, max_lines=3))) text.append("%s %-9s # %s" % (o, default, textwrap.fill(desc, limit, subsequent_indent=subdent, max_lines=3)))
return text
def _do_lookup_snippet(text, doc):
def _do_lookup_snippet(doc):
text = []
snippet = "lookup('%s', " % doc.get('plugin', doc.get('name')) snippet = "lookup('%s', " % doc.get('plugin', doc.get('name'))
comment = [] comment = []
for o in sorted(doc['options'].keys()): for o in sorted(doc['options'].keys()):
opt = doc['options'][o] opt = doc['options'][o]
@ -1342,7 +1359,7 @@ def _do_lookup_snippet(text, doc):
required = opt.get('required', False) required = opt.get('required', False)
if not isinstance(required, bool): if not isinstance(required, bool):
raise("Incorrect value for 'Required', a boolean is needed.: %s" % required) raise ValueError("Incorrect value for 'Required', a boolean is needed: %s" % required)
if required: if required:
default = '<REQUIRED>' default = '<REQUIRED>'
@ -1355,7 +1372,10 @@ def _do_lookup_snippet(text, doc):
snippet += ', %s=%s' % (o, default) snippet += ', %s=%s' % (o, default)
snippet += ")" snippet += ")"
if comment: if comment:
text.extend(comment) text.extend(comment)
text.append('') text.append('')
text.append(snippet) text.append(snippet)
return text

Loading…
Cancel
Save