Skip to content

Commit 9632a78

Browse files
CopilotKSXGitHub
andcommitted
fix(lib): move pub mod usage_md into the cfg(feature = \"cli\") group
Co-authored-by: KSXGitHub <11488886+KSXGitHub@users.noreply.github.com>
1 parent d92ac00 commit 9632a78

4 files changed

Lines changed: 78 additions & 63 deletions

File tree

cli/usage_md.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use clap::CommandFactory;
2-
use parallel_disk_usage::{args::Args, usage_md::render};
1+
use parallel_disk_usage::usage_md::render_usage_md;
32

43
fn main() {
5-
println!("{}", render(Args::command()).trim_end());
4+
println!("{}", render_usage_md().trim_end());
65
}

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub mod app;
1616
pub mod args;
1717
#[cfg(feature = "cli")]
1818
pub mod runtime_error;
19+
#[cfg(feature = "cli")]
20+
pub mod usage_md;
1921

2022
/// The main program.
2123
#[cfg(feature = "cli")]
@@ -46,7 +48,6 @@ pub mod reporter;
4648
pub mod size;
4749
pub mod status_board;
4850
pub mod tree_builder;
49-
pub mod usage_md;
5051
pub mod visualizer;
5152

5253
pub use zero_copy_pads;

src/usage_md.rs

Lines changed: 72 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,83 @@
1+
use itertools::Itertools;
12
use std::borrow::Cow;
23

3-
use itertools::Itertools;
4+
use clap::{Arg, Command, CommandFactory};
45

5-
pub fn render(mut command: clap::Command) -> String {
6+
use crate::args::Args;
7+
8+
/// Renders a Markdown reference page for `pdu`'s CLI.
9+
///
10+
/// The output includes:
11+
/// - A `# Usage` section with a `sh` code block
12+
/// - A `## Arguments` section listing positional arguments
13+
/// - A `## Options` section with a subsection per option flag
14+
/// - A `## Examples` section parsed from `after_long_help`
15+
pub fn render_usage_md() -> String {
16+
let mut command: Command = Args::command();
617
let mut out = String::new();
718

819
// Usage section
9-
let usage = command.render_usage().to_string();
10-
if let Some(rest) = usage.strip_prefix("Usage: ") {
11-
out.push_str("# Usage\n\n```sh\n");
12-
out.push_str(rest.trim());
13-
out.push_str("\n```\n\n");
14-
}
20+
let usage_str = command.render_usage().to_string();
21+
let usage_cmd = usage_str
22+
.trim()
23+
.splitn(2, ':')
24+
.nth(1)
25+
.map(str::trim)
26+
.unwrap_or("");
27+
out.push_str("# Usage\n\n```sh\n");
28+
out.push_str(usage_cmd);
29+
out.push_str("\n```\n\n");
1530

1631
// Arguments section – positional, non-hidden args
17-
let positional_args: Vec<clap::Arg> = command
18-
.get_arguments()
19-
.filter(|a| a.is_positional() && !a.is_hide_set() && !a.is_hide_long_help_set())
20-
.cloned()
21-
.collect();
22-
if !positional_args.is_empty() {
23-
out.push_str("## Arguments\n\n");
24-
for arg in &positional_args {
25-
render_argument(arg, &mut out);
32+
let mut arguments_heading_written = false;
33+
for arg in command.get_arguments() {
34+
if !arg.is_positional() || arg.is_hide_set() || arg.is_hide_long_help_set() {
35+
continue;
36+
}
37+
if !arguments_heading_written {
38+
arguments_heading_written = true;
39+
out.push_str("## Arguments\n\n");
2640
}
41+
render_argument(arg, &mut out);
42+
}
43+
if arguments_heading_written {
2744
out.push('\n');
2845
}
2946

3047
// Options section – non-positional, non-hidden args
31-
let option_args: Vec<clap::Arg> = command
32-
.get_arguments()
33-
.filter(|a| !a.is_positional() && !a.is_hide_set() && !a.is_hide_long_help_set())
34-
.cloned()
35-
.collect();
36-
if !option_args.is_empty() {
37-
out.push_str("## Options\n\n");
38-
for arg in &option_args {
39-
render_option(arg, &mut out);
48+
let mut options_heading_written = false;
49+
for arg in command.get_arguments() {
50+
if arg.is_positional() || arg.is_hide_set() || arg.is_hide_long_help_set() {
51+
continue;
4052
}
53+
if !options_heading_written {
54+
options_heading_written = true;
55+
out.push_str("## Options\n\n");
56+
}
57+
render_option(arg, &mut out);
4158
}
4259

4360
// Examples section – parse from after_long_help text
4461
if let Some(after_help) = command.get_after_long_help() {
4562
let text = after_help.to_string();
46-
let lines: Vec<&str> = text.lines().collect();
47-
if let Some(pos) = lines.iter().position(|l| l.trim() == "Examples:") {
63+
let mut lines_iter = text.lines();
64+
let mut has_examples = false;
65+
for line in lines_iter.by_ref() {
66+
if line.trim() == "Examples:" {
67+
has_examples = true;
68+
break;
69+
}
70+
}
71+
if has_examples {
4872
out.push_str("## Examples\n\n");
49-
render_examples_section(&lines[pos + 1..], &mut out);
73+
render_examples_section(lines_iter, &mut out);
5074
}
5175
}
5276

5377
out
5478
}
5579

56-
fn render_argument(arg: &clap::Arg, out: &mut String) {
80+
fn render_argument(arg: &Arg, out: &mut String) {
5781
let name = arg
5882
.get_value_names()
5983
.and_then(|names| names.first())
@@ -73,7 +97,7 @@ fn render_argument(arg: &clap::Arg, out: &mut String) {
7397
out.push_str(&format!("* `{display_name}`: {desc}\n"));
7498
}
7599

76-
fn render_option(arg: &clap::Arg, out: &mut String) {
100+
fn render_option(arg: &Arg, out: &mut String) {
77101
let primary_long = arg.get_long().expect("option must have a long flag");
78102
let primary_name = format!("--{primary_long}");
79103

@@ -115,7 +139,7 @@ fn render_option(arg: &clap::Arg, out: &mut String) {
115139

116140
// Default values – skip "false" (clap's implicit default for boolean flags)
117141
let default_values: Vec<_> = if arg.is_hide_default_value_set() {
118-
vec![]
142+
Vec::new()
119143
} else {
120144
arg.get_default_values()
121145
.iter()
@@ -125,7 +149,7 @@ fn render_option(arg: &clap::Arg, out: &mut String) {
125149

126150
// Possible values (choices)
127151
let possible_values: Vec<_> = if arg.is_hide_possible_values_set() {
128-
vec![]
152+
Vec::new()
129153
} else {
130154
arg.get_possible_values()
131155
.into_iter()
@@ -141,7 +165,10 @@ fn render_option(arg: &clap::Arg, out: &mut String) {
141165
out.push_str(&format!("* _Aliases:_ {aliases_str}.\n"));
142166
}
143167
if !default_values.is_empty() {
144-
let default_str = default_values.iter().map(|v| v.to_string_lossy()).join(", ");
168+
let default_str = default_values
169+
.iter()
170+
.map(|v| v.to_string_lossy())
171+
.join(", ");
145172
out.push_str(&format!("* _Default:_ `{default_str}`.\n"));
146173
}
147174
if !possible_values.is_empty() {
@@ -171,7 +198,7 @@ fn render_option(arg: &clap::Arg, out: &mut String) {
171198
}
172199

173200
/// Returns the help text for an argument: `get_help()` with `get_long_help()` appended if set.
174-
fn get_help_text(arg: &clap::Arg) -> String {
201+
fn get_help_text(arg: &Arg) -> String {
175202
let mut parts: Vec<String> = Vec::new();
176203
if let Some(h) = arg.get_help() {
177204
parts.push(h.to_string());
@@ -182,33 +209,21 @@ fn get_help_text(arg: &clap::Arg) -> String {
182209
parts.join("\n")
183210
}
184211

185-
fn render_examples_section(lines: &[&str], out: &mut String) {
186-
let mut i = 0;
187-
while i < lines.len() {
188-
let trimmed = lines[i].trim();
212+
fn render_examples_section<'a>(lines: impl Iterator<Item = &'a str>, out: &mut String) {
213+
let mut current_title: Option<&'a str> = None;
214+
for line in lines {
215+
let trimmed = line.trim();
189216
if trimmed.is_empty() {
190-
i += 1;
191217
continue;
192218
}
193-
// A line starting with `$ ` is a bare command (no preceding description).
194219
if let Some(cmd) = trimmed.strip_prefix('$').map(str::trim) {
195-
out.push_str(&format!("### `{cmd}`\n\n```sh\n{cmd}\n```\n\n"));
196-
i += 1;
197-
} else {
198-
// Description line — the very next non-empty line should be `$ <cmd>`.
199-
let desc = trimmed;
200-
i += 1;
201-
while i < lines.len() && lines[i].trim().is_empty() {
202-
i += 1;
203-
}
204-
if i < lines.len() {
205-
if let Some(cmd) = lines[i].trim().strip_prefix('$').map(str::trim) {
206-
out.push_str(&format!("### {desc}\n\n```sh\n{cmd}\n```\n\n"));
207-
i += 1;
208-
continue;
209-
}
220+
if let Some(title) = current_title.take() {
221+
out.push_str(&format!("### {title}\n\n```sh\n{cmd}\n```\n\n"));
222+
} else {
223+
out.push_str(&format!("### `{cmd}`\n\n```sh\n{cmd}\n```\n\n"));
210224
}
211-
out.push_str(&format!("### {desc}\n\n"));
225+
} else {
226+
current_title = Some(trimmed);
212227
}
213228
}
214229
}

tests/sync_help.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
pub mod _utils;
1111

1212
use clap::CommandFactory;
13-
use parallel_disk_usage::{args::Args, usage_md::render};
13+
use parallel_disk_usage::{args::Args, usage_md::render_usage_md};
1414

1515
#[test]
1616
fn long_help_is_up_to_date() {
@@ -34,7 +34,7 @@ fn short_help_is_up_to_date() {
3434

3535
#[test]
3636
fn usage_md_is_up_to_date() {
37-
let actual = render(Args::command());
37+
let actual = render_usage_md();
3838
let expected = include_str!("../USAGE.md");
3939
assert!(
4040
actual.trim_end() == expected.trim_end(),

0 commit comments

Comments
 (0)