diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/dns/update-docs-fragments.py | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/dns/update-docs-fragments.py')
-rwxr-xr-x | ansible_collections/community/dns/update-docs-fragments.py | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/ansible_collections/community/dns/update-docs-fragments.py b/ansible_collections/community/dns/update-docs-fragments.py new file mode 100755 index 000000000..9ceba70f1 --- /dev/null +++ b/ansible_collections/community/dns/update-docs-fragments.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +import importlib +import os +import re +import sys +import textwrap + +import yaml + + +PROVIDERS = ['hetzner', 'hosttech'] + +DEPENDENT_FRAGMENTS = [ + ('RECORD_TYPE_CHOICES', [ + ('record_type', 'options.type', [ + 'module_record', + 'module_record_info', + 'module_record_set', + 'module_record_set_info', + ]), + ]), + ('RECORD_DEFAULT_TTL', [ + ('record_default_ttl', 'options.ttl', [ + 'module_record', + 'module_record_set', + ]), + ]), + ('RECORD_TYPE_CHOICES_RECORD_SETS_MODULE', [ + ('record_type', 'options.record_sets.suboptions.type', [ + 'module_record_sets', + ]), + ('record_default_ttl', 'options.record_sets.suboptions.ttl', [ + 'module_record_sets', + ]), + ]), + ('RECORD_TYPE_CHOICES_RECORDS_INVENTORY', [ + ('record_type', 'options.filters.suboptions.type', [ + 'inventory_records', + ]), + ]), + ('ZONE_ID_TYPE', [ + ('zone_id_type', 'options.zone_id', [ + 'module_record', + 'module_record_info', + 'module_record_set', + 'module_record_set_info', + 'module_record_sets', + 'module_zone_info', + 'inventory_records', + ]), + ]), +] + + +def get_provider_informations(providers): + files_to_remove = [] + + def add_init_py(path): + path = os.path.join(path, '__init__.py') + if os.path.exists(path): + return + with open(path, 'wb') as f: + f.write(b'') + files_to_remove.append(path) + + try: + sys.path.append(os.path.join('..', '..', '..')) + + add_init_py(os.path.join('..', '..')) + add_init_py(os.path.join('..')) + add_init_py(os.path.join('.')) + add_init_py(os.path.join('plugins')) + add_init_py(os.path.join('plugins', 'module_utils')) + + provider_infos = {} + errors = [] + + for provider in providers: + add_init_py(os.path.join('plugins', 'module_utils', provider)) + full_py_path = 'ansible_collections.community.dns.plugins.module_utils.{0}.api'.format(provider) + full_pathname = os.path.join('plugins', 'module_utils', provider, 'api.py') + try: + loader = importlib.machinery.SourceFileLoader(full_py_path, full_pathname) + spec = importlib.util.spec_from_loader(full_py_path, loader) + the_module = importlib.util.module_from_spec(spec) + loader.exec_module(the_module) + except Exception as e: + errors.append('{0}: Error while importing module {1}: {2}'.format(full_pathname, full_py_path, e)) + continue + + create_provider_info_fn_name = 'create_{0}_provider_information'.format(provider) + try: + create_provider_info_fn = provider_information = the_module.__dict__[create_provider_info_fn_name] + provider_infos[provider] = create_provider_info_fn() + except KeyError as e: + errors.append('{0}: Cannot find function {1}'.format(full_pathname, create_provider_info_fn)) + except Exception as e: + errors.append('{0}: Error while invoking function {1}: {2}'.format(full_pathname, create_provider_info_fn, e)) + + return provider_infos, errors + finally: + for path in files_to_remove: + os.remove(path) + + +class DocFragmentParseError(Exception): + def __init__(self, path, error_message): + self.path = path + self.error_message = error_message + super(DocFragmentParseError, self).__init__('Error while parsing {0}: {1}'.format(path, error_message)) + + +DOC_FRAGMENT_START_MATCHER = re.compile(r"^ ([A-Z_]+) = r'''$") + + +class Dumper(yaml.SafeDumper): + def ignore_aliases(self, *args): + return True + + def increase_indent(self, flow=False, *args, **kwargs): + self.best_indent = kwargs.pop('ident_override', 4) + return super().increase_indent(*args, **kwargs) + + def expect_block_sequence(self): + self.increase_indent(flow=False, indentless=False, ident_override=2) + self.state = self.expect_first_block_sequence_item + + +class DocFragment: + def __init__(self, path, prefix_lines, name, lines): + self.prefix_lines = prefix_lines + self.name = name + self.lines = lines + + try: + self.data = yaml.safe_load('\n'.join(self.lines)) + except Exception as e: + raise DocFragmentParseError(path, 'Error while parsing part {0}: {1}'.format(name, e)) + + def recreate_lines(self): + data = yaml.dump(self.data, default_flow_style=False, indent=4, Dumper=Dumper, sort_keys=False) + self.lines = data.splitlines() + + def serialize_lines(self): + return self.prefix_lines + [" {0} = r'''".format(self.name)] + self.lines + ["'''"] + + +class DocFragmentFile: + def __init__(self, path): + with open(path, 'r', encoding='utf-8') as f: + lines = f.read().splitlines() + + self.prefix = [] + self.fragments = [] + self.fragments_by_name = {} + + where = 'prefix' + for line in lines: + if where == 'prefix': + self.prefix.append(line) + if line == 'class ModuleDocFragment(object):': + where = 'body' + body_prefix = [] + body_name = None + body_lines = [] + elif where == 'body': + if body_name is None: + m = DOC_FRAGMENT_START_MATCHER.match(line) + if m: + body_name = m.group(1) + else: + body_prefix.append(line) + elif line == "'''": + fragment = DocFragment(path, body_prefix, body_name, body_lines) + self.fragments.append(fragment) + self.fragments_by_name[body_name] = fragment + body_prefix = [] + body_name = None + body_lines = [] + else: + body_lines.append(line) + + if where == 'prefix': + raise DocFragmentParseError(path, 'Cannot find body') + + def serialize_to_string(self): + lines = [] + lines.extend(self.prefix) + for fragment in self.fragments: + lines.extend(fragment.serialize_lines()) + lines.append('') + return '\n'.join(lines) + + +def doc_fragment_fn(name): + return os.path.join('plugins', 'doc_fragments', '{0}.py'.format(name)) + + +def load_doc_fragment(name): + fn = doc_fragment_fn(name) + return DocFragmentFile(fn) + + +def load_single_doc_fragment(name): + fragment = 'DOCUMENTATION' + if '.' in name: + name, fragment = name.split('.', 1) + fragment = fragment.upper() + doc_fragment = load_doc_fragment(name) + return doc_fragment.fragments_by_name[fragment] + + +def write_doc_fragment(name, doc_fragment): + fn = doc_fragment_fn(name) + data = doc_fragment.serialize_to_string() + with open(fn, 'w', encoding='utf-8') as f: + f.write(data) + + +def compare_doc_fragment(name, doc_fragment): + fn = doc_fragment_fn(name) + data = doc_fragment.serialize_to_string() + with open(fn, 'r', encoding='utf-8') as f: + compare_data = f.read() + return data == compare_data + + +def augment_fragment(provider_fragment, provider_info): + data = { + 'record_type': {'choices': sorted(provider_info.get_supported_record_types())}, + 'zone_id_type': {'type': provider_info.get_zone_id_type()}, + 'record_id_type': {'type': provider_info.get_record_id_type()}, + 'record_default_ttl': {'default': provider_info.get_record_default_ttl()} + } + + for fragment_name, fragment_insertion_data in DEPENDENT_FRAGMENTS: + insertion_fragment = provider_fragment.fragments_by_name.get(fragment_name) + if insertion_fragment is None: + insertion_fragment = DocFragment('', [], fragment_name, []) + provider_fragment.fragments.append(insertion_fragment) + provider_fragment.fragments_by_name[fragment_name] = insertion_fragment + + insertion_fragment.data = {} + all_doc_fragment_names = set() + for what, insertion_point, doc_fragment_names in fragment_insertion_data: + all_doc_fragment_names.update(doc_fragment_names) + doc_fragments = [load_single_doc_fragment(doc_fragment) for doc_fragment in doc_fragment_names] + insertion_point = insertion_point.split('.') + + insertion_pos = insertion_fragment.data + original_pos = doc_fragments[0].data # FIXME + for depth, part in enumerate(insertion_point): + if part not in insertion_pos: + insertion_pos[part] = {} + insertion_pos = insertion_pos[part] + original_pos = original_pos[part] + if depth >= 2: + for x in original_pos: + if x not in insertion_pos: + insertion_pos[x] = original_pos[x] + insertion_pos.update(data[what]) + + insertion_fragment.prefix_lines = [ + '', + ' # WARNING: This section is automatically generated by update-docs-fragments.py.', + ] + insertion_fragment.prefix_lines.extend([ + ' # {0}'.format(line) + for line in textwrap.wrap( + 'It is used to augment the docs fragment{0} {1}.'.format( + 's' if len(all_doc_fragment_names) != 1 else '', + ', '.join(sorted(all_doc_fragment_names))), + width=80) + ]) + insertion_fragment.prefix_lines.append(' # DO NOT EDIT MANUALLY!') + insertion_fragment.recreate_lines() + + +def main(program, arguments): + lint = '--lint' in arguments + provider_infos, errors = get_provider_informations(PROVIDERS) + try: + for provider, provider_info in sorted(provider_infos.items()): + try: + doc_fragment = load_doc_fragment(provider) + + augment_fragment(doc_fragment, provider_info) + + if not compare_doc_fragment(provider, doc_fragment): + path = doc_fragment_fn(provider) + if lint: + errors.append('{0}: Needs to be updated by update-docs-fragments.py'.format(path)) + else: + print('Writing {0}...'.format(path)) + write_doc_fragment(provider, doc_fragment) + + except DocFragmentParseError as e: + errors.append('{0}: Error while parsing docs fragment: {1}'.format(e.path, e.error_message)) + + except Exception as e: + errors.append('{0}: Unexpected error: {1}'.format(program, e)) + + for error in errors: + print(error) + return 5 if errors else 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[0], sys.argv[1:])) |