1use super::*;
23
24pub type Compiler = Box<dyn Fn(&mut State, &Path) -> Result<Value>>;
41
42pub use pandoc::pandoc;
43mod pandoc {
44 use super::*;
45 use serde::{self, Deserialize};
46 use serde_json;
47 use serde_json::{Map, Value};
48 use std::collections::HashMap;
49 pub fn pandoc() -> Compiler {
50 Box::new(|state: &mut State, path: &Path| {
51 let metadata = Command::new("pandoc")
52 .args(&["-t", "json"])
53 .arg(&path)
54 .output()
55 .map_err(|err| format!("failed to execute pandoc: {}", err))?;
56 let pandoc_json: PandocJsonOutput =
57 serde_json::from_str(&String::from_utf8_lossy(&metadata.stdout))
58 .unwrap_or_default();
59 let mut metadata_map: Map<String, Value> = parse_metadata(pandoc_json);
60 if state.verbosity() > 2 {
61 println!(
62 "Parsed metadata for {}: {:#?}",
63 path.display(),
64 &metadata_map
65 );
66 }
67 let output = Command::new("pandoc")
68 .arg(&path)
69 .output()
70 .map_err(|err| format!("failed to execute pandoc: {}", err))?;
71 metadata_map.insert(
72 "body".to_string(),
73 Value::String(String::from_utf8_lossy(&output.stdout).to_string()),
74 );
75 Ok(Value::Object(metadata_map))
76 })
77 }
78
79 fn parse_metadata(output: PandocJsonOutput) -> Map<String, Value> {
80 let meta = output.meta;
81
82 meta.into_iter()
83 .map(|(key, metaval)| (key, metaval.into()))
84 .collect::<_>()
85 }
86
87 #[derive(Deserialize, Debug, Default)]
88 struct PandocJsonOutput {
89 blocks: Value,
90 #[serde(rename = "pandoc-api-version")]
91 pandoc_api_version: Value,
92 meta: HashMap<String, PandocMetaValue>,
93 }
94
95 #[derive(Deserialize, Debug)]
96 #[serde(tag = "t", content = "c")]
97 enum PandocMetaValue {
98 MetaMap(HashMap<String, PandocMetaValue>),
99 MetaList(Vec<PandocMetaValue>),
100 MetaBool(bool),
101 MetaString(String),
102 MetaInlines(Vec<PandocMetaInline>),
103 MetaBlocks(Value),
104 }
105
106 impl Into<Value> for PandocMetaValue {
107 fn into(self) -> Value {
108 use PandocMetaValue::*;
109 match self {
110 MetaMap(map) => Value::Object(
111 map.into_iter()
112 .map(|(key, metaval)| (key.clone(), metaval.into()))
113 .collect(),
114 ),
115 MetaList(list) => Value::Array(
116 list.into_iter()
117 .map(|metaval| metaval.into())
118 .collect::<Vec<Value>>(),
119 ),
120 MetaBool(v) => Value::Bool(v),
121 MetaString(v) => Value::String(v),
122 MetaInlines(inlines_list) => Value::String(inlines_list.into_iter().fold(
123 String::new(),
124 |mut acc, inline| {
125 let inline: String = inline.into();
126 acc.extend(inline.chars());
127 acc
128 },
129 )),
130 MetaBlocks(_) => Value::String(String::new()),
131 }
132 }
133 }
134
135 #[derive(Deserialize, Debug)]
136 #[serde(tag = "t", content = "c")]
137 enum PandocMetaInline {
138 Str(String),
139 Emph(Vec<PandocMetaInline>),
140 Strong(Vec<PandocMetaInline>),
141 Strikeout(Vec<PandocMetaInline>),
142 Superscript(Vec<PandocMetaInline>),
143 Subscript(Vec<PandocMetaInline>),
144 SmallCaps(Vec<PandocMetaInline>),
145 Quoted(Value),
146 Cite(Value),
147 Code(Value),
148 Space,
149 SoftBreak,
150 LineBreak,
151 Math(Value),
152 RawPandocMetaInline(Value),
153 Link(Value),
154 Image(Value),
155 Note(Value),
156 Span(Value),
157 }
158
159 impl Into<String> for PandocMetaInline {
160 fn into(self) -> String {
161 use PandocMetaInline::*;
162 match self {
163 Str(inner) => inner,
164 Emph(list) => list.into_iter().fold(String::new(), |mut acc, el| {
165 let el: String = el.into();
166 acc.extend(el.chars());
167 acc
168 }),
169 Strong(list) => list.into_iter().fold(String::new(), |mut acc, el| {
170 let el: String = el.into();
171 acc.extend(el.chars());
172 acc
173 }),
174 Space => String::from(" "),
175 SoftBreak => String::from("\n"),
176 LineBreak => String::from("\n"),
177 Strikeout(list) => list.into_iter().fold(String::new(), |mut acc, el| {
178 let el: String = el.into();
179 acc.extend(el.chars());
180 acc
181 }),
182 Superscript(list) => list.into_iter().fold(String::new(), |mut acc, el| {
183 let el: String = el.into();
184 acc.extend(el.chars());
185 acc
186 }),
187 Subscript(list) => list.into_iter().fold(String::new(), |mut acc, el| {
188 let el: String = el.into();
189 acc.extend(el.chars());
190 acc
191 }),
192 SmallCaps(list) => list.into_iter().fold(String::new(), |mut acc, el| {
193 let el: String = el.into();
194 acc.extend(el.chars());
195 acc
196 }),
197 Quoted(_) => String::new(),
198 Cite(_) => String::new(),
199 Code(_) => String::new(),
200 Math(_) => String::new(),
201 RawPandocMetaInline(_) => String::new(),
202 Link(_) => String::new(),
203 Image(_) => String::new(),
204 Note(_) => String::new(),
205 Span(_) => String::new(),
206 }
207 }
208 }
209}
210
211pub use rss::*;
212
213mod rss {
214 use super::*;
215 use serde::{self, Serialize};
216 use serde_json::json;
217
218 #[derive(Serialize)]
219 pub struct RssItem {
220 pub title: String,
221 pub description: String,
222 pub link: String,
223 pub last_build_date: String,
224 pub pub_date: String,
225 pub ttl: i32,
226 }
227
228 const RSS_TEMPLATE: &'static str = r#"<?xml version="1.0" encoding="UTF-8" ?>
229<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
230<channel>
231 <title>{{ config.title }}</title>
232 <description><![CDATA[{{ include config.description }}]]></description>
233 <link>{{ config.link }}</link>
234 <atom:link href="{{ config.link }}/{{ path }}" rel="self" type="application/rss+xml" />
235 <pubDate> Thu, 01 Jan 1970 00:00:00 +0000 </pubDate>
236 {{#each items}}
237 <item>
238 <title>{{ title }}</title>
239 <description><![CDATA[{{ include description }}]]></description>
240 <link>{{ config.link }}{{ link }}</link>
241 <guid>{{ config.link }}{{ link }}</guid>
242 <pubDate>{{ pub_date }}</pubDate>
243 </item>
244{{/each~}}
245
246</channel>
247</rss>"#;
248 pub fn rss_feed(snapshot_name: String, configuration: RssItem) -> Compiler {
249 Box::new(move |state: &mut State, dest_path: &Path| {
250 if !state.snapshots.contains_key(&snapshot_name) {
251 Err(format!("There are no snapshots with key `{}`, is the source rule empty (ie producing no items) or have you typed the name wrong?", &snapshot_name))?;
253 }
254
255 let snapshot = &state.snapshots[&snapshot_name];
256 let mut rss_items = Vec::with_capacity(snapshot.len());
257 for artifact in snapshot.iter() {
258 if let Value::Object(ref map) = &state.artifacts[&artifact].metadata {
259 macro_rules! get_property {
260 ($key:literal, $default:expr) => {
261 map.get($key)
262 .and_then(|t| {
263 if let Value::String(ref var) = t {
264 Some(var.to_string())
265 } else {
266 None
267 }
268 })
269 .unwrap_or_else(|| $default)
270 };
271 }
272 rss_items.push(RssItem {
273 title: get_property!("title", format!("No title, uuid: {}", artifact)),
274 description: get_property!("body", String::new()),
275 link: format!(
276 "{}/{}",
277 &configuration.link,
278 &state.artifacts[&artifact].path.display()
279 ),
280 last_build_date: String::new(),
281 pub_date: get_property!(
282 "date",
283 "Thu, 01 Jan 1970 00:00:00 +0000".to_string()
284 ),
285 ttl: 1800,
286 });
287 }
288 }
289 let mut handlebars = Handlebars::new();
290 handlebars.register_helper("include", Box::new(include_helper));
291
292 let test = handlebars.render_template(
293 RSS_TEMPLATE,
294 &json!({ "items": rss_items, "config": configuration, "path": dest_path }),
295 )?;
296 Ok(json!({ "body": test }))
297 })
298 }
299}
300
301pub fn compiler_seq(compiler_a: Compiler, compiler_b: Compiler) -> Compiler {
302 Box::new(move |state: &mut State, path: &Path| {
303 let a = compiler_a(state, &path)?;
304 let b = compiler_b(state, &path)?;
305 let a = match (a, b) {
306 (Value::Object(mut map_a), Value::Object(map_b)) => {
307 map_a.extend(map_b.into_iter());
308 Value::Object(map_a)
309 }
310 (a, _) => a,
311 };
312 Ok(a)
313 })
314}