1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use std::path::PathBuf;
use super::{parse_file_or_stdin, write_or_stdout};
use clap::Args;
use usage::docs::markdown::MarkdownRenderer;
/// Generate markdown documentation from usage specs
#[derive(Args)]
#[clap(visible_alias = "md")]
pub struct Markdown {
/// A usage spec taken in as a file, use "-" to read from stdin
#[clap(short, long)]
file: PathBuf,
// /// Pass a usage spec in an argument instead of a file
// #[clap(short, long, required_unless_present = "file", overrides_with = "file")]
// spec: Option<String>,
/// Render each subcommand as a separate markdown file
#[clap(short, long, requires = "out_dir", conflicts_with = "out_file")]
multi: bool,
/// Escape HTML in markdown
#[clap(long)]
html_encode: bool,
/// Output markdown files to this directory (required when using --multi)
#[clap(long, value_hint = clap::ValueHint::DirPath, requires = "multi")]
out_dir: Option<PathBuf>,
/// Output file path for single-file markdown generation, or "-" for stdout (default)
#[clap(long, value_hint = clap::ValueHint::FilePath)]
out_file: Option<PathBuf>,
/// Replace `<pre>` tags with markdown code fences
#[clap(long)]
replace_pre_with_code_fences: bool,
/// Prefix to add to all URLs
#[clap(long)]
url_prefix: Option<String>,
}
impl Markdown {
pub fn run(&self) -> miette::Result<()> {
// The banner belongs to every generated document, so build it in one place rather
// than once per output path.
let render = |md: &str| {
format!(
"<!-- @generated by usage-cli from usage spec -->\n{}\n",
md.trim()
)
};
// File-only, deliberately: every path this receives is a join onto `--out-dir`, so a
// `-` meaning stdout cannot turn up here.
let write = |path: &PathBuf, md: &str| -> miette::Result<()> {
eprintln!("writing to {}", path.display());
xx::file::write(path, render(md))?;
Ok(())
};
let spec = parse_file_or_stdin(&self.file)?;
let mut ctx = MarkdownRenderer::new(spec.clone())
.with_html_encode(self.html_encode)
.with_replace_pre_with_code_fences(self.replace_pre_with_code_fences);
if let Some(url_prefix) = &self.url_prefix {
ctx = ctx.with_url_prefix(url_prefix);
}
if self.multi {
ctx = ctx.with_multi(true);
let commands = spec.cmd.all_subcommands().into_iter().filter(|c| !c.hide);
for cmd in commands {
let md = ctx.render_cmd(cmd)?;
let dir = cmd
.full_cmd
.iter()
.take(cmd.full_cmd.len() - 1)
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join("/");
let path = self
.out_dir
.as_ref()
.unwrap()
.join(dir)
.join(format!("{}.md", cmd.name));
write(&path, &md)?;
}
let md_idx = ctx.render_index()?;
let path_idx = self.out_dir.as_ref().unwrap().join("index.md");
write(&path_idx, &md_idx)?;
} else {
let md = ctx.render_spec()?;
write_or_stdout(self.out_file.as_deref(), &render(&md))?;
}
Ok(())
}
}