Skip to content

Commit e9e552f

Browse files
authored
Add flat output option to docs command (#1411)
* add flat output option to docs command * Flat -> Text
1 parent 648ee91 commit e9e552f

4 files changed

Lines changed: 397 additions & 7 deletions

File tree

cli/docs/cli.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1451,8 +1451,16 @@
14511451
},
14521452
{
14531453
"name": "docs",
1454-
"about": "Generate CLI docs in JSON format",
1454+
"about": "List all CLI commands",
14551455
"args": [
1456+
{
1457+
"long": "format",
1458+
"values": [
1459+
"text",
1460+
"json"
1461+
],
1462+
"help": "Output format"
1463+
},
14561464
{
14571465
"long": "profile",
14581466
"help": "Configuration profile to use for commands",

cli/src/cmd_docs.rs

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,26 @@ use crate::{println_nopipe, RunnableCmd};
1111
use super::cmd_version::built_info;
1212
use anyhow::Result;
1313
use async_trait::async_trait;
14-
use clap::{Command, Parser};
14+
use clap::{Command, Parser, ValueEnum};
1515
use serde::Serialize;
1616

17-
/// Generate CLI docs in JSON format
17+
/// List all CLI commands
1818
#[derive(Parser, Debug, Clone)]
1919
#[command(verbatim_doc_comment)]
2020
#[command(name = "docs")]
21-
pub struct CmdDocs;
21+
pub struct CmdDocs {
22+
/// Output format
23+
#[clap(long, default_value = "text")]
24+
format: DocsFormat,
25+
}
26+
27+
#[derive(ValueEnum, Debug, Clone)]
28+
enum DocsFormat {
29+
/// Command and description
30+
Text,
31+
/// Full JSON tree with args and metadata
32+
Json,
33+
}
2234

2335
/// Arg to CLI command for the JSON doc
2436
#[derive(Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -105,15 +117,51 @@ fn to_json(cmd: &Command) -> JsonDoc {
105117
}
106118
}
107119

120+
fn print_flat(cmd: &Command, prefix: &str) {
121+
let name = if prefix.is_empty() {
122+
cmd.get_name().to_string()
123+
} else {
124+
format!("{} {}", prefix, cmd.get_name())
125+
};
126+
127+
let mut subs: Vec<_> = cmd
128+
.get_subcommands()
129+
.filter(|c| c.get_name() != "help")
130+
.collect();
131+
132+
// Print this command if it's a leaf or if it's runnable on its own
133+
// (i.e., subcommands exist but aren't required, like `oxide disk import`)
134+
if subs.is_empty() || !cmd.is_subcommand_required_set() {
135+
let about = cmd
136+
.get_about()
137+
.map(|a| format!(" — {a}"))
138+
.unwrap_or_default();
139+
println_nopipe!("{name}{about}");
140+
}
141+
subs.sort_by_key(|c| c.get_name().to_owned());
142+
for sub in subs {
143+
print_flat(sub, &name);
144+
}
145+
}
146+
108147
#[async_trait]
109148
impl RunnableCmd for CmdDocs {
110149
async fn run(&self, _ctx: &Context) -> Result<()> {
111150
let cli = crate::make_cli();
112151
let mut app = cli.command_take();
113152
app.build();
114-
let json_doc = to_json(&app);
115-
let pretty_json = serde_json::to_string_pretty(&json_doc)?;
116-
println_nopipe!("{}", pretty_json);
153+
154+
match self.format {
155+
DocsFormat::Json => {
156+
let json_doc = to_json(&app);
157+
let pretty_json = serde_json::to_string_pretty(&json_doc)?;
158+
println_nopipe!("{}", pretty_json);
159+
}
160+
DocsFormat::Text => {
161+
print_flat(&app, "");
162+
}
163+
}
164+
117165
Ok(())
118166
}
119167
}

0 commit comments

Comments
 (0)