use crate::prelude::*;
use handlebars::Handlebars;
use serde::Serialize;
use std::{fs::File, io::Write, path::PathBuf};
pub const ADVISORY_TEMPLATE_NAME: &str = "advisory.md.hbs";
const ADVISORY_TEMPLATE_STRING: &str = r#"---
title: "{{id}}: {{package}}: {{title}}"
description: "{{summary}}"
date: {{date}}
tags: {{tags}}
permalink: /advisories/{{id}}:output_ext
---
### Description
{{{description}}}
{{#if url ~}}
### More Info
<{{{url}}}>
{{/if~}}
### Patched Versions
{{#if patched_versions~}}
{{#each patched_versions ~}}
- `{{{this}}}`
{{/each~}}
{{~else~}}
- None!
{{~/if}}
{{#if unaffected_versions}}
### Unaffected Versions
{{#each unaffected_versions ~}}
- `{{{this}}}`
{{/each ~}}
{{/if ~}}
"#;
pub fn render_advisories() {
let mut handlebars = Handlebars::new();
handlebars.set_strict_mode(true);
handlebars.register_escape_fn(handlebars::no_escape);
handlebars
.register_template_string(ADVISORY_TEMPLATE_NAME, ADVISORY_TEMPLATE_STRING)
.unwrap();
let advisories: Vec<AdvisoryParams> = rustsec::Database::fetch()
.unwrap()
.iter()
.map(AdvisoryParams::from)
.collect();
for advisory in &advisories {
let output_path =
PathBuf::from("_posts").join(format!("{}-{}.md", advisory.date, advisory.id));
let mut rendered = handlebars.render(ADVISORY_TEMPLATE_NAME, advisory).unwrap();
rendered = rendered.replace("<", "<").replace(">", ">");
let mut output_file = File::create(&output_path).unwrap();
output_file.write_all(rendered.as_bytes()).unwrap();
status_ok!("Rendered", "{}", output_path.display());
}
status_ok!(
"Completed",
"{} advisories rendered as Markdown",
advisories.len()
);
}
#[derive(Debug, Serialize)]
pub struct AdvisoryParams {
pub id: String,
pub package: String,
pub title: String,
pub summary: String,
pub description: String,
pub date: String,
pub tags: String,
pub url: Option<String>,
pub patched_versions: Vec<String>,
pub unaffected_versions: Option<Vec<String>>,
}
impl<'a> From<&'a rustsec::Advisory> for AdvisoryParams {
fn from(advisory: &rustsec::Advisory) -> AdvisoryParams {
let patched_versions = advisory
.versions
.patched
.iter()
.map(|req| req.to_string())
.collect();
let unaffected_versions = if advisory.versions.unaffected.is_empty() {
None
} else {
Some(
advisory
.versions
.unaffected
.iter()
.map(|req| req.to_string())
.collect(),
)
};
let mut summary = advisory.description.replace('\n', " ").replace(" ", " ");
summary.retain(|c| matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='9' | ' ' | ',' | '.'));
let mut tags = vec![advisory.metadata.package.to_string()];
tags.extend(
advisory
.metadata
.keywords
.iter()
.map(|kw| kw.as_str().to_owned()),
);
Self {
id: advisory.metadata.id.to_string(),
package: advisory.metadata.package.to_string(),
title: advisory.title.clone(),
summary: summary.trim().to_owned(),
description: advisory.description.trim().to_owned(),
date: advisory.metadata.date.as_str().to_owned(),
tags: tags.join(" "),
url: advisory.metadata.url.clone(),
patched_versions,
unaffected_versions,
}
}
}