From c96e8f15e5efc2c0ca464cc52d0961d80195909a Mon Sep 17 00:00:00 2001 From: Callum Law Date: Fri, 1 May 2026 12:00:23 +1200 Subject: [PATCH] Meta: Make CSS dimension loading reusable Aside from the existing users this will also be used within the CSSGrammar parser --- ...rate_libweb_css_numeric_factory_methods.py | 7 +- Meta/Generators/generate_libweb_css_units.py | 89 +------------- Meta/Utils/css_dimensions.py | 116 ++++++++++++++++++ 3 files changed, 124 insertions(+), 88 deletions(-) create mode 100644 Meta/Utils/css_dimensions.py diff --git a/Meta/Generators/generate_libweb_css_numeric_factory_methods.py b/Meta/Generators/generate_libweb_css_numeric_factory_methods.py index 3f35b737fe..55fcd33bf3 100644 --- a/Meta/Generators/generate_libweb_css_numeric_factory_methods.py +++ b/Meta/Generators/generate_libweb_css_numeric_factory_methods.py @@ -6,7 +6,6 @@ # SPDX-License-Identifier: BSD-2-Clause import argparse -import json import sys from pathlib import Path @@ -14,6 +13,8 @@ from typing import TextIO sys.path.append(str(Path(__file__).resolve().parent.parent)) +from Utils.css_dimensions import get_css_dimensions +from Utils.css_dimensions import load_css_dimensions from Utils.utils import make_name_acceptable_cpp from Utils.utils import snake_casify @@ -130,8 +131,8 @@ def main(): parser.add_argument("-j", "--json", required=True, help="Path to the JSON file to read from") args = parser.parse_args() - with open(args.json, "r", encoding="utf-8") as input_file: - units_data = json.load(input_file) + load_css_dimensions(args.json) + units_data = get_css_dimensions() with open(args.header, "w", encoding="utf-8") as output_file: write_header_file(output_file, units_data) diff --git a/Meta/Generators/generate_libweb_css_units.py b/Meta/Generators/generate_libweb_css_units.py index 78b0f4126f..e0e0ce337f 100644 --- a/Meta/Generators/generate_libweb_css_units.py +++ b/Meta/Generators/generate_libweb_css_units.py @@ -6,7 +6,6 @@ # SPDX-License-Identifier: BSD-2-Clause import argparse -import json import sys from pathlib import Path @@ -14,90 +13,13 @@ from typing import TextIO sys.path.append(str(Path(__file__).resolve().parent.parent)) +from Utils.css_dimensions import get_css_dimensions +from Utils.css_dimensions import load_css_dimensions from Utils.utils import snake_casify from Utils.utils import title_casify from Utils.utils import underlying_type_for_enum -def json_is_valid(dimensions_data: dict, json_path: str) -> bool: - is_valid = True - most_recent_dimension_name = "" - for dimension_name, units in dimensions_data.items(): - # Dimensions should be in alphabetical order - if dimension_name.lower() < most_recent_dimension_name.lower(): - print( - f"{json_path}: Dimension `{dimension_name}` is in the wrong position. " - "Please keep this list alphabetical!", - file=sys.stderr, - ) - is_valid = False - most_recent_dimension_name = dimension_name - - most_recent_unit_name = "" - canonical_unit = None - for unit_name, unit in units.items(): - # Units should be in alphabetical order - if unit_name.lower() < most_recent_unit_name.lower(): - print( - f"{json_path}: {dimension_name} unit `{unit_name}` is in the wrong position. " - "Please keep this list alphabetical!", - file=sys.stderr, - ) - is_valid = False - most_recent_unit_name = unit_name - - is_canonical_unit = unit.get("is-canonical-unit") is True - number_of_canonical_unit = unit.get("number-of-canonical-unit") - relative_to = unit.get("relative-to") - provided_count = ( - (1 if is_canonical_unit else 0) - + (1 if number_of_canonical_unit is not None else 0) - + (1 if relative_to is not None else 0) - ) - if provided_count != 1: - print( - f"{json_path}: {dimension_name} unit `{unit_name}` must have exactly 1 of " - "`is-canonical-unit: true`, `number-of-canonical-unit`, or `relative-to` provided.", - file=sys.stderr, - ) - is_valid = False - if is_canonical_unit: - if canonical_unit is not None: - print( - f"{json_path}: {dimension_name} unit `{unit_name}` marked canonical, " - f"but `{canonical_unit}` was already. Must have exactly 1.", - file=sys.stderr, - ) - is_valid = False - else: - canonical_unit = unit_name - if relative_to is not None: - if dimension_name == "length": - if relative_to not in ("font", "viewport"): - print( - f"{json_path}: {dimension_name} unit `{unit_name}` is marked as relative to " - f"`{relative_to}`, which is unsupported.", - file=sys.stderr, - ) - is_valid = False - else: - print( - f"{json_path}: {dimension_name} unit `{unit_name}` is marked as relative, " - "but only relative length units are currently supported.", - file=sys.stderr, - ) - is_valid = False - - if canonical_unit is None: - print( - f"{json_path}: {dimension_name} has no unit marked as canonical. Must have exactly 1.", - file=sys.stderr, - ) - is_valid = False - - return is_valid - - def canonical_unit_name(units: dict) -> str: for unit_name, unit in units.items(): if unit.get("is-canonical-unit") is True: @@ -341,11 +263,8 @@ def main(): parser.add_argument("-j", "--json", required=True, help="Path to the JSON file to read from") args = parser.parse_args() - with open(args.json, "r", encoding="utf-8") as input_file: - dimensions_data = json.load(input_file) - - if not json_is_valid(dimensions_data, args.json): - sys.exit(1) + load_css_dimensions(args.json) + dimensions_data = get_css_dimensions() with open(args.header, "w", encoding="utf-8") as output_file: write_header_file(output_file, dimensions_data) diff --git a/Meta/Utils/css_dimensions.py b/Meta/Utils/css_dimensions.py new file mode 100644 index 0000000000..00e29fb602 --- /dev/null +++ b/Meta/Utils/css_dimensions.py @@ -0,0 +1,116 @@ +import json +import sys + +from typing import Any + +_css_dimensions: dict[str, Any] | None = None + + +def json_is_valid(dimensions_data: dict[str, Any], json_path: str) -> bool: + is_valid = True + most_recent_dimension_name = "" + for dimension_name, units in dimensions_data.items(): + if dimension_name.lower() < most_recent_dimension_name.lower(): + print( + f"{json_path}: Dimension `{dimension_name}` is in the wrong position. " + "Please keep this list alphabetical!", + file=sys.stderr, + ) + is_valid = False + most_recent_dimension_name = dimension_name + + if not isinstance(units, dict): + print(f"{json_path}: Dimension `{dimension_name}` is not an object", file=sys.stderr) + is_valid = False + continue + + most_recent_unit_name = "" + canonical_unit = None + for unit_name, unit in units.items(): + # Units should be in alphabetical order + if unit_name.lower() < most_recent_unit_name.lower(): + print( + f"{json_path}: {dimension_name} unit `{unit_name}` is in the wrong position. " + "Please keep this list alphabetical!", + file=sys.stderr, + ) + is_valid = False + most_recent_unit_name = unit_name + + if not isinstance(unit, dict): + print(f"{json_path}: {dimension_name} unit `{unit_name}` is not an object", file=sys.stderr) + is_valid = False + continue + + is_canonical_unit = unit.get("is-canonical-unit") is True + number_of_canonical_unit = unit.get("number-of-canonical-unit") + relative_to = unit.get("relative-to") + provided_count = ( + (1 if is_canonical_unit else 0) + + (1 if number_of_canonical_unit is not None else 0) + + (1 if relative_to is not None else 0) + ) + if provided_count != 1: + print( + f"{json_path}: {dimension_name} unit `{unit_name}` must have exactly 1 of " + "`is-canonical-unit: true`, `number-of-canonical-unit`, or `relative-to` provided.", + file=sys.stderr, + ) + is_valid = False + if is_canonical_unit: + if canonical_unit is not None: + print( + f"{json_path}: {dimension_name} unit `{unit_name}` marked canonical, " + f"but `{canonical_unit}` was already. Must have exactly 1.", + file=sys.stderr, + ) + is_valid = False + else: + canonical_unit = unit_name + if relative_to is not None: + if dimension_name == "length": + if relative_to not in ("font", "viewport"): + print( + f"{json_path}: {dimension_name} unit `{unit_name}` is marked as relative to " + f"`{relative_to}`, which is unsupported.", + file=sys.stderr, + ) + is_valid = False + else: + print( + f"{json_path}: {dimension_name} unit `{unit_name}` is marked as relative, " + "but only relative length units are currently supported.", + file=sys.stderr, + ) + is_valid = False + + if canonical_unit is None: + print( + f"{json_path}: {dimension_name} has no unit marked as canonical. Must have exactly 1.", + file=sys.stderr, + ) + is_valid = False + + return is_valid + + +def load_css_dimensions(json_path: str) -> None: + global _css_dimensions + + with open(json_path, "r", encoding="utf-8") as json_file: + dimensions_data = json.load(json_file) + + if not isinstance(dimensions_data, dict): + raise RuntimeError(f"{json_path}: expected a JSON object") + + if not json_is_valid(dimensions_data, json_path): + raise RuntimeError(f"{json_path}: invalid CSS dimensions data") + + _css_dimensions = dimensions_data + + +def get_css_dimensions() -> dict[str, Any]: + if _css_dimensions is None: + raise RuntimeError("CSS dimensions have not been initialized") + + return _css_dimensions