1use 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
13pub fn render(clog: &ChangeLog, out: &OutputPreferences) -> Result<String> {
15 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 text.map(|s| post_process(&s, &out.post_processors))
27}
28
29fn 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
55fn post_process(output: &str, post_processors: &[PostProcessor]) -> String {
57 let mut processors = Vec::new();
59 for processor in post_processors {
60 if let Ok(lookup) = Regex::new(&processor.lookup) {
62 info!("Using post-processor {:#?}", lookup);
64
65 processors.push((lookup, processor.replace.as_str()));
67 } else {
68 warn!("Post-processor {:#?} is invalid", processor);
70 }
71 }
72
73 let mut processed = Vec::new();
75
76 for line in output.lines() {
78 let mut next: String = line.to_string();
80
81 for processor in &processors {
83 next = processor.0.replace_all(&next, processor.1).to_string();
85 }
86
87 processed.push(next);
89 }
90
91 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}