Files
solidcraft-upstream/xtask/src/features.rs
T
bherbruckandClaude Opus 5.5 296683229d Rename SolveCraft to SolidCraft everywhere, with compatibility shims (#389)
* WIP deep rename (not for push)

* WIP2

* WIP3

* WIP4 wxs fix

* rustfmt

* Rename the remaining identifiers in merged crash-report code

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 17:17:01 -04:00

105 lines
4.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! `cargo xtask features`: SolidCraft's own feature checklist, `docs/features.md`.
//!
//! The page lists every registered command by area (the first part of its dotted id), with its
//! label, id and toolbar place. It comes only from SolidCraft's command registry
//! (`solidcraft-cli commands`), so it can't drift from what the program does; it compares with
//! nothing else.
use std::path::Path;
use serde_json::Value;
/// Areas in the order the page lists them (id prefix, heading). Other prefixes follow,
/// alphabetically, under their prefix.
const AREAS: &[(&str, &str)] = &[
("sketch", "Sketch"),
("solid", "Solid modelling"),
("surface", "Surfaces"),
("construct", "Construction geometry"),
("sheet", "Sheet metal"),
("plastic", "Plastic parts"),
("component", "Components"),
("joint", "Assembly joints"),
("config", "Configurations"),
("timeline", "Timeline"),
("inspect", "Inspect"),
("file", "Files"),
("doc", "Documents"),
("edit", "Edit"),
];
pub fn run(root: &Path) -> Result<(), String> {
let out = crate::cargo()
.args(["run", "-q", "--release", "-p", "solidcraft-cli", "--", "commands"])
.output()
.map_err(|e| format!("solidcraft-cli: {e}"))?;
if !out.status.success() {
return Err(format!("solidcraft-cli commands failed:\n{}", String::from_utf8_lossy(&out.stderr)));
}
let cmds: Value = serde_json::from_slice(&out.stdout).map_err(|e| format!("commands JSON: {e}"))?;
let list = cmds.as_array().map(Vec::as_slice).unwrap_or_default();
let md = page(list);
std::fs::write(root.join("docs/features.md"), md).map_err(|e| format!("docs/features.md: {e}"))?;
println!("features: {} commands → docs/features.md", list.len());
Ok(())
}
fn area(id: &str) -> &str {
id.split('.').next().unwrap_or_default()
}
/// The checklist page for the registry's commands.
pub fn page(cmds: &[Value]) -> String {
let s = |c: &Value, k: &str| c[k].as_str().unwrap_or_default().to_string();
let mut prefixes: Vec<String> = cmds.iter().map(|c| area(&s(c, "id")).to_string()).collect();
prefixes.sort();
prefixes.dedup();
let mut order: Vec<(String, String)> =
AREAS.iter().filter(|(p, _)| prefixes.iter().any(|x| x == p)).map(|(p, h)| (p.to_string(), h.to_string())).collect();
for p in prefixes.iter().filter(|p| !AREAS.iter().any(|(a, _)| a == p)) {
order.push((p.clone(), format!("`{p}.*`")));
}
let mut md = format!(
"# Features\n\nSolidCraft's commands by area, generated by `cargo xtask features` from the command registry \
(`solidcraft-cli commands`). **{} commands.** Every command runs the same way from the toolbar, a script, \
`solidcraft-cli exec <id> '<json>'`, the control channel and MCP; parameters are in the \
[command reference](book/src/reference/commands.md) (`cargo xtask book`). Status and plans: [ROADMAP](../ROADMAP.md).\n",
cmds.len()
);
for (prefix, heading) in &order {
let in_area: Vec<&Value> = cmds.iter().filter(|c| area(&s(c, "id")) == prefix).collect();
md += &format!("\n## {heading} ({})\n\n| Command | Id | Toolbar |\n|---|---|---|\n", in_area.len());
for c in in_area {
let place = match (s(c, "tab"), s(c, "panel")) {
(t, _) if t.is_empty() => "–".to_string(),
(t, p) if p.is_empty() => t,
(t, p) => format!("{t} › {p}"),
};
let key = c["shortcut"].as_str().map(|k| format!(" ({k})")).unwrap_or_default();
md += &format!("| {}{key} | `{}` | {place} |\n", s(c, "label").replace('|', "/"), s(c, "id"));
}
}
md
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn page_groups_by_area() {
let cmds: Value = serde_json::from_str(
r#"[{"id":"solid.extrude","label":"Extrude","tab":"SOLID","panel":"CREATE","shortcut":"E"},
{"id":"sketch.line","label":"Line","tab":"SKETCH","panel":"CREATE"},
{"id":"view.fit","label":"Fit","tab":"","panel":""}]"#,
)
.unwrap();
let md = page(cmds.as_array().unwrap());
assert!(md.contains("**3 commands.**"));
let (sk, so, other) = (md.find("## Sketch (1)").unwrap(), md.find("## Solid modelling (1)").unwrap(), md.find("## `view.*` (1)").unwrap());
assert!(sk < so && so < other);
assert!(md.contains("| Extrude (E) | `solid.extrude` | SOLID › CREATE |"));
assert!(md.contains("| Fit | `view.fit` | – |"));
}
}