diff --git a/tests/sanity/extra/botmeta.py b/tests/sanity/extra/botmeta.py index 459d3ba14d..37dff871b0 100755 --- a/tests/sanity/extra/botmeta.py +++ b/tests/sanity/extra/botmeta.py @@ -3,10 +3,9 @@ # 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 """Check BOTMETA file.""" -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type -import ast +from __future__ import annotations + import os import re import sys @@ -66,26 +65,27 @@ AUTHOR_REGEX = re.compile(r'^\w.*\(@([\w-]+)\)(?![\w.])') def read_authors(filename): data = {} try: - with open(filename, 'rb') as b_module_data: - M = ast.parse(b_module_data.read()) + documentation = [] + in_docs = False + with open(filename, 'r', encoding='utf-8') as f: + for line in f: + if line.startswith('DOCUMENTATION ='): + in_docs = True + elif line.startswith(("'''", '"""')) and in_docs: + in_docs = False + elif in_docs: + documentation.append(line) + if in_docs: + print(f'{filename}: cannot find DOCUMENTATION end') + return [] + if not documentation: + print(f'{filename}: cannot find DOCUMENTATION') + return [] - for child in M.body: - if isinstance(child, ast.Assign): - for t in child.targets: - try: - theid = t.id - except AttributeError: - # skip errors can happen when trying to use the normal code - continue - - if theid == 'DOCUMENTATION': - if isinstance(child.value, ast.Dict): - data = ast.literal_eval(child.value) - else: - data = yaml.safe_load(child.value.s) + data = yaml.safe_load('\n'.join(documentation)) except Exception as e: - print('%s:%d:%d: Cannot load DOCUMENTATION: %s' % (filename, 0, 0, e)) + print(f'{filename}:0:0: Cannot load DOCUMENTATION: {e}') return [] author = data.get('author') or [] @@ -108,21 +108,21 @@ def validate(filename, filedata): return if filename.startswith(('plugins/doc_fragments/', 'plugins/module_utils/')): return - # Compile lis tof all active and inactive maintainers + # Compile list of all active and inactive maintainers all_maintainers = filedata['maintainers'] + filedata['ignore'] - if not filename.startswith('plugins/filter/'): + if not filename.startswith(('plugins/action/', 'plugins/doc_fragments/', 'plugins/filter/', 'plugins/module_utils/', 'plugins/plugin_utils/')): maintainers = read_authors(filename) for maintainer in maintainers: maintainer = extract_author_name(maintainer) if maintainer is not None and maintainer not in all_maintainers: - msg = 'Author %s not mentioned as active or inactive maintainer for %s (mentioned are: %s)' % ( - maintainer, filename, ', '.join(all_maintainers)) - print('%s:%d:%d: %s' % (FILENAME, 0, 0, msg)) + others = ', '.join(all_maintainers) + msg = f'Author {maintainer} not mentioned as active or inactive maintainer for {filename} (mentioned are: {others})' + print(f'{FILENAME}:0:0: {msg}') should_have_no_maintainer = filename in IGNORE_NO_MAINTAINERS if not all_maintainers and not should_have_no_maintainer: - print('%s:%d:%d: %s' % (FILENAME, 0, 0, 'No (active or inactive) maintainer mentioned for %s' % filename)) + print(f'{FILENAME}:0:0: No (active or inactive) maintainer mentioned for {filename}') if all_maintainers and should_have_no_maintainer: - print('%s:%d:%d: %s' % (FILENAME, 0, 0, 'Please remove %s from the ignore list of %s' % (filename, sys.argv[0]))) + print(f'{FILENAME}:0:0: Please remove {filename} from the ignore list of {sys.argv[0]}') def main(): @@ -131,12 +131,12 @@ def main(): with open(FILENAME, 'rb') as f: botmeta = yaml.safe_load(f) except yaml.error.MarkedYAMLError as ex: - print('%s:%d:%d: YAML load failed: %s' % (FILENAME, ex.context_mark.line + - 1, ex.context_mark.column + 1, re.sub(r'\s+', ' ', str(ex)))) + msg = re.sub(r'\s+', ' ', str(ex)) + print('f{FILENAME}:{ex.context_mark.line + 1}:{ex.context_mark.column + 1}: YAML load failed: {msg}') return except Exception as ex: # pylint: disable=broad-except - print('%s:%d:%d: YAML load failed: %s' % - (FILENAME, 0, 0, re.sub(r'\s+', ' ', str(ex)))) + msg = re.sub(r'\s+', ' ', str(ex)) + print(f'{FILENAME}:0:0: YAML load failed: {msg}') return # Validate schema @@ -169,7 +169,7 @@ def main(): except MultipleInvalid as ex: for error in ex.errors: # No way to get line/column numbers - print('%s:%d:%d: %s' % (FILENAME, 0, 0, humanize_error(botmeta, error))) + print(f'{FILENAME}:0:0: {humanize_error(botmeta, error)}') return # Preprocess (substitute macros, convert to lists) @@ -181,7 +181,7 @@ def main(): macro = m.group(1) replacement = (macros[macro] or '') if macro == 'team_ansible_core': - return '$team_ansible_core %s' % replacement + return f'$team_ansible_core {replacement}' return replacement return macro_re.sub(f, text) @@ -196,13 +196,13 @@ def main(): if k in LIST_ENTRIES: filedata[k] = v.split() except KeyError as e: - print('%s:%d:%d: %s' % (FILENAME, 0, 0, 'Found unknown macro %s' % e)) + print(f'{FILENAME}:0:0: Found unknown macro {e}') return # Scan all files unmatched = set(files) for dirs in ('docs/docsite/rst', 'plugins', 'tests', 'changelogs'): - for dirpath, dirnames, filenames in os.walk(dirs): + for dirpath, _dirnames, filenames in os.walk(dirs): for file in sorted(filenames): if file.endswith('.pyc'): continue @@ -217,10 +217,10 @@ def main(): if file in unmatched: unmatched.remove(file) if not matching_files: - print('%s:%d:%d: %s' % (FILENAME, 0, 0, 'Did not find any entry for %s' % filename)) + print(f'{FILENAME}:0:0: Did not find any entry for {filename}') matching_files.sort(key=lambda kv: kv[0]) - filedata = dict() + filedata = {} for k in LIST_ENTRIES: filedata[k] = [] for dummy, data in matching_files: @@ -231,7 +231,7 @@ def main(): validate(filename, filedata) for file in unmatched: - print('%s:%d:%d: %s' % (FILENAME, 0, 0, 'Entry %s was not used' % file)) + print(f'{FILENAME}:0:0: Entry {file} was not used') if __name__ == '__main__':