mirror of
https://github.com/cobalt-org/cobalt.rs
synced 2024-11-15 00:17:29 +00:00
refactor(config): Port to new cobalt-config crate
BREAKING CHANGE: Old-style frontmatter is removed and some config keys are renamed: - Sort Order: Asc / Desc were lowercased - Sass Style: Nested / Expanded / Compact / Compressed were lowercased - Pagination: All, Tags, Categories, Dates were lowercased
This commit is contained in:
parent
3e5bdad921
commit
0b3ef05ae3
42 changed files with 394 additions and 1994 deletions
32
Cargo.lock
generated
32
Cargo.lock
generated
|
@ -482,6 +482,7 @@ dependencies = [
|
||||||
"assert_fs 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"assert_fs 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"cobalt-config 0.16.0",
|
||||||
"deunicode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"deunicode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -510,6 +511,22 @@ dependencies = [
|
||||||
"walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cobalt-config"
|
||||||
|
version = "0.16.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"deunicode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"liquid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"status 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-random"
|
name = "const-random"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -1368,6 +1385,11 @@ dependencies = [
|
||||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onig"
|
name = "onig"
|
||||||
version = "4.3.3"
|
version = "4.3.3"
|
||||||
|
@ -1988,6 +2010,14 @@ dependencies = [
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "status"
|
||||||
|
version = "0.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "string"
|
name = "string"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -2657,6 +2687,7 @@ dependencies = [
|
||||||
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
|
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
|
||||||
"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
|
"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
|
||||||
"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
|
"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
|
||||||
|
"checksum once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "891f486f630e5c5a4916c7e16c4b24a53e78c860b646e9f8e005e4f16847bfed"
|
||||||
"checksum onig 4.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8518fcb2b1b8c2f45f0ad499df4fda6087fc3475ca69a185c173b8315d2fb383"
|
"checksum onig 4.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8518fcb2b1b8c2f45f0ad499df4fda6087fc3475ca69a185c173b8315d2fb383"
|
||||||
"checksum onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0"
|
"checksum onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0"
|
||||||
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||||
|
@ -2729,6 +2760,7 @@ dependencies = [
|
||||||
"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
|
"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
|
||||||
"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86"
|
"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86"
|
||||||
"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85"
|
"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85"
|
||||||
|
"checksum status 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "36f7e6db3d58ab9e499a2722b0ddaaffeda2d76865a1749d5411182176c594f9"
|
||||||
"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d"
|
"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d"
|
||||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
|
|
|
@ -21,6 +21,7 @@ name = "cobalt"
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
cobalt-config = { version = "0.16", path = "crates/config" }
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
liquid = { version = "0.19.0", features = ["jekyll-filters"] }
|
liquid = { version = "0.19.0", features = ["jekyll-filters"] }
|
||||||
deunicode = "1.0.0"
|
deunicode = "1.0.0"
|
||||||
|
|
|
@ -7,7 +7,6 @@ use env_logger;
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use cobalt;
|
|
||||||
|
|
||||||
pub fn get_config_args() -> Vec<clap::Arg<'static, 'static>> {
|
pub fn get_config_args() -> Vec<clap::Arg<'static, 'static>> {
|
||||||
[
|
[
|
||||||
|
@ -36,16 +35,16 @@ pub fn get_config_args() -> Vec<clap::Arg<'static, 'static>> {
|
||||||
.to_vec()
|
.to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_config(matches: &clap::ArgMatches) -> Result<cobalt::ConfigBuilder> {
|
pub fn get_config(matches: &clap::ArgMatches) -> Result<cobalt_config::Config> {
|
||||||
let config_path = matches.value_of("config");
|
let config_path = matches.value_of("config");
|
||||||
|
|
||||||
// Fetch config information if available
|
// Fetch config information if available
|
||||||
let mut config = if let Some(config_path) = config_path {
|
let mut config = if let Some(config_path) = config_path {
|
||||||
cobalt::ConfigBuilder::from_file(config_path)
|
cobalt_config::Config::from_file(config_path)
|
||||||
.with_context(|_| failure::format_err!("Error reading config file {:?}", config_path))?
|
.with_context(|_| failure::format_err!("Error reading config file {:?}", config_path))?
|
||||||
} else {
|
} else {
|
||||||
let cwd = env::current_dir().expect("How does this fail?");
|
let cwd = env::current_dir().expect("How does this fail?");
|
||||||
cobalt::ConfigBuilder::from_cwd(cwd)?
|
cobalt_config::Config::from_cwd(cwd)?
|
||||||
};
|
};
|
||||||
|
|
||||||
config.abs_dest = matches.value_of("destination").map(path::PathBuf::from);
|
config.abs_dest = matches.value_of("destination").map(path::PathBuf::from);
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub fn build_command_args() -> clap::App<'static, 'static> {
|
||||||
|
|
||||||
pub fn build_command(matches: &clap::ArgMatches) -> Result<()> {
|
pub fn build_command(matches: &clap::ArgMatches) -> Result<()> {
|
||||||
let config = args::get_config(matches)?;
|
let config = args::get_config(matches)?;
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
|
|
||||||
build(config.clone())?;
|
build(config.clone())?;
|
||||||
info!("Build successful");
|
info!("Build successful");
|
||||||
|
@ -43,7 +43,7 @@ pub fn clean_command_args() -> clap::App<'static, 'static> {
|
||||||
|
|
||||||
pub fn clean_command(matches: &clap::ArgMatches) -> Result<()> {
|
pub fn clean_command(matches: &clap::ArgMatches) -> Result<()> {
|
||||||
let config = args::get_config(matches)?;
|
let config = args::get_config(matches)?;
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
|
|
||||||
clean(&config)
|
clean(&config)
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ pub fn import_command_args() -> clap::App<'static, 'static> {
|
||||||
|
|
||||||
pub fn import_command(matches: &clap::ArgMatches) -> Result<()> {
|
pub fn import_command(matches: &clap::ArgMatches) -> Result<()> {
|
||||||
let config = args::get_config(matches)?;
|
let config = args::get_config(matches)?;
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
|
|
||||||
clean(&config)?;
|
clean(&config)?;
|
||||||
build(config.clone())?;
|
build(config.clone())?;
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub fn debug_command(matches: &clap::ArgMatches) -> Result<()> {
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
("config", _) => {
|
("config", _) => {
|
||||||
let config = args::get_config(matches)?;
|
let config = args::get_config(matches)?;
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
println!("{}", config);
|
println!("{}", config);
|
||||||
}
|
}
|
||||||
("highlight", Some(matches)) => match matches.subcommand() {
|
("highlight", Some(matches)) => match matches.subcommand() {
|
||||||
|
@ -48,7 +48,7 @@ pub fn debug_command(matches: &clap::ArgMatches) -> Result<()> {
|
||||||
},
|
},
|
||||||
("files", Some(matches)) => {
|
("files", Some(matches)) => {
|
||||||
let config = args::get_config(matches)?;
|
let config = args::get_config(matches)?;
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
let collection = matches.value_of("COLLECTION");
|
let collection = matches.value_of("COLLECTION");
|
||||||
match collection {
|
match collection {
|
||||||
Some("assets") => {
|
Some("assets") => {
|
||||||
|
|
|
@ -62,7 +62,7 @@ pub fn new_command_args() -> clap::App<'static, 'static> {
|
||||||
|
|
||||||
pub fn new_command(matches: &clap::ArgMatches) -> Result<()> {
|
pub fn new_command(matches: &clap::ArgMatches) -> Result<()> {
|
||||||
let config = args::get_config(matches)?;
|
let config = args::get_config(matches)?;
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
|
|
||||||
let title = matches.value_of("TITLE").unwrap();
|
let title = matches.value_of("TITLE").unwrap();
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ pub fn rename_command_args() -> clap::App<'static, 'static> {
|
||||||
|
|
||||||
pub fn rename_command(matches: &clap::ArgMatches) -> Result<()> {
|
pub fn rename_command(matches: &clap::ArgMatches) -> Result<()> {
|
||||||
let config = args::get_config(matches)?;
|
let config = args::get_config(matches)?;
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
|
|
||||||
let source = path::PathBuf::from(matches.value_of("SRC").unwrap());
|
let source = path::PathBuf::from(matches.value_of("SRC").unwrap());
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ pub fn publish_command(matches: &clap::ArgMatches) -> Result<()> {
|
||||||
file.push(path::Path::new(filename));
|
file.push(path::Path::new(filename));
|
||||||
let file = file;
|
let file = file;
|
||||||
let config = args::get_config(matches)?;
|
let config = args::get_config(matches)?;
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
|
|
||||||
publish_document(&config, &file)
|
publish_document(&config, &file)
|
||||||
.with_context(|_| failure::format_err!("Could not publish `{:?}`", file))?;
|
.with_context(|_| failure::format_err!("Could not publish `{:?}`", file))?;
|
||||||
|
@ -310,11 +310,10 @@ pub fn create_new_document(
|
||||||
DEFAULT.get(file_type).unwrap_or(&POST_MD).to_string()
|
DEFAULT.get(file_type).unwrap_or(&POST_MD).to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let doc = cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::parse(&source)?;
|
let doc = cobalt_model::Document::parse(&source)?;
|
||||||
let (front, content) = doc.parts();
|
let (mut front, content) = doc.into_parts();
|
||||||
let front = front.set_title(title.to_owned());
|
front.title = Some(title.to_owned());
|
||||||
let doc =
|
let doc = cobalt_model::Document::new(front, content);
|
||||||
cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::new(front, content);
|
|
||||||
let doc = doc.to_string();
|
let doc = doc.to_string();
|
||||||
|
|
||||||
create_file(&file, &doc)?;
|
create_file(&file, &doc)?;
|
||||||
|
@ -358,8 +357,8 @@ pub fn rename_document(
|
||||||
};
|
};
|
||||||
|
|
||||||
let doc = cobalt_model::files::read_file(&source)?;
|
let doc = cobalt_model::files::read_file(&source)?;
|
||||||
let doc = cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::parse(&doc)?;
|
let doc = cobalt_model::Document::parse(&doc)?;
|
||||||
let (front, content) = doc.parts();
|
let (mut front, content) = doc.into_parts();
|
||||||
|
|
||||||
let pages = config.pages.clone().build()?;
|
let pages = config.pages.clone().build()?;
|
||||||
let posts = config.posts.clone().build()?;
|
let posts = config.posts.clone().build()?;
|
||||||
|
@ -375,8 +374,8 @@ pub fn rename_document(
|
||||||
.expect("file was found under the root");
|
.expect("file was found under the root");
|
||||||
front
|
front
|
||||||
.clone()
|
.clone()
|
||||||
.merge_path(rel_src)
|
.merge(&cobalt_config::Frontmatter::from_path(rel_src))
|
||||||
.merge(posts.default.clone())
|
.merge(&posts.default)
|
||||||
} else if pages.pages.includes_file(&target)
|
} else if pages.pages.includes_file(&target)
|
||||||
|| pages
|
|| pages
|
||||||
.drafts
|
.drafts
|
||||||
|
@ -389,19 +388,18 @@ pub fn rename_document(
|
||||||
.expect("file was found under the root");
|
.expect("file was found under the root");
|
||||||
front
|
front
|
||||||
.clone()
|
.clone()
|
||||||
.merge_path(rel_src)
|
.merge(&cobalt_config::Frontmatter::from_path(rel_src))
|
||||||
.merge(pages.default.clone())
|
.merge(&pages.default)
|
||||||
} else {
|
} else {
|
||||||
failure::bail!(
|
failure::bail!(
|
||||||
"Target file wouldn't be a member of any collection: {:?}",
|
"Target file wouldn't be a member of any collection: {:?}",
|
||||||
target
|
target
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
let full_front = full_front.build()?;
|
let full_front = cobalt_model::Frontmatter::from_config(full_front)?;
|
||||||
|
|
||||||
let new_front = front.set_title(Some(title.to_string()));
|
front.title = Some(title.to_string());
|
||||||
let doc =
|
let doc = cobalt_model::Document::new(front, content);
|
||||||
cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::new(new_front, content);
|
|
||||||
let doc = doc.to_string();
|
let doc = doc.to_string();
|
||||||
cobalt_model::files::write_document_file(doc, target)?;
|
cobalt_model::files::write_document_file(doc, target)?;
|
||||||
|
|
||||||
|
@ -420,8 +418,8 @@ fn prepend_date_to_filename(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// avoid prepend to existing date prefix
|
// avoid prepend to existing date prefix
|
||||||
|
|
||||||
let file_stem = cobalt_model::file_stem(file);
|
let file_stem = cobalt_config::path::file_stem(file);
|
||||||
let (_, file_stem) = cobalt_model::parse_file_stem(file_stem);
|
let (_, file_stem) = cobalt_config::path::parse_file_stem(file_stem);
|
||||||
let file_name = format!(
|
let file_name = format!(
|
||||||
"{}{}.{}",
|
"{}{}.{}",
|
||||||
(**date).format("%Y-%m-%d-"),
|
(**date).format("%Y-%m-%d-"),
|
||||||
|
@ -475,14 +473,14 @@ fn move_from_drafts_to_posts(
|
||||||
|
|
||||||
pub fn publish_document(config: &cobalt_model::Config, file: &path::Path) -> Result<()> {
|
pub fn publish_document(config: &cobalt_model::Config, file: &path::Path) -> Result<()> {
|
||||||
let doc = cobalt_model::files::read_file(file)?;
|
let doc = cobalt_model::files::read_file(file)?;
|
||||||
let doc = cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::parse(&doc)?;
|
let doc = cobalt_model::Document::parse(&doc)?;
|
||||||
let (front, content) = doc.parts();
|
let (mut front, content) = doc.into_parts();
|
||||||
|
|
||||||
let date = cobalt_model::DateTime::now();
|
let date = cobalt_model::DateTime::now();
|
||||||
let front = front.set_draft(false).set_published_date(date);
|
front.is_draft = Some(false);
|
||||||
|
front.published_date = Some(date);
|
||||||
|
|
||||||
let doc =
|
let doc = cobalt_model::Document::new(front, content);
|
||||||
cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::new(front, content);
|
|
||||||
let doc = doc.to_string();
|
let doc = doc.to_string();
|
||||||
cobalt_model::files::write_document_file(doc, file)?;
|
cobalt_model::files::write_document_file(doc, file)?;
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ pub fn serve_command(matches: &clap::ArgMatches) -> Result<()> {
|
||||||
let mut config = args::get_config(matches)?;
|
let mut config = args::get_config(matches)?;
|
||||||
debug!("Overriding config `site.base_url` with `{}`", ip);
|
debug!("Overriding config `site.base_url` with `{}`", ip);
|
||||||
config.site.base_url = Some(format!("http://{}", ip));
|
config.site.base_url = Some(format!("http://{}", ip));
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
let dest = path::Path::new(&config.destination).to_owned();
|
let dest = path::Path::new(&config.destination).to_owned();
|
||||||
|
|
||||||
build::build(config.clone())?;
|
build::build(config.clone())?;
|
||||||
|
|
|
@ -309,7 +309,11 @@ fn parse_drafts(
|
||||||
.expect("file was found under the root");
|
.expect("file was found under the root");
|
||||||
let new_path = rel_real.join(rel_src);
|
let new_path = rel_real.join(rel_src);
|
||||||
|
|
||||||
let default_front = collection.default.clone().set_draft(true);
|
let default_front = cobalt_config::Frontmatter {
|
||||||
|
is_draft: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.merge(&collection.default);
|
||||||
|
|
||||||
let doc = Document::parse(&file_path, &new_path, default_front)
|
let doc = Document::parse(&file_path, &new_path, default_front)
|
||||||
.with_context(|_| failure::format_err!("Failed to parse {}", rel_src.display()))?;
|
.with_context(|_| failure::format_err!("Failed to parse {}", rel_src.display()))?;
|
||||||
|
|
|
@ -15,6 +15,20 @@ pub struct AssetsBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssetsBuilder {
|
impl AssetsBuilder {
|
||||||
|
pub fn from_config(
|
||||||
|
config: cobalt_config::Assets,
|
||||||
|
source: &path::Path,
|
||||||
|
ignore: &[String],
|
||||||
|
template_extensions: &[String],
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
sass: sass::SassBuilder::from_config(config.sass, source),
|
||||||
|
source: Some(source.to_owned()),
|
||||||
|
ignore: ignore.to_vec(),
|
||||||
|
template_extensions: template_extensions.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Result<Assets> {
|
pub fn build(self) -> Result<Assets> {
|
||||||
let AssetsBuilder {
|
let AssetsBuilder {
|
||||||
sass,
|
sass,
|
||||||
|
|
|
@ -1,26 +1,13 @@
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
||||||
|
use cobalt_config::Frontmatter;
|
||||||
|
use cobalt_config::SortOrder;
|
||||||
use liquid;
|
use liquid;
|
||||||
|
|
||||||
use super::files;
|
use super::files;
|
||||||
use super::slug;
|
use super::slug;
|
||||||
use super::FrontmatterBuilder;
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub enum SortOrder {
|
|
||||||
None,
|
|
||||||
Asc,
|
|
||||||
Desc,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SortOrder {
|
|
||||||
fn default() -> SortOrder {
|
|
||||||
SortOrder::Desc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields, default)]
|
#[serde(deny_unknown_fields, default)]
|
||||||
pub struct CollectionBuilder {
|
pub struct CollectionBuilder {
|
||||||
|
@ -38,16 +25,112 @@ pub struct CollectionBuilder {
|
||||||
pub jsonfeed: Option<String>,
|
pub jsonfeed: Option<String>,
|
||||||
pub base_url: Option<String>,
|
pub base_url: Option<String>,
|
||||||
pub publish_date_in_filename: bool,
|
pub publish_date_in_filename: bool,
|
||||||
pub default: FrontmatterBuilder,
|
pub default: Frontmatter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CollectionBuilder {
|
impl CollectionBuilder {
|
||||||
pub fn new() -> Self {
|
pub fn from_page_config(
|
||||||
Self::default()
|
config: cobalt_config::PageCollection,
|
||||||
|
source: &path::Path,
|
||||||
|
site: &cobalt_config::Site,
|
||||||
|
posts: &cobalt_config::PostCollection,
|
||||||
|
common_default: &cobalt_config::Frontmatter,
|
||||||
|
ignore: &[String],
|
||||||
|
template_extensions: &[String],
|
||||||
|
) -> Self {
|
||||||
|
let mut ignore = ignore.to_vec();
|
||||||
|
ignore.push(format!("/{}", posts.dir));
|
||||||
|
if let Some(ref drafts_dir) = posts.drafts_dir {
|
||||||
|
ignore.push(format!("/{}", drafts_dir));
|
||||||
|
}
|
||||||
|
let mut config: cobalt_config::Collection = config.into();
|
||||||
|
// Use `site` because the pages are effectively the site
|
||||||
|
config.title = Some(site.title.clone().unwrap_or_else(|| "".to_owned()));
|
||||||
|
config.description = site.description.clone();
|
||||||
|
Self::from_config(
|
||||||
|
config,
|
||||||
|
"pages",
|
||||||
|
false,
|
||||||
|
source,
|
||||||
|
site,
|
||||||
|
common_default,
|
||||||
|
ignore,
|
||||||
|
template_extensions,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge_frontmatter(mut self, secondary: FrontmatterBuilder) -> Self {
|
pub fn from_post_config(
|
||||||
self.default = self.default.merge(secondary);
|
config: cobalt_config::PostCollection,
|
||||||
|
source: &path::Path,
|
||||||
|
site: &cobalt_config::Site,
|
||||||
|
include_drafts: bool,
|
||||||
|
common_default: &cobalt_config::Frontmatter,
|
||||||
|
ignore: &[String],
|
||||||
|
template_extensions: &[String],
|
||||||
|
) -> Self {
|
||||||
|
let mut config: cobalt_config::Collection = config.into();
|
||||||
|
// Default with `site` for people quickly bootstrapping a blog, the blog and site are
|
||||||
|
// effectively equivalent.
|
||||||
|
if config.title.is_none() {
|
||||||
|
config.title = Some(site.title.clone().unwrap_or_else(|| "".to_owned()));
|
||||||
|
}
|
||||||
|
if config.description.is_none() {
|
||||||
|
config.description = site.description.clone();
|
||||||
|
}
|
||||||
|
Self::from_config(
|
||||||
|
config,
|
||||||
|
"posts",
|
||||||
|
include_drafts,
|
||||||
|
source,
|
||||||
|
site,
|
||||||
|
common_default,
|
||||||
|
ignore.to_vec(),
|
||||||
|
template_extensions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_config(
|
||||||
|
config: cobalt_config::Collection,
|
||||||
|
slug: &str,
|
||||||
|
include_drafts: bool,
|
||||||
|
source: &path::Path,
|
||||||
|
site: &cobalt_config::Site,
|
||||||
|
common_default: &cobalt_config::Frontmatter,
|
||||||
|
ignore: Vec<String>,
|
||||||
|
template_extensions: &[String],
|
||||||
|
) -> Self {
|
||||||
|
let cobalt_config::Collection {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
dir,
|
||||||
|
drafts_dir,
|
||||||
|
order,
|
||||||
|
rss,
|
||||||
|
jsonfeed,
|
||||||
|
publish_date_in_filename,
|
||||||
|
default,
|
||||||
|
} = config;
|
||||||
|
Self {
|
||||||
|
title: title,
|
||||||
|
slug: Some(slug.to_owned()),
|
||||||
|
description: description,
|
||||||
|
source: Some(source.to_owned()),
|
||||||
|
dir: dir,
|
||||||
|
drafts_dir: drafts_dir,
|
||||||
|
include_drafts,
|
||||||
|
template_extensions: template_extensions.to_vec(),
|
||||||
|
ignore,
|
||||||
|
order,
|
||||||
|
rss,
|
||||||
|
jsonfeed,
|
||||||
|
base_url: site.base_url.clone(),
|
||||||
|
publish_date_in_filename,
|
||||||
|
default: default.merge(&common_default),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge_frontmatter(mut self, secondary: &Frontmatter) -> Self {
|
||||||
|
self.default = self.default.merge(&secondary);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +186,10 @@ impl CollectionBuilder {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let default = default.set_collection(slug.clone());
|
let default = default.merge(&Frontmatter {
|
||||||
|
collection: Some(slug.clone()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
let new = Collection {
|
let new = Collection {
|
||||||
title,
|
title,
|
||||||
|
@ -164,7 +250,7 @@ pub struct Collection {
|
||||||
pub rss: Option<String>,
|
pub rss: Option<String>,
|
||||||
pub jsonfeed: Option<String>,
|
pub jsonfeed: Option<String>,
|
||||||
pub base_url: Option<String>,
|
pub base_url: Option<String>,
|
||||||
pub default: FrontmatterBuilder,
|
pub default: Frontmatter,
|
||||||
pub attributes: liquid::value::Object,
|
pub attributes: liquid::value::Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
||||||
use failure::ResultExt;
|
|
||||||
use liquid;
|
|
||||||
use serde_yaml;
|
use serde_yaml;
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
|
@ -10,330 +8,28 @@ use crate::error::*;
|
||||||
use super::assets;
|
use super::assets;
|
||||||
use super::collection;
|
use super::collection;
|
||||||
use super::files;
|
use super::files;
|
||||||
use super::frontmatter;
|
|
||||||
use super::mark;
|
use super::mark;
|
||||||
use super::sass;
|
|
||||||
use super::site;
|
use super::site;
|
||||||
use super::template;
|
use super::template;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
#[serde(deny_unknown_fields, default)]
|
#[serde(deny_unknown_fields, default)]
|
||||||
pub struct SyntaxHighlight {
|
pub struct Config {
|
||||||
pub theme: String,
|
pub source: path::PathBuf,
|
||||||
pub enabled: bool,
|
pub destination: path::PathBuf,
|
||||||
}
|
pub pages: collection::CollectionBuilder,
|
||||||
|
pub posts: collection::CollectionBuilder,
|
||||||
impl Default for SyntaxHighlight {
|
pub site: site::SiteBuilder,
|
||||||
fn default() -> Self {
|
pub layouts_dir: path::PathBuf,
|
||||||
Self {
|
pub liquid: template::LiquidBuilder,
|
||||||
theme: "base16-ocean.dark".to_owned(),
|
pub markdown: mark::MarkdownBuilder,
|
||||||
enabled: true,
|
pub assets: assets::AssetsBuilder,
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
|
||||||
pub struct PageConfig {
|
|
||||||
pub default: frontmatter::FrontmatterBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PageConfig {
|
|
||||||
fn builder(
|
|
||||||
self,
|
|
||||||
source: &path::Path,
|
|
||||||
site: &SiteConfig,
|
|
||||||
posts: &PostConfig,
|
|
||||||
common_default: &frontmatter::FrontmatterBuilder,
|
|
||||||
ignore: &[String],
|
|
||||||
template_extensions: &[String],
|
|
||||||
) -> collection::CollectionBuilder {
|
|
||||||
let mut ignore = ignore.to_vec();
|
|
||||||
ignore.push(format!("/{}", posts.dir));
|
|
||||||
if let Some(ref drafts_dir) = posts.drafts_dir {
|
|
||||||
ignore.push(format!("/{}", drafts_dir));
|
|
||||||
}
|
|
||||||
// Use `site` because the pages are effectively the site
|
|
||||||
collection::CollectionBuilder {
|
|
||||||
title: Some(site.title.clone().unwrap_or_else(|| "".to_owned())),
|
|
||||||
slug: Some("pages".to_owned()),
|
|
||||||
description: site.description.clone(),
|
|
||||||
source: Some(source.to_owned()),
|
|
||||||
dir: Some(".".to_owned()),
|
|
||||||
drafts_dir: None,
|
|
||||||
include_drafts: false,
|
|
||||||
template_extensions: template_extensions.to_vec(),
|
|
||||||
ignore,
|
|
||||||
order: collection::SortOrder::None,
|
|
||||||
rss: None,
|
|
||||||
jsonfeed: None,
|
|
||||||
base_url: site.base_url.clone(),
|
|
||||||
publish_date_in_filename: false,
|
|
||||||
default: self
|
|
||||||
.default
|
|
||||||
.merge_excerpt_separator("".to_owned())
|
|
||||||
.merge(common_default.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
|
||||||
pub struct PostConfig {
|
|
||||||
pub title: Option<String>,
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub dir: String,
|
|
||||||
pub drafts_dir: Option<String>,
|
|
||||||
pub order: collection::SortOrder,
|
|
||||||
pub rss: Option<String>,
|
|
||||||
pub jsonfeed: Option<String>,
|
|
||||||
pub publish_date_in_filename: bool,
|
|
||||||
pub default: frontmatter::FrontmatterBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PostConfig {
|
|
||||||
fn builder(
|
|
||||||
self,
|
|
||||||
source: &path::Path,
|
|
||||||
site: &SiteConfig,
|
|
||||||
include_drafts: bool,
|
|
||||||
common_default: &frontmatter::FrontmatterBuilder,
|
|
||||||
ignore: &[String],
|
|
||||||
template_extensions: &[String],
|
|
||||||
) -> collection::CollectionBuilder {
|
|
||||||
let PostConfig {
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
dir,
|
|
||||||
drafts_dir,
|
|
||||||
order,
|
|
||||||
rss,
|
|
||||||
jsonfeed,
|
|
||||||
publish_date_in_filename,
|
|
||||||
default,
|
|
||||||
} = self;
|
|
||||||
// Default with `site` for people quickly bootstrapping a blog, the blog and site are
|
|
||||||
// effectively equivalent.
|
|
||||||
collection::CollectionBuilder {
|
|
||||||
title: Some(
|
|
||||||
title
|
|
||||||
.or_else(|| site.title.clone())
|
|
||||||
.unwrap_or_else(|| "".to_owned()),
|
|
||||||
),
|
|
||||||
slug: Some("posts".to_owned()),
|
|
||||||
description: description.or_else(|| site.description.clone()),
|
|
||||||
source: Some(source.to_owned()),
|
|
||||||
dir: Some(dir),
|
|
||||||
drafts_dir,
|
|
||||||
include_drafts,
|
|
||||||
template_extensions: template_extensions.to_vec(),
|
|
||||||
ignore: ignore.to_vec(),
|
|
||||||
order,
|
|
||||||
rss,
|
|
||||||
jsonfeed,
|
|
||||||
base_url: site.base_url.clone(),
|
|
||||||
publish_date_in_filename,
|
|
||||||
default: default.merge(common_default.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PostConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
title: Default::default(),
|
|
||||||
description: Default::default(),
|
|
||||||
dir: "posts".to_owned(),
|
|
||||||
drafts_dir: Default::default(),
|
|
||||||
order: Default::default(),
|
|
||||||
rss: Default::default(),
|
|
||||||
jsonfeed: Default::default(),
|
|
||||||
publish_date_in_filename: true,
|
|
||||||
default: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
|
||||||
pub struct SiteConfig {
|
|
||||||
pub title: Option<String>,
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub base_url: Option<String>,
|
|
||||||
pub sitemap: Option<String>,
|
pub sitemap: Option<String>,
|
||||||
pub data: Option<liquid::value::Object>,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub data_dir: &'static str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SiteConfig {
|
impl Config {
|
||||||
fn builder(self, source: &path::Path) -> site::SiteBuilder {
|
pub fn from_config(source: cobalt_config::Config) -> Result<Self> {
|
||||||
site::SiteBuilder {
|
let cobalt_config::Config {
|
||||||
title: self.title,
|
|
||||||
description: self.description,
|
|
||||||
base_url: self.base_url,
|
|
||||||
data: self.data,
|
|
||||||
data_dir: Some(source.join(self.data_dir)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SiteConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
title: Default::default(),
|
|
||||||
description: Default::default(),
|
|
||||||
base_url: Default::default(),
|
|
||||||
sitemap: Default::default(),
|
|
||||||
data: Default::default(),
|
|
||||||
data_dir: "_data",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
|
||||||
pub struct SassConfig {
|
|
||||||
#[serde(skip)]
|
|
||||||
pub import_dir: &'static str,
|
|
||||||
pub style: sass::SassOutputStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SassConfig {
|
|
||||||
fn builder(self, source: &path::Path) -> sass::SassBuilder {
|
|
||||||
let mut sass = sass::SassBuilder::new();
|
|
||||||
sass.style = self.style;
|
|
||||||
sass.import_dir = source
|
|
||||||
.join(self.import_dir)
|
|
||||||
.into_os_string()
|
|
||||||
.into_string()
|
|
||||||
.ok();
|
|
||||||
sass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SassConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
import_dir: "_sass",
|
|
||||||
style: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
|
||||||
pub struct AssetsConfig {
|
|
||||||
pub sass: SassConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AssetsConfig {
|
|
||||||
fn builder(
|
|
||||||
self,
|
|
||||||
source: &path::Path,
|
|
||||||
ignore: &[String],
|
|
||||||
template_extensions: &[String],
|
|
||||||
) -> assets::AssetsBuilder {
|
|
||||||
assets::AssetsBuilder {
|
|
||||||
sass: self.sass.builder(source),
|
|
||||||
source: Some(source.to_owned()),
|
|
||||||
ignore: ignore.to_vec(),
|
|
||||||
template_extensions: template_extensions.to_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
|
||||||
pub struct ConfigBuilder {
|
|
||||||
#[serde(skip)]
|
|
||||||
pub root: path::PathBuf,
|
|
||||||
pub source: String,
|
|
||||||
pub destination: String,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub abs_dest: Option<path::PathBuf>,
|
|
||||||
pub include_drafts: bool,
|
|
||||||
pub default: frontmatter::FrontmatterBuilder,
|
|
||||||
pub pages: PageConfig,
|
|
||||||
pub posts: PostConfig,
|
|
||||||
pub site: SiteConfig,
|
|
||||||
pub template_extensions: Vec<String>,
|
|
||||||
pub ignore: Vec<String>,
|
|
||||||
pub syntax_highlight: SyntaxHighlight,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub layouts_dir: &'static str,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub includes_dir: &'static str,
|
|
||||||
pub assets: AssetsConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ConfigBuilder {
|
|
||||||
fn default() -> ConfigBuilder {
|
|
||||||
ConfigBuilder {
|
|
||||||
root: Default::default(),
|
|
||||||
source: "./".to_owned(),
|
|
||||||
destination: "./_site".to_owned(),
|
|
||||||
abs_dest: Default::default(),
|
|
||||||
include_drafts: false,
|
|
||||||
default: Default::default(),
|
|
||||||
pages: Default::default(),
|
|
||||||
posts: Default::default(),
|
|
||||||
site: Default::default(),
|
|
||||||
template_extensions: vec!["md".to_owned(), "liquid".to_owned()],
|
|
||||||
ignore: Default::default(),
|
|
||||||
syntax_highlight: SyntaxHighlight::default(),
|
|
||||||
layouts_dir: "_layouts",
|
|
||||||
includes_dir: "_includes",
|
|
||||||
assets: AssetsConfig::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigBuilder {
|
|
||||||
pub fn from_file<P: Into<path::PathBuf>>(path: P) -> Result<ConfigBuilder> {
|
|
||||||
Self::from_file_internal(path.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_file_internal(path: path::PathBuf) -> Result<ConfigBuilder> {
|
|
||||||
let content = files::read_file(&path)?;
|
|
||||||
|
|
||||||
let mut config = if content.trim().is_empty() {
|
|
||||||
ConfigBuilder::default()
|
|
||||||
} else {
|
|
||||||
serde_yaml::from_str(&content)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut root = path;
|
|
||||||
root.pop(); // Remove filename
|
|
||||||
config.root = root;
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_cwd<P: Into<path::PathBuf>>(cwd: P) -> Result<ConfigBuilder> {
|
|
||||||
Self::from_cwd_internal(cwd.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_cwd_internal(cwd: path::PathBuf) -> Result<ConfigBuilder> {
|
|
||||||
let file_path = files::find_project_file(&cwd, "_cobalt.yml");
|
|
||||||
let config = file_path
|
|
||||||
.map(|p| {
|
|
||||||
debug!("Using config file {:?}", &p);
|
|
||||||
Self::from_file(&p).with_context(|_| format!("Error reading config file {:?}", p))
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
warn!("No _cobalt.yml file found in current directory, using default config.");
|
|
||||||
let config = ConfigBuilder {
|
|
||||||
root: cwd,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
Ok(config)
|
|
||||||
})?;
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> Result<Config> {
|
|
||||||
let ConfigBuilder {
|
|
||||||
root,
|
root,
|
||||||
source,
|
source,
|
||||||
destination,
|
destination,
|
||||||
|
@ -349,7 +45,7 @@ impl ConfigBuilder {
|
||||||
layouts_dir,
|
layouts_dir,
|
||||||
includes_dir,
|
includes_dir,
|
||||||
assets,
|
assets,
|
||||||
} = self;
|
} = source;
|
||||||
|
|
||||||
if include_drafts {
|
if include_drafts {
|
||||||
debug!("Draft mode enabled");
|
debug!("Draft mode enabled");
|
||||||
|
@ -373,7 +69,8 @@ impl ConfigBuilder {
|
||||||
let source = root.join(source);
|
let source = root.join(source);
|
||||||
let destination = abs_dest.unwrap_or_else(|| root.join(destination));
|
let destination = abs_dest.unwrap_or_else(|| root.join(destination));
|
||||||
|
|
||||||
let pages = pages.builder(
|
let pages = collection::CollectionBuilder::from_page_config(
|
||||||
|
pages,
|
||||||
&source,
|
&source,
|
||||||
&site,
|
&site,
|
||||||
&posts,
|
&posts,
|
||||||
|
@ -382,7 +79,8 @@ impl ConfigBuilder {
|
||||||
&template_extensions,
|
&template_extensions,
|
||||||
);
|
);
|
||||||
|
|
||||||
let posts = posts.builder(
|
let posts = collection::CollectionBuilder::from_post_config(
|
||||||
|
posts,
|
||||||
&source,
|
&source,
|
||||||
&site,
|
&site,
|
||||||
include_drafts,
|
include_drafts,
|
||||||
|
@ -392,9 +90,10 @@ impl ConfigBuilder {
|
||||||
);
|
);
|
||||||
|
|
||||||
let sitemap = site.sitemap.clone();
|
let sitemap = site.sitemap.clone();
|
||||||
let site = site.builder(&source);
|
let site = site::SiteBuilder::from_config(site, &source);
|
||||||
|
|
||||||
let assets = assets.builder(&source, &ignore, &template_extensions);
|
let assets =
|
||||||
|
assets::AssetsBuilder::from_config(assets, &source, &ignore, &template_extensions);
|
||||||
|
|
||||||
let includes_dir = source.join(includes_dir);
|
let includes_dir = source.join(includes_dir);
|
||||||
let layouts_dir = source.join(layouts_dir);
|
let layouts_dir = source.join(layouts_dir);
|
||||||
|
@ -425,33 +124,9 @@ impl ConfigBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ConfigBuilder {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let mut converted = serde_yaml::to_string(self).map_err(|_| fmt::Error)?;
|
|
||||||
converted.drain(..4);
|
|
||||||
write!(f, "{}", converted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
|
||||||
pub struct Config {
|
|
||||||
pub source: path::PathBuf,
|
|
||||||
pub destination: path::PathBuf,
|
|
||||||
pub pages: collection::CollectionBuilder,
|
|
||||||
pub posts: collection::CollectionBuilder,
|
|
||||||
pub site: site::SiteBuilder,
|
|
||||||
pub layouts_dir: path::PathBuf,
|
|
||||||
pub liquid: template::LiquidBuilder,
|
|
||||||
pub markdown: mark::MarkdownBuilder,
|
|
||||||
pub assets: assets::AssetsBuilder,
|
|
||||||
pub sitemap: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Config {
|
fn default() -> Config {
|
||||||
ConfigBuilder::default()
|
Config::from_config(cobalt_config::Config::default())
|
||||||
.build()
|
|
||||||
.expect("default config should not fail")
|
.expect("default config should not fail")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -464,70 +139,16 @@ impl fmt::Display for Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_file_ok() {
|
|
||||||
let result = ConfigBuilder::from_file("tests/fixtures/config/_cobalt.yml").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
result.root,
|
|
||||||
path::Path::new("tests/fixtures/config").to_path_buf()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_file_alternate_name() {
|
|
||||||
let result = ConfigBuilder::from_file("tests/fixtures/config/rss.yml").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
result.root,
|
|
||||||
path::Path::new("tests/fixtures/config").to_path_buf()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_file_empty() {
|
|
||||||
let result = ConfigBuilder::from_file("tests/fixtures/config/empty.yml").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
result.root,
|
|
||||||
path::Path::new("tests/fixtures/config").to_path_buf()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_file_invalid_syntax() {
|
|
||||||
let result = ConfigBuilder::from_file("tests/fixtures/config/invalid_syntax.yml");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_file_not_found() {
|
|
||||||
let result = ConfigBuilder::from_file("tests/fixtures/config/config_does_not_exist.yml");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_cwd_ok() {
|
|
||||||
let result = ConfigBuilder::from_cwd("tests/fixtures/config/child").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
result.root,
|
|
||||||
path::Path::new("tests/fixtures/config").to_path_buf()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_cwd_not_found() {
|
|
||||||
let result = ConfigBuilder::from_cwd("tests/fixtures").unwrap();
|
|
||||||
assert_eq!(result.root, path::Path::new("tests/fixtures").to_path_buf());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_build_default() {
|
fn test_build_default() {
|
||||||
let config = ConfigBuilder::default();
|
let config = cobalt_config::Config::default();
|
||||||
config.build().unwrap();
|
Config::from_config(config).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_build_dest() {
|
fn test_build_dest() {
|
||||||
let result = ConfigBuilder::from_file("tests/fixtures/config/_cobalt.yml").unwrap();
|
let config = cobalt_config::Config::from_file("tests/fixtures/config/_cobalt.yml").unwrap();
|
||||||
let result = result.build().unwrap();
|
let result = Config::from_config(config).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.source,
|
result.source,
|
||||||
path::Path::new("tests/fixtures/config").to_path_buf()
|
path::Path::new("tests/fixtures/config").to_path_buf()
|
||||||
|
@ -540,9 +161,9 @@ fn test_build_dest() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_build_abs_dest() {
|
fn test_build_abs_dest() {
|
||||||
let mut result = ConfigBuilder::from_file("tests/fixtures/config/_cobalt.yml").unwrap();
|
let mut config = cobalt_config::Config::from_file("tests/fixtures/config/_cobalt.yml").unwrap();
|
||||||
result.abs_dest = Some(path::PathBuf::from("hello/world"));
|
config.abs_dest = Some(path::PathBuf::from("hello/world"));
|
||||||
let result = result.build().unwrap();
|
let result = Config::from_config(config).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.source,
|
result.source,
|
||||||
path::Path::new("tests/fixtures/config").to_path_buf()
|
path::Path::new("tests/fixtures/config").to_path_buf()
|
||||||
|
|
|
@ -1,268 +0,0 @@
|
||||||
use std::convert;
|
|
||||||
use std::fmt;
|
|
||||||
use std::ops;
|
|
||||||
|
|
||||||
use chrono;
|
|
||||||
use chrono::TimeZone;
|
|
||||||
use serde;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)]
|
|
||||||
pub struct DateTime(chrono::DateTime<chrono::FixedOffset>);
|
|
||||||
|
|
||||||
impl DateTime {
|
|
||||||
pub fn now() -> Self {
|
|
||||||
let d = chrono::Utc::now().with_timezone(&chrono::FixedOffset::east(0));
|
|
||||||
DateTime(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse<S: AsRef<str>>(d: S) -> Option<Self> {
|
|
||||||
Self::parse_str(d.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_str(d: &str) -> Option<DateTime> {
|
|
||||||
chrono::DateTime::parse_from_str(d, "%Y-%m-%d %H:%M:%S %z")
|
|
||||||
.ok()
|
|
||||||
.map(DateTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format(&self) -> String {
|
|
||||||
self.0.format("%Y-%m-%d %H:%M:%S %z").to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_offset(&self, secs: i32) -> Option<Self> {
|
|
||||||
let timezone = chrono::FixedOffset::east_opt(secs);
|
|
||||||
timezone.map(|tz| self.0.with_timezone(&tz).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DateTime {
|
|
||||||
fn default() -> Self {
|
|
||||||
let d = chrono::Utc
|
|
||||||
.timestamp(0, 0)
|
|
||||||
.with_timezone(&chrono::FixedOffset::east(0));
|
|
||||||
DateTime(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ops::Deref for DateTime {
|
|
||||||
type Target = chrono::DateTime<chrono::FixedOffset>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ops::DerefMut for DateTime {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl convert::From<chrono::DateTime<chrono::FixedOffset>> for DateTime {
|
|
||||||
fn from(v: chrono::DateTime<chrono::FixedOffset>) -> Self {
|
|
||||||
DateTime(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl convert::From<DateTime> for chrono::DateTime<chrono::FixedOffset> {
|
|
||||||
fn from(v: DateTime) -> Self {
|
|
||||||
v.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl chrono::Datelike for DateTime {
|
|
||||||
#[inline]
|
|
||||||
fn year(&self) -> i32 {
|
|
||||||
self.0.year()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn month(&self) -> u32 {
|
|
||||||
self.0.month()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn month0(&self) -> u32 {
|
|
||||||
self.0.month0()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn day(&self) -> u32 {
|
|
||||||
self.0.day()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn day0(&self) -> u32 {
|
|
||||||
self.0.day0()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn ordinal(&self) -> u32 {
|
|
||||||
self.0.ordinal()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn ordinal0(&self) -> u32 {
|
|
||||||
self.0.ordinal0()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn weekday(&self) -> chrono::Weekday {
|
|
||||||
self.0.weekday()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn iso_week(&self) -> chrono::IsoWeek {
|
|
||||||
self.0.iso_week()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_year(&self, year: i32) -> Option<DateTime> {
|
|
||||||
self.0.with_year(year).map(|d| d.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_month(&self, month: u32) -> Option<DateTime> {
|
|
||||||
self.0.with_month(month).map(|d| d.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_month0(&self, month0: u32) -> Option<DateTime> {
|
|
||||||
self.0.with_month0(month0).map(|d| d.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_day(&self, day: u32) -> Option<DateTime> {
|
|
||||||
self.0.with_day(day).map(|d| d.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_day0(&self, day0: u32) -> Option<DateTime> {
|
|
||||||
self.0.with_day(day0).map(|d| d.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_ordinal(&self, ordinal: u32) -> Option<DateTime> {
|
|
||||||
self.0.with_ordinal(ordinal).map(|d| d.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_ordinal0(&self, ordinal0: u32) -> Option<DateTime> {
|
|
||||||
self.0.with_ordinal0(ordinal0).map(|d| d.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl chrono::Timelike for DateTime {
|
|
||||||
#[inline]
|
|
||||||
fn hour(&self) -> u32 {
|
|
||||||
self.0.hour()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn minute(&self) -> u32 {
|
|
||||||
self.0.minute()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn second(&self) -> u32 {
|
|
||||||
self.0.second()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn nanosecond(&self) -> u32 {
|
|
||||||
self.0.nanosecond()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_hour(&self, hour: u32) -> Option<DateTime> {
|
|
||||||
self.0.with_hour(hour).map(|d| d.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_minute(&self, min: u32) -> Option<DateTime> {
|
|
||||||
self.0.with_minute(min).map(|d| d.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_second(&self, sec: u32) -> Option<DateTime> {
|
|
||||||
self.0.with_second(sec).map(|d| d.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_nanosecond(&self, nano: u32) -> Option<DateTime> {
|
|
||||||
self.0.with_nanosecond(nano).map(|d| d.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl serde::Serialize for DateTime {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
serializer.collect_str(&self.format())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DateTimeVisitor;
|
|
||||||
|
|
||||||
impl<'de> serde::de::Visitor<'de> for DateTimeVisitor {
|
|
||||||
type Value = DateTime;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(formatter, "a formatted date and time string")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<DateTime, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
DateTime::parse(value).ok_or_else(|| {
|
|
||||||
E::custom(format!(
|
|
||||||
"Invalid datetime '{}', must be `YYYY-MM-DD HH:MM:SS +/-TTTT",
|
|
||||||
value
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> serde::de::Deserialize<'de> for DateTime {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::de::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserializer.deserialize_str(DateTimeVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use chrono::{Datelike, Timelike};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn format() {
|
|
||||||
let d = DateTime::default()
|
|
||||||
.with_year(2016)
|
|
||||||
.and_then(|d| d.with_month(1))
|
|
||||||
.and_then(|d| d.with_day(1))
|
|
||||||
.and_then(|d| d.with_hour(20))
|
|
||||||
.and_then(|d| d.with_offset(1 * 60 * 60))
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(d.format(), "2016-01-01 21:00:00 +0100");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
let expected = DateTime::default()
|
|
||||||
.with_year(2016)
|
|
||||||
.and_then(|d| d.with_month(1))
|
|
||||||
.and_then(|d| d.with_day(1))
|
|
||||||
.and_then(|d| d.with_hour(3))
|
|
||||||
.and_then(|d| d.with_offset(1 * 60 * 60))
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(DateTime::parse("2016-1-1 4:00:00 +0100").unwrap(), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_leading_zero() {
|
|
||||||
let expected = DateTime::default()
|
|
||||||
.with_year(2016)
|
|
||||||
.and_then(|d| d.with_month(1))
|
|
||||||
.and_then(|d| d.with_day(1))
|
|
||||||
.and_then(|d| d.with_hour(3))
|
|
||||||
.and_then(|d| d.with_offset(1 * 60 * 60))
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
DateTime::parse("2016-01-01 04:00:00 +0100").unwrap(),
|
|
||||||
expected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,173 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use regex;
|
|
||||||
|
|
||||||
use super::frontmatter;
|
|
||||||
use crate::error::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Default, Clone)]
|
|
||||||
pub struct DocumentBuilder<T: frontmatter::Front> {
|
|
||||||
front: T,
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: frontmatter::Front> DocumentBuilder<T> {
|
|
||||||
pub fn new(front: T, content: String) -> Self {
|
|
||||||
Self { front, content }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parts(self) -> (T, String) {
|
|
||||||
let Self { front, content } = self;
|
|
||||||
(front, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(content: &str) -> Result<Self> {
|
|
||||||
let (front, content) = split_document(content)?;
|
|
||||||
let front = front
|
|
||||||
.map(|s| T::parse(s))
|
|
||||||
.map_or(Ok(None), |r| r.map(Some))?
|
|
||||||
.unwrap_or_else(T::default);
|
|
||||||
let content = content.to_owned();
|
|
||||||
Ok(Self { front, content })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: frontmatter::Front> fmt::Display for DocumentBuilder<T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let front = self.front.to_string().map_err(|_| fmt::Error)?;
|
|
||||||
if front.trim().is_empty() {
|
|
||||||
write!(f, "{}", self.content)
|
|
||||||
} else {
|
|
||||||
write!(f, "---\n{}\n---\n{}", front, self.content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split_document(content: &str) -> Result<(Option<&str>, &str)> {
|
|
||||||
lazy_static! {
|
|
||||||
static ref FRONT_MATTER_DIVIDE: regex::Regex = regex::Regex::new(r"---\s*\r?\n").unwrap();
|
|
||||||
static ref FRONT_MATTER: regex::Regex =
|
|
||||||
regex::Regex::new(r"\A---\s*\r?\n([\s\S]*\n)?---\s*\r?\n").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if FRONT_MATTER.is_match(content) {
|
|
||||||
// skip first empty string
|
|
||||||
let mut splits = FRONT_MATTER_DIVIDE.splitn(content, 3).skip(1);
|
|
||||||
|
|
||||||
// split between dividers
|
|
||||||
let front_split = splits.next().unwrap_or("");
|
|
||||||
|
|
||||||
// split after second divider
|
|
||||||
let content_split = splits.next().unwrap_or("");
|
|
||||||
|
|
||||||
if front_split.is_empty() {
|
|
||||||
Ok((None, content_split))
|
|
||||||
} else {
|
|
||||||
Ok((Some(front_split), content_split))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
deprecated_split_front_matter(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deprecated_split_front_matter(content: &str) -> Result<(Option<&str>, &str)> {
|
|
||||||
lazy_static! {
|
|
||||||
static ref FRONT_MATTER_DIVIDE: regex::Regex =
|
|
||||||
regex::Regex::new(r"(\A|\n)---\s*\r?\n").unwrap();
|
|
||||||
}
|
|
||||||
if FRONT_MATTER_DIVIDE.is_match(content) {
|
|
||||||
warn!("Trailing separators are deprecated. We recommend frontmatters be surrounded, above and below, with ---");
|
|
||||||
|
|
||||||
let mut splits = FRONT_MATTER_DIVIDE.splitn(content, 2);
|
|
||||||
|
|
||||||
// above the split are the attributes
|
|
||||||
let front_split = splits.next().unwrap_or("");
|
|
||||||
|
|
||||||
// everything below the split becomes the new content
|
|
||||||
let content_split = splits.next().unwrap_or("");
|
|
||||||
|
|
||||||
if front_split.is_empty() {
|
|
||||||
Ok((None, content_split))
|
|
||||||
} else {
|
|
||||||
Ok((Some(front_split), content_split))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok((None, content))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn split_document_empty() {
|
|
||||||
let input = "";
|
|
||||||
let (cobalt_model, content) = split_document(input).unwrap();
|
|
||||||
assert!(cobalt_model.is_none());
|
|
||||||
assert_eq!(content, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn split_document_no_front_matter() {
|
|
||||||
let input = "Body";
|
|
||||||
let (cobalt_model, content) = split_document(input).unwrap();
|
|
||||||
assert!(cobalt_model.is_none());
|
|
||||||
assert_eq!(content, "Body");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn split_document_deprecated_empty_front_matter() {
|
|
||||||
let input = "---\nBody";
|
|
||||||
let (cobalt_model, content) = split_document(input).unwrap();
|
|
||||||
assert!(cobalt_model.is_none());
|
|
||||||
assert_eq!(content, "Body");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn split_document_empty_front_matter() {
|
|
||||||
let input = "---\n---\nBody";
|
|
||||||
let (cobalt_model, content) = split_document(input).unwrap();
|
|
||||||
assert!(cobalt_model.is_none());
|
|
||||||
assert_eq!(content, "Body");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn split_document_deprecated_empty_body() {
|
|
||||||
let input = "cobalt_model\n---\n";
|
|
||||||
let (cobalt_model, content) = split_document(input).unwrap();
|
|
||||||
assert_eq!(cobalt_model.unwrap(), "cobalt_model");
|
|
||||||
assert_eq!(content, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn split_document_empty_body() {
|
|
||||||
let input = "---\ncobalt_model\n---\n";
|
|
||||||
let (cobalt_model, content) = split_document(input).unwrap();
|
|
||||||
assert_eq!(cobalt_model.unwrap(), "cobalt_model\n");
|
|
||||||
assert_eq!(content, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn split_document_front_matter_and_body() {
|
|
||||||
let input = "---\ncobalt_model\n---\nbody";
|
|
||||||
let (cobalt_model, content) = split_document(input).unwrap();
|
|
||||||
assert_eq!(cobalt_model.unwrap(), "cobalt_model\n");
|
|
||||||
assert_eq!(content, "body");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn split_document_no_new_line_after_front_matter() {
|
|
||||||
let input = "invalid_front_matter---\nbody";
|
|
||||||
let (cobalt_model, content) = split_document(input).unwrap();
|
|
||||||
assert!(cobalt_model.is_none());
|
|
||||||
assert_eq!(content, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn document_format_empty_has_no_front() {
|
|
||||||
let doc = DocumentBuilder::<frontmatter::FrontmatterBuilder>::default();
|
|
||||||
let doc = doc.to_string();
|
|
||||||
assert_eq!(doc, "");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,286 +1,36 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path;
|
|
||||||
|
|
||||||
use chrono::Datelike;
|
use cobalt_config::DateTime;
|
||||||
|
use cobalt_config::SourceFormat;
|
||||||
use liquid;
|
use liquid;
|
||||||
use regex;
|
|
||||||
use serde;
|
|
||||||
use serde_yaml;
|
|
||||||
|
|
||||||
|
use super::pagination;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
|
||||||
use super::datetime;
|
#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize)]
|
||||||
use super::pagination_config;
|
|
||||||
use super::slug;
|
|
||||||
|
|
||||||
const PATH_ALIAS: &str = "/{{parent}}/{{name}}{{ext}}";
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref PERMALINK_ALIASES: HashMap<&'static str, &'static str> = [("path", PATH_ALIAS),]
|
|
||||||
.iter()
|
|
||||||
.map(|&(k, v)| (k, v))
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub enum SourceFormat {
|
|
||||||
Raw,
|
|
||||||
Markdown,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SourceFormat {
|
|
||||||
fn default() -> SourceFormat {
|
|
||||||
SourceFormat::Raw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(epage): Remove the serde traits and instead provide an impl based on if serde traits exist
|
|
||||||
pub trait Front:
|
|
||||||
Default + fmt::Display + for<'de> serde::Deserialize<'de> + serde::Serialize
|
|
||||||
{
|
|
||||||
fn parse(content: &str) -> Result<Self> {
|
|
||||||
let front: Self = serde_yaml::from_str(content)?;
|
|
||||||
Ok(front)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_string(&self) -> Result<String> {
|
|
||||||
let mut converted = serde_yaml::to_string(self)?;
|
|
||||||
converted.drain(..4);
|
|
||||||
if converted == "{}" {
|
|
||||||
converted.clear();
|
|
||||||
}
|
|
||||||
Ok(converted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
#[serde(deny_unknown_fields, default)]
|
||||||
pub struct FrontmatterBuilder {
|
pub struct Frontmatter {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub permalink: cobalt_config::Permalink,
|
||||||
pub permalink: Option<String>,
|
pub slug: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub title: String,
|
||||||
pub slug: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub title: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub excerpt: Option<String>,
|
pub excerpt: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub categories: Vec<String>,
|
||||||
pub categories: Option<Vec<String>>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub tags: Option<Vec<String>>,
|
pub tags: Option<Vec<String>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub excerpt_separator: String,
|
||||||
pub excerpt_separator: Option<String>,
|
pub published_date: Option<DateTime>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub format: SourceFormat,
|
||||||
pub published_date: Option<datetime::DateTime>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub format: Option<SourceFormat>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub layout: Option<String>,
|
pub layout: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub is_draft: bool,
|
||||||
pub is_draft: Option<bool>,
|
pub weight: i32,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub collection: String,
|
||||||
pub weight: Option<i32>,
|
|
||||||
#[serde(skip_serializing_if = "liquid::value::Object::is_empty")]
|
|
||||||
pub data: liquid::value::Object,
|
pub data: liquid::value::Object,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub pagination: Option<pagination::PaginationConfig>,
|
||||||
pub pagination: Option<pagination_config::PaginationConfigBuilder>,
|
|
||||||
// Controlled by where the file is found. We might allow control over the type at a later
|
|
||||||
// point but we need to first define those semantics.
|
|
||||||
#[serde(skip)]
|
|
||||||
pub collection: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrontmatterBuilder {
|
impl Frontmatter {
|
||||||
pub fn new() -> Self {
|
pub fn from_config(config: cobalt_config::Frontmatter) -> Result<Frontmatter> {
|
||||||
Self::default()
|
let cobalt_config::Frontmatter {
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_permalink<S: Into<Option<String>>>(self, permalink: S) -> Self {
|
|
||||||
Self {
|
|
||||||
permalink: permalink.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_slug<S: Into<Option<String>>>(self, slug: S) -> Self {
|
|
||||||
Self {
|
|
||||||
slug: slug.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_title<S: Into<Option<String>>>(self, title: S) -> Self {
|
|
||||||
Self {
|
|
||||||
title: title.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_description<S: Into<Option<String>>>(self, description: S) -> Self {
|
|
||||||
Self {
|
|
||||||
description: description.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_excerpt<S: Into<Option<String>>>(self, excerpt: S) -> Self {
|
|
||||||
Self {
|
|
||||||
excerpt: excerpt.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_categories<S: Into<Option<Vec<String>>>>(self, categories: S) -> Self {
|
|
||||||
Self {
|
|
||||||
categories: categories.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_tags<S: Into<Option<Vec<String>>>>(self, tags: S) -> Self {
|
|
||||||
Self {
|
|
||||||
tags: tags.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_pagination<S: Into<Option<pagination_config::PaginationConfigBuilder>>>(
|
|
||||||
self,
|
|
||||||
pagination: S,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
pagination: pagination.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_excerpt_separator<S: Into<Option<String>>>(self, excerpt_separator: S) -> Self {
|
|
||||||
Self {
|
|
||||||
excerpt_separator: excerpt_separator.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_published_date<D: Into<Option<datetime::DateTime>>>(
|
|
||||||
self,
|
|
||||||
published_date: D,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
published_date: published_date.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn set_format<S: Into<Option<SourceFormat>>>(self, format: S) -> Self {
|
|
||||||
Self {
|
|
||||||
format: format.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_layout<S: Into<Option<String>>>(self, layout: S) -> Self {
|
|
||||||
Self {
|
|
||||||
layout: layout.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_draft<B: Into<Option<bool>>>(self, is_draft: B) -> Self {
|
|
||||||
Self {
|
|
||||||
is_draft: is_draft.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_weight<I: Into<Option<i32>>>(self, weight: I) -> Self {
|
|
||||||
Self {
|
|
||||||
weight: weight.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_collection<S: Into<Option<String>>>(self, collection: S) -> Self {
|
|
||||||
Self {
|
|
||||||
collection: collection.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_permalink<S: Into<Option<String>>>(self, permalink: S) -> Self {
|
|
||||||
self.merge(Self::new().set_permalink(permalink.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_slug<S: Into<Option<String>>>(self, slug: S) -> Self {
|
|
||||||
self.merge(Self::new().set_slug(slug.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_title<S: Into<Option<String>>>(self, title: S) -> Self {
|
|
||||||
self.merge(Self::new().set_title(title.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_description<S: Into<Option<String>>>(self, description: S) -> Self {
|
|
||||||
self.merge(Self::new().set_description(description.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_excerpt<S: Into<Option<String>>>(self, excerpt: S) -> Self {
|
|
||||||
self.merge(Self::new().set_excerpt(excerpt.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_categories<S: Into<Option<Vec<String>>>>(self, categories: S) -> Self {
|
|
||||||
self.merge(Self::new().set_categories(categories.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_tags<S: Into<Option<Vec<String>>>>(self, tags: S) -> Self {
|
|
||||||
self.merge(Self::new().set_tags(tags.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_excerpt_separator<S: Into<Option<String>>>(self, excerpt_separator: S) -> Self {
|
|
||||||
self.merge(Self::new().set_excerpt_separator(excerpt_separator.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_published_date<D: Into<Option<datetime::DateTime>>>(
|
|
||||||
self,
|
|
||||||
published_date: D,
|
|
||||||
) -> Self {
|
|
||||||
self.merge(Self::new().set_published_date(published_date.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_pagination<S: Into<Option<pagination_config::PaginationConfigBuilder>>>(
|
|
||||||
self,
|
|
||||||
secondary: S,
|
|
||||||
) -> Self {
|
|
||||||
self.merge(Self::new().set_pagination(secondary.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn merge_format<S: Into<Option<SourceFormat>>>(self, format: S) -> Self {
|
|
||||||
self.merge(Self::new().set_format(format.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_layout<S: Into<Option<String>>>(self, layout: S) -> Self {
|
|
||||||
self.merge(Self::new().set_layout(layout.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_draft<B: Into<Option<bool>>>(self, draft: B) -> Self {
|
|
||||||
self.merge(Self::new().set_draft(draft.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_weight<I: Into<Option<i32>>>(self, weight: I) -> Self {
|
|
||||||
self.merge(Self::new().set_weight(weight.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn merge_collection<S: Into<Option<String>>>(self, collection: S) -> Self {
|
|
||||||
self.merge(Self::new().set_collection(collection.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_data(self, other_data: liquid::value::Object) -> Self {
|
|
||||||
let Self {
|
|
||||||
permalink,
|
permalink,
|
||||||
slug,
|
slug,
|
||||||
title,
|
title,
|
||||||
|
@ -297,154 +47,17 @@ impl FrontmatterBuilder {
|
||||||
collection,
|
collection,
|
||||||
data,
|
data,
|
||||||
pagination,
|
pagination,
|
||||||
} = self;
|
} = config;
|
||||||
Self {
|
|
||||||
permalink,
|
|
||||||
slug,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
excerpt,
|
|
||||||
categories,
|
|
||||||
tags,
|
|
||||||
excerpt_separator,
|
|
||||||
published_date,
|
|
||||||
format,
|
|
||||||
layout,
|
|
||||||
is_draft,
|
|
||||||
weight,
|
|
||||||
collection,
|
|
||||||
data: merge_objects(data, other_data),
|
|
||||||
pagination,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge(self, other: Self) -> Self {
|
let collection = collection.unwrap_or_default();
|
||||||
let Self {
|
|
||||||
permalink,
|
|
||||||
slug,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
excerpt,
|
|
||||||
categories,
|
|
||||||
tags,
|
|
||||||
excerpt_separator,
|
|
||||||
published_date,
|
|
||||||
format,
|
|
||||||
layout,
|
|
||||||
is_draft,
|
|
||||||
weight,
|
|
||||||
collection,
|
|
||||||
data,
|
|
||||||
pagination,
|
|
||||||
} = self;
|
|
||||||
let Self {
|
|
||||||
permalink: other_permalink,
|
|
||||||
slug: other_slug,
|
|
||||||
title: other_title,
|
|
||||||
description: other_description,
|
|
||||||
excerpt: other_excerpt,
|
|
||||||
categories: other_categories,
|
|
||||||
tags: other_tags,
|
|
||||||
excerpt_separator: other_excerpt_separator,
|
|
||||||
published_date: other_published_date,
|
|
||||||
format: other_format,
|
|
||||||
layout: other_layout,
|
|
||||||
is_draft: other_is_draft,
|
|
||||||
weight: other_weight,
|
|
||||||
collection: other_collection,
|
|
||||||
data: other_data,
|
|
||||||
pagination: other_pagination,
|
|
||||||
} = other;
|
|
||||||
Self {
|
|
||||||
permalink: permalink.or_else(|| other_permalink),
|
|
||||||
slug: slug.or_else(|| other_slug),
|
|
||||||
title: title.or_else(|| other_title),
|
|
||||||
description: description.or_else(|| other_description),
|
|
||||||
excerpt: excerpt.or_else(|| other_excerpt),
|
|
||||||
categories: categories.or_else(|| other_categories),
|
|
||||||
tags: tags.or_else(|| other_tags),
|
|
||||||
excerpt_separator: excerpt_separator.or_else(|| other_excerpt_separator),
|
|
||||||
published_date: published_date.or_else(|| other_published_date),
|
|
||||||
format: format.or_else(|| other_format),
|
|
||||||
layout: layout.or_else(|| other_layout),
|
|
||||||
is_draft: is_draft.or_else(|| other_is_draft),
|
|
||||||
weight: weight.or_else(|| other_weight),
|
|
||||||
collection: collection.or_else(|| other_collection),
|
|
||||||
data: merge_objects(data, other_data),
|
|
||||||
pagination: merge_pagination(pagination, other_pagination),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge_path<P: AsRef<path::Path>>(self, relpath: P) -> Self {
|
let permalink = permalink.unwrap_or_default();
|
||||||
self.merge_path_ref(relpath.as_ref())
|
if let cobalt_config::Permalink::Explicit(permalink) = &permalink {
|
||||||
}
|
if !permalink.starts_with('/') {
|
||||||
|
failure::bail!("Unsupported permalink alias '{}'", permalink);
|
||||||
fn merge_path_ref(mut self, relpath: &path::Path) -> Self {
|
|
||||||
if self.format.is_none() {
|
|
||||||
let ext = relpath.extension().and_then(|os| os.to_str()).unwrap_or("");
|
|
||||||
let format = match ext {
|
|
||||||
"md" => SourceFormat::Markdown,
|
|
||||||
_ => SourceFormat::Raw,
|
|
||||||
};
|
|
||||||
self.format = Some(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.published_date.is_none() || self.slug.is_none() {
|
|
||||||
let file_stem = file_stem(relpath);
|
|
||||||
let (file_date, file_stem) = parse_file_stem(file_stem);
|
|
||||||
if self.published_date.is_none() {
|
|
||||||
self.published_date = file_date;
|
|
||||||
}
|
|
||||||
if self.slug.is_none() {
|
|
||||||
let slug = slug::slugify(file_stem);
|
|
||||||
self.slug = Some(slug);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.title.is_none() {
|
|
||||||
let slug = self
|
|
||||||
.slug
|
|
||||||
.as_ref()
|
|
||||||
.expect("slug has been unconditionally initialized");
|
|
||||||
let title = slug::titleize_slug(slug);
|
|
||||||
self.title = Some(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> Result<Frontmatter> {
|
|
||||||
let Self {
|
|
||||||
permalink,
|
|
||||||
slug,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
excerpt,
|
|
||||||
categories,
|
|
||||||
tags,
|
|
||||||
excerpt_separator,
|
|
||||||
published_date,
|
|
||||||
format,
|
|
||||||
layout,
|
|
||||||
is_draft,
|
|
||||||
weight,
|
|
||||||
collection,
|
|
||||||
data,
|
|
||||||
pagination,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let collection = collection.unwrap_or_else(|| "".to_owned());
|
|
||||||
|
|
||||||
let permalink = permalink.unwrap_or_else(|| PATH_ALIAS.to_owned());
|
|
||||||
let permalink = if !permalink.starts_with('/') {
|
|
||||||
let resolved = *PERMALINK_ALIASES.get(permalink.as_str()).ok_or_else(|| {
|
|
||||||
failure::format_err!("Unsupported permalink alias '{}'", permalink)
|
|
||||||
})?;
|
|
||||||
resolved.to_owned()
|
|
||||||
} else {
|
|
||||||
permalink
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(ref tags) = tags {
|
if let Some(ref tags) = tags {
|
||||||
if tags.iter().any(|x| x.trim().is_empty()) {
|
if tags.iter().any(|x| x.trim().is_empty()) {
|
||||||
failure::bail!("Empty strings are not allowed in tags");
|
failure::bail!("Empty strings are not allowed in tags");
|
||||||
|
@ -456,7 +69,8 @@ impl FrontmatterBuilder {
|
||||||
tags
|
tags
|
||||||
};
|
};
|
||||||
let fm = Frontmatter {
|
let fm = Frontmatter {
|
||||||
pagination: pagination.and_then(|p| p.build(&permalink)),
|
pagination: pagination
|
||||||
|
.and_then(|p| pagination::PaginationConfig::from_config(p, &permalink)),
|
||||||
permalink,
|
permalink,
|
||||||
slug: slug.ok_or_else(|| failure::err_msg("No slug"))?,
|
slug: slug.ok_or_else(|| failure::err_msg("No slug"))?,
|
||||||
title: title.ok_or_else(|| failure::err_msg("No title"))?,
|
title: title.ok_or_else(|| failure::err_msg("No title"))?,
|
||||||
|
@ -466,7 +80,7 @@ impl FrontmatterBuilder {
|
||||||
tags,
|
tags,
|
||||||
excerpt_separator: excerpt_separator.unwrap_or_else(|| "\n\n".to_owned()),
|
excerpt_separator: excerpt_separator.unwrap_or_else(|| "\n\n".to_owned()),
|
||||||
published_date,
|
published_date,
|
||||||
format: format.unwrap_or_else(SourceFormat::default),
|
format: format.unwrap_or_else(super::SourceFormat::default),
|
||||||
layout,
|
layout,
|
||||||
is_draft: is_draft.unwrap_or(false),
|
is_draft: is_draft.unwrap_or(false),
|
||||||
weight: weight.unwrap_or(0),
|
weight: weight.unwrap_or(0),
|
||||||
|
@ -478,7 +92,7 @@ impl FrontmatterBuilder {
|
||||||
failure::bail!("Unsupported `pagination` field");
|
failure::bail!("Unsupported `pagination` field");
|
||||||
} else {
|
} else {
|
||||||
if let Some(pagination) = &fm.pagination {
|
if let Some(pagination) = &fm.pagination {
|
||||||
if !pagination_config::is_date_index_sorted(&pagination.date_index) {
|
if !pagination::is_date_index_sorted(&pagination.date_index) {
|
||||||
failure::bail!("date_index is not correctly sorted: Year > Month > Day...");
|
failure::bail!("date_index is not correctly sorted: Year > Month > Day...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,405 +101,13 @@ impl FrontmatterBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FrontmatterBuilder {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let converted = Front::to_string(self).map_err(|_| fmt::Error)?;
|
|
||||||
write!(f, "{}", converted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Front for FrontmatterBuilder {}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
|
||||||
pub struct Frontmatter {
|
|
||||||
pub permalink: String,
|
|
||||||
pub slug: String,
|
|
||||||
pub title: String,
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub excerpt: Option<String>,
|
|
||||||
pub categories: Vec<String>,
|
|
||||||
pub tags: Option<Vec<String>>,
|
|
||||||
pub excerpt_separator: String,
|
|
||||||
pub published_date: Option<datetime::DateTime>,
|
|
||||||
pub format: SourceFormat,
|
|
||||||
pub layout: Option<String>,
|
|
||||||
pub is_draft: bool,
|
|
||||||
pub weight: i32,
|
|
||||||
pub collection: String,
|
|
||||||
pub data: liquid::value::Object,
|
|
||||||
pub pagination: Option<pagination_config::PaginationConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Front for Frontmatter {}
|
|
||||||
|
|
||||||
impl fmt::Display for Frontmatter {
|
impl fmt::Display for Frontmatter {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let converted = Front::to_string(self).map_err(|_| fmt::Error)?;
|
let converted = serde_yaml::to_string(self).ok();
|
||||||
write!(f, "{}", converted)
|
if converted.as_ref().map(|s| s.as_str()) == Some("---\n{}") {
|
||||||
}
|
Ok(())
|
||||||
}
|
} else {
|
||||||
|
write!(f, "{}", &converted.unwrap()[4..])
|
||||||
/// Shallow merge of `liquid::value::Object`'s
|
}
|
||||||
fn merge_objects(
|
|
||||||
mut primary: liquid::value::Object,
|
|
||||||
secondary: liquid::value::Object,
|
|
||||||
) -> liquid::value::Object {
|
|
||||||
for (key, value) in secondary {
|
|
||||||
primary
|
|
||||||
.entry(key.to_owned())
|
|
||||||
.or_insert_with(|| value.clone());
|
|
||||||
}
|
|
||||||
primary
|
|
||||||
}
|
|
||||||
|
|
||||||
fn merge_pagination(
|
|
||||||
primary: Option<pagination_config::PaginationConfigBuilder>,
|
|
||||||
secondary: Option<pagination_config::PaginationConfigBuilder>,
|
|
||||||
) -> Option<pagination_config::PaginationConfigBuilder> {
|
|
||||||
match (primary, secondary) {
|
|
||||||
(Some(primary), Some(secondary)) => Some(primary.merge(&secondary)),
|
|
||||||
(Some(primary), None) => Some(primary),
|
|
||||||
(None, Some(secondary)) => Some(secondary),
|
|
||||||
(None, None) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The base-name without an extension. Correlates to Jekyll's :name path tag
|
|
||||||
pub fn file_stem<P: AsRef<path::Path>>(p: P) -> String {
|
|
||||||
file_stem_path(p.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_stem_path(p: &path::Path) -> String {
|
|
||||||
p.file_stem()
|
|
||||||
.map(|os| os.to_string_lossy().into_owned())
|
|
||||||
.unwrap_or_else(|| "".to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_file_stem(stem: String) -> (Option<datetime::DateTime>, String) {
|
|
||||||
lazy_static! {
|
|
||||||
static ref DATE_PREFIX_REF: regex::Regex =
|
|
||||||
regex::Regex::new(r"^(\d{4})-(\d{1,2})-(\d{1,2})[- ](.*)$").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let parts = DATE_PREFIX_REF.captures(&stem).and_then(|caps| {
|
|
||||||
let year: i32 = caps
|
|
||||||
.get(1)
|
|
||||||
.expect("unconditional capture")
|
|
||||||
.as_str()
|
|
||||||
.parse()
|
|
||||||
.expect("regex gets back an integer");
|
|
||||||
let month: u32 = caps
|
|
||||||
.get(2)
|
|
||||||
.expect("unconditional capture")
|
|
||||||
.as_str()
|
|
||||||
.parse()
|
|
||||||
.expect("regex gets back an integer");
|
|
||||||
let day: u32 = caps
|
|
||||||
.get(3)
|
|
||||||
.expect("unconditional capture")
|
|
||||||
.as_str()
|
|
||||||
.parse()
|
|
||||||
.expect("regex gets back an integer");
|
|
||||||
let published = datetime::DateTime::default()
|
|
||||||
.with_year(year)
|
|
||||||
.and_then(|d| d.with_month(month))
|
|
||||||
.and_then(|d| d.with_day(day));
|
|
||||||
published.map(|p| {
|
|
||||||
(
|
|
||||||
Some(p),
|
|
||||||
caps.get(4)
|
|
||||||
.expect("unconditional capture")
|
|
||||||
.as_str()
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
parts.unwrap_or((None, stem))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn file_stem_absolute_path() {
|
|
||||||
let input = path::PathBuf::from("/embedded/path/___filE-worlD-__09___.md");
|
|
||||||
let actual = file_stem(input.as_path());
|
|
||||||
assert_eq!(actual, "___filE-worlD-__09___");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_file_stem_empty() {
|
|
||||||
assert_eq!(parse_file_stem("".to_owned()), (None, "".to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_file_stem_none() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_file_stem("First Blog Post".to_owned()),
|
|
||||||
(None, "First Blog Post".to_owned())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_file_stem_out_of_range_month() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_file_stem("2017-30-5 First Blog Post".to_owned()),
|
|
||||||
(None, "2017-30-5 First Blog Post".to_owned())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_file_stem_out_of_range_day() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_file_stem("2017-3-50 First Blog Post".to_owned()),
|
|
||||||
(None, "2017-3-50 First Blog Post".to_owned())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_file_stem_single_digit() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_file_stem("2017-3-5 First Blog Post".to_owned()),
|
|
||||||
(
|
|
||||||
Some(
|
|
||||||
datetime::DateTime::default()
|
|
||||||
.with_year(2017)
|
|
||||||
.unwrap()
|
|
||||||
.with_month(3)
|
|
||||||
.unwrap()
|
|
||||||
.with_day(5)
|
|
||||||
.unwrap()
|
|
||||||
),
|
|
||||||
"First Blog Post".to_owned()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_file_stem_double_digit() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_file_stem("2017-12-25 First Blog Post".to_owned()),
|
|
||||||
(
|
|
||||||
Some(
|
|
||||||
datetime::DateTime::default()
|
|
||||||
.with_year(2017)
|
|
||||||
.unwrap()
|
|
||||||
.with_month(12)
|
|
||||||
.unwrap()
|
|
||||||
.with_day(25)
|
|
||||||
.unwrap()
|
|
||||||
),
|
|
||||||
"First Blog Post".to_owned()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_file_stem_double_digit_leading_zero() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_file_stem("2017-03-05 First Blog Post".to_owned()),
|
|
||||||
(
|
|
||||||
Some(
|
|
||||||
datetime::DateTime::default()
|
|
||||||
.with_year(2017)
|
|
||||||
.unwrap()
|
|
||||||
.with_month(3)
|
|
||||||
.unwrap()
|
|
||||||
.with_day(5)
|
|
||||||
.unwrap()
|
|
||||||
),
|
|
||||||
"First Blog Post".to_owned()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_file_stem_dashed() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_file_stem("2017-3-5-First-Blog-Post".to_owned()),
|
|
||||||
(
|
|
||||||
Some(
|
|
||||||
datetime::DateTime::default()
|
|
||||||
.with_year(2017)
|
|
||||||
.unwrap()
|
|
||||||
.with_month(3)
|
|
||||||
.unwrap()
|
|
||||||
.with_day(5)
|
|
||||||
.unwrap()
|
|
||||||
),
|
|
||||||
"First-Blog-Post".to_owned()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn frontmatter_title_from_path() {
|
|
||||||
let front = FrontmatterBuilder::new()
|
|
||||||
.merge_path("./parent/file.md")
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(front.title, "File");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn frontmatter_slug_from_md_path() {
|
|
||||||
let front = FrontmatterBuilder::new()
|
|
||||||
.merge_path("./parent/file.md")
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(front.slug, "file");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn frontmatter_markdown_from_path() {
|
|
||||||
let front = FrontmatterBuilder::new()
|
|
||||||
.merge_path("./parent/file.md")
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(front.format, SourceFormat::Markdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn frontmatter_raw_from_path() {
|
|
||||||
let front = FrontmatterBuilder::new()
|
|
||||||
.merge_path("./parent/file.liquid")
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(front.format, SourceFormat::Raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn frontmatter_global_merge() {
|
|
||||||
let empty = FrontmatterBuilder::new();
|
|
||||||
let a = FrontmatterBuilder {
|
|
||||||
permalink: Some("permalink a".to_owned()),
|
|
||||||
slug: Some("slug a".to_owned()),
|
|
||||||
title: Some("title a".to_owned()),
|
|
||||||
description: Some("description a".to_owned()),
|
|
||||||
excerpt: Some("excerpt a".to_owned()),
|
|
||||||
categories: Some(vec!["a".to_owned(), "b".to_owned()]),
|
|
||||||
tags: Some(vec!["a".to_owned(), "b".to_owned()]),
|
|
||||||
excerpt_separator: Some("excerpt_separator a".to_owned()),
|
|
||||||
published_date: Some(datetime::DateTime::default()),
|
|
||||||
format: Some(SourceFormat::Markdown),
|
|
||||||
layout: Some("layout a".to_owned()),
|
|
||||||
is_draft: Some(true),
|
|
||||||
weight: Some(0),
|
|
||||||
collection: Some("pages".to_owned()),
|
|
||||||
data: liquid::value::Object::new(),
|
|
||||||
pagination: Some(Default::default()),
|
|
||||||
};
|
|
||||||
let b = FrontmatterBuilder {
|
|
||||||
permalink: Some("permalink b".to_owned()),
|
|
||||||
slug: Some("slug b".to_owned()),
|
|
||||||
title: Some("title b".to_owned()),
|
|
||||||
description: Some("description b".to_owned()),
|
|
||||||
excerpt: Some("excerpt b".to_owned()),
|
|
||||||
categories: Some(vec!["b".to_owned(), "a".to_owned()]),
|
|
||||||
tags: Some(vec!["b".to_owned(), "a".to_owned()]),
|
|
||||||
excerpt_separator: Some("excerpt_separator b".to_owned()),
|
|
||||||
published_date: Some(datetime::DateTime::default()),
|
|
||||||
format: Some(SourceFormat::Raw),
|
|
||||||
layout: Some("layout b".to_owned()),
|
|
||||||
is_draft: Some(true),
|
|
||||||
weight: Some(0),
|
|
||||||
collection: Some("posts".to_owned()),
|
|
||||||
data: liquid::value::Object::new(),
|
|
||||||
pagination: Some(Default::default()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let merge_b_into_a = a.clone().merge(b.clone());
|
|
||||||
assert_eq!(merge_b_into_a, a);
|
|
||||||
|
|
||||||
let merge_empty_into_a = a.clone().merge(empty.clone());
|
|
||||||
assert_eq!(merge_empty_into_a, a);
|
|
||||||
|
|
||||||
let merge_a_into_empty = empty.clone().merge(a.clone());
|
|
||||||
assert_eq!(merge_a_into_empty, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn frontmatter_local_merge() {
|
|
||||||
let a = FrontmatterBuilder {
|
|
||||||
permalink: Some("permalink a".to_owned()),
|
|
||||||
slug: Some("slug a".to_owned()),
|
|
||||||
title: Some("title a".to_owned()),
|
|
||||||
description: Some("description a".to_owned()),
|
|
||||||
excerpt: Some("excerpt a".to_owned()),
|
|
||||||
categories: Some(vec!["a".to_owned(), "b".to_owned()]),
|
|
||||||
tags: Some(vec!["a".to_owned(), "b".to_owned()]),
|
|
||||||
excerpt_separator: Some("excerpt_separator a".to_owned()),
|
|
||||||
published_date: None,
|
|
||||||
format: Some(SourceFormat::Markdown),
|
|
||||||
layout: Some("layout a".to_owned()),
|
|
||||||
is_draft: Some(true),
|
|
||||||
weight: Some(0),
|
|
||||||
collection: Some("pages".to_owned()),
|
|
||||||
data: liquid::value::Object::new(),
|
|
||||||
pagination: Some(Default::default()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let merge_b_into_a = a
|
|
||||||
.clone()
|
|
||||||
.merge_permalink("permalink b".to_owned())
|
|
||||||
.merge_slug("slug b".to_owned())
|
|
||||||
.merge_title("title b".to_owned())
|
|
||||||
.merge_description("description b".to_owned())
|
|
||||||
.merge_excerpt("excerpt b".to_owned())
|
|
||||||
.merge_categories(vec!["a".to_owned(), "b".to_owned()])
|
|
||||||
.merge_tags(vec!["a".to_owned(), "b".to_owned()])
|
|
||||||
.merge_excerpt_separator("excerpt_separator b".to_owned())
|
|
||||||
.merge_format(SourceFormat::Raw)
|
|
||||||
.merge_layout("layout b".to_owned())
|
|
||||||
.merge_draft(true)
|
|
||||||
.merge_weight(0)
|
|
||||||
.merge_collection("posts".to_owned());
|
|
||||||
assert_eq!(merge_b_into_a, a);
|
|
||||||
|
|
||||||
let merge_empty_into_a = a
|
|
||||||
.clone()
|
|
||||||
.merge_permalink(None)
|
|
||||||
.merge_slug(None)
|
|
||||||
.merge_title(None)
|
|
||||||
.merge_description(None)
|
|
||||||
.merge_excerpt(None)
|
|
||||||
.merge_categories(None)
|
|
||||||
.merge_tags(None)
|
|
||||||
.merge_excerpt_separator(None)
|
|
||||||
.merge_format(None)
|
|
||||||
.merge_layout(None)
|
|
||||||
.merge_draft(None)
|
|
||||||
.merge_weight(None)
|
|
||||||
.merge_collection(None);
|
|
||||||
assert_eq!(merge_empty_into_a, a);
|
|
||||||
|
|
||||||
let merge_a_into_empty = FrontmatterBuilder::new()
|
|
||||||
.merge_permalink("permalink a".to_owned())
|
|
||||||
.merge_slug("slug a".to_owned())
|
|
||||||
.merge_title("title a".to_owned())
|
|
||||||
.merge_description("description a".to_owned())
|
|
||||||
.merge_excerpt("excerpt a".to_owned())
|
|
||||||
.merge_categories(vec!["a".to_owned(), "b".to_owned()])
|
|
||||||
.merge_tags(vec!["a".to_owned(), "b".to_owned()])
|
|
||||||
.merge_excerpt_separator("excerpt_separator a".to_owned())
|
|
||||||
.merge_format(SourceFormat::Markdown)
|
|
||||||
.merge_layout("layout a".to_owned())
|
|
||||||
.merge_draft(true)
|
|
||||||
.merge_weight(0)
|
|
||||||
.merge_collection("pages".to_owned())
|
|
||||||
.merge_pagination(Some(Default::default()));
|
|
||||||
assert_eq!(merge_a_into_empty, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn frontmatter_defaults() {
|
|
||||||
FrontmatterBuilder::new()
|
|
||||||
.set_title("Title".to_owned())
|
|
||||||
.set_slug("Slug".to_owned())
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use pulldown_cmark as cmark;
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::syntax_highlight::decorate_markdown;
|
use crate::syntax_highlight::decorate_markdown;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct MarkdownBuilder {
|
pub struct MarkdownBuilder {
|
||||||
pub theme: String,
|
pub theme: String,
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
mod assets;
|
mod assets;
|
||||||
mod collection;
|
mod collection;
|
||||||
mod config;
|
mod config;
|
||||||
mod datetime;
|
|
||||||
mod document;
|
|
||||||
mod frontmatter;
|
mod frontmatter;
|
||||||
mod mark;
|
mod mark;
|
||||||
mod sass;
|
mod sass;
|
||||||
|
@ -10,36 +8,27 @@ mod site;
|
||||||
mod template;
|
mod template;
|
||||||
|
|
||||||
pub mod files;
|
pub mod files;
|
||||||
pub mod pagination_config;
|
pub mod pagination;
|
||||||
pub mod permalink;
|
pub mod permalink;
|
||||||
pub mod slug;
|
pub mod slug;
|
||||||
|
|
||||||
|
pub use cobalt_config::DateTime;
|
||||||
|
pub use cobalt_config::Document;
|
||||||
|
pub use cobalt_config::Permalink;
|
||||||
|
pub use cobalt_config::SassOutputStyle;
|
||||||
|
pub use cobalt_config::SortOrder;
|
||||||
|
pub use cobalt_config::SourceFormat;
|
||||||
|
|
||||||
pub use self::assets::Assets;
|
pub use self::assets::Assets;
|
||||||
pub use self::assets::AssetsBuilder;
|
pub use self::assets::AssetsBuilder;
|
||||||
pub use self::collection::Collection;
|
pub use self::collection::Collection;
|
||||||
pub use self::collection::CollectionBuilder;
|
pub use self::collection::CollectionBuilder;
|
||||||
pub use self::collection::SortOrder;
|
|
||||||
pub use self::config::AssetsConfig;
|
|
||||||
pub use self::config::Config;
|
pub use self::config::Config;
|
||||||
pub use self::config::ConfigBuilder;
|
|
||||||
pub use self::config::PageConfig;
|
|
||||||
pub use self::config::PostConfig;
|
|
||||||
pub use self::config::SassConfig;
|
|
||||||
pub use self::config::SiteConfig;
|
|
||||||
pub use self::config::SyntaxHighlight;
|
|
||||||
pub use self::datetime::DateTime;
|
|
||||||
pub use self::document::DocumentBuilder;
|
|
||||||
pub use self::frontmatter::file_stem;
|
|
||||||
pub use self::frontmatter::parse_file_stem;
|
|
||||||
pub use self::frontmatter::Front;
|
|
||||||
pub use self::frontmatter::Frontmatter;
|
pub use self::frontmatter::Frontmatter;
|
||||||
pub use self::frontmatter::FrontmatterBuilder;
|
|
||||||
pub use self::frontmatter::SourceFormat;
|
|
||||||
pub use self::mark::Markdown;
|
pub use self::mark::Markdown;
|
||||||
pub use self::mark::MarkdownBuilder;
|
pub use self::mark::MarkdownBuilder;
|
||||||
pub use self::sass::SassBuilder;
|
pub use self::sass::SassBuilder;
|
||||||
pub use self::sass::SassCompiler;
|
pub use self::sass::SassCompiler;
|
||||||
pub use self::sass::SassOutputStyle;
|
|
||||||
pub use self::site::SiteBuilder;
|
pub use self::site::SiteBuilder;
|
||||||
pub use self::template::Liquid;
|
pub use self::template::Liquid;
|
||||||
pub use self::template::LiquidBuilder;
|
pub use self::template::LiquidBuilder;
|
||||||
|
|
61
src/cobalt_model/pagination.rs
Normal file
61
src/cobalt_model/pagination.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
use cobalt_config::SortOrder;
|
||||||
|
|
||||||
|
pub use cobalt_config::DateIndex;
|
||||||
|
pub use cobalt_config::Include;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, PartialEq, Eq)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct PaginationConfig {
|
||||||
|
pub include: Include,
|
||||||
|
pub per_page: i32,
|
||||||
|
pub front_permalink: cobalt_config::Permalink,
|
||||||
|
pub permalink_suffix: String,
|
||||||
|
pub order: SortOrder,
|
||||||
|
pub sort_by: Vec<String>,
|
||||||
|
pub date_index: Vec<DateIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaginationConfig {
|
||||||
|
pub fn from_config(
|
||||||
|
config: cobalt_config::Pagination,
|
||||||
|
permalink: &cobalt_config::Permalink,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let config = config.merge(&cobalt_config::Pagination::with_defaults());
|
||||||
|
let cobalt_config::Pagination {
|
||||||
|
include,
|
||||||
|
per_page,
|
||||||
|
permalink_suffix,
|
||||||
|
order,
|
||||||
|
sort_by,
|
||||||
|
date_index,
|
||||||
|
} = config;
|
||||||
|
let include = include.expect("default applied");
|
||||||
|
let per_page = per_page.expect("default applied");
|
||||||
|
let permalink_suffix = permalink_suffix.expect("default applied");
|
||||||
|
let order = order.expect("default applied");
|
||||||
|
let sort_by = sort_by.expect("default applied");
|
||||||
|
let date_index = date_index.expect("default applied");
|
||||||
|
|
||||||
|
if include == Include::None {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Self {
|
||||||
|
include,
|
||||||
|
per_page,
|
||||||
|
front_permalink: permalink.to_owned(),
|
||||||
|
permalink_suffix,
|
||||||
|
order,
|
||||||
|
sort_by,
|
||||||
|
date_index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO to be replaced by a call to `is_sorted()` once it's stabilized
|
||||||
|
pub fn is_date_index_sorted(v: &Vec<DateIndex>) -> bool {
|
||||||
|
let mut copy = v.clone();
|
||||||
|
copy.sort_unstable();
|
||||||
|
copy.eq(v)
|
||||||
|
}
|
|
@ -1,194 +0,0 @@
|
||||||
use std::convert::Into;
|
|
||||||
use std::vec::Vec;
|
|
||||||
|
|
||||||
use super::SortOrder;
|
|
||||||
|
|
||||||
pub const DEFAULT_PERMALINK_SUFFIX: &str = "{{num}}/";
|
|
||||||
|
|
||||||
pub const DEFAULT_PER_PAGE: i32 = 10;
|
|
||||||
lazy_static! {
|
|
||||||
static ref DEFAULT_SORT: Vec<String> = vec!["weight".to_string(), "published_date".to_string()];
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref DEFAULT_DATE_INDEX: Vec<DateIndex> = vec![DateIndex::Year, DateIndex::Month];
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub enum Include {
|
|
||||||
None,
|
|
||||||
All,
|
|
||||||
Tags,
|
|
||||||
Categories,
|
|
||||||
Dates,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<&'static str> for Include {
|
|
||||||
fn into(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Include::None => "",
|
|
||||||
Include::All => "all",
|
|
||||||
Include::Tags => "tags",
|
|
||||||
Include::Categories => "categories",
|
|
||||||
Include::Dates => "dates",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Include {
|
|
||||||
fn default() -> Include {
|
|
||||||
Include::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub enum DateIndex {
|
|
||||||
Year,
|
|
||||||
Month,
|
|
||||||
Day,
|
|
||||||
Hour,
|
|
||||||
Minute,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO to be replaced by a call to `is_sorted()` once it's stabilized
|
|
||||||
pub fn is_date_index_sorted(v: &Vec<DateIndex>) -> bool {
|
|
||||||
let mut copy = v.clone();
|
|
||||||
copy.sort_unstable();
|
|
||||||
copy.eq(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
|
||||||
pub struct PaginationConfigBuilder {
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub include: Option<Include>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub per_page: Option<i32>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub permalink_suffix: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub order: Option<SortOrder>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub sort_by: Option<Vec<String>>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub date_index: Option<Vec<DateIndex>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PaginationConfigBuilder {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_include<S: Into<Option<Include>>>(self, include: S) -> Self {
|
|
||||||
Self {
|
|
||||||
include: include.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_per_page<S: Into<Option<i32>>>(self, per_page: S) -> Self {
|
|
||||||
Self {
|
|
||||||
per_page: per_page.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_permalink_suffix<S: Into<Option<String>>>(self, permalink_suffix: S) -> Self {
|
|
||||||
Self {
|
|
||||||
permalink_suffix: permalink_suffix.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_order<S: Into<Option<SortOrder>>>(self, order: S) -> Self {
|
|
||||||
Self {
|
|
||||||
order: order.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_sort_by<S: Into<Option<Vec<String>>>>(self, sort_by: S) -> Self {
|
|
||||||
Self {
|
|
||||||
sort_by: sort_by.into(),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge(mut self, secondary: &PaginationConfigBuilder) -> PaginationConfigBuilder {
|
|
||||||
if self.include.is_none() {
|
|
||||||
self.include = secondary.include;
|
|
||||||
}
|
|
||||||
if self.per_page.is_none() {
|
|
||||||
self.per_page = secondary.per_page;
|
|
||||||
}
|
|
||||||
if self.permalink_suffix.is_none() {
|
|
||||||
self.permalink_suffix = secondary.permalink_suffix.clone();
|
|
||||||
}
|
|
||||||
if self.order.is_none() {
|
|
||||||
self.order = secondary.order;
|
|
||||||
}
|
|
||||||
if self.sort_by.is_none() {
|
|
||||||
self.sort_by = secondary.sort_by.clone();
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self, permalink: &str) -> Option<PaginationConfig> {
|
|
||||||
let Self {
|
|
||||||
include,
|
|
||||||
per_page,
|
|
||||||
permalink_suffix,
|
|
||||||
order,
|
|
||||||
sort_by,
|
|
||||||
date_index,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let include = include.unwrap_or(Include::None);
|
|
||||||
if include == Include::None {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let per_page = per_page.unwrap_or(DEFAULT_PER_PAGE);
|
|
||||||
let permalink_suffix =
|
|
||||||
permalink_suffix.unwrap_or_else(|| DEFAULT_PERMALINK_SUFFIX.to_owned());
|
|
||||||
let order = order.unwrap_or(SortOrder::Desc);
|
|
||||||
let sort_by = sort_by.unwrap_or_else(|| DEFAULT_SORT.to_vec());
|
|
||||||
let date_index = date_index.unwrap_or_else(|| DEFAULT_DATE_INDEX.to_vec());
|
|
||||||
Some(PaginationConfig {
|
|
||||||
include,
|
|
||||||
per_page,
|
|
||||||
front_permalink: permalink.to_owned(),
|
|
||||||
permalink_suffix,
|
|
||||||
order,
|
|
||||||
sort_by,
|
|
||||||
date_index,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[serde(deny_unknown_fields, default)]
|
|
||||||
pub struct PaginationConfig {
|
|
||||||
pub include: Include,
|
|
||||||
pub per_page: i32,
|
|
||||||
pub front_permalink: String,
|
|
||||||
pub permalink_suffix: String,
|
|
||||||
pub order: SortOrder,
|
|
||||||
pub sort_by: Vec<String>,
|
|
||||||
pub date_index: Vec<DateIndex>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PaginationConfig {
|
|
||||||
fn default() -> PaginationConfig {
|
|
||||||
PaginationConfig {
|
|
||||||
include: Default::default(),
|
|
||||||
per_page: DEFAULT_PER_PAGE,
|
|
||||||
permalink_suffix: DEFAULT_PERMALINK_SUFFIX.to_owned(),
|
|
||||||
front_permalink: Default::default(),
|
|
||||||
order: SortOrder::Desc,
|
|
||||||
sort_by: DEFAULT_SORT.to_vec(),
|
|
||||||
date_index: DEFAULT_DATE_INDEX.to_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +1,13 @@
|
||||||
use std::ffi;
|
use std::ffi;
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
||||||
|
use cobalt_config::SassOutputStyle;
|
||||||
#[cfg(feature = "sass")]
|
#[cfg(feature = "sass")]
|
||||||
use sass_rs;
|
use sass_rs;
|
||||||
|
|
||||||
use super::files;
|
use super::files;
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub enum SassOutputStyle {
|
|
||||||
Nested,
|
|
||||||
Expanded,
|
|
||||||
Compact,
|
|
||||||
Compressed,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SassOutputStyle {
|
|
||||||
fn default() -> Self {
|
|
||||||
SassOutputStyle::Nested
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields, default)]
|
#[serde(deny_unknown_fields, default)]
|
||||||
pub struct SassBuilder {
|
pub struct SassBuilder {
|
||||||
|
@ -30,8 +16,15 @@ pub struct SassBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SassBuilder {
|
impl SassBuilder {
|
||||||
pub fn new() -> Self {
|
pub fn from_config(config: cobalt_config::Sass, source: &path::Path) -> Self {
|
||||||
Default::default()
|
Self {
|
||||||
|
style: config.style,
|
||||||
|
import_dir: source
|
||||||
|
.join(config.import_dir)
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.ok(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> SassCompiler {
|
pub fn build(self) -> SassCompiler {
|
||||||
|
|
|
@ -19,10 +19,20 @@ pub struct SiteBuilder {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub base_url: Option<String>,
|
pub base_url: Option<String>,
|
||||||
pub data: Option<liquid::value::Object>,
|
pub data: Option<liquid::value::Object>,
|
||||||
pub data_dir: Option<path::PathBuf>,
|
pub data_dir: path::PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SiteBuilder {
|
impl SiteBuilder {
|
||||||
|
pub fn from_config(config: cobalt_config::Site, source: &path::Path) -> Self {
|
||||||
|
Self {
|
||||||
|
title: config.title,
|
||||||
|
description: config.description,
|
||||||
|
base_url: config.base_url,
|
||||||
|
data: config.data,
|
||||||
|
data_dir: source.join(config.data_dir),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Result<liquid::value::Object> {
|
pub fn build(self) -> Result<liquid::value::Object> {
|
||||||
let SiteBuilder {
|
let SiteBuilder {
|
||||||
title,
|
title,
|
||||||
|
@ -53,9 +63,7 @@ impl SiteBuilder {
|
||||||
attributes.insert("base_url".into(), liquid::value::Value::scalar(base_url));
|
attributes.insert("base_url".into(), liquid::value::Value::scalar(base_url));
|
||||||
}
|
}
|
||||||
let mut data = data.unwrap_or_default();
|
let mut data = data.unwrap_or_default();
|
||||||
if let Some(ref data_dir) = data_dir {
|
insert_data_dir(&mut data, &data_dir)?;
|
||||||
insert_data_dir(&mut data, data_dir)?;
|
|
||||||
}
|
|
||||||
if !data.is_empty() {
|
if !data.is_empty() {
|
||||||
attributes.insert("data".into(), liquid::value::Value::Object(data));
|
attributes.insert("data".into(), liquid::value::Value::Object(data));
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ fn load_partials_from_path(root: path::PathBuf) -> Result<liquid::Partials> {
|
||||||
Ok(source)
|
Ok(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct LiquidBuilder {
|
pub struct LiquidBuilder {
|
||||||
pub includes_dir: path::PathBuf,
|
pub includes_dir: path::PathBuf,
|
||||||
|
|
|
@ -194,23 +194,25 @@ impl Document {
|
||||||
pub fn parse(
|
pub fn parse(
|
||||||
src_path: &Path,
|
src_path: &Path,
|
||||||
rel_path: &Path,
|
rel_path: &Path,
|
||||||
default_front: cobalt_model::FrontmatterBuilder,
|
default_front: cobalt_config::Frontmatter,
|
||||||
) -> Result<Document> {
|
) -> Result<Document> {
|
||||||
trace!("Parsing {:?}", rel_path);
|
trace!("Parsing {:?}", rel_path);
|
||||||
let content = files::read_file(src_path)?;
|
let content = files::read_file(src_path)?;
|
||||||
let builder =
|
let builder = cobalt_config::Document::parse(&content)?;
|
||||||
cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::parse(&content)?;
|
let (front, content) = builder.into_parts();
|
||||||
let (front, content) = builder.parts();
|
let front = front
|
||||||
let front = front.merge_path(rel_path).merge(default_front);
|
.merge(&cobalt_config::Frontmatter::from_path(rel_path))
|
||||||
|
.merge(&default_front);
|
||||||
|
|
||||||
let front = front.build()?;
|
let front = cobalt_model::Frontmatter::from_config(front)?;
|
||||||
|
|
||||||
let (file_path, url_path) = {
|
let (file_path, url_path) = {
|
||||||
let perma_attributes = permalink_attributes(&front, rel_path);
|
let perma_attributes = permalink_attributes(&front, rel_path);
|
||||||
let url_path = permalink::explode_permalink(&front.permalink, &perma_attributes)
|
let url_path =
|
||||||
.with_context(|_| {
|
permalink::explode_permalink(front.permalink.as_str(), &perma_attributes)
|
||||||
failure::format_err!("Failed to create permalink `{}`", front.permalink)
|
.with_context(|_| {
|
||||||
})?;
|
failure::format_err!("Failed to create permalink `{}`", front.permalink)
|
||||||
|
})?;
|
||||||
let file_path = permalink::format_url_as_file(&url_path);
|
let file_path = permalink::format_url_as_file(&url_path);
|
||||||
(file_path, url_path)
|
(file_path, url_path)
|
||||||
};
|
};
|
||||||
|
@ -314,7 +316,7 @@ impl Document {
|
||||||
cobalt_model::SourceFormat::Raw => html,
|
cobalt_model::SourceFormat::Raw => html,
|
||||||
cobalt_model::SourceFormat::Markdown => markdown.parse(&html)?,
|
cobalt_model::SourceFormat::Markdown => markdown.parse(&html)?,
|
||||||
};
|
};
|
||||||
Ok(html.to_owned())
|
Ok(html)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders excerpt and adds it to attributes of the document.
|
/// Renders excerpt and adds it to attributes of the document.
|
||||||
|
|
|
@ -15,7 +15,6 @@ extern crate difference;
|
||||||
|
|
||||||
pub use crate::cobalt::build;
|
pub use crate::cobalt::build;
|
||||||
pub use crate::cobalt_model::Config;
|
pub use crate::cobalt_model::Config;
|
||||||
pub use crate::cobalt_model::ConfigBuilder;
|
|
||||||
pub use crate::error::Error;
|
pub use crate::error::Error;
|
||||||
|
|
||||||
pub mod cobalt_model;
|
pub mod cobalt_model;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
use chrono::Timelike;
|
use chrono::Timelike;
|
||||||
|
|
||||||
use crate::cobalt_model::pagination_config::DateIndex;
|
use crate::cobalt_model::pagination::DateIndex;
|
||||||
|
|
||||||
use crate::cobalt_model::DateTime;
|
use crate::cobalt_model::DateTime;
|
||||||
use crate::document::Document;
|
use crate::document::Document;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use crate::cobalt_model::pagination_config::Include;
|
use crate::cobalt_model::pagination::Include;
|
||||||
use crate::cobalt_model::pagination_config::PaginationConfig;
|
use crate::cobalt_model::pagination::PaginationConfig;
|
||||||
use crate::cobalt_model::permalink;
|
use crate::cobalt_model::permalink;
|
||||||
use crate::cobalt_model::slug;
|
use crate::cobalt_model::slug;
|
||||||
use crate::cobalt_model::SortOrder;
|
use crate::cobalt_model::SortOrder;
|
||||||
|
@ -139,7 +139,7 @@ fn interpret_permalink(
|
||||||
index: Option<&liquid::value::Value>,
|
index: Option<&liquid::value::Value>,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let mut attributes = document::permalink_attributes(&doc.front, &doc.file_path);
|
let mut attributes = document::permalink_attributes(&doc.front, &doc.file_path);
|
||||||
let permalink = permalink::explode_permalink(&config.front_permalink, &attributes)?;
|
let permalink = permalink::explode_permalink(config.front_permalink.as_str(), &attributes)?;
|
||||||
let permalink_path = std::path::Path::new(&permalink);
|
let permalink_path = std::path::Path::new(&permalink);
|
||||||
let pagination_root = permalink_path.extension().map_or_else(
|
let pagination_root = permalink_path.extension().map_or_else(
|
||||||
|| permalink.clone(),
|
|| permalink.clone(),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::cobalt_model::pagination_config::PaginationConfig;
|
use crate::cobalt_model::pagination::PaginationConfig;
|
||||||
use crate::cobalt_model::slug;
|
use crate::cobalt_model::slug;
|
||||||
use crate::document::Document;
|
use crate::document::Document;
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,11 @@ struct CodeBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderable for CodeBlock {
|
impl Renderable for CodeBlock {
|
||||||
fn render_to(&self, writer: &mut Write, _context: &mut Context) -> Result<(), liquid::Error> {
|
fn render_to(
|
||||||
|
&self,
|
||||||
|
writer: &mut dyn Write,
|
||||||
|
_context: &mut Context,
|
||||||
|
) -> Result<(), liquid::Error> {
|
||||||
if let Some(ref lang) = self.lang {
|
if let Some(ref lang) = self.lang {
|
||||||
write!(
|
write!(
|
||||||
writer,
|
writer,
|
||||||
|
@ -110,7 +114,7 @@ impl liquid::compiler::ParseBlock for CodeBlockParser {
|
||||||
mut arguments: TagTokenIter,
|
mut arguments: TagTokenIter,
|
||||||
mut tokens: TagBlock,
|
mut tokens: TagBlock,
|
||||||
_options: &Language,
|
_options: &Language,
|
||||||
) -> Result<Box<Renderable>, liquid::Error> {
|
) -> Result<Box<dyn Renderable>, liquid::Error> {
|
||||||
let lang = arguments
|
let lang = arguments
|
||||||
.expect_next("Identifier or literal expected.")
|
.expect_next("Identifier or literal expected.")
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -167,7 +171,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn codeblock_renders_rust() {
|
fn codeblock_renders_rust() {
|
||||||
let highlight: Box<liquid::compiler::ParseBlock> =
|
let highlight: Box<dyn liquid::compiler::ParseBlock> =
|
||||||
Box::new(CodeBlockParser::new("base16-ocean.dark".to_owned()));
|
Box::new(CodeBlockParser::new("base16-ocean.dark".to_owned()));
|
||||||
let parser = liquid::ParserBuilder::new()
|
let parser = liquid::ParserBuilder::new()
|
||||||
.block(highlight)
|
.block(highlight)
|
||||||
|
|
|
@ -249,7 +249,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn highlight_block_renders_rust() {
|
fn highlight_block_renders_rust() {
|
||||||
let highlight: Box<liquid::compiler::ParseBlock> =
|
let highlight: Box<dyn liquid::compiler::ParseBlock> =
|
||||||
Box::new(CodeBlockParser::new("base16-ocean.dark".to_owned()));
|
Box::new(CodeBlockParser::new("base16-ocean.dark".to_owned()));
|
||||||
let parser = liquid::ParserBuilder::new()
|
let parser = liquid::ParserBuilder::new()
|
||||||
.block(highlight)
|
.block(highlight)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
layout: default.txt
|
layout: default.txt
|
||||||
---
|
---
|
||||||
This is my Index page!
|
This is my Index page!
|
||||||
|
|
2
tests/fixtures/pagination_all/index.liquid
vendored
2
tests/fixtures/pagination_all/index.liquid
vendored
|
@ -2,7 +2,7 @@
|
||||||
permalink: /
|
permalink: /
|
||||||
layout: default.liquid
|
layout: default.liquid
|
||||||
pagination:
|
pagination:
|
||||||
include: All
|
include: all
|
||||||
---
|
---
|
||||||
This is my Index page!
|
This is my Index page!
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
layout: default.liquid
|
layout: default.liquid
|
||||||
permalink: /
|
permalink: /
|
||||||
pagination:
|
pagination:
|
||||||
include: All
|
include: all
|
||||||
order: Asc
|
order: asc
|
||||||
permalink_suffix: ./_p/{{num}}
|
permalink_suffix: ./_p/{{num}}
|
||||||
---
|
---
|
||||||
This is my Index page!
|
This is my Index page!
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
layout: default.liquid
|
layout: default.liquid
|
||||||
permalink: /
|
permalink: /
|
||||||
pagination:
|
pagination:
|
||||||
include: All
|
include: all
|
||||||
sort_by: ["title"]
|
sort_by: ["title"]
|
||||||
permalink_suffix: ./_p/{{num}}
|
permalink_suffix: ./_p/{{num}}
|
||||||
---
|
---
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
permalink: /categories.html
|
permalink: /categories.html
|
||||||
layout: default.liquid
|
layout: default.liquid
|
||||||
pagination:
|
pagination:
|
||||||
include: Categories
|
include: categories
|
||||||
permalink_suffix: ./_p/{{num}}
|
permalink_suffix: ./_p/{{num}}
|
||||||
---
|
---
|
||||||
{% if paginator.index_title %}
|
{% if paginator.index_title %}
|
||||||
|
|
2
tests/fixtures/pagination_dates/index.liquid
vendored
2
tests/fixtures/pagination_dates/index.liquid
vendored
|
@ -2,7 +2,7 @@
|
||||||
permalink: /dates.html
|
permalink: /dates.html
|
||||||
layout: default.liquid
|
layout: default.liquid
|
||||||
pagination:
|
pagination:
|
||||||
include: Dates
|
include: dates
|
||||||
permalink_suffix: ./_p/{{num}}
|
permalink_suffix: ./_p/{{num}}
|
||||||
---
|
---
|
||||||
{% if paginator.index_title %}
|
{% if paginator.index_title %}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
layout: default.liquid
|
layout: default.liquid
|
||||||
permalink: /
|
permalink: /
|
||||||
pagination:
|
pagination:
|
||||||
include: All
|
include: all
|
||||||
per_page: 1
|
per_page: 1
|
||||||
permalink_suffix: ./_p/{{num}}
|
permalink_suffix: ./_p/{{num}}
|
||||||
---
|
---
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
permalink: /
|
permalink: /
|
||||||
layout: default.liquid
|
layout: default.liquid
|
||||||
pagination:
|
pagination:
|
||||||
include: All
|
include: all
|
||||||
sort_by: ["weight"]
|
sort_by: ["weight"]
|
||||||
---
|
---
|
||||||
This is my Index page!
|
This is my Index page!
|
||||||
|
|
2
tests/fixtures/pagination_tags/index.liquid
vendored
2
tests/fixtures/pagination_tags/index.liquid
vendored
|
@ -2,7 +2,7 @@
|
||||||
permalink: /tags
|
permalink: /tags
|
||||||
layout: default.liquid
|
layout: default.liquid
|
||||||
pagination:
|
pagination:
|
||||||
include: Tags
|
include: tags
|
||||||
permalink_suffix: _p/{{num}}
|
permalink_suffix: _p/{{num}}
|
||||||
per_page: 2
|
per_page: 2
|
||||||
---
|
---
|
||||||
|
|
2
tests/fixtures/post_order/_cobalt.yml
vendored
2
tests/fixtures/post_order/_cobalt.yml
vendored
|
@ -1,4 +1,4 @@
|
||||||
posts:
|
posts:
|
||||||
order: Asc
|
order: asc
|
||||||
site:
|
site:
|
||||||
title: cobalt blog
|
title: cobalt blog
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
layout: default.liquid
|
layout: default.liquid
|
||||||
|
|
||||||
title: a post inside posts/2017/01/05/
|
title: a post inside posts/2017/01/05/
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
layout: default.liquid
|
layout: default.liquid
|
||||||
|
|
||||||
title: a post inside posts/2017/01/08/
|
title: a post inside posts/2017/01/08/
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
assets:
|
assets:
|
||||||
sass:
|
sass:
|
||||||
style: Compressed
|
style: compressed
|
||||||
|
|
|
@ -69,9 +69,9 @@ fn run_test(name: &str) -> Result<(), cobalt::Error> {
|
||||||
.copy_from(format!("tests/fixtures/{}", name), &["**"])
|
.copy_from(format!("tests/fixtures/{}", name), &["**"])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut config = cobalt::ConfigBuilder::from_cwd(target.path())?;
|
let mut config = cobalt_config::Config::from_cwd(target.path())?;
|
||||||
config.destination = "./_dest".into();
|
config.destination = "./_dest".into();
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
let result = cobalt::build(config);
|
let result = cobalt::build(config);
|
||||||
|
|
||||||
// Always explicitly close to catch errors, especially on Windows.
|
// Always explicitly close to catch errors, especially on Windows.
|
||||||
|
@ -86,9 +86,9 @@ fn test_with_expected(name: &str) -> Result<(), cobalt::Error> {
|
||||||
.copy_from(format!("tests/fixtures/{}", name), &["**"])
|
.copy_from(format!("tests/fixtures/{}", name), &["**"])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut config = cobalt::ConfigBuilder::from_cwd(target.path())?;
|
let mut config = cobalt_config::Config::from_cwd(target.path())?;
|
||||||
config.destination = "./_dest".into();
|
config.destination = "./_dest".into();
|
||||||
let config = config.build()?;
|
let config = cobalt::cobalt_model::Config::from_config(config)?;
|
||||||
let destination = config.destination.clone();
|
let destination = config.destination.clone();
|
||||||
let result = cobalt::build(config);
|
let result = cobalt::build(config);
|
||||||
|
|
||||||
|
@ -229,7 +229,6 @@ pub fn ignore_files() {
|
||||||
pub fn yaml_error() {
|
pub fn yaml_error() {
|
||||||
let err = test_with_expected("yaml_error");
|
let err = test_with_expected("yaml_error");
|
||||||
assert!(err.is_err());
|
assert!(err.is_err());
|
||||||
let err: exitfailure::ExitFailure = err.unwrap_err().into();
|
|
||||||
let error_message = format!("{:?}", err);
|
let error_message = format!("{:?}", err);
|
||||||
assert_contains!(error_message, "unexpected character");
|
assert_contains!(error_message, "unexpected character");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue