changelog/
output.rs

1// Copyright 2017-2018 by Aldrin J D'Souza.
2// Licensed under the MIT License <https://opensource.org/licenses/MIT>
3
4/// All output concerns.
5use std::fmt;
6use regex::Regex;
7use handlebars::{Handlebars, Helper, RenderContext, RenderError};
8use serde_json::to_string_pretty;
9use super::{ChangeLog, OutputPreferences, PostProcessor, Result};
10
11type RenderResult = ::std::result::Result<(), RenderError>;
12
13/// Render the changelog with the given output preferences
14pub fn render(clog: &ChangeLog, out: &OutputPreferences) -> Result<String> {
15    // Depending on the output format, render the log to text
16    let text = if out.json {
17        to_string_pretty(clog).map_err(|e| format_err!("JSON render failed: {}", e))
18    } else {
19        let mut hbs = Handlebars::new();
20        hbs.register_helper("tidy-change", Box::new(tidy));
21        hbs.template_render(&out.get_template()?, clog)
22            .map_err(|e| format_err!("Handlebar render failed: {}", e))
23    };
24
25    // Run the post processors on the output
26    text.map(|s| post_process(&s, &out.post_processors))
27}
28
29/// A handlebar helper to tidy up markdown lists used to render changes.
30fn tidy(h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> RenderResult {
31    if let Some(indent) = h.param(0).and_then(|v| v.value().as_str()) {
32        if let Some(text) = h.param(1).and_then(|v| v.value().as_str()) {
33            let mut lines = text.lines();
34            if let Some(first) = lines.next() {
35                writeln!(rc.writer, "{}", first.trim())?;
36            }
37            for line in lines {
38                writeln!(rc.writer, "{}{}", indent, line)?;
39            }
40        }
41    }
42    Ok(())
43}
44
45impl fmt::Display for ChangeLog {
46    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47        let out = OutputPreferences::default();
48        match render(self, &out) {
49            Ok(fine) => write!(f, "{}", fine),
50            Err(err) => write!(f, "Error: {}", err),
51        }
52    }
53}
54
55/// Post process the output before returning it
56fn post_process(output: &str, post_processors: &[PostProcessor]) -> String {
57    // Compile the post processor regular expressions
58    let mut processors = Vec::new();
59    for processor in post_processors {
60        // Processor the lookup regular expression
61        if let Ok(lookup) = Regex::new(&processor.lookup) {
62            // Inform
63            info!("Using post-processor {:#?}", lookup);
64
65            // Remember the regex and the replacement string
66            processors.push((lookup, processor.replace.as_str()));
67        } else {
68            // Invalid regex, warn and ignore
69            warn!("Post-processor {:#?} is invalid", processor);
70        }
71    }
72
73    // Track the processed output
74    let mut processed = Vec::new();
75
76    // Take each line in the output
77    for line in output.lines() {
78        // Make a mutable copy
79        let mut next: String = line.to_string();
80
81        // Run all available processors through it
82        for processor in &processors {
83            // Replace the pattern as appropriate
84            next = processor.0.replace_all(&next, processor.1).to_string();
85        }
86
87        // Replace line with the processed
88        processed.push(next);
89    }
90
91    // Return what we ended with
92    processed.join("\n")
93}
94
95#[cfg(test)]
96mod tests {
97    use super::PostProcessor;
98
99    #[test]
100    fn post_process() {
101        let input = String::from("Fixed JIRA-1234\nfoo");
102        let mut jira = PostProcessor::default();
103        jira.lookup = r"JIRA-(?P<t>\d+)".to_string();
104        jira.replace = r"[JIRA-$t](https://our.jira/$t)".to_string();
105        let out = super::post_process(&input, &vec![jira]);
106        assert_eq!(&out, "Fixed [JIRA-1234](https://our.jira/1234)\nfoo");
107
108        let mut bad = PostProcessor::default();
109        bad.lookup = r"JIRA-?(P<t\d+".to_string();
110        bad.replace = r"whatever".to_string();
111        let out = super::post_process(&input, &vec![bad]);
112        assert_eq!(&out, &input);
113    }
114}