diff options
Diffstat (limited to 'hacking/build_library/build_ansible/command_plugins/dump_keywords.py')
-rw-r--r-- | hacking/build_library/build_ansible/command_plugins/dump_keywords.py | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/hacking/build_library/build_ansible/command_plugins/dump_keywords.py b/hacking/build_library/build_ansible/command_plugins/dump_keywords.py new file mode 100644 index 0000000..e937931 --- /dev/null +++ b/hacking/build_library/build_ansible/command_plugins/dump_keywords.py @@ -0,0 +1,121 @@ +# coding: utf-8 +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import importlib +import os.path +import pathlib +import re +from ansible.module_utils.compat.version import LooseVersion + +import jinja2 +import yaml +from jinja2 import Environment, FileSystemLoader + +from ansible.module_utils._text import to_bytes + +# Pylint doesn't understand Python3 namespace modules. +from ..change_detection import update_file_if_different # pylint: disable=relative-beyond-top-level +from ..commands import Command # pylint: disable=relative-beyond-top-level + + +DEFAULT_TEMPLATE_DIR = str(pathlib.Path(__file__).resolve().parents[4] / 'docs/templates') +TEMPLATE_FILE = 'playbooks_keywords.rst.j2' +PLAYBOOK_CLASS_NAMES = ['Play', 'Role', 'Block', 'Task'] + + +def load_definitions(keyword_definitions_file): + docs = {} + with open(keyword_definitions_file) as f: + docs = yaml.safe_load(f) + + return docs + + +def extract_keywords(keyword_definitions): + pb_keywords = {} + for pb_class_name in PLAYBOOK_CLASS_NAMES: + if pb_class_name == 'Play': + module_name = 'ansible.playbook' + else: + module_name = 'ansible.playbook.{0}'.format(pb_class_name.lower()) + module = importlib.import_module(module_name) + playbook_class = getattr(module, pb_class_name, None) + if playbook_class is None: + raise ImportError("We weren't able to import the module {0}".format(module_name)) + + # Maintain order of the actual class names for our output + # Build up a mapping of playbook classes to the attributes that they hold + pb_keywords[pb_class_name] = {k: v for (k, v) in playbook_class.fattributes.items() + # Filter private attributes as they're not usable in playbooks + if not v.private} + + # pick up definitions if they exist + for keyword in tuple(pb_keywords[pb_class_name]): + if keyword in keyword_definitions: + pb_keywords[pb_class_name][keyword] = keyword_definitions[keyword] + else: + # check if there is an alias, otherwise undocumented + alias = getattr(playbook_class.fattributes.get(keyword), 'alias', None) + if alias and alias in keyword_definitions: + pb_keywords[pb_class_name][alias] = keyword_definitions[alias] + del pb_keywords[pb_class_name][keyword] + else: + pb_keywords[pb_class_name][keyword] = ' UNDOCUMENTED!! ' + + # loop is really with_ for users + if pb_class_name == 'Task': + pb_keywords[pb_class_name]['with_<lookup_plugin>'] = ( + 'The same as ``loop`` but magically adds the output of any lookup plugin to' + ' generate the item list.') + + # local_action is implicit with action + if 'action' in pb_keywords[pb_class_name]: + pb_keywords[pb_class_name]['local_action'] = ('Same as action but also implies' + ' ``delegate_to: localhost``') + + return pb_keywords + + +def generate_page(pb_keywords, template_dir): + env = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True,) + template = env.get_template(TEMPLATE_FILE) + tempvars = {'pb_keywords': pb_keywords, 'playbook_class_names': PLAYBOOK_CLASS_NAMES} + + keyword_page = template.render(tempvars) + if LooseVersion(jinja2.__version__) < LooseVersion('2.10'): + # jinja2 < 2.10's indent filter indents blank lines. Cleanup + keyword_page = re.sub(' +\n', '\n', keyword_page) + + return keyword_page + + +class DocumentKeywords(Command): + name = 'document-keywords' + + @classmethod + def init_parser(cls, add_parser): + parser = add_parser(cls.name, description='Generate playbook keyword documentation from' + ' code and descriptions') + parser.add_argument("-T", "--template-dir", action="store", dest="template_dir", + default=DEFAULT_TEMPLATE_DIR, + help="directory containing Jinja2 templates") + parser.add_argument("-o", "--output-dir", action="store", dest="output_dir", + default='/tmp/', help="Output directory for rst files") + parser.add_argument("keyword_defs", metavar="KEYWORD-DEFINITIONS.yml", type=str, + help="Source for playbook keyword docs") + + @staticmethod + def main(args): + keyword_definitions = load_definitions(args.keyword_defs) + pb_keywords = extract_keywords(keyword_definitions) + + keyword_page = generate_page(pb_keywords, args.template_dir) + outputname = os.path.join(args.output_dir, TEMPLATE_FILE.replace('.j2', '')) + update_file_if_different(outputname, to_bytes(keyword_page)) + + return 0 |