Skip to content

Commit 7089cf2

Browse files
committed
docs: system reference docs generator fixed
1 parent b109cfb commit 7089cf2

File tree

1 file changed

+141
-65
lines changed

1 file changed

+141
-65
lines changed

scripts/systems_reference.py

Lines changed: 141 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ class SystemInfo:
133133
inline_subnamespaces: Set[str] = field(
134134
default_factory=set
135135
) # Track inline subnamespaces (e.g., "si2019", "codata2018")
136+
imported_systems: Set[str] = field(
137+
default_factory=set
138+
) # Track systems imported via using declarations (e.g., {"si"})
136139

137140

138141
class SystemsParser:
@@ -146,6 +149,15 @@ def __init__(self, systems_dir: Path):
146149
# systems_dir is src/systems/include/mp-units/systems, so we need to go up 5 levels to get to repo root
147150
self.source_root = systems_dir.parent.parent.parent.parent.parent
148151

152+
@staticmethod
153+
def _strip_comments(content: str) -> str:
154+
"""Remove C++ comments (both // and /* */) from source code"""
155+
# Remove multi-line comments /* ... */
156+
content = re.sub(r"/\*.*?\*/", "", content, flags=re.DOTALL)
157+
# Remove single-line comments //...
158+
content = re.sub(r"//.*?$", "", content, flags=re.MULTILINE)
159+
return content
160+
149161
def parse_all_systems(self):
150162
"""Parse all system header files, following include order"""
151163
# First, parse core framework entities
@@ -228,13 +240,14 @@ def _parse_core_framework(self):
228240
unit_path = self.source_root / "src/core/include/mp-units/framework/unit.h"
229241
if unit_path.exists():
230242
try:
231-
content = unit_path.read_text()
232-
# Only parse content after "common dimensionless units" comment
233-
# and before "Common unit" comment to avoid parsing examples
234-
start_marker = content.find("// common dimensionless units")
235-
end_marker = content.find("// Common unit")
243+
raw_content = unit_path.read_text()
244+
# Find markers before stripping comments
245+
start_marker = raw_content.find("// common dimensionless units")
246+
end_marker = raw_content.find("// Common unit")
236247
if start_marker != -1 and end_marker != -1:
237-
content = content[start_marker:end_marker]
248+
# Extract the section, then strip comments
249+
section_content = raw_content[start_marker:end_marker]
250+
content = self._strip_comments(section_content)
238251
# Parse units at mp_units namespace level (no sub-namespace)
239252
self._parse_units(
240253
content, core_system, str(unit_path), namespace_to_search=None
@@ -249,7 +262,7 @@ def _parse_core_framework(self):
249262

250263
def parse_system_with_includes(self, main_header: Path):
251264
"""Parse a system header and all its includes in order"""
252-
content = main_header.read_text()
265+
content = self._strip_comments(main_header.read_text())
253266

254267
# Extract includes from this header
255268
include_pattern = r"#include\s+<mp-units/systems/([^>]+)>"
@@ -298,7 +311,7 @@ def parse_system_header(self, header_file: Path):
298311
return
299312

300313
self.parsed_files.add(header_file)
301-
content = header_file.read_text()
314+
content = self._strip_comments(header_file.read_text())
302315

303316
# Extract namespace
304317
namespace_match = re.search(r"namespace\s+mp_units::(\w+)", content)
@@ -1306,11 +1319,17 @@ def _parse_using_declarations(self, content: str, system: SystemInfo, file: str)
13061319
if "std" in full_namespace:
13071320
continue
13081321

1322+
# Extract the source system from the namespace
1323+
# e.g., "si" from "si" or "si::non_si" from "si::non_si"
1324+
source_system = full_namespace.split("::")[0]
1325+
system.imported_systems.add(source_system)
1326+
13091327
unit = Unit(
13101328
name=unit_name,
13111329
symbol=f"(imported from {full_namespace})",
13121330
definition=f"using {full_namespace}::{unit_name}",
13131331
namespace=f"mp_units::{system.namespace}",
1332+
origin_namespace=f"mp_units::{full_namespace}",
13141333
file=file,
13151334
is_alias=True,
13161335
)
@@ -2667,6 +2686,34 @@ def _linkify_definition(self, definition: str, current_system: SystemInfo) -> st
26672686
# Collect all possible references from all systems
26682687
all_refs = {} # name -> (system_namespace, anchor_name)
26692688

2689+
# Build priority-aware reference map
2690+
# For the current system, prioritize units from imported systems
2691+
priority_refs = (
2692+
{}
2693+
) # name -> (system_namespace, anchor_name) for imported systems
2694+
2695+
if current_system.imported_systems:
2696+
for imported_sys in current_system.imported_systems:
2697+
if imported_sys in self.parser.systems:
2698+
imported_system = self.parser.systems[imported_sys]
2699+
for unit in imported_system.units:
2700+
if not unit.is_alias:
2701+
subns_prefix = None
2702+
if unit.origin_namespace:
2703+
parts = unit.origin_namespace.replace(
2704+
"mp_units::", ""
2705+
).split("::")
2706+
if len(parts) > 1:
2707+
subns_prefix = parts[-1]
2708+
elif unit.subnamespace:
2709+
subns_prefix = unit.subnamespace
2710+
anchor_id = (
2711+
f"{subns_prefix}-{unit.name}"
2712+
if subns_prefix
2713+
else unit.name
2714+
)
2715+
priority_refs[unit.name] = (imported_sys, anchor_id)
2716+
26702717
for sys_ns, system in self.parser.systems.items():
26712718
# Add units
26722719
for unit in system.units:
@@ -2681,8 +2728,11 @@ def _linkify_definition(self, definition: str, current_system: SystemInfo) -> st
26812728

26822729
anchor_id = f"{subns_prefix}-{unit.name}" if subns_prefix else unit.name
26832730

2684-
all_refs[unit.name] = (sys_ns, anchor_id)
2685-
# Also add qualified names
2731+
# For unqualified names, only add if not an alias and not already present
2732+
# This prevents aliases and redefinitions from overwriting the primary definition
2733+
if not unit.is_alias and unit.name not in all_refs:
2734+
all_refs[unit.name] = (sys_ns, anchor_id)
2735+
# Always add qualified names (these are system-specific)
26862736
all_refs[f"{sys_ns}::{unit.name}"] = (sys_ns, anchor_id)
26872737
# If there's a subnamespace, also add subnamespace::name format
26882738
if subns_prefix:
@@ -2694,15 +2744,19 @@ def _linkify_definition(self, definition: str, current_system: SystemInfo) -> st
26942744

26952745
# Add point origins
26962746
for origin in system.point_origins:
2697-
all_refs[origin.name] = (sys_ns, origin.name)
2747+
# Only add unqualified name if not already present (first system wins)
2748+
if origin.name not in all_refs:
2749+
all_refs[origin.name] = (sys_ns, origin.name)
26982750
all_refs[f"{sys_ns}::{origin.name}"] = (sys_ns, origin.name)
26992751
if origin.secondary_namespaces:
27002752
for sec_ns in origin.secondary_namespaces:
27012753
all_refs[f"{sec_ns}::{origin.name}"] = (sys_ns, origin.name)
27022754

27032755
# Add quantities
27042756
for qty in system.quantities:
2705-
all_refs[qty.name] = (sys_ns, qty.name)
2757+
# Only add unqualified name if not already present (first system wins)
2758+
if qty.name not in all_refs:
2759+
all_refs[qty.name] = (sys_ns, qty.name)
27062760
all_refs[f"{sys_ns}::{qty.name}"] = (sys_ns, qty.name)
27072761
if qty.secondary_namespaces:
27082762
for sec_ns in qty.secondary_namespaces:
@@ -2852,6 +2906,14 @@ def make_link(text, url):
28522906
target_sys, anchor = all_refs["dimensionless"]
28532907
return make_link(display_text, f"{target_sys}.md#{anchor}")
28542908

2909+
# Check priority refs first (imported systems)
2910+
if unqualified_check in priority_refs:
2911+
target_sys, anchor = priority_refs[unqualified_check]
2912+
if target_sys == current_system.namespace:
2913+
return make_link(display_text, f"#{anchor}")
2914+
else:
2915+
return make_link(display_text, f"{target_sys}.md#{anchor}")
2916+
28552917
if current_sys_key in all_refs:
28562918
# Found in current system
28572919
target_sys, anchor = all_refs[current_sys_key]
@@ -3201,59 +3263,73 @@ def extract_metadata(self):
32013263

32023264
def _generate_cpp_program(self) -> str:
32033265
"""Generate C++ program that outputs metadata for all quantities"""
3204-
lines = [
3205-
"// Auto-generated program to extract quantity metadata",
3206-
"#include <mp-units/systems/isq.h>",
3207-
"#include <mp-units/systems/isq_angle.h>",
3208-
"#include <mp-units/systems/angular.h>",
3209-
"#include <mp-units/systems/natural.h>",
3210-
"#include <mp-units/systems/iec80000.h>",
3211-
"#include <iostream>",
3212-
"",
3213-
"using namespace mp_units;",
3214-
"",
3215-
"constexpr std::string_view get_parent(QuantitySpec auto qs)",
3216-
"{",
3217-
" if constexpr (requires { qs._parent_; })",
3218-
" return detail::type_name<std::remove_const_t<decltype(qs._parent_)>>();",
3219-
" else",
3220-
' return "<root>";',
3221-
"}",
3222-
"",
3223-
"constexpr std::string_view character_to_string(quantity_character ch)",
3224-
"{",
3225-
" switch (ch) {",
3226-
" case quantity_character::real_scalar:",
3227-
' return "Real";',
3228-
" case quantity_character::complex_scalar:",
3229-
' return "Complex";',
3230-
" case quantity_character::vector:",
3231-
' return "Vector";',
3232-
" case quantity_character::tensor:",
3233-
' return "Tensor";',
3234-
" default:",
3235-
' return "Unknown";',
3236-
" }",
3237-
"}",
3238-
"",
3239-
"template<QuantitySpec QS>",
3240-
"void print_quantity(std::string_view namespace_name, std::string_view name, QS qs)",
3241-
"{",
3242-
' std::cout << namespace_name << "," // namespace',
3243-
' << name << "," // quantity name',
3244-
' << character_to_string(qs.character) << "," // character',
3245-
' << dimension_symbol(qs.dimension) << "," // dimension',
3246-
' << std::boolalpha << (qs == detail::get_kind_tree_root(qs)) << "," // is kind',
3247-
' << detail::type_name<decltype(get_kind(qs))>() << "," // kind quantity',
3248-
' << get_parent(qs) << "," // parent quantity',
3249-
' << detail::type_name<decltype(detail::get_hierarchy_root(qs))>() << "\\n"; // hierarchy root',
3250-
"}",
3251-
"",
3252-
"#define PRINT_QTY(ns, qty) print_quantity(#ns, #qty, ns::qty)",
3253-
"",
3254-
"int main()",
3255-
"{",
3256-
]
3266+
# Discover all system header files dynamically
3267+
systems_include_dir = (
3268+
self.source_dir / "src" / "systems" / "include" / "mp-units" / "systems"
3269+
)
3270+
system_headers = sorted(
3271+
[
3272+
f.name
3273+
for f in systems_include_dir.iterdir()
3274+
if f.is_file() and f.suffix == ".h"
3275+
]
3276+
)
3277+
3278+
lines = ["// Auto-generated program to extract quantity metadata"]
3279+
3280+
# Add includes for all discovered system headers
3281+
for header in system_headers:
3282+
lines.append(f"#include <mp-units/systems/{header}>")
3283+
3284+
lines.extend(
3285+
[
3286+
"#include <iostream>",
3287+
"",
3288+
"using namespace mp_units;",
3289+
"",
3290+
"constexpr std::string_view get_parent(QuantitySpec auto qs)",
3291+
"{",
3292+
" if constexpr (requires { qs._parent_; })",
3293+
" return detail::type_name<std::remove_const_t<decltype(qs._parent_)>>();",
3294+
" else",
3295+
' return "<root>";',
3296+
"}",
3297+
"",
3298+
"constexpr std::string_view character_to_string(quantity_character ch)",
3299+
"{",
3300+
" switch (ch) {",
3301+
" case quantity_character::real_scalar:",
3302+
' return "Real";',
3303+
" case quantity_character::complex_scalar:",
3304+
' return "Complex";',
3305+
" case quantity_character::vector:",
3306+
' return "Vector";',
3307+
" case quantity_character::tensor:",
3308+
' return "Tensor";',
3309+
" default:",
3310+
' return "Unknown";',
3311+
" }",
3312+
"}",
3313+
"",
3314+
"template<QuantitySpec QS>",
3315+
"void print_quantity(std::string_view namespace_name, std::string_view name, QS qs)",
3316+
"{",
3317+
' std::cout << namespace_name << "," // namespace',
3318+
' << name << "," // quantity name',
3319+
' << character_to_string(qs.character) << "," // character',
3320+
' << dimension_symbol(qs.dimension) << "," // dimension',
3321+
' << std::boolalpha << (qs == detail::get_kind_tree_root(qs)) << "," // is kind',
3322+
' << detail::type_name<decltype(get_kind(qs))>() << "," // kind quantity',
3323+
' << get_parent(qs) << "," // parent quantity',
3324+
' << detail::type_name<decltype(detail::get_hierarchy_root(qs))>() << "\\n"; // hierarchy root',
3325+
"}",
3326+
"",
3327+
"#define PRINT_QTY(ns, qty) print_quantity(#ns, #qty, ns::qty)",
3328+
"",
3329+
"int main()",
3330+
"{",
3331+
]
3332+
)
32573333

32583334
# Add dimensionless (special case - no namespace)
32593335
lines.append(' print_quantity("", "dimensionless", dimensionless);')

0 commit comments

Comments
 (0)