@@ -3,6 +3,8 @@ | |||||
## 0.3.2 (unreleased) | ## 0.3.2 (unreleased) | ||||
- Fix `serve` command trying to read all files as markdown | - Fix `serve` command trying to read all files as markdown | ||||
- Add many syntax highlighting themes | |||||
- Fix date being serialised incorrectly in page `extra` section of front-matter | |||||
## 0.3.1 (2018-02-15) | ## 0.3.1 (2018-02-15) | ||||
@@ -192,7 +192,7 @@ dependencies = [ | |||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"tera 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"tera 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", | "toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"utils 0.1.0", | "utils 0.1.0", | ||||
] | ] | ||||
@@ -272,7 +272,7 @@ name = "errors" | |||||
version = "0.1.0" | version = "0.1.0" | ||||
dependencies = [ | dependencies = [ | ||||
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", | "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"tera 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"tera 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", | "toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
@@ -310,7 +310,7 @@ dependencies = [ | |||||
"regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", | "regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"tera 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"tera 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", | "toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
@@ -794,7 +794,7 @@ dependencies = [ | |||||
"front_matter 0.1.0", | "front_matter 0.1.0", | ||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"tera 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"tera 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"utils 0.1.0", | "utils 0.1.0", | ||||
] | ] | ||||
@@ -1000,7 +1000,7 @@ dependencies = [ | |||||
"slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"syntect 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | "syntect 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"templates 0.1.0", | "templates 0.1.0", | ||||
"tera 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"tera 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"utils 0.1.0", | "utils 0.1.0", | ||||
] | ] | ||||
@@ -1123,7 +1123,7 @@ dependencies = [ | |||||
"taxonomies 0.1.0", | "taxonomies 0.1.0", | ||||
"tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"templates 0.1.0", | "templates 0.1.0", | ||||
"tera 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"tera 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"utils 0.1.0", | "utils 0.1.0", | ||||
"walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)", | "walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
@@ -1211,7 +1211,7 @@ dependencies = [ | |||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"tera 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"tera 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"utils 0.1.0", | "utils 0.1.0", | ||||
] | ] | ||||
@@ -1235,13 +1235,13 @@ dependencies = [ | |||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | "pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"taxonomies 0.1.0", | "taxonomies 0.1.0", | ||||
"tera 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"tera 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"utils 0.1.0", | "utils 0.1.0", | ||||
] | ] | ||||
[[package]] | [[package]] | ||||
name = "tera" | name = "tera" | ||||
version = "0.11.4" | |||||
version = "0.11.5" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -1411,7 +1411,7 @@ version = "0.1.0" | |||||
dependencies = [ | dependencies = [ | ||||
"errors 0.1.0", | "errors 0.1.0", | ||||
"tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"tera 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"tera 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | ] | ||||
[[package]] | [[package]] | ||||
@@ -1638,7 +1638,7 @@ dependencies = [ | |||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" | ||||
"checksum syntect 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db9fffcb25a761118df53811bd1cfcd54cf57fcbc51e1ea3167ae263477129ad" | "checksum syntect 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db9fffcb25a761118df53811bd1cfcd54cf57fcbc51e1ea3167ae263477129ad" | ||||
"checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e" | "checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e" | ||||
"checksum tera 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d5fcce006fbde2ac587dc056940d2c329ee49f2e4de7d9ddfdbbc60efb9f9207" | |||||
"checksum tera 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fc1a35d04c2444875b1319293fbc72c00215ae6220f8c70f9f14fefa5eaae0c6" | |||||
"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" | "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" | ||||
"checksum term-painter 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "dcaa948f0e3e38470cd8dc8dcfe561a75c9e43f28075bb183845be2b9b3c08cf" | "checksum term-painter 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "dcaa948f0e3e38470cd8dc8dcfe561a75c9e43f28075bb183845be2b9b3c08cf" | ||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" | ||||
@@ -223,12 +223,7 @@ impl ser::Serialize for Page { | |||||
state.serialize_field("content", &self.content)?; | state.serialize_field("content", &self.content)?; | ||||
state.serialize_field("title", &self.meta.title)?; | state.serialize_field("title", &self.meta.title)?; | ||||
state.serialize_field("description", &self.meta.description)?; | state.serialize_field("description", &self.meta.description)?; | ||||
// From a TOML datetime to a String first | |||||
let date = match self.meta.date { | |||||
Some(ref d) => Some(d.to_string()), | |||||
None => None, | |||||
}; | |||||
state.serialize_field("date", &date)?; | |||||
state.serialize_field("date", &self.meta.date)?; | |||||
state.serialize_field("slug", &self.slug)?; | state.serialize_field("slug", &self.slug)?; | ||||
state.serialize_field("path", &self.path)?; | state.serialize_field("path", &self.path)?; | ||||
state.serialize_field("components", &self.components)?; | state.serialize_field("components", &self.components)?; | ||||
@@ -98,16 +98,13 @@ pub fn populate_previous_and_next_pages(input: &[Page]) -> Vec<Page> { | |||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use std::str::FromStr; | |||||
use toml::value::Datetime; | |||||
use front_matter::{PageFrontMatter, SortBy}; | use front_matter::{PageFrontMatter, SortBy}; | ||||
use page::Page; | use page::Page; | ||||
use super::{sort_pages, populate_previous_and_next_pages}; | use super::{sort_pages, populate_previous_and_next_pages}; | ||||
fn create_page_with_date(date: &str) -> Page { | fn create_page_with_date(date: &str) -> Page { | ||||
let mut front_matter = PageFrontMatter::default(); | let mut front_matter = PageFrontMatter::default(); | ||||
front_matter.date = Some(Datetime::from_str(date).unwrap()); | |||||
front_matter.date = Some(date.to_string()); | |||||
Page::new("content/hello.md", front_matter) | Page::new("content/hello.md", front_matter) | ||||
} | } | ||||
@@ -1,12 +1,65 @@ | |||||
use std::collections::HashMap; | |||||
use std::result::{Result as StdResult}; | |||||
use chrono::prelude::*; | use chrono::prelude::*; | ||||
use tera::Value; | |||||
use tera::{Map, Value}; | |||||
use serde::{Deserialize, Deserializer}; | |||||
use toml; | use toml; | ||||
use errors::Result; | use errors::Result; | ||||
fn from_toml_datetime<'de, D>(deserializer: D) -> StdResult<Option<String>, D::Error> | |||||
where | |||||
D: Deserializer<'de>, | |||||
{ | |||||
toml::value::Datetime::deserialize(deserializer) | |||||
.map(|s| Some(s.to_string())) | |||||
} | |||||
/// Returns key/value for a converted date from TOML. | |||||
/// If the table itself is the TOML struct, only return its value without the key | |||||
fn convert_toml_date(table: Map<String, Value>) -> Value { | |||||
let mut new = Map::new(); | |||||
for (k, v) in table.into_iter() { | |||||
if k == "$__toml_private_datetime" { | |||||
return v; | |||||
} | |||||
match v { | |||||
Value::Object(mut o) => { | |||||
// that was a toml datetime object, just return the date | |||||
if let Some(toml_date) = o.remove("$__toml_private_datetime") { | |||||
new.insert(k, toml_date); | |||||
return Value::Object(new); | |||||
} | |||||
new.insert(k, convert_toml_date(o)); | |||||
}, | |||||
_ => { new.insert(k, v); } | |||||
} | |||||
} | |||||
Value::Object(new) | |||||
} | |||||
/// TOML datetimes will be serialized as a struct but we want the | |||||
/// stringified version for json, otherwise they are going to be weird | |||||
fn fix_toml_dates(table: Map<String, Value>) -> Value { | |||||
let mut new = Map::new(); | |||||
for (key, value) in table { | |||||
match value { | |||||
Value::Object(mut o) => { | |||||
new.insert(key, convert_toml_date(o)); | |||||
}, | |||||
_ => { new.insert(key, value); }, | |||||
} | |||||
} | |||||
Value::Object(new) | |||||
} | |||||
/// The front matter of every page | /// The front matter of every page | ||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||||
pub struct PageFrontMatter { | pub struct PageFrontMatter { | ||||
@@ -15,7 +68,8 @@ pub struct PageFrontMatter { | |||||
/// Description in <meta> that appears when linked, e.g. on twitter | /// Description in <meta> that appears when linked, e.g. on twitter | ||||
pub description: Option<String>, | pub description: Option<String>, | ||||
/// Date if we want to order pages (ie blog post) | /// Date if we want to order pages (ie blog post) | ||||
pub date: Option<toml::value::Datetime>, | |||||
#[serde(default, deserialize_with = "from_toml_datetime")] | |||||
pub date: Option<String>, | |||||
/// Whether this page is a draft and should be ignored for pagination etc | /// Whether this page is a draft and should be ignored for pagination etc | ||||
pub draft: Option<bool>, | pub draft: Option<bool>, | ||||
/// The page slug. Will be used instead of the filename if present | /// The page slug. Will be used instead of the filename if present | ||||
@@ -41,12 +95,13 @@ pub struct PageFrontMatter { | |||||
#[serde(skip_serializing)] | #[serde(skip_serializing)] | ||||
pub template: Option<String>, | pub template: Option<String>, | ||||
/// Any extra parameter present in the front matter | /// Any extra parameter present in the front matter | ||||
pub extra: Option<HashMap<String, Value>>, | |||||
#[serde(default)] | |||||
pub extra: Map<String, Value>, | |||||
} | } | ||||
impl PageFrontMatter { | impl PageFrontMatter { | ||||
pub fn parse(toml: &str) -> Result<PageFrontMatter> { | pub fn parse(toml: &str) -> Result<PageFrontMatter> { | ||||
let f: PageFrontMatter = match toml::from_str(toml) { | |||||
let mut f: PageFrontMatter = match toml::from_str(toml) { | |||||
Ok(d) => d, | Ok(d) => d, | ||||
Err(e) => bail!(e), | Err(e) => bail!(e), | ||||
}; | }; | ||||
@@ -69,17 +124,20 @@ impl PageFrontMatter { | |||||
} | } | ||||
} | } | ||||
f.extra = match fix_toml_dates(f.extra) { | |||||
Value::Object(o) => o, | |||||
_ => unreachable!("Got something other than a table in page extra"), | |||||
}; | |||||
Ok(f) | Ok(f) | ||||
} | } | ||||
/// Converts the TOML datetime to a Chrono naive datetime | /// Converts the TOML datetime to a Chrono naive datetime | ||||
pub fn date(&self) -> Option<NaiveDateTime> { | pub fn date(&self) -> Option<NaiveDateTime> { | ||||
if let Some(ref d) = self.date { | if let Some(ref d) = self.date { | ||||
let d2 = d.to_string(); | |||||
if d2.contains('T') { | |||||
DateTime::parse_from_rfc3339(&d2).ok().and_then(|s| Some(s.naive_local())) | |||||
if d.contains('T') { | |||||
DateTime::parse_from_rfc3339(&d).ok().and_then(|s| Some(s.naive_local())) | |||||
} else { | } else { | ||||
NaiveDate::parse_from_str(&d2, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0, 0, 0))) | |||||
NaiveDate::parse_from_str(&d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0, 0, 0))) | |||||
} | } | ||||
} else { | } else { | ||||
None | None | ||||
@@ -117,7 +175,7 @@ impl Default for PageFrontMatter { | |||||
weight: None, | weight: None, | ||||
aliases: None, | aliases: None, | ||||
template: None, | template: None, | ||||
extra: None, | |||||
extra: Map::new(), | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -125,12 +183,14 @@ impl Default for PageFrontMatter { | |||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use tera::to_value; | |||||
use super::PageFrontMatter; | use super::PageFrontMatter; | ||||
#[test] | #[test] | ||||
fn can_have_empty_front_matter() { | fn can_have_empty_front_matter() { | ||||
let content = r#" "#; | let content = r#" "#; | ||||
let res = PageFrontMatter::parse(content); | let res = PageFrontMatter::parse(content); | ||||
println!("{:?}", res); | |||||
assert!(res.is_ok()); | assert!(res.is_ok()); | ||||
} | } | ||||
@@ -251,4 +311,32 @@ mod tests { | |||||
let res = PageFrontMatter::parse(content); | let res = PageFrontMatter::parse(content); | ||||
assert!(res.is_err()); | assert!(res.is_err()); | ||||
} | } | ||||
#[test] | |||||
fn can_parse_dates_in_extra() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
[extra] | |||||
some-date = 2002-14-01"#; | |||||
let res = PageFrontMatter::parse(content); | |||||
println!("{:?}", res); | |||||
assert!(res.is_ok()); | |||||
assert_eq!(res.unwrap().extra["some-date"], to_value("2002-14-01").unwrap()); | |||||
} | |||||
#[test] | |||||
fn can_parse_nested_dates_in_extra() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
[extra.something] | |||||
some-date = 2002-14-01"#; | |||||
let res = PageFrontMatter::parse(content); | |||||
println!("{:?}", res); | |||||
assert!(res.is_ok()); | |||||
assert_eq!(res.unwrap().extra["something"]["some-date"], to_value("2002-14-01").unwrap()); | |||||
} | |||||
} | } |