diff --git a/CHANGELOG.md b/CHANGELOG.md index d63b464..4eb8fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix missing serialized data for sections - Change the single item template context for categories/tags +- Add a `get_url` global Tera function ## 0.0.5 (2017-05-15) diff --git a/Cargo.lock b/Cargo.lock index 671ab90..3e9278a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = "0.0.5" dependencies = [ "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.24.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -155,7 +155,7 @@ dependencies = [ [[package]] name = "clap" -version = "2.24.1" +version = "2.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -163,9 +163,9 @@ dependencies = [ "bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -941,7 +941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-segmentation" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -991,7 +991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "vec_map" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1084,7 +1084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" "checksum chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d9123be86fd2a8f627836c235ecdf331fdd067ecf7ac05aa1a68fbcf2429f056" -"checksum clap 2.24.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7541069be0b8aec41030802abe8b5cdef0490070afaa55418adea93b1e431e0" +"checksum clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8f69e518f967224e628896b54e41ff6acfb4dcfefc5076325c36525dac900f" "checksum cmake 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "92278eb79412c8f75cfc89e707a1bb3a6490b68f7f2e78d15c774f30fe701122" "checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" @@ -1179,7 +1179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764" "checksum unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a078ebdd62c0e71a709c3d53d2af693fe09fe93fbff8344aebe289b78f9032" "checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff" -"checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3" +"checksum unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8083c594e02b8ae1654ae26f0ade5158b119bd88ad0e8227a5d8fcd72407946" "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unidecode 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2adb95ee07cd579ed18131f2d9e7a17c25a4b76022935c7f2460d2bfae89fd2" @@ -1187,7 +1187,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b351086021ebc264aea3ab4f94d61d889d98e5e9ec2d985d993f50133537fd3a" "checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" -"checksum vec_map 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8cdc8b93bd0198ed872357fb2e667f7125646b1762f16d60b2c96350d361897" +"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c66c0b9792f0a765345452775f3adbd28dde9d33f30d13e5dcc5ae17cf6f3780" "checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" diff --git a/src/bin/rebuild.rs b/src/bin/rebuild.rs index 93e778f..4f75bb9 100644 --- a/src/bin/rebuild.rs +++ b/src/bin/rebuild.rs @@ -119,6 +119,8 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { } }; } + // Ensure we have our fn updated so it doesn't contain the permalinks deleted + site.register_get_url_fn(); // Deletion is something that doesn't happen all the time so we // don't need to optimise it too much return site.build(); @@ -153,6 +155,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { return Ok(()); }, None => { + site.register_get_url_fn(); // New section, only render that one site.populate_sections(); return site.render_section(&site.sections[path], true); @@ -163,6 +166,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { // A page was edited match site.add_page(path, true)? { Some(prev) => { + site.register_get_url_fn(); // Updating a page let current = site.pages[path].clone(); // Front matter didn't change, only content did @@ -201,6 +205,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { }, None => { + site.register_get_url_fn(); // It's a new page! site.populate_sections(); site.populate_tags_and_categories(); diff --git a/src/markdown.rs b/src/markdown.rs index f4a236b..2905252 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -14,6 +14,7 @@ use tera::{Tera, Context}; use config::Config; use errors::{Result, ResultExt}; +use site::resolve_internal_link; // We need to put those in a struct to impl Send and sync @@ -257,21 +258,11 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap, ter return Event::Html(Owned("".to_owned())); } if link.starts_with("./") { - // First we remove the ./ since that's gutenberg specific - let clean_link = link.replacen("./", "", 1); - // Then we remove any potential anchor - // parts[0] will be the file path and parts[1] the anchor if present - let parts = clean_link.split('#').collect::>(); - match permalinks.get(parts[0]) { - Some(p) => { - let url = if parts.len() > 1 { - format!("{}#{}", p, parts[1]) - } else { - p.to_string() - }; + match resolve_internal_link(link, permalinks) { + Ok(url) => { return Event::Start(Tag::Link(Owned(url), title.clone())); }, - None => { + Err(_) => { error = Some(format!("Relative link {} not found.", link).into()); return Event::Html(Owned("".to_string())); } diff --git a/src/site.rs b/src/site.rs index 608a108..11b8a4c 100644 --- a/src/site.rs +++ b/src/site.rs @@ -125,10 +125,16 @@ impl Site { self.populate_tags_and_categories(); self.tera.register_global_function("get_page", global_fns::make_get_page(&self.pages)); + self.register_get_url_fn(); Ok(()) } + /// Separate fn as it can be called in the serve command + pub fn register_get_url_fn(&mut self) { + self.tera.register_global_function("get_url", global_fns::make_get_url(self.permalinks.clone())); + } + /// Add a page to the site /// The `render` parameter is used in the serve command, when rebuilding a page. /// If `true`, it will also render the markdown for that page @@ -545,3 +551,53 @@ impl Site { Ok(()) } } + + +/// Resolves an internal link (of the `./posts/something.md#hey` sort) to its absolute link +pub fn resolve_internal_link(link: &str, permalinks: &HashMap) -> Result { + // First we remove the ./ since that's gutenberg specific + let clean_link = link.replacen("./", "", 1); + // Then we remove any potential anchor + // parts[0] will be the file path and parts[1] the anchor if present + let parts = clean_link.split('#').collect::>(); + match permalinks.get(parts[0]) { + Some(p) => { + if parts.len() > 1 { + Ok(format!("{}#{}", p, parts[1])) + } else { + Ok(p.to_string()) + } + }, + None => bail!(format!("Relative link {} not found.", link)), + } +} + + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use super::resolve_internal_link; + + #[test] + fn can_resolve_valid_internal_link() { + let mut permalinks = HashMap::new(); + permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string()); + let res = resolve_internal_link("./pages/about.md", &permalinks).unwrap(); + assert_eq!(res, "https://vincent.is/about"); + } + + #[test] + fn can_resolve_internal_links_with_anchors() { + let mut permalinks = HashMap::new(); + permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string()); + let res = resolve_internal_link("./pages/about.md#hello", &permalinks).unwrap(); + assert_eq!(res, "https://vincent.is/about#hello"); + } + + #[test] + fn errors_resolve_inexistant_internal_link() { + let res = resolve_internal_link("./pages/about.md#hello", &HashMap::new()); + assert!(res.is_err()); + } +} diff --git a/src/templates/global_fns.rs b/src/templates/global_fns.rs index 0c154b0..f875ddc 100644 --- a/src/templates/global_fns.rs +++ b/src/templates/global_fns.rs @@ -4,6 +4,7 @@ use std::path::{PathBuf}; use tera::{GlobalFn, Value, from_value, to_value, Result}; use content::Page; +use site::resolve_internal_link; pub fn make_get_page(all_pages: &HashMap) -> GlobalFn { @@ -27,3 +28,18 @@ pub fn make_get_page(all_pages: &HashMap) -> GlobalFn { } }) } + +pub fn make_get_url(permalinks: HashMap,) -> GlobalFn { + Box::new(move |args| -> Result { + match args.get("link") { + Some(val) => match from_value::(val.clone()) { + Ok(v) => match resolve_internal_link(&v, &permalinks) { + Ok(url) => Ok(to_value(url).unwrap()), + Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", v).into()) + }, + Err(_) => Err(format!("`get_url` received link={:?} but it requires a string", val).into()), + }, + None => Err("`get_url` requires a `link` argument.".into()), + } + }) +}