Skip to content

Commit 193af22

Browse files
authored
feat: generate part of sidebar (#20040)
1 parent 73b4fcc commit 193af22

File tree

5 files changed

+690
-489
lines changed

5 files changed

+690
-489
lines changed

docs/cli/help.rs

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ struct Args {
6262
#[arg(long)]
6363
root_summary: bool,
6464

65+
/// Whether to generate TypeScript sidebar files
66+
#[arg(long)]
67+
sidebar: bool,
68+
6569
/// Print verbose output
6670
#[arg(short, long)]
6771
verbose: bool,
@@ -140,10 +144,18 @@ fn main() -> io::Result<()> {
140144
if args.verbose {
141145
println!("Updating root summary in \"{}\"", path.to_string_lossy());
142146
}
143-
// TODO: This is where we update the cli reference sidebar.ts
144147
update_root_summary(path, &root_summary)?;
145148
}
146149

150+
// Generate TypeScript sidebar files.
151+
if args.sidebar {
152+
let vocs_dir = Path::new(args.root_dir.as_str()).join("vocs");
153+
if args.verbose {
154+
println!("Generating TypeScript sidebar files in \"{}\"", vocs_dir.display());
155+
}
156+
generate_sidebar_files(&vocs_dir, &output, args.verbose)?;
157+
}
158+
147159
Ok(())
148160
}
149161

@@ -247,6 +259,186 @@ fn update_root_summary(root_dir: &Path, root_summary: &str) -> io::Result<()> {
247259
write_file(&summary_file, root_summary)
248260
}
249261

262+
/// Generates TypeScript sidebar files for each command.
263+
fn generate_sidebar_files(vocs_dir: &Path, output: &[(Cmd, String)], verbose: bool) -> io::Result<()> {
264+
// Group commands by their root command name (reth or op-reth)
265+
// Also create a map of commands to their help output
266+
let mut commands_by_root: std::collections::HashMap<String, Vec<&Cmd>> = std::collections::HashMap::new();
267+
let mut help_map: std::collections::HashMap<String, String> = std::collections::HashMap::new();
268+
269+
for (cmd, help_output) in output {
270+
let root_name = cmd.command_name().to_string();
271+
commands_by_root.entry(root_name.clone()).or_insert_with(Vec::new).push(cmd);
272+
// Store help output for each command using its string representation as key
273+
help_map.insert(cmd.to_string(), help_output.clone());
274+
}
275+
276+
// Generate sidebar file for each root command
277+
for (root_name, cmds) in commands_by_root {
278+
// Get root help output
279+
let root_help = help_map.get(&root_name).map(|s| s.as_str());
280+
let sidebar_content = generate_sidebar_ts(&root_name, cmds, root_help, &help_map)?;
281+
let file_name = match root_name.as_str() {
282+
"reth" => "sidebar-cli-reth.ts",
283+
"op-reth" => "sidebar-cli-op-reth.ts",
284+
_ => {
285+
if verbose {
286+
println!("Skipping unknown command: {}", root_name);
287+
}
288+
continue;
289+
}
290+
};
291+
292+
let sidebar_file = vocs_dir.join(file_name);
293+
if verbose {
294+
println!("Writing sidebar file: {}", sidebar_file.display());
295+
}
296+
write_file(&sidebar_file, &sidebar_content)?;
297+
}
298+
299+
Ok(())
300+
}
301+
302+
/// Generates TypeScript code for a sidebar file.
303+
fn generate_sidebar_ts(
304+
root_name: &str,
305+
commands: Vec<&Cmd>,
306+
root_help: Option<&str>,
307+
help_map: &std::collections::HashMap<String, String>,
308+
) -> io::Result<String> {
309+
// Find all top-level commands (commands with exactly one subcommand)
310+
let mut top_level_commands: Vec<&Cmd> = commands
311+
.iter()
312+
.copied()
313+
.filter(|cmd| cmd.subcommands.len() == 1)
314+
.collect();
315+
316+
// Remove duplicates using a set
317+
let mut seen = std::collections::HashSet::new();
318+
top_level_commands.retain(|cmd| {
319+
let key = &cmd.subcommands[0];
320+
seen.insert(key.clone())
321+
});
322+
323+
// Sort by the order they appear in help output, not alphabetically
324+
if let Some(help) = root_help {
325+
let help_order = parse_sub_commands(help);
326+
top_level_commands.sort_by(|a, b| {
327+
let a_name = &a.subcommands[0];
328+
let b_name = &b.subcommands[0];
329+
let a_pos = help_order.iter().position(|x| x == a_name).unwrap_or(usize::MAX);
330+
let b_pos = help_order.iter().position(|x| x == b_name).unwrap_or(usize::MAX);
331+
a_pos.cmp(&b_pos)
332+
});
333+
}
334+
335+
// Generate TypeScript code
336+
let var_name = match root_name {
337+
"reth" => "rethCliSidebar",
338+
"op-reth" => "opRethCliSidebar",
339+
_ => "cliSidebar",
340+
};
341+
342+
let mut ts_code = String::from("import { SidebarItem } from \"vocs\";\n\n");
343+
ts_code.push_str(&format!("export const {}: SidebarItem = {{\n", var_name));
344+
ts_code.push_str(&format!(" text: \"{}\",\n", root_name));
345+
ts_code.push_str(&format!(" link: \"/cli/{}\",\n", root_name));
346+
ts_code.push_str(" collapsed: false,\n");
347+
ts_code.push_str(" items: [\n");
348+
349+
for (idx, cmd) in top_level_commands.iter().enumerate() {
350+
let is_last = idx == top_level_commands.len() - 1;
351+
if let Some(item_str) = build_sidebar_item(root_name, cmd, &commands, 1, help_map, is_last) {
352+
ts_code.push_str(&item_str);
353+
}
354+
}
355+
356+
ts_code.push_str(" ]\n");
357+
ts_code.push_str("};\n\n");
358+
359+
Ok(ts_code)
360+
}
361+
362+
/// Builds a sidebar item for a command and its children.
363+
/// Returns TypeScript code string.
364+
fn build_sidebar_item(
365+
root_name: &str,
366+
cmd: &Cmd,
367+
all_commands: &[&Cmd],
368+
depth: usize,
369+
help_map: &std::collections::HashMap<String, String>,
370+
is_last: bool,
371+
) -> Option<String> {
372+
let full_cmd_name = cmd.to_string();
373+
let link_path = format!("/cli/{}", full_cmd_name.replace(" ", "/"));
374+
375+
// Find all direct child commands (commands whose subcommands start with this command's subcommands)
376+
let mut children: Vec<&Cmd> = all_commands
377+
.iter()
378+
.copied()
379+
.filter(|other_cmd| {
380+
other_cmd.subcommands.len() == cmd.subcommands.len() + 1
381+
&& other_cmd.subcommands[..cmd.subcommands.len()] == cmd.subcommands[..]
382+
})
383+
.collect();
384+
385+
// Sort children by the order they appear in help output, not alphabetically
386+
if children.len() > 1 {
387+
// Get help output for this command to determine subcommand order
388+
if let Some(help_output) = help_map.get(&full_cmd_name) {
389+
let help_order = parse_sub_commands(help_output);
390+
children.sort_by(|a, b| {
391+
let a_name = a.subcommands.last().unwrap();
392+
let b_name = b.subcommands.last().unwrap();
393+
let a_pos = help_order.iter().position(|x| x == a_name).unwrap_or(usize::MAX);
394+
let b_pos = help_order.iter().position(|x| x == b_name).unwrap_or(usize::MAX);
395+
a_pos.cmp(&b_pos)
396+
});
397+
} else {
398+
// Fall back to alphabetical if we can't get help
399+
children.sort_by(|a, b| {
400+
a.subcommands.last().unwrap().cmp(b.subcommands.last().unwrap())
401+
});
402+
}
403+
}
404+
405+
let indent = " ".repeat(depth);
406+
let mut item_str = String::new();
407+
408+
item_str.push_str(&format!("{}{{\n", indent));
409+
item_str.push_str(&format!("{} text: \"{}\",\n", indent, full_cmd_name));
410+
item_str.push_str(&format!("{} link: \"{}\"", indent, link_path));
411+
412+
if !children.is_empty() {
413+
item_str.push_str(",\n");
414+
item_str.push_str(&format!("{} collapsed: true,\n", indent));
415+
item_str.push_str(&format!("{} items: [\n", indent));
416+
417+
for (idx, child_cmd) in children.iter().enumerate() {
418+
let child_is_last = idx == children.len() - 1;
419+
if let Some(child_str) = build_sidebar_item(root_name, child_cmd, all_commands, depth + 1, help_map, child_is_last) {
420+
item_str.push_str(&child_str);
421+
}
422+
}
423+
424+
item_str.push_str(&format!("{} ]\n", indent));
425+
if is_last {
426+
item_str.push_str(&format!("{}}}\n", indent));
427+
} else {
428+
item_str.push_str(&format!("{}}},\n", indent));
429+
}
430+
} else {
431+
item_str.push_str("\n");
432+
if is_last {
433+
item_str.push_str(&format!("{}}}\n", indent));
434+
} else {
435+
item_str.push_str(&format!("{}}},\n", indent));
436+
}
437+
}
438+
439+
Some(item_str)
440+
}
441+
250442
/// Preprocesses the help output of a command.
251443
fn preprocess_help(s: &str) -> Cow<'_, str> {
252444
static REPLACEMENTS: LazyLock<Vec<(Regex, &str)>> = LazyLock::new(|| {

docs/cli/update.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ cmd=(
1414
--root-dir "$DOCS_ROOT/"
1515
--root-indentation 2
1616
--root-summary
17+
--sidebar
1718
--verbose
1819
--out-dir "$VOCS_PAGES_ROOT/cli/"
1920
"$RETH" "$OP_RETH"

0 commit comments

Comments
 (0)