1pub mod markdown;
4pub mod stack_diagram;
5pub mod template;
6
7use crate::error::{IoError, Result};
8use crate::image_ops::{documentation, Alias, Documentation};
9use markdown::{
10 find_headers, header, markdown_anchor_name, markdown_internal_link, markdown_table, Header,
11};
12use snafu::ResultExt;
13use std::fmt::Write;
14use template::{parse_diagram, parse_example};
15
16fn dir_name(section: &str) -> String {
18 section
19 .trim()
20 .replace(",", "")
21 .replace(" ", "-")
22 .to_lowercase()
23}
24
25pub fn generate_guide(verbose: bool) -> Result<()> {
27 let read_path = "GUIDE_template.txt";
28 let write_path = "GUIDE.md";
29
30 let template = std::fs::read_to_string(read_path).context(IoError {
31 context: format!("Unable to read file '{}", read_path),
32 })?;
33
34 let rendered = render_guide(&template, verbose)?;
35
36 std::fs::write(write_path, rendered).context(IoError {
37 context: format!("Unable to write to file '{}'", write_path),
38 })?;
39
40 Ok(())
41}
42
43fn render_guide(template: &str, verbose: bool) -> Result<String> {
44 let mut result = String::new();
45 let mut examples = Vec::new();
46 let mut current_section = Header {
47 level: 0,
48 title: "root".into(),
49 };
50 let mut current_section_example_count = 0;
51
52 let mut lines = template.lines();
53 while let Some(line) = lines.next() {
54 match line.trim() {
55 "$EXAMPLE(" => {
56 let example = parse_example(&mut lines);
57 let instantiated = example.instantiate(
58 "images".into(),
59 format!("images/{}", dir_name(¤t_section.title)),
60 format!("ex{}", current_section_example_count),
61 );
62 write!(result, "{}", instantiated.render_for_documentation())?;
63 examples.push(instantiated);
64 current_section_example_count += 1;
65 }
66 "$STACK_DIAGRAM(" => {
67 let diagram = parse_diagram(&mut lines);
68 result.push_str(&diagram.render_for_markdown());
69 }
70 _ => {
71 if let Some(header) = header(line) {
72 current_section = header;
73 current_section_example_count = 0;
74 }
75 writeln!(result, "{}", line)?;
76 }
77 }
78 }
79
80 for example in &examples {
81 if verbose {
82 println!(
83 "Running example\n\t'{}'",
84 example.command_line_for_documentation()
85 );
86 }
87 example.run(verbose)?;
88 }
89
90 let mut operations = String::new();
91
92 let mut summaries = Vec::new();
94 for doc in documentation().iter() {
95 summaries.push(summary_from_documentation(doc));
96 for alias in &doc.aliases {
97 summaries.push(summary_from_alias(alias, doc.operation));
98 }
99 }
100 summaries.sort_by_key(|s| s.name);
101 let operations_table = markdown_table(
102 &["Operation", "Usage", "Description"],
103 summaries.iter(),
104 row_from_summary,
105 );
106
107 writeln!(operations, "{}", operations_table)?;
108
109 for docs in documentation().iter() {
111 writeln!(operations, "\n### {}\n", docs.operation)?;
112 writeln!(operations, "Usage: `{}`\n", docs.usage)?;
113 writeln!(operations, "{}", docs.explanation)?;
114 for alias in &docs.aliases {
115 writeln!(operations, "\n##### Alias: {}", alias.name)?;
116 writeln!(operations, "\nUsage: `{}`\n", alias.usage)?;
117 writeln!(operations, "{}", alias.description)?;
118 }
119 if !docs.examples.is_empty() {
120 writeln!(operations, "\n#### Examples\n")?;
121 for (count, example) in docs.examples.iter().enumerate() {
122 let instantiated = example.instantiate(
123 "images".into(),
124 "images/operations".into(),
125 format!("{}_{}", docs.operation, count),
126 );
127 write!(operations, "{}", instantiated.render_for_documentation())?;
128 instantiated.run(verbose)?;
129 }
130 }
131 }
132
133 let result = result.replace("$OPERATIONS", &operations);
134
135 let headers = find_headers(&result);
136 let (min_level, max_level) = (2, 2);
137
138 let mut toc = Vec::new();
139 for header in headers
140 .iter()
141 .filter(|h| (h.level >= min_level) && (h.level <= max_level))
142 {
143 let prefix = " ".repeat(2 * (header.level - min_level));
144 toc.push(format!(
145 "{} - {}",
146 prefix,
147 markdown_internal_link(&header.title)
148 ));
149 }
150
151 let toc = toc.join("\n");
152 let result = result.replace("$TABLE_OF_CONTENTS", &toc);
153
154 Ok(result)
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
158struct Summary {
159 name: &'static str,
160 usage: &'static str,
161 explanation: String,
162 alias_for: Option<&'static str>,
163}
164
165fn summary_from_documentation(doc: &Documentation) -> Summary {
166 Summary {
167 name: doc.operation,
168 usage: doc.usage,
169 explanation: doc.explanation.into(),
170 alias_for: None,
171 }
172}
173
174fn summary_from_alias(alias: &Alias, operation: &'static str) -> Summary {
175 Summary {
176 name: alias.name,
177 usage: alias.usage,
178 explanation: format!("See {}.", markdown_internal_link(operation)),
179 alias_for: Some(operation),
180 }
181}
182
183fn row_from_summary(summary: &Summary) -> Vec<String> {
184 vec",
187 summary.name,
188 markdown_anchor_name(summary.alias_for.unwrap_or(&summary.name))
189 ),
190 format!("`{}`", summary.usage.replace("|", "\\|")),
191 summary.explanation.split('\n').nth(0).unwrap().into(),
192 ]
193}