diff options
| author | Hypothalamus <sehraf@users.noreply.github.com> | 2025-05-13 18:34:55 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-13 18:34:55 +0200 |
| commit | 4542244c4fff5a5087ff84994d0f5e69826b5bd0 (patch) | |
| tree | 195667e1aab7e110942c37d8e33d0b18f1ca43c5 /docs/gen/gen.py | |
| parent | a58e9d1dc96c949dd0330e812a699de2272d3df8 (diff) | |
| download | box64-4542244c4fff5a5087ff84994d0f5e69826b5bd0.tar.gz box64-4542244c4fff5a5087ff84994d0f5e69826b5bd0.zip | |
[DOCS] improve consistency between documentation and code (#2631)
* [fix][Docs] is `XXXX` consistantly * [feat][Docs] add simple validation script that checks consistency between `usage.json` and `env.h` * [DOCS] include `validate.py` in `gen.py`
Diffstat (limited to 'docs/gen/gen.py')
| -rw-r--r-- | docs/gen/gen.py | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/docs/gen/gen.py b/docs/gen/gen.py index 8a8913ea..a21dc57d 100644 --- a/docs/gen/gen.py +++ b/docs/gen/gen.py @@ -2,6 +2,7 @@ import json import os +import re from collections import defaultdict script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -132,3 +133,174 @@ Example: =cut """) + +#################### +# Validation +#################### + +PADDING = 30 +INCLUDE_BLANK = False + +def get_usage_entry(usage, define): + entry = list(e for e in usage if e["name"] == define) + if len(entry) == 0: + print(f"{define:<{PADDING}}: missing usage.json entry") + elif len(entry) > 1: + print(f"{define:<{PADDING}}: multiple usage entries found", len(entry)) + else: + return entry[0] + return None + +# check `default` uniqueness +for entry in data: + i = list(d["default"] for d in entry["options"]).count(True) + if i > 1: + print(f"{entry["name"]:<{PADDING}}: multiple default values usage.json") + + +# regex to match env.h C code +regex = { + "INTEGER": re.compile( + r"^\s*INTEGER\((?P<define>\w+), (?P<name>\w+), (?P<default>\w+), (?P<min>\w+), (?P<max>\w+)\)" + ), + "INTEGER64": re.compile( + r"^\s*INTEGER64\((?P<define>\w+), (?P<name>\w+), (?P<default>\w+)\)" + ), + "BOOLEAN": re.compile( + r"^\s*BOOLEAN\((?P<define>\w+), (?P<name>\w+), (?P<default>\w+)\)" + ), + "ADDRESS": re.compile(r"^\s*ADDRESS\((?P<define>\w+), (?P<name>\w+)\)"), + "STRING": re.compile(r"^\s*STRING\((?P<define>\w+), (?P<name>\w+)\)"), +} + +env_file = os.path.join(script_dir, "../../src/include/env.h") +with open(env_file, "r") as file: + matches = {} + for line in file.readlines(): + for t, r in regex.items(): + m = r.search(line) + if m: + # filter out comments and other non useful code + if m.group("define") == "NAME": + continue + + if t not in matches: + matches[t] = {} + matches[t][m.group("define")] = m + break + +for define, m in matches["INTEGER"].items(): + name = m.group("name") + default = ( + 0 if m.group("default") == "DEFAULT_LOG_LEVEL" else int(m.group("default")) + ) + min = int(m.group("min")) + max = int(m.group("max")) + + # Check default in valid range + if default < min or default > max: + print(f"{define:<{PADDING}}: default lays outside of min/max range") + + # Check consistency with usage.json + if e := get_usage_entry(data, define): + # guess min/max values if possible + min2 = max2 = None + # blank means that the entry has an 'XXXX' entry which usually indicated that arbitrary values are valid + blank = False + default2 = None + for o in e["options"]: + if o["key"] == "XXXX": + blank = True + continue + + val = int(o["key"]) # not supposed to fail + + min2 = min2 if min2 is not None and min2 < val else val + max2 = max2 if max2 is not None and max2 > val else val + + if o["default"]: + default2 = val + + if min2 and min2 != min: + if not blank or (blank and INCLUDE_BLANK): + print( + f"{define:<{PADDING}}: min value mismatch: env.h={min}, usage.json={min2}{(' (possible false positive)' if blank else '')}" + ) + if max2 and max2 != max: + if not blank or (blank and INCLUDE_BLANK): + print( + f"{define:<{PADDING}}: max value mismatch: env.h={max}, usage.json={max2}{(' (possible false positive)' if blank else '')}" + ) + if default2 and default2 != default: + print( + f"{define:<{PADDING}}: default value mismatch: env.h={default}, usage.json={default2}" + ) + +for define, m in matches["INTEGER64"].items(): + # similar to INTEGER but without min/max + name = m.group("name") + default = int(m.group("default")) + + # Check consistency with usage.json + if e := get_usage_entry(data, define): + default2 = None + for o in e["options"]: + if o["key"] == "XXXX": + continue + + val = int(o["key"]) + + if o["default"]: + default2 = val + + if default2 and default2 != default: + print( + f"{define:<{PADDING}}: default value mismatch: env.h={default}, usage.json={default2}" + ) + +for define, m in matches["BOOLEAN"].items(): + name = m.group("name") + default = bool(m.group("default")) + + # Check consistency with usage.json + if e := get_usage_entry(data, define): + default2 = None + for o in e["options"]: + try: + val = bool(o["key"]) + except ValueError: + print(f"{define:<{PADDING}}: failed to parse boolean {o["key"]}") + + if o["default"]: + default2 = val + + if default2 and default2 != default: + print( + f"{define:<{PADDING}}: default value mismatch: env.h={default}, usage.json={default2}" + ) + +# ADDRESS and STRING are not that interesting, just check that they exist in usage.json +for define, m in matches["ADDRESS"].items(): + _ = get_usage_entry(data, define) +for define, m in matches["STRING"].items(): + # skip BOX64_ENV[1-5] entries, they mismatch but this is fine + if define.startswith("BOX64_ENV"): + continue + _ = get_usage_entry(data, define) + +# check that everything from usage.json is in env.h +for e in data: + define = e["name"] + + # skip BOX64_ENV[1-5] entries, they mismatch but this is fine + if define.startswith("BOX64_ENV"): + continue + + found = False + for t in matches: + for d in matches[t]: + if d == define: + found = True + + if not found: + print(f"{define:<{PADDING}}: missing env.h entry") |