|
| 1 | +# Copyright (c) 2024 Nordic Semiconductor ASA |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause |
| 4 | + |
| 5 | +from pathlib import Path |
| 6 | +from compare import AnyChange, CompareResult |
| 7 | +from jinja2 import Template |
| 8 | +from args import args |
| 9 | + |
| 10 | + |
| 11 | +def compile_messages(messages): |
| 12 | + result = {} |
| 13 | + for key in list(messages.keys()): |
| 14 | + message = messages[key] |
| 15 | + if message.startswith('ignore'): |
| 16 | + level = 0 |
| 17 | + elif message.startswith('notice'): |
| 18 | + level = 1 |
| 19 | + elif message.startswith('warning'): |
| 20 | + level = 2 |
| 21 | + elif message.startswith('critical'): |
| 22 | + level = 3 |
| 23 | + else: |
| 24 | + raise ValueError(f'Unknown level of message: {message}') |
| 25 | + result[key] = (Template(messages[key]), level) |
| 26 | + return result |
| 27 | + |
| 28 | + |
| 29 | +messages: 'dict[str, tuple[Template, int]]' = compile_messages({ |
| 30 | + 'typedef-added': 'ignore', |
| 31 | + 'typedef-deleted': 'critical: Type "{{old.name}}" definition deleted.', |
| 32 | + 'typedef-modified-file': 'warning: Type "{{new.name}}" definition moved to a different file.', |
| 33 | + 'typedef-modified-desc': 'notice: Type "{{new.name}}" definition description changed.', |
| 34 | + 'typedef-modified-type': 'warning: Type "{{new.name}}" definition changed.', |
| 35 | + 'var-added': 'ignore', |
| 36 | + 'var-deleted': 'critical: Variable "{{old.name}}" deleted.', |
| 37 | + 'var-modified-file': 'warning: Variable "{{new.name}}" moved to a different file.', |
| 38 | + 'var-modified-desc': 'notice: Variable "{{new.name}}" description changed.', |
| 39 | + 'var-modified-type': 'warning: Variable "{{new.name}}" type changed.', |
| 40 | + 'enum_value-added': 'ignore', |
| 41 | + 'enum_value-deleted': 'critical: Enum value "{{old.name}}" deleted.', |
| 42 | + 'enum_value-modified-value': 'warning: Enum value "{{new.name}}" changed.', |
| 43 | + 'enum_value-modified-desc': 'notice: Enum value "{{new.name}}" description changed.', |
| 44 | + 'enum_value-modified-file': 'warning: Enum value "{{new.name}}" moved to a different file.', |
| 45 | + 'enum-added': 'ignore', |
| 46 | + 'enum-deleted': 'critical: Enum "{{old.name}}" deleted.', |
| 47 | + 'enum-modified-file': 'warning: Enum "{{new.name}}" moved to a different file.', |
| 48 | + 'enum-modified-desc': 'notice: Enum "{{new.name}}" description changed.', |
| 49 | + 'struct-added': 'ignore', |
| 50 | + 'struct-deleted': 'critical: Structure "{{old.name}}" deleted.', |
| 51 | + 'struct-modified-file': 'warning: Structure "{{new.name}}" moved to a different file.', |
| 52 | + 'struct-modified-desc': 'notice: Structure "{{new.name}}" description changed.', |
| 53 | + 'func-added': 'ignore', |
| 54 | + 'func-deleted': 'critical: Function "{{old.name}}" deleted.', |
| 55 | + 'func-modified-return_type': 'warning: Function "{{new.name}}" return type changed.', |
| 56 | + 'func-modified-file': 'warning: Function "{{new.name}}" moved to a different file.', |
| 57 | + 'func-modified-desc': 'notice: Function "{{new.name}}" description changed.', |
| 58 | + 'def-added': 'ignore', |
| 59 | + 'def-deleted': 'critical: Definition "{{old.name}}" deleted.', |
| 60 | + 'def-modified-value': 'notice: Definition "{{new.name}}" value changed.', |
| 61 | + 'def-modified-file': 'warning: Definition "{{new.name}}" moved to a different file.', |
| 62 | + 'def-modified-desc': 'notice: Definition "{{new.name}}" description changed.', |
| 63 | + 'field-added': 'ignore', |
| 64 | + 'field-deleted': 'critical: Structure "{{struct.new.name}}" field "{{new.name}}" deleted.', |
| 65 | + 'field-modified-index': 'ignore', |
| 66 | + 'field-modified-type': 'warning: Structure "{{struct.new.name}}" field "{{new.name}}" type changed.', |
| 67 | + 'field-modified-desc': 'notice: Structure "{{struct.new.name}}" field "{{new.name}}" description changed.', |
| 68 | + 'param-added': 'critical: Parameter "{{new.name}}" added in "{{parent.new.name}}".', |
| 69 | + 'param-deleted': 'critical: Parameter "{{old.name}}" deleted from "{{parent.new.name}}".', |
| 70 | + 'param-modified-index': 'critical: Parameter "{{new.name}}" reordered in "{{parent.new.name}}".', |
| 71 | + 'param-modified-type': 'warning: Parameter "{{new.name}}" type changed in "{{parent.new.name}}".', |
| 72 | + 'param-modified-desc': 'notice: Parameter "{{new.name}}" description changed in "{{parent.new.name}}".', |
| 73 | +}) |
| 74 | + |
| 75 | + |
| 76 | +github_commands = [ |
| 77 | + '::ignore', |
| 78 | + '::notice', |
| 79 | + '::warning', |
| 80 | + '::error' |
| 81 | +] |
| 82 | + |
| 83 | +github_titles = [ |
| 84 | + 'Ignore', |
| 85 | + 'Notice', |
| 86 | + 'Warning', |
| 87 | + 'Critical', |
| 88 | +] |
| 89 | + |
| 90 | +def encode(text: str, is_message: bool): |
| 91 | + if is_message: |
| 92 | + return text.replace('%', '%25').replace('\r', '%0D').replace('\n', '%0A') |
| 93 | + else: |
| 94 | + return text.replace('%', '%25').replace('\r', '%0D').replace('\n', '%0A').replace(',', '%2C').replace('::', '%3A%3A') |
| 95 | + |
| 96 | +def show_message(file: Path, line: 'str | int | None', message: str, level: int): |
| 97 | + if args.resolve_paths is not None: |
| 98 | + file = args.resolve_paths.joinpath(file).absolute() |
| 99 | + if args.relative_to is not None: |
| 100 | + file = file.relative_to(args.relative_to) |
| 101 | + if args.format == 'github': |
| 102 | + command = github_commands[level] |
| 103 | + title = f'Compatibility {github_titles[level]}' |
| 104 | + if line is not None: |
| 105 | + print(f'{command} file={encode(str(file), False)},line={line},title={title}::{encode(message, True)}') |
| 106 | + else: |
| 107 | + print(f'{command} file={encode(str(file), False)},title={title}::{encode(message, True)}') |
| 108 | + elif line is not None: |
| 109 | + print(f'{file}:{line}: {message}') |
| 110 | + else: |
| 111 | + print(f'{file}: {message}') |
| 112 | + |
| 113 | + |
| 114 | + |
| 115 | +def generate_changes(stats: 'list[int]', changes: 'list[AnyChange]', |
| 116 | + location: 'tuple[Path, int | None]', **kwargs) -> int: |
| 117 | + for change in changes: |
| 118 | + prefix = f'{change.kind}-{change.action}' |
| 119 | + if change.new and hasattr(change.new, 'file') and change.new.file: |
| 120 | + if change.new and hasattr(change.new, 'line') and change.new.line: |
| 121 | + loc = (Path(change.new.file), change.new.line) |
| 122 | + else: |
| 123 | + loc = (Path(change.new.file), None) |
| 124 | + else: |
| 125 | + loc = location |
| 126 | + for key, (template, level) in messages.items(): |
| 127 | + if key.startswith(prefix) and (level > 0): |
| 128 | + data = {} |
| 129 | + for name in dir(change): |
| 130 | + value = getattr(change, name) |
| 131 | + if (not callable(value)) and (not name.startswith('_')): |
| 132 | + data[name] = value |
| 133 | + for name, value in kwargs.items(): |
| 134 | + data[name] = value |
| 135 | + message = template.render(**data) |
| 136 | + if key == prefix: |
| 137 | + show_message(loc[0], loc[1], message, level) |
| 138 | + stats[level] += 1 |
| 139 | + else: |
| 140 | + field = key[len(prefix) + 1:] |
| 141 | + value = getattr(change, field) |
| 142 | + if (value): |
| 143 | + show_message(loc[0], loc[1], message, level) |
| 144 | + stats[level] += 1 |
| 145 | + if prefix == 'struct-modified': |
| 146 | + level = generate_changes(stats, change.fields, loc, struct=change) |
| 147 | + elif prefix in ('func-modified', 'def-modified'): |
| 148 | + level = generate_changes(stats, change.params, loc, parent=change) |
| 149 | + |
| 150 | + |
| 151 | +def generate(compare_result: CompareResult) -> 'list[int]': |
| 152 | + stats = [0, 0, 0, 0] |
| 153 | + for group in compare_result.groups: |
| 154 | + if (group.name): |
| 155 | + print(f'=== Group {group.name}: {group.title} ===') |
| 156 | + generate_changes(stats, group.changes, (Path(), None)) |
| 157 | + return stats |
0 commit comments