Skip to content

Commit 197648f

Browse files
authored
Merge pull request #149 from pablomarcel/develop
updated pages.yml
2 parents f9380a7 + 705f0fe commit 197648f

1 file changed

Lines changed: 155 additions & 43 deletions

File tree

.github/workflows/pages.yml

Lines changed: 155 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ jobs:
6767
6868
mkdir -p _site
6969
touch _site/.nojekyll
70+
: > _site/.docs_routes.txt
7071
7172
mapfile -t DOCS < <(find . -maxdepth 4 -type d -name docs | sort)
7273
@@ -79,6 +80,7 @@ jobs:
7980
echo "==> Copying $ROUTE"
8081
mkdir -p "$DEST"
8182
cp -R "$D/_build/html/"* "$DEST/"
83+
printf '%s\n' "$ROUTE" >> _site/.docs_routes.txt
8284
else
8385
echo "==> No built docs for $ROUTE"
8486
fi
@@ -92,49 +94,177 @@ jobs:
9294
from pathlib import Path
9395
9496
site = Path("_site")
95-
96-
def discover_routes() -> list[str]:
97-
"""Discover built Sphinx sites from the assembled _site folder."""
97+
manifest = site / ".docs_routes.txt"
98+
99+
def read_routes() -> list[str]:
100+
"""Read only the package routes copied by the workflow.
101+
102+
Do not discover routes with ``_site.rglob('index.html')``. Sphinx
103+
builds many internal pages such as ``modules/index.html`` and
104+
``_sources``/``_static`` support files. Treating every nested
105+
``index.html`` as a package creates duplicate cards such as
106+
"Modules". The manifest contains only the real package doc roots.
107+
"""
108+
if not manifest.exists():
109+
return []
98110
routes: list[str] = []
99-
for index in sorted(site.rglob("index.html")):
100-
if index == site / "index.html":
111+
for line in manifest.read_text(encoding="utf-8").splitlines():
112+
route = line.strip().strip("/")
113+
if not route or route.startswith("_"):
101114
continue
102-
rel = index.parent.relative_to(site).as_posix()
103-
if rel and not rel.startswith("_"):
104-
routes.append(rel)
115+
if (site / route / "index.html").exists():
116+
routes.append(route)
105117
return sorted(set(routes))
106118
107-
routes = discover_routes()
119+
routes = read_routes()
120+
121+
DISPLAY = {
122+
"introduction": (
123+
"IN",
124+
"Introduction",
125+
"Introductory examples, helper utilities, and foundational control-system workflows.",
126+
),
127+
"control_systems": (
128+
"CS",
129+
"Control Systems",
130+
"Core control-system analysis, modeling, and command-line engineering workflows.",
131+
),
132+
"fluid_systems": (
133+
"FL",
134+
"Fluid Systems",
135+
"Fluid-system modeling utilities and package-level API documentation.",
136+
),
137+
"frequency_response": (
138+
"FR",
139+
"Frequency Response",
140+
"Frequency-domain analysis, plotting, margins, and stability-oriented design documentation.",
141+
),
142+
"mechanical_systems": (
143+
"MS",
144+
"Mechanical Systems",
145+
"Mechanical-system modeling utilities and package-level API documentation.",
146+
),
147+
"pid_controllers": (
148+
"PID",
149+
"PID Controllers",
150+
"PID controller workflows, tuning utilities, and closed-loop response documentation.",
151+
),
152+
"root_locus_analysis": (
153+
"RL",
154+
"Root Locus Analysis",
155+
"Root-locus analysis, gain studies, and classical control design documentation.",
156+
),
157+
"state_space_analysis": (
158+
"SA",
159+
"State Space Analysis",
160+
"State-space modeling, simulation, response analysis, and reproducible CLI workflows.",
161+
),
162+
"state_space_design": (
163+
"SD",
164+
"State Space Design",
165+
"State-space design, controller synthesis, observers, and related workflows.",
166+
),
167+
"transient_analysis": (
168+
"TR",
169+
"Transient Analysis",
170+
"Time-domain response, transient metrics, stability tests, and simulation-oriented workflows.",
171+
),
172+
}
173+
174+
TOP_ORDER = [
175+
"introduction",
176+
"control_systems",
177+
"transient_analysis",
178+
"frequency_response",
179+
"root_locus_analysis",
180+
"state_space_analysis",
181+
"state_space_design",
182+
"pid_controllers",
183+
"mechanical_systems",
184+
"fluid_systems",
185+
]
186+
187+
TOOL_DISPLAY = {
188+
"bodeTool": ("B", "Bode Tool", "Bode magnitude/phase plotting and frequency-response workflows."),
189+
"nyquistTool": ("N", "Nyquist Tool", "Nyquist analysis and stability-oriented frequency-response workflows."),
190+
"routhTool": ("R", "Routh Tool", "Routh-Hurwitz tabulation, stability checks, and symbolic/numeric workflows."),
191+
"hurwitzTool": ("H", "Hurwitz Tool", "Hurwitz matrices, polynomial stability tests, and supporting utilities."),
192+
"rootLocusTool": ("RL", "Root Locus Tool", "Root-locus plots, gain studies, and classical design workflows."),
193+
"stateTool": ("ST", "State Tool", "State-space calculations, matrix workflows, and CLI utilities."),
194+
"stateRespTool": ("SR", "State Response Tool", "State-response simulation, transition matrices, and output analysis."),
195+
"stateSolnTool": ("SS", "State Solution Tool", "State-transition and solution workflows for state-space models."),
196+
"stateTransTool": ("Φ", "State Transformation Tool", "Coordinate transformations and state-space canonical-form workflows."),
197+
"pidTool": ("PID", "PID Tool", "PID controller tuning, evaluation, and closed-loop response workflows."),
198+
"transientTool": ("tr", "Transient Tool", "Step, impulse, and time-domain response metrics."),
199+
"steadyStateTool": ("ss", "Steady-State Tool", "Steady-state error, system type, and tracking-performance calculations."),
200+
"transferFunctionTool": ("G", "Transfer Function Tool", "Transfer-function manipulation, poles, zeros, and response utilities."),
201+
"secondOrderTool": ("ζ", "Second-Order Tool", "Canonical second-order response metrics and design relationships."),
202+
"blockDiagramTool": ("Σ", "Block Diagram Tool", "Block-diagram reduction and signal-flow style analysis utilities."),
203+
"compensatorTool": ("C", "Compensator Tool", "Lead, lag, and lead-lag design workflows for classical control."),
204+
"modelingTool": ("M", "Modeling Tool", "Modeling utilities and system representation workflows."),
205+
}
108206
109207
def titleize_token(token: str) -> str:
110-
acronyms = {"api", "cli", "pid", "lqr", "mimo", "siso"}
208+
acronyms = {"api", "cli", "pid", "lqr", "mimo", "siso", "ode", "tf"}
111209
lower = token.lower()
112210
if lower in acronyms:
113211
return lower.upper()
114212
return token[:1].upper() + token[1:]
115213
214+
def split_camel(name: str) -> str:
215+
out = []
216+
prev = ""
217+
for ch in name:
218+
if prev and ch.isupper() and (prev.islower() or prev.isdigit()):
219+
out.append(" ")
220+
out.append(ch)
221+
prev = ch
222+
return "".join(out)
223+
224+
def group_key(route: str) -> str:
225+
return route.split("/", 1)[0]
226+
227+
def leaf_name(route: str) -> str:
228+
return route.split("/")[-1]
229+
116230
def titleize(route: str) -> str:
117-
"""Create a readable title from the actual route path."""
118-
name = route.split("/")[-1]
119-
cleaned = name.replace("_", " ").replace("-", " ")
120-
return " ".join(titleize_token(part) for part in cleaned.split()) or route
231+
top = group_key(route)
232+
leaf = leaf_name(route)
233+
if route in DISPLAY:
234+
return DISPLAY[route][1]
235+
if leaf in TOOL_DISPLAY:
236+
return TOOL_DISPLAY[leaf][1]
237+
name = split_camel(leaf).replace("_", " ").replace("-", " ")
238+
return " ".join(titleize_token(part) for part in name.split()) or route
121239
122240
def group_title(group: str) -> str:
241+
if group in DISPLAY:
242+
return DISPLAY[group][1]
123243
cleaned = group.replace("_", " ").replace("-", " ")
124244
return " ".join(titleize_token(part) for part in cleaned.split()) or group
125245
126246
def icon_for(route: str, idx: int) -> str:
247+
if route in DISPLAY:
248+
return DISPLAY[route][0]
249+
leaf = leaf_name(route)
250+
if leaf in TOOL_DISPLAY:
251+
return TOOL_DISPLAY[leaf][0]
127252
title = titleize(route)
128253
words = title.split()
129-
if not words:
130-
return str(idx + 1)
131254
if len(words) >= 2:
132255
return (words[0][0] + words[1][0]).upper()
133-
word = words[0]
134-
return word[:3].upper() if len(word) <= 3 else word[:2].upper()
256+
if words:
257+
return words[0][:3].upper() if len(words[0]) <= 3 else words[0][:2].upper()
258+
return str(idx + 1)
135259
136260
def desc_for(route: str) -> str:
137-
"""Generate useful text without depending on stale package names."""
261+
if route in DISPLAY:
262+
return DISPLAY[route][2]
263+
leaf = leaf_name(route)
264+
if leaf in TOOL_DISPLAY:
265+
return TOOL_DISPLAY[leaf][2]
266+
267+
top = group_key(route)
138268
title = titleize(route)
139269
low = route.lower()
140270
if "root" in low and "locus" in low:
@@ -147,40 +277,22 @@ jobs:
147277
return "PID controller workflows, tuning utilities, and closed-loop response documentation."
148278
if "transient" in low:
149279
return "Time-domain response, transient metrics, and simulation-oriented workflows."
150-
if "fluid" in low:
151-
return "Fluid-systems modeling utilities and package-level API documentation."
152-
if "mechanical" in low:
153-
return "Mechanical-systems modeling utilities and package-level API documentation."
154-
if "introduction" in low:
155-
return "Introductory examples, helper utilities, and foundational control-system workflows."
156-
if "control" in low:
157-
return "Control-system analysis, design utilities, and command-line engineering workflows."
280+
if top in DISPLAY:
281+
return DISPLAY[top][2]
158282
return f"Package-level API documentation and reproducible CLI workflows for {title}."
159283
160284
def route_sort_key(route: str) -> tuple[int, str]:
161-
preferred = [
162-
"introduction",
163-
"control_systems",
164-
"transient_analysis",
165-
"frequency_response",
166-
"root_locus_analysis",
167-
"state_space_analysis",
168-
"state_space_design",
169-
"pid_controllers",
170-
"mechanical_systems",
171-
"fluid_systems",
172-
]
173-
top = route.split("/", 1)[0]
285+
top = group_key(route)
174286
try:
175-
rank = preferred.index(top)
287+
rank = TOP_ORDER.index(top)
176288
except ValueError:
177-
rank = len(preferred)
289+
rank = len(TOP_ORDER)
178290
return rank, route
179291
180292
routes = sorted(routes, key=route_sort_key)
181293
grouped: dict[str, list[str]] = defaultdict(list)
182294
for route in routes:
183-
grouped[route.split("/", 1)[0]].append(route)
295+
grouped[group_key(route)].append(route)
184296
185297
package_count = len(routes)
186298
first_link = f"{routes[0]}/" if routes else "#"

0 commit comments

Comments
 (0)