1use super::*;
2use roff::{bold, italic, list, Roff, Troffable};
3
4#[derive(Debug)]
6pub struct Manual {
7 name: String,
8 about: Option<String>,
9 description: Option<String>,
10 authors: Vec<Author>,
11 flags: Vec<Flag>,
12 options: Vec<Opt>,
13 environment: Vec<Env>,
14 arguments: Vec<Arg>,
15 custom_sections: Vec<Section>,
16 examples: Vec<Example>,
17 custom_synopsis_expansion: Option<String>,
18}
19
20impl Manual {
21 pub fn new(name: &str) -> Self {
23 Self {
24 name: name.into(),
25 about: None,
26 description: None,
27 authors: vec![],
28 flags: vec![],
29 options: vec![],
30 arguments: vec![],
31 environment: vec![],
32 custom_sections: vec![],
33 examples: vec![],
34 custom_synopsis_expansion: None,
35 }
36 }
37
38 pub fn about<S: Into<String>>(mut self, about: S) -> Self {
40 self.about = Some(about.into());
41 self
42 }
43
44 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
46 self.description = Some(description.into());
47 self
48 }
49
50 pub fn author(mut self, author: Author) -> Self {
52 self.authors.push(author);
53 self
54 }
55
56 pub fn env(mut self, env: Env) -> Self {
58 self.environment.push(env);
59 self
60 }
61
62 pub fn flag(mut self, flag: Flag) -> Self {
64 self.flags.push(flag);
65 self
66 }
67
68 pub fn option(mut self, opt: Opt) -> Self {
70 self.options.push(opt);
71 self
72 }
73
74 pub fn custom(mut self, custom_section: Section) -> Self {
76 self.custom_sections.push(custom_section);
77 self
78 }
79
80 pub fn example(mut self, example: Example) -> Self {
82 self.examples.push(example);
83 self
84 }
85
86 pub fn arg(mut self, arg: Arg) -> Self {
90 self.arguments.push(arg);
91 self
92 }
93
94 pub fn custom_synopsis_expansion<S: Into<String>>(mut self, expansion: S) -> Self {
95 self.custom_synopsis_expansion = Some(expansion.into());
96 self
97 }
98
99 pub fn render(self) -> String {
101 let man_num = 1;
102 let mut page = Roff::new(&self.name, man_num);
103 page = about(page, &self.name, &self.about);
104 page = synopsis(
105 page,
106 &self.name,
107 &self.flags,
108 &self.options,
109 &self.arguments,
110 &self.custom_synopsis_expansion,
111 );
112 page = description(page, &self.description);
113 page = flags(page, &self.flags);
114 page = options(page, &self.options);
115 page = env(page, &self.environment);
116 for section in self.custom_sections.into_iter() {
117 page = custom(page, section);
118 }
119 page = exit_status(page);
120 page = examples(page, &self.examples);
121 page = authors(page, &self.authors);
122 page.render()
123 }
124}
125
126fn about(page: Roff, name: &str, desc: &Option<String>) -> Roff {
134 let desc = match desc {
135 Some(ref desc) => format!("{} - {}", name, desc),
136 None => name.to_owned(),
137 };
138
139 page.section("NAME", &[desc])
140}
141
142fn description(page: Roff, desc: &Option<String>) -> Roff {
150 if let Some(desc) = desc {
151 page.section("DESCRIPTION", &[desc.to_owned()])
152 } else {
153 page
154 }
155}
156
157fn synopsis(
159 page: Roff,
160 name: &str,
161 flags: &[Flag],
162 options: &[Opt],
163 args: &[Arg],
164 custom_synopsis_expansion: &Option<String>,
165) -> Roff {
166 let flags = match flags.len() {
167 0 => "".into(),
168 _ => " [FLAGS]".into(),
169 };
170 let options = match options.len() {
171 0 => "".into(),
172 _ => " [OPTIONS]".into(),
173 };
174
175 let mut msg = vec![];
176 msg.push(bold(name));
177 match custom_synopsis_expansion {
178 Some(custom) => {
179 msg.push(format!(" {}",custom));
180 },
181 None => {
182 msg.push(flags);
183 msg.push(options);
184 }
185 };
186
187 for arg in args {
188 msg.push(format!(" {}", arg.name));
189 }
190
191 page.section("SYNOPSIS", &msg)
192}
193
194fn authors(page: Roff, authors: &[Author]) -> Roff {
203 let title = match authors.len() {
204 0 => return page,
205 1 => "AUTHOR",
206 _ => "AUTHORS",
207 };
208
209 let last = authors.len() - 1;
210 let mut auth_values = vec![];
211 auth_values.push(init_list());
212 for (index, author) in authors.iter().enumerate() {
213 auth_values.push(author.name.to_owned());
214
215 if let Some(ref email) = author.email {
216 auth_values.push(format!(" <{}>", email))
217 };
218
219 if index != last {
220 auth_values.push(String::from("\n"));
221 }
222 }
223
224 page.section(title, &auth_values)
225}
226
227fn flags(page: Roff, flags: &[Flag]) -> Roff {
234 if flags.is_empty() {
235 return page;
236 }
237
238 let last = flags.len() - 1;
239 let mut arr: Vec<String> = vec![];
240 for (index, flag) in flags.iter().enumerate() {
241 let mut args: Vec<String> = vec![];
242 if let Some(ref short) = flag.short {
243 args.push(bold(&short));
244 }
245 if let Some(ref long) = flag.long {
246 if !args.is_empty() {
247 args.push(", ".to_string());
248 }
249 args.push(bold(&long));
250 }
251 let desc = match flag.help {
252 Some(ref desc) => desc.to_string(),
253 None => "".to_string(),
254 };
255 arr.push(list(&args, &[desc]));
256
257 if index != last {
258 arr.push(String::from("\n\n"));
259 }
260 }
261 page.section("FLAGS", &arr)
262}
263
264fn options(page: Roff, options: &[Opt]) -> Roff {
271 if options.is_empty() {
272 return page;
273 }
274
275 let last = options.len() - 1;
276 let mut arr: Vec<String> = vec![];
277 for (index, opt) in options.iter().enumerate() {
278 let mut args: Vec<String> = vec![];
279 if let Some(ref short) = opt.short {
280 args.push(bold(&short));
281 }
282 if let Some(ref long) = opt.long {
283 if !args.is_empty() {
284 args.push(", ".to_string());
285 }
286 args.push(bold(&long));
287 }
288 args.push(" ".into());
289 args.push(italic(&opt.name));
290 if let Some(ref default) = opt.default {
291 if !args.is_empty() {
292 args.push(" ".to_string());
293 }
294 args.push("[".into());
295 args.push("default:".into());
296 args.push(" ".into());
297 args.push(italic(&default));
298 args.push("]".into());
299 }
300 let desc = match opt.help {
301 Some(ref desc) => desc.to_string(),
302 None => "".to_string(),
303 };
304 arr.push(list(&args, &[desc]));
305
306 if index != last {
307 arr.push(String::from("\n\n"));
308 }
309 }
310 page.section("OPTIONS", &arr)
311}
312
313fn env(page: Roff, environment: &[Env]) -> Roff {
321 if environment.is_empty() {
322 return page;
323 }
324
325 let last = environment.len() - 1;
326 let mut arr: Vec<String> = vec![];
327 for (index, env) in environment.iter().enumerate() {
328 let mut args: Vec<String> = vec![];
329 args.push(bold(&env.name));
330 if let Some(ref default) = env.default {
331 if !args.is_empty() {
332 args.push(" ".to_string());
333 }
334 args.push("[".into());
335 args.push("default:".into());
336 args.push(" ".into());
337 args.push(italic(&default));
338 args.push("]".into());
339 }
340 let desc = match env.help {
341 Some(ref desc) => desc.to_string(),
342 None => "".to_string(),
343 };
344 arr.push(list(&args, &[desc]));
345
346 if index != last {
347 arr.push(String::from("\n\n"));
348 }
349 }
350 page.section("ENVIRONMENT", &arr)
351}
352
353fn exit_status(page: Roff) -> Roff {
369 page.section(
370 "EXIT STATUS",
371 &[
372 list(&[bold("0")], &["Successful program execution.\n\n"]),
373 list(&[bold("1")], &["Unsuccessful program execution.\n\n"]),
374 list(&[bold("101")], &["The program panicked."]),
375 ],
376 )
377}
378
379fn custom(page: Roff, custom_section: Section) -> Roff {
394 let mut paragraphs: Vec<String> = vec![];
395 for paragraph in custom_section.paragraphs.into_iter() {
396 paragraphs.push(paragraph);
397 paragraphs.push("\n\n".into())
398 }
399 for flag_or_section in custom_section.flags_and_options {
400 paragraphs.push(flag_or_section.render());
401 paragraphs.push("\n\n".into())
402 }
403
404 page.section(&custom_section.name, ¶graphs)
405}
406
407fn examples(page: Roff, examples: &[Example]) -> Roff {
424 if examples.is_empty() {
425 return page;
426 };
427 let mut arr = vec![];
428 for example in examples {
429 let text = example.text.unwrap_or("");
430 let mut full_command = String::from(example.prompt);
431 if let Some(command) = example.command {
432 full_command.push_str(" ");
433 full_command.push_str(command);
434 };
435 let output = match example.output {
436 Some(output) => {
437 let mut full_output = String::from("\n.br\n");
440 full_output.push_str(output);
441 full_output.push_str("\n");
442 full_output
443 }
444 None => String::from("\n"),
445 };
446 let example = list(&[text], &[bold(full_command.as_str()), output]);
447 arr.push(example);
448 }
449 page.section("examples", &arr)
450}
451
452fn init_list() -> String {
460 String::from(".P\n.RS 2\n.nf\n")
461}