Skip to main content

imagecli/documentation/
mod.rs

1//! Functions for generating documentation for this application.
2
3pub 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
16/// Name of directory to use as output for examples in this section.
17fn dir_name(section: &str) -> String {
18    section
19        .trim()
20        .replace(",", "")
21        .replace(" ", "-")
22        .to_lowercase()
23}
24
25/// Reads GUIDE_template.md and writes GUIDE.md.
26pub 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(&current_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    // Summary table
93    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    // Detailed per-op documentation
110    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![
185        format!(
186            "[{}](#{})",
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}