127 lines
4.9 KiB
Python
127 lines
4.9 KiB
Python
import re
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DOC_API = ROOT / 'docs' / 'api.md'
|
|
DOC_RUN = ROOT / 'docs' / 'run_events_payloads.md'
|
|
|
|
# Ensure project root is importable so `import mainloop` works when this script is
|
|
# executed from the scripts/ folder.
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
|
|
def main():
|
|
# Delayed imports to avoid module-level import ordering issues (E402 in linters)
|
|
import mainloop
|
|
import controller as controller_module
|
|
|
|
mapping_keys = set(mainloop.mapping.keys())
|
|
run_mapping_values = set(mainloop.run_mapping.values())
|
|
|
|
# extract controller emitted run keys by source scan
|
|
controller_src = Path(controller_module.__file__).read_text(encoding='utf-8')
|
|
controller_run_keys = set()
|
|
for m in re.finditer(r"self\.run\([^\)]*self\.run_mapping\[\s*[\'\"]([^\'\"]+)[\'\"]\s*\]", controller_src):
|
|
controller_run_keys.add(m.group(1))
|
|
for m in re.finditer(r"self\.run\([^\)]*\"(/run/[a-zA-Z0-9_\-/]+)\"", controller_src):
|
|
controller_run_keys.add(m.group(1))
|
|
# read docs and extract endpoints conservatively (only full endpoints starting with /get/ /set/ /run/)
|
|
api_text = DOC_API.read_text(encoding='utf-8')
|
|
run_text = DOC_RUN.read_text(encoding='utf-8') if DOC_RUN.exists() else ''
|
|
|
|
# include delete endpoints as well (e.g. /delete/data/deepl_auth_key)
|
|
endpoint_pattern = re.compile(r"(/(?:get|set|run|delete)[A-Za-z0-9_\-/]*)")
|
|
|
|
doc_endpoints = set(m.group(1) for m in endpoint_pattern.finditer(api_text + '\n' + run_text))
|
|
|
|
# Remove umbrella placeholder artifacts that sometimes appear due to
|
|
# comma-separated lists or pattern fragments in the markdown. These are
|
|
# not concrete endpoints and should not be treated as documented endpoints
|
|
# for parity checking.
|
|
umbrella_tokens = {
|
|
'/get', '/set', '/run', '/get/data', '/set/data', '/set/enable', '/set/disable'
|
|
}
|
|
# Remove exact umbrella tokens and any accidental entries that end with a
|
|
# trailing slash (these are artifacts of pattern matching in markdown).
|
|
doc_endpoints = {e for e in doc_endpoints if e not in umbrella_tokens and not e.endswith('/')}
|
|
|
|
# Compare
|
|
missing_in_docs = mapping_keys - doc_endpoints
|
|
# A documented endpoint is valid if it corresponds to either an incoming mapping (mapping_keys)
|
|
# or an outgoing run event (run_mapping_values). Treat extra_in_docs as anything documented
|
|
# that is neither in mapping_keys nor in run_mapping_values.
|
|
extra_in_docs = doc_endpoints - (mapping_keys | run_mapping_values)
|
|
|
|
missing_run_in_docs = run_mapping_values - doc_endpoints
|
|
|
|
# Normalize controller keys to run_mapping values
|
|
normalized = set()
|
|
for k in controller_run_keys:
|
|
if k.startswith('/run/'):
|
|
normalized.add(k)
|
|
else:
|
|
if k in mainloop.run_mapping:
|
|
normalized.add(mainloop.run_mapping[k])
|
|
else:
|
|
normalized.add(k)
|
|
|
|
extra_controller_keys = normalized - run_mapping_values
|
|
|
|
report = []
|
|
report.append('=== Runtime verification report ===')
|
|
report.append(f'Code mapping endpoints: {len(mapping_keys)}')
|
|
report.append(f'Code run_mapping entries: {len(run_mapping_values)}')
|
|
report.append(f'Controller emitted run keys: {len(controller_run_keys)}')
|
|
report.append(f'Documented endpoints (docs): {len(doc_endpoints)}')
|
|
|
|
report.append('\n--- Endpoints present in code but NOT documented ---')
|
|
if missing_in_docs:
|
|
for e in sorted(missing_in_docs):
|
|
report.append(' - ' + e)
|
|
else:
|
|
report.append(' - None')
|
|
|
|
report.append('\n--- Endpoints documented but NOT in code ---')
|
|
if extra_in_docs:
|
|
for e in sorted(extra_in_docs):
|
|
report.append(' - ' + e)
|
|
else:
|
|
report.append(' - None')
|
|
|
|
report.append('\n--- Run events present in code (run_mapping) but NOT documented ---')
|
|
if missing_run_in_docs:
|
|
for e in sorted(missing_run_in_docs):
|
|
report.append(' - ' + e)
|
|
else:
|
|
report.append(' - None')
|
|
|
|
report.append('\n--- Run keys emitted in controller (normalized) but NOT in run_mapping values ---')
|
|
if extra_controller_keys:
|
|
for e in sorted(extra_controller_keys):
|
|
report.append(' - ' + e)
|
|
else:
|
|
report.append(' - None')
|
|
|
|
print('\n'.join(report))
|
|
|
|
# Also output JSON for downstream processing
|
|
out = {
|
|
'mapping_keys': sorted(mapping_keys),
|
|
'run_mapping_values': sorted(run_mapping_values),
|
|
'controller_run_keys': sorted(controller_run_keys),
|
|
'doc_endpoints': sorted(doc_endpoints),
|
|
'missing_in_docs': sorted(missing_in_docs),
|
|
'extra_in_docs': sorted(extra_in_docs),
|
|
'missing_run_in_docs': sorted(missing_run_in_docs),
|
|
'extra_controller_keys': sorted(extra_controller_keys),
|
|
}
|
|
print('\nJSON_OUTPUT_START')
|
|
print(json.dumps(out))
|
|
print('JSON_OUTPUT_END')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|