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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::{any::Any, iter};
use cargo_metadata::camino::{Utf8Path, Utf8PathBuf};
use clap_mangen::Man;
use time::OffsetDateTime;
use crate::{config::Config, Result, Run};
#[cfg_attr(doc, doc = include_str!("../../doc/cargo-xtask-dist-build-man.md"))]
#[derive(Debug, Clone, Default, clap::Args)]
#[non_exhaustive]
pub struct DistBuildMan {}
impl Run for DistBuildMan {
fn run(&self, config: &Config) -> Result<()> {
self.run(config)
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl DistBuildMan {
#[tracing::instrument(name = "dist-build-man", skip_all, err)]
pub fn run(&self, config: &Config) -> Result<()> {
tracing::info!("Building man pages...");
let Self {} = self;
let config = config.dist()?;
let man_dir = config.dist_working_directory(None).join("man");
let section = "1";
crate::fs::remove_dir(&man_dir)?;
for package in config.packages() {
for target in package.targets() {
if let Some(cmd) = target.command() {
let it = dist_build_man_pages(&man_dir, package.name(), cmd.clone(), section)?;
for res in it {
let (path, man) = res?;
let mut file = crate::fs::create_file(&path)?;
man.render(&mut file)?;
}
}
}
}
Ok(())
}
}
fn dist_build_man_pages(
man_dir: &Utf8Path,
package_name: &str,
cmd: clap::Command,
section: impl Into<String>,
) -> Result<impl Iterator<Item = Result<(Utf8PathBuf, Man)>>> {
let capitalized_name = {
let mut cs = package_name.chars();
match cs.next() {
Some(c) => c.to_uppercase().collect::<String>() + cs.as_str(),
None => String::new(),
}
};
let section = section.into();
let now = OffsetDateTime::now_utc();
let manual_name = format!("{capitalized_name} Command Manual");
let date = format!(
"{:04}-{:02}-{:02}",
now.year(),
u8::from(now.month()),
now.day()
);
let source = format!(
"{} {}",
cmd.get_name().replace(' ', "-"),
cmd.get_version()
.or_else(|| cmd.get_long_version())
.unwrap_or_default()
);
let man_dir = man_dir.to_owned();
let it = iterate_commands(cmd).map(move |cmd| {
let command_name = cmd.get_name().replace(' ', "-");
let filename = format!("{command_name}.{}", section);
let path = man_dir.join(&filename);
let man = Man::new(cmd)
.title(command_name.to_uppercase())
.section(§ion)
.date(&date)
.source(&source)
.manual(&manual_name);
Ok((path, man))
});
Ok(it)
}
fn iterate_commands(cmd: clap::Command) -> Box<dyn Iterator<Item = clap::Command>> {
#[allow(clippy::needless_collect)]
let subcommands = cmd.get_subcommands().cloned().collect::<Vec<_>>();
let command_name = cmd.get_name().to_string();
let command_version = cmd.get_version().map(str::to_string);
let command_long_version = cmd.get_long_version().map(str::to_string);
let command_author = cmd.get_author().map(str::to_string);
let it = iter::once(cmd).chain(
subcommands
.into_iter()
.map(move |mut subcommand| {
let name = format!("{command_name} {}", subcommand.get_name());
subcommand = subcommand.name(name);
if subcommand.get_version().is_none() {
if let Some(version) = &command_version {
subcommand = subcommand.version(version);
}
}
if subcommand.get_long_version().is_none() {
if let Some(long_version) = &command_long_version {
subcommand = subcommand.long_version(long_version);
}
}
if subcommand.get_author().is_none() {
if let Some(author) = &command_author {
subcommand = subcommand.author(author);
}
}
subcommand
})
.flat_map(iterate_commands),
);
Box::new(it)
}