diff --git a/changelogs/fragments/doc_errors.yml b/changelogs/fragments/doc_errors.yml new file mode 100644 index 00000000000..6b4d137f109 --- /dev/null +++ b/changelogs/fragments/doc_errors.yml @@ -0,0 +1,2 @@ +minor_changes: + - documentation construction now gives more information on error. diff --git a/lib/ansible/parsing/plugin_docs.py b/lib/ansible/parsing/plugin_docs.py index 31d083e5dab..73c69e11597 100644 --- a/lib/ansible/parsing/plugin_docs.py +++ b/lib/ansible/parsing/plugin_docs.py @@ -5,12 +5,10 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type import ast -import os -import pyclbr import tokenize from ansible import constants as C -from ansible.errors import AnsibleError +from ansible.errors import AnsibleError, AnsibleParserError from ansible.module_utils._text import to_text, to_native from ansible.parsing.yaml.loader import AnsibleLoader from ansible.utils.display import Display @@ -26,6 +24,14 @@ string_to_vars = { } +def _var2string(value): + ''' reverse lookup of the dict above ''' + global string_to_vars + for k, v in string_to_vars.items(): + if v == value: + return k + + def _init_doc_dict(): ''' initialize a return dict for docs with the expected structure ''' return {k: None for k in string_to_vars.values()} @@ -41,12 +47,12 @@ def read_docstring_from_yaml_file(filename, verbose=True, ignore_errors=True): try: with open(filename, 'rb') as yamlfile: file_data = AnsibleLoader(yamlfile.read(), file_name=filename).get_single_data() - - except Exception: - if verbose: - display.error("unable to parse %s" % filename) + except Exception as e: + msg = "Unable to parse yaml file '%s': %s" % (filename, to_native(e)) if not ignore_errors: - raise + raise AnsibleParserError(msg, orig_exc=e) + elif verbose: + display.error(msg) for key in string_to_vars: data[string_to_vars[key]] = file_data.get(key, None) @@ -54,14 +60,6 @@ def read_docstring_from_yaml_file(filename, verbose=True, ignore_errors=True): return data -def _find_jinja_function(filename, verbose=True, ignore_errors=True): - - # for finding filters/tests - module_name = os.path.splitext(os.path.basename(filename))[0] - paths = [os.path.dirname(filename)] - mdata = pyclbr.readmodule_ex(module_name, paths) - - def read_docstring_from_python_module(filename, verbose=True, ignore_errors=True): """ Use tokenization to search for assignment of the documentation variables in the given file. @@ -97,11 +95,11 @@ def read_docstring_from_python_module(filename, verbose=True, ignore_errors=True try: data[next_string] = AnsibleLoader(value, file_name=filename).get_single_data() except Exception as e: - if ignore_errors: - if verbose: - display.error("unable to parse %s" % filename) - else: - raise + msg = "Unable to parse docs '%s' in python file '%s': %s" % (_var2string(next_string), filename, to_native(e)) + if not ignore_errors: + raise AnsibleParserError(msg, orig_exc=e) + elif verbose: + display.error(msg) next_string = None @@ -132,7 +130,7 @@ def read_docstring_from_python_file(filename, verbose=True, ignore_errors=True): theid = t.id except AttributeError: # skip errors can happen when trying to use the normal code - display.warning("Failed to assign id for %s on %s, skipping" % (t, filename)) + display.warning("Building documentation, failed to assign id for %s on %s, skipping" % (t, filename)) continue if theid in string_to_vars: @@ -148,13 +146,14 @@ def read_docstring_from_python_file(filename, verbose=True, ignore_errors=True): # string should be yaml if already not a dict data[varkey] = AnsibleLoader(child.value.s, file_name=filename).get_single_data() - display.debug('assigned: %s' % varkey) + display.debug('Documenation assigned: %s' % varkey) - except Exception: - if verbose: - display.error("unable to parse %s" % filename) + except Exception as e: + msg = "Unable to parse documentation in python file '%s': %s" % (filename, to_native(e)) if not ignore_errors: - raise + raise AnsibleParserError(msg, orig_exc=e) + elif verbose: + display.error(msg) return data @@ -162,7 +161,7 @@ def read_docstring_from_python_file(filename, verbose=True, ignore_errors=True): def read_docstring(filename, verbose=True, ignore_errors=True): ''' returns a documentation dictionary from Ansible plugin docstrings ''' - # TODO: ensure adjacency to code (including ps1 for py files) + # NOTE: adjacency of doc file to code file is responsibility of caller if filename.endswith(C.YAML_DOC_EXTENSIONS): docstring = read_docstring_from_yaml_file(filename, verbose=verbose, ignore_errors=ignore_errors) elif filename.endswith(C.PYTHON_DOC_EXTENSIONS):