@@ -2,8 +2,8 @@ | |||||
name = "gutenberg" | name = "gutenberg" | ||||
version = "0.0.4" | version = "0.0.4" | ||||
dependencies = [ | dependencies = [ | ||||
"base64 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"base64 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"clap 2.23.3 (registry+https://github.com/rust-lang/crates.io-index)", | "clap 2.23.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"error-chain 0.10.0 (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)", | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -74,7 +74,7 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "base64" | name = "base64" | ||||
version = "0.5.0" | |||||
version = "0.5.1" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -123,7 +123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
[[package]] | [[package]] | ||||
name = "bytes" | name = "bytes" | ||||
version = "0.4.2" | |||||
version = "0.4.3" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -146,7 +146,7 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "chrono" | name = "chrono" | ||||
version = "0.3.0" | |||||
version = "0.3.1" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", | "num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -823,7 +823,7 @@ name = "tera" | |||||
version = "0.10.1" | version = "0.10.1" | ||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"error-chain 0.10.0 (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)", | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"humansize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | "humansize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -895,7 +895,7 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "toml" | name = "toml" | ||||
version = "0.4.0" | version = "0.4.0" | ||||
source = "git+https://github.com/alexcrichton/toml-rs#95b3545938f67ca98d313be5c9c8930ee2407a30" | |||||
source = "git+https://github.com/alexcrichton/toml-rs#58f51ef03b88e06745c4113e13ea2738e1af247d" | |||||
dependencies = [ | dependencies = [ | ||||
"serde 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | "serde 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
@@ -1034,7 +1034,7 @@ version = "0.7.1" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"bytes 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"bytes 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"httparse 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | "httparse 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"mio 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", | "mio 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -1072,7 +1072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" | "checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" | ||||
"checksum backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f551bc2ddd53aea015d453ef0b635af89444afa5ed2405dd0b2062ad5d600d80" | "checksum backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f551bc2ddd53aea015d453ef0b635af89444afa5ed2405dd0b2062ad5d600d80" | ||||
"checksum backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d192fd129132fbc97497c1f2ec2c2c5174e376b95f535199ef4fe0a293d33842" | "checksum backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d192fd129132fbc97497c1f2ec2c2c5174e376b95f535199ef4fe0a293d33842" | ||||
"checksum base64 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6c902f607515b17ee069f2757c58a6d4b2afa7411b8995f96c4a3c19247b5fcf" | |||||
"checksum base64 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "124e5332dfc4e387b4ca058909aa175c0c3eccf03846b7c1a969b9ad067b8df2" | |||||
"checksum bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "55eb0b7fd108527b0c77860f75eca70214e11a8b4c6ef05148c54c05a25d48ad" | "checksum bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "55eb0b7fd108527b0c77860f75eca70214e11a8b4c6ef05148c54c05a25d48ad" | ||||
"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" | "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" | ||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" | ||||
@@ -1080,10 +1080,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
"checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" | "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" | ||||
"checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" | "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" | ||||
"checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" | "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" | ||||
"checksum bytes 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3941933da81d8717b427c2ddc2d73567cd15adb6c57514a2726d9ee598a5439a" | |||||
"checksum bytes 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f9edb851115d67d1f18680f9326901768a91d37875b87015518357c6ce22b553" | |||||
"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" | "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.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" | ||||
"checksum chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158b0bd7d75cbb6bf9c25967a48a2e9f77da95876b858eadfabaa99cd069de6e" | |||||
"checksum chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d9123be86fd2a8f627836c235ecdf331fdd067ecf7ac05aa1a68fbcf2429f056" | |||||
"checksum clap 2.23.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f57e9b63057a545ad2ecd773ea61e49422ed1b1d63d74d5da5ecaee55b3396cd" | "checksum clap 2.23.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f57e9b63057a545ad2ecd773ea61e49422ed1b1d63d74d5da5ecaee55b3396cd" | ||||
"checksum cmake 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "d18d68987ed4c516dcc3e7913659bfa4076f5182eea4a7e0038bb060953e76ac" | "checksum cmake 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "d18d68987ed4c516dcc3e7913659bfa4076f5182eea4a7e0038bb060953e76ac" | ||||
"checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" | "checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" | ||||
@@ -124,13 +124,17 @@ You can also set the `template` variable to change which template will be used t | |||||
Sections will also automatically pick up their subsections, allowing you to make some complex pages layout and | Sections will also automatically pick up their subsections, allowing you to make some complex pages layout and | ||||
table of contents. | table of contents. | ||||
You can define how a section pages are sorted using the `sort_by` key in the front-matter. The choices are `date` (default), `order` | |||||
and `none`. Pages that can be sorted will currently be silently dropped: the final page will be rendered but it will not appear in | |||||
You can define how a section pages are sorted using the `sort_by` key in the front-matter. The choices are `date`, `order` | |||||
and `none` (default). Pages that can't be sorted will currently be silently dropped: the final page will be rendered but it will not appear in | |||||
the `pages` variable in the section template. | the `pages` variable in the section template. | ||||
A special case is the `_index.md` at the root of the `content` directory which represents the homepage. It is only there | A special case is the `_index.md` at the root of the `content` directory which represents the homepage. It is only there | ||||
to control pagination and sorting of the homepage. | to control pagination and sorting of the homepage. | ||||
You can also paginate section, including the index by setting the `paginate_by` field in the front matter to an integer. | |||||
This represents the number of pages for each pager of the paginator. | |||||
You will need to access pages through the `paginator` object. (TODO: document that). | |||||
### Code highlighting themes | ### Code highlighting themes | ||||
Code highlighting can be turned on by setting `highlight_code = true` in `config.toml`. | Code highlighting can be turned on by setting `highlight_code = true` in `config.toml`. | ||||
@@ -75,11 +75,11 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> { | |||||
let (tx, rx) = channel(); | let (tx, rx) = channel(); | ||||
let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); | let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); | ||||
watcher.watch("content/", RecursiveMode::Recursive) | watcher.watch("content/", RecursiveMode::Recursive) | ||||
.chain_err(|| format!("Can't watch the `content` folder. Does it exist?"))?; | |||||
.chain_err(|| "Can't watch the `content` folder. Does it exist?")?; | |||||
watcher.watch("static/", RecursiveMode::Recursive) | watcher.watch("static/", RecursiveMode::Recursive) | ||||
.chain_err(|| format!("Can't watch the `static` folder. Does it exist?"))?; | |||||
.chain_err(|| "Can't watch the `static` folder. Does it exist?")?; | |||||
watcher.watch("templates/", RecursiveMode::Recursive) | watcher.watch("templates/", RecursiveMode::Recursive) | ||||
.chain_err(|| format!("Can't watch the `templates` folder. Does it exist?"))?; | |||||
.chain_err(|| "Can't watch the `templates` folder. Does it exist?")?; | |||||
let ws_address = format!("{}:{}", interface, "1112"); | let ws_address = format!("{}:{}", interface, "1112"); | ||||
@@ -44,7 +44,7 @@ pub struct FrontMatter { | |||||
pub draft: Option<bool>, | pub draft: Option<bool>, | ||||
/// Only one category allowed | /// Only one category allowed | ||||
pub category: Option<String>, | pub category: Option<String>, | ||||
/// Whether to sort by "date", "order" or "none" | |||||
/// Whether to sort by "date", "order" or "none". Defaults to `none`. | |||||
#[serde(skip_serializing)] | #[serde(skip_serializing)] | ||||
pub sort_by: Option<SortBy>, | pub sort_by: Option<SortBy>, | ||||
/// Integer to use to order content. Lowest is at the bottom, highest first | /// Integer to use to order content. Lowest is at the bottom, highest first | ||||
@@ -52,6 +52,12 @@ pub struct FrontMatter { | |||||
/// Optional template, if we want to specify which template to render for that page | /// Optional template, if we want to specify which template to render for that page | ||||
#[serde(skip_serializing)] | #[serde(skip_serializing)] | ||||
pub template: Option<String>, | pub template: Option<String>, | ||||
/// How many pages to be displayed per paginated page. No pagination will happen if this isn't set | |||||
#[serde(skip_serializing)] | |||||
pub paginate_by: Option<usize>, | |||||
/// Path to be used by pagination: the page number will be appended after it. Defaults to `page`. | |||||
#[serde(skip_serializing)] | |||||
pub paginate_path: Option<String>, | |||||
/// Any extra parameter present in the front matter | /// Any extra parameter present in the front matter | ||||
pub extra: Option<HashMap<String, Value>>, | pub extra: Option<HashMap<String, Value>>, | ||||
} | } | ||||
@@ -62,7 +68,7 @@ impl FrontMatter { | |||||
bail!("Front matter of file is missing"); | bail!("Front matter of file is missing"); | ||||
} | } | ||||
let f: FrontMatter = match toml::from_str(toml) { | |||||
let mut f: FrontMatter = match toml::from_str(toml) { | |||||
Ok(d) => d, | Ok(d) => d, | ||||
Err(e) => bail!(e), | Err(e) => bail!(e), | ||||
}; | }; | ||||
@@ -79,6 +85,12 @@ impl FrontMatter { | |||||
} | } | ||||
} | } | ||||
if f.paginate_path.is_none() { | |||||
f.paginate_path = Some("page".to_string()); | |||||
} | |||||
Ok(f) | Ok(f) | ||||
} | } | ||||
@@ -106,6 +118,14 @@ impl FrontMatter { | |||||
None => SortBy::Date, | None => SortBy::Date, | ||||
} | } | ||||
} | } | ||||
/// Only applies to section, whether it is paginated or not. | |||||
pub fn is_paginated(&self) -> bool { | |||||
match self.paginate_by { | |||||
Some(v) => v > 0, | |||||
None => false | |||||
} | |||||
} | |||||
} | } | ||||
impl Default for FrontMatter { | impl Default for FrontMatter { | ||||
@@ -122,6 +142,8 @@ impl Default for FrontMatter { | |||||
sort_by: None, | sort_by: None, | ||||
order: None, | order: None, | ||||
template: None, | template: None, | ||||
paginate_by: None, | |||||
paginate_path: None, | |||||
extra: None, | extra: None, | ||||
} | } | ||||
} | } | ||||
@@ -27,6 +27,7 @@ mod front_matter; | |||||
mod site; | mod site; | ||||
mod markdown; | mod markdown; | ||||
mod section; | mod section; | ||||
mod pagination; | |||||
/// Additional filters for Tera | /// Additional filters for Tera | ||||
mod filters; | mod filters; | ||||
@@ -244,10 +244,10 @@ impl ser::Serialize for Page { | |||||
/// Any pages that doesn't have a date when the sorting method is date or order | /// Any pages that doesn't have a date when the sorting method is date or order | ||||
/// when the sorting method is order will be ignored. | /// when the sorting method is order will be ignored. | ||||
pub fn sort_pages(pages: Vec<Page>, section: Option<&Section>) -> (Vec<Page>, Vec<Page>) { | pub fn sort_pages(pages: Vec<Page>, section: Option<&Section>) -> (Vec<Page>, Vec<Page>) { | ||||
let sort_by = if let Some(ref sec) = section { | |||||
sec.meta.sort_by() | |||||
let sort_by = if let Some(s) = section { | |||||
s.meta.sort_by() | |||||
} else { | } else { | ||||
SortBy::Date | |||||
SortBy::None | |||||
}; | }; | ||||
match sort_by { | match sort_by { | ||||
@@ -390,9 +390,9 @@ mod tests { | |||||
]; | ]; | ||||
let (pages, _) = sort_pages(input, None); | let (pages, _) = sort_pages(input, None); | ||||
// Should be sorted by date | // Should be sorted by date | ||||
assert_eq!(pages[0].clone().meta.date.unwrap(), "2019-01-01"); | |||||
assert_eq!(pages[1].clone().meta.date.unwrap(), "2018-01-01"); | |||||
assert_eq!(pages[2].clone().meta.date.unwrap(), "2017-01-01"); | |||||
assert_eq!(pages[0].clone().meta.date.unwrap(), "2018-01-01"); | |||||
assert_eq!(pages[1].clone().meta.date.unwrap(), "2017-01-01"); | |||||
assert_eq!(pages[2].clone().meta.date.unwrap(), "2019-01-01"); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -0,0 +1,244 @@ | |||||
use std::collections::HashMap; | |||||
use tera::{Context, to_value, Value}; | |||||
use errors::{Result, ResultExt}; | |||||
use page::Page; | |||||
use section::Section; | |||||
use site::Site; | |||||
/// A list of all the pages in the paginator with their index and links | |||||
#[derive(Clone, Debug, PartialEq, Serialize)] | |||||
pub struct Pager<'a> { | |||||
/// The page number in the paginator (1-indexed) | |||||
index: usize, | |||||
/// Permalink to that page | |||||
permalink: String, | |||||
/// Path to that page | |||||
path: String, | |||||
/// All pages for the pager | |||||
pages: Vec<&'a Page> | |||||
} | |||||
impl<'a> Pager<'a> { | |||||
fn new(index: usize, pages: Vec<&'a Page>, permalink: String, path: String) -> Pager<'a> { | |||||
Pager { | |||||
index: index, | |||||
permalink: permalink, | |||||
path: path, | |||||
pages: pages, | |||||
} | |||||
} | |||||
} | |||||
#[derive(Clone, Debug, PartialEq)] | |||||
pub struct Paginator<'a> { | |||||
/// All pages in the section | |||||
all_pages: &'a [Page], | |||||
/// Pages split in chunks of `paginate_by` | |||||
pub pagers: Vec<Pager<'a>>, | |||||
/// How many content pages on a paginated page at max | |||||
paginate_by: usize, | |||||
/// The section struct we're building the paginator for | |||||
section: &'a Section, | |||||
} | |||||
impl<'a> Paginator<'a> { | |||||
pub fn new(all_pages: &'a [Page], section: &'a Section) -> Paginator<'a> { | |||||
let paginate_by = section.meta.paginate_by.unwrap(); | |||||
let paginate_path = match section.meta.paginate_path { | |||||
Some(ref p) => p, | |||||
None => unreachable!(), | |||||
}; | |||||
let mut pages = vec![]; | |||||
let mut current_page = vec![]; | |||||
for page in all_pages { | |||||
current_page.push(page); | |||||
if current_page.len() == paginate_by { | |||||
pages.push(current_page); | |||||
current_page = vec![]; | |||||
} | |||||
} | |||||
if !current_page.is_empty() { | |||||
pages.push(current_page); | |||||
} | |||||
let mut pagers = vec![]; | |||||
for index in 0..pages.len() { | |||||
// First page has no pagination path | |||||
if index == 0 { | |||||
pagers.push(Pager::new(1, pages[index].clone(), section.permalink.clone(), section.path.clone())); | |||||
continue; | |||||
} | |||||
let page_path = format!("{}/{}", paginate_path, index + 1); | |||||
let permalink = if section.permalink.ends_with('/') { | |||||
format!("{}{}", section.permalink, page_path) | |||||
} else { | |||||
format!("{}/{}", section.permalink, page_path) | |||||
}; | |||||
pagers.push(Pager::new( | |||||
index + 1, | |||||
pages[index].clone(), | |||||
permalink, | |||||
if section.is_index() { format!("{}", page_path) } else { format!("{}/{}", section.path, page_path) } | |||||
)); | |||||
} | |||||
//println!("{:?}", pagers); | |||||
Paginator { | |||||
all_pages: all_pages, | |||||
pagers: pagers, | |||||
paginate_by: paginate_by, | |||||
section: section, | |||||
} | |||||
} | |||||
pub fn build_paginator_context(&self, current_pager: &Pager) -> HashMap<&str, Value> { | |||||
let mut paginator = HashMap::new(); | |||||
// the pager index is 1-indexed so we want a 0-indexed one for indexing there | |||||
let pager_index = current_pager.index - 1; | |||||
// Global variables | |||||
paginator.insert("paginate_by", to_value(self.paginate_by).unwrap()); | |||||
paginator.insert("first", to_value(&self.section.permalink).unwrap()); | |||||
let last_pager = &self.pagers[self.pagers.len() - 1]; | |||||
paginator.insert("last", to_value(&last_pager.permalink).unwrap()); | |||||
paginator.insert("pagers", to_value(&self.pagers).unwrap()); | |||||
// Variables for this specific page | |||||
if pager_index > 0 { | |||||
let prev_pager = &self.pagers[pager_index - 1]; | |||||
paginator.insert("previous", to_value(&prev_pager.permalink).unwrap()); | |||||
} else { | |||||
paginator.insert("previous", to_value::<Option<()>>(None).unwrap()); | |||||
} | |||||
if pager_index < self.pagers.len() - 1 { | |||||
let next_pager = &self.pagers[pager_index + 1]; | |||||
paginator.insert("next", to_value(&next_pager.permalink).unwrap()); | |||||
} else { | |||||
paginator.insert("next", to_value::<Option<()>>(None).unwrap()); | |||||
} | |||||
paginator.insert("pages", to_value(¤t_pager.pages).unwrap()); | |||||
paginator.insert("current_index", to_value(current_pager.index).unwrap()); | |||||
paginator | |||||
} | |||||
pub fn render_pager(&self, pager: &Pager, site: &Site) -> Result<String> { | |||||
let mut context = Context::new(); | |||||
context.add("config", &site.config); | |||||
context.add("section", self.section); | |||||
context.add("current_url", &pager.permalink); | |||||
context.add("current_path", &pager.path); | |||||
context.add("paginator", &self.build_paginator_context(pager)); | |||||
if self.section.is_index() { | |||||
context.add("section", &site.sections); | |||||
} | |||||
site.tera.render(&self.section.get_template_name(), &context) | |||||
.chain_err(|| format!("Failed to render pager {} of section '{}'", pager.index, self.section.file_path.display())) | |||||
} | |||||
} | |||||
#[cfg(test)] | |||||
mod tests { | |||||
use tera::{to_value}; | |||||
use front_matter::FrontMatter; | |||||
use page::Page; | |||||
use section::Section; | |||||
use super::{Paginator}; | |||||
fn create_section(is_index: bool) -> Section { | |||||
let mut f = FrontMatter::default(); | |||||
f.paginate_by = Some(2); | |||||
f.paginate_path = Some("page".to_string()); | |||||
let mut s = Section::new("content/_index.md", f); | |||||
if !is_index { | |||||
s.path = "posts".to_string(); | |||||
s.permalink = "https://vincent.is/posts".to_string(); | |||||
s.components = vec!["posts".to_string()]; | |||||
} else { | |||||
s.permalink = "https://vincent.is".to_string(); | |||||
} | |||||
s | |||||
} | |||||
#[test] | |||||
fn test_can_create_paginator() { | |||||
let pages = vec![ | |||||
Page::new(FrontMatter::default()), | |||||
Page::new(FrontMatter::default()), | |||||
Page::new(FrontMatter::default()), | |||||
]; | |||||
let section = create_section(false); | |||||
let paginator = Paginator::new(pages.as_slice(), §ion); | |||||
assert_eq!(paginator.pagers.len(), 2); | |||||
assert_eq!(paginator.pagers[0].index, 1); | |||||
assert_eq!(paginator.pagers[0].pages.len(), 2); | |||||
assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/posts"); | |||||
assert_eq!(paginator.pagers[0].path, "posts"); | |||||
assert_eq!(paginator.pagers[1].index, 2); | |||||
assert_eq!(paginator.pagers[1].pages.len(), 1); | |||||
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/posts/page/2"); | |||||
assert_eq!(paginator.pagers[1].path, "posts/page/2"); | |||||
} | |||||
#[test] | |||||
fn test_can_create_paginator_for_index() { | |||||
let pages = vec![ | |||||
Page::new(FrontMatter::default()), | |||||
Page::new(FrontMatter::default()), | |||||
Page::new(FrontMatter::default()), | |||||
]; | |||||
let section = create_section(true); | |||||
let paginator = Paginator::new(pages.as_slice(), §ion); | |||||
assert_eq!(paginator.pagers.len(), 2); | |||||
assert_eq!(paginator.pagers[0].index, 1); | |||||
assert_eq!(paginator.pagers[0].pages.len(), 2); | |||||
assert_eq!(paginator.pagers[0].permalink, "https://vincent.is"); | |||||
assert_eq!(paginator.pagers[0].path, ""); | |||||
assert_eq!(paginator.pagers[1].index, 2); | |||||
assert_eq!(paginator.pagers[1].pages.len(), 1); | |||||
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/page/2"); | |||||
assert_eq!(paginator.pagers[1].path, "page/2"); | |||||
} | |||||
#[test] | |||||
fn test_can_build_paginator_context() { | |||||
let pages = vec![ | |||||
Page::new(FrontMatter::default()), | |||||
Page::new(FrontMatter::default()), | |||||
Page::new(FrontMatter::default()), | |||||
]; | |||||
let section = create_section(false); | |||||
let paginator = Paginator::new(pages.as_slice(), §ion); | |||||
assert_eq!(paginator.pagers.len(), 2); | |||||
let context = paginator.build_paginator_context(&paginator.pagers[0]); | |||||
assert_eq!(context["paginate_by"], to_value(2).unwrap()); | |||||
assert_eq!(context["first"], to_value("https://vincent.is/posts").unwrap()); | |||||
assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2").unwrap()); | |||||
assert_eq!(context["previous"], to_value::<Option<()>>(None).unwrap()); | |||||
assert_eq!(context["next"], to_value("https://vincent.is/posts/page/2").unwrap()); | |||||
assert_eq!(context["current_index"], to_value(1).unwrap()); | |||||
let context = paginator.build_paginator_context(&paginator.pagers[1]); | |||||
assert_eq!(context["paginate_by"], to_value(2).unwrap()); | |||||
assert_eq!(context["first"], to_value("https://vincent.is/posts").unwrap()); | |||||
assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2").unwrap()); | |||||
assert_eq!(context["next"], to_value::<Option<()>>(None).unwrap()); | |||||
assert_eq!(context["previous"], to_value("https://vincent.is/posts").unwrap()); | |||||
assert_eq!(context["current_index"], to_value(2).unwrap()); | |||||
} | |||||
} |
@@ -8,7 +8,7 @@ use config::Config; | |||||
use front_matter::{FrontMatter, split_content}; | use front_matter::{FrontMatter, split_content}; | ||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use utils::{read_file, find_content_components}; | use utils::{read_file, find_content_components}; | ||||
use page::Page; | |||||
use page::{Page, sort_pages}; | |||||
#[derive(Clone, Debug, PartialEq)] | #[derive(Clone, Debug, PartialEq)] | ||||
@@ -34,7 +34,9 @@ pub struct Section { | |||||
} | } | ||||
impl Section { | impl Section { | ||||
pub fn new(file_path: &Path, meta: FrontMatter) -> Section { | |||||
pub fn new<P: AsRef<Path>>(file_path: P, meta: FrontMatter) -> Section { | |||||
let file_path = file_path.as_ref(); | |||||
Section { | Section { | ||||
file_path: file_path.to_path_buf(), | file_path: file_path.to_path_buf(), | ||||
relative_path: "".to_string(), | relative_path: "".to_string(), | ||||
@@ -54,8 +56,11 @@ impl Section { | |||||
section.components = find_content_components(§ion.file_path); | section.components = find_content_components(§ion.file_path); | ||||
section.path = section.components.join("/"); | section.path = section.components.join("/"); | ||||
section.permalink = config.make_permalink(§ion.path); | section.permalink = config.make_permalink(§ion.path); | ||||
section.relative_path = format!("{}/_index.md", section.components.join("/")); | |||||
if section.components.len() == 0 { | |||||
section.relative_path = "_index.md".to_string(); | |||||
} else { | |||||
section.relative_path = format!("{}/_index.md", section.components.join("/")); | |||||
} | |||||
Ok(section) | Ok(section) | ||||
} | } | ||||
@@ -68,15 +73,22 @@ impl Section { | |||||
Section::parse(path, &content, config) | Section::parse(path, &content, config) | ||||
} | } | ||||
pub fn get_template_name(&self) -> String { | |||||
match self.meta.template { | |||||
Some(ref l) => l.to_string(), | |||||
None => { | |||||
if self.is_index() { | |||||
return "index.html".to_string(); | |||||
} | |||||
"section.html".to_string() | |||||
}, | |||||
} | |||||
} | |||||
/// Renders the page using the default layout, unless specified in front-matter | /// Renders the page using the default layout, unless specified in front-matter | ||||
pub fn render_html(&self, tera: &Tera, config: &Config) -> Result<String> { | pub fn render_html(&self, tera: &Tera, config: &Config) -> Result<String> { | ||||
let tpl_name = match self.meta.template { | |||||
Some(ref l) => l.to_string(), | |||||
None => "section.html".to_string() | |||||
}; | |||||
let tpl_name = self.get_template_name(); | |||||
// TODO: create a helper to create context to ensure all contexts | |||||
// have the same names | |||||
let mut context = Context::new(); | let mut context = Context::new(); | ||||
context.add("config", config); | context.add("config", config); | ||||
context.add("section", self); | context.add("section", self); | ||||
@@ -86,6 +98,10 @@ impl Section { | |||||
tera.render(&tpl_name, &context) | tera.render(&tpl_name, &context) | ||||
.chain_err(|| format!("Failed to render section '{}'", self.file_path.display())) | .chain_err(|| format!("Failed to render section '{}'", self.file_path.display())) | ||||
} | } | ||||
pub fn is_index(&self) -> bool { | |||||
self.components.len() == 0 | |||||
} | |||||
} | } | ||||
impl ser::Serialize for Section { | impl ser::Serialize for Section { | ||||
@@ -95,7 +111,8 @@ impl ser::Serialize for Section { | |||||
state.serialize_field("description", &self.meta.description)?; | state.serialize_field("description", &self.meta.description)?; | ||||
state.serialize_field("path", &format!("/{}", self.path))?; | state.serialize_field("path", &format!("/{}", self.path))?; | ||||
state.serialize_field("permalink", &self.permalink)?; | state.serialize_field("permalink", &self.permalink)?; | ||||
state.serialize_field("pages", &self.pages)?; | |||||
let (sorted_pages, _) = sort_pages(self.pages.clone(), Some(self)); | |||||
state.serialize_field("pages", &sorted_pages)?; | |||||
state.serialize_field("subsections", &self.subsections)?; | state.serialize_field("subsections", &self.subsections)?; | ||||
state.end() | state.end() | ||||
} | } | ||||
@@ -11,6 +11,7 @@ use walkdir::WalkDir; | |||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use config::{Config, get_config}; | use config::{Config, get_config}; | ||||
use page::{Page, populate_previous_and_next_pages, sort_pages}; | use page::{Page, populate_previous_and_next_pages, sort_pages}; | ||||
use pagination::Paginator; | |||||
use utils::{create_file, create_directory}; | use utils::{create_file, create_directory}; | ||||
use section::{Section}; | use section::{Section}; | ||||
use filters; | use filters; | ||||
@@ -28,11 +29,23 @@ lazy_static! { | |||||
("shortcodes/youtube.html", include_str!("templates/shortcodes/youtube.html")), | ("shortcodes/youtube.html", include_str!("templates/shortcodes/youtube.html")), | ||||
("shortcodes/vimeo.html", include_str!("templates/shortcodes/vimeo.html")), | ("shortcodes/vimeo.html", include_str!("templates/shortcodes/vimeo.html")), | ||||
("shortcodes/gist.html", include_str!("templates/shortcodes/gist.html")), | ("shortcodes/gist.html", include_str!("templates/shortcodes/gist.html")), | ||||
("internal/alias.html", include_str!("templates/internal/alias.html")), | |||||
]).unwrap(); | ]).unwrap(); | ||||
tera | tera | ||||
}; | }; | ||||
} | } | ||||
/// Renders the `internal/alias.html` template that will redirect | |||||
/// via refresh to the url given | |||||
fn render_alias(url: &str, tera: &Tera) -> Result<String> { | |||||
let mut context = Context::new(); | |||||
context.add("url", &url); | |||||
tera.render("internal/alias.html", &context) | |||||
.chain_err(|| format!("Failed to render alias for '{}'", url)) | |||||
} | |||||
#[derive(Debug, PartialEq)] | #[derive(Debug, PartialEq)] | ||||
enum RenderList { | enum RenderList { | ||||
@@ -201,7 +214,7 @@ impl Site { | |||||
for (parent_path, section) in &mut self.sections { | for (parent_path, section) in &mut self.sections { | ||||
// TODO: avoid this clone | // TODO: avoid this clone | ||||
let (sorted_pages, _) = sort_pages(section.pages.clone(), Some(§ion)); | |||||
let (sorted_pages, _) = sort_pages(section.pages.clone(), Some(section)); | |||||
section.pages = sorted_pages; | section.pages = sorted_pages; | ||||
match grandparent_paths.get(parent_path) { | match grandparent_paths.get(parent_path) { | ||||
@@ -303,22 +316,21 @@ impl Site { | |||||
// probably just an update so just re-parse that page | // probably just an update so just re-parse that page | ||||
self.add_page_and_render(path)?; | self.add_page_and_render(path)?; | ||||
} | } | ||||
} else { | |||||
} else if is_section { | |||||
// File doesn't exist -> a deletion so we remove it from everything | // File doesn't exist -> a deletion so we remove it from everything | ||||
if is_section { | |||||
if !is_index_section { | |||||
let relative_path = self.sections[path].relative_path.clone(); | |||||
self.sections.remove(path); | |||||
self.permalinks.remove(&relative_path); | |||||
} else { | |||||
self.index = None; | |||||
} | |||||
} else { | |||||
let relative_path = self.pages[path].relative_path.clone(); | |||||
self.pages.remove(path); | |||||
if !is_index_section { | |||||
let relative_path = self.sections[path].relative_path.clone(); | |||||
self.sections.remove(path); | |||||
self.permalinks.remove(&relative_path); | self.permalinks.remove(&relative_path); | ||||
} else { | |||||
self.index = None; | |||||
} | } | ||||
} else { | |||||
let relative_path = self.pages[path].relative_path.clone(); | |||||
self.pages.remove(path); | |||||
self.permalinks.remove(&relative_path); | |||||
} | } | ||||
self.populate_sections(); | self.populate_sections(); | ||||
self.populate_tags_and_categories(); | self.populate_tags_and_categories(); | ||||
self.build() | self.build() | ||||
@@ -333,6 +345,7 @@ impl Site { | |||||
} | } | ||||
} | } | ||||
/// Renders a single content page | |||||
pub fn render_page(&self, page: &Page) -> Result<()> { | pub fn render_page(&self, page: &Page) -> Result<()> { | ||||
let public = self.output_path.clone(); | let public = self.output_path.clone(); | ||||
if !public.exists() { | if !public.exists() { | ||||
@@ -366,6 +379,7 @@ impl Site { | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
/// Renders all content, categories, tags and index pages | |||||
pub fn build_pages(&self) -> Result<()> { | pub fn build_pages(&self) -> Result<()> { | ||||
let public = self.output_path.clone(); | let public = self.output_path.clone(); | ||||
if !public.exists() { | if !public.exists() { | ||||
@@ -374,7 +388,7 @@ impl Site { | |||||
// Sort the pages first | // Sort the pages first | ||||
// TODO: avoid the clone() | // TODO: avoid the clone() | ||||
let (mut sorted_pages, cannot_sort_pages) = sort_pages(self.pages.values().map(|p| p.clone()).collect(), self.index.as_ref()); | |||||
let (mut sorted_pages, cannot_sort_pages) = sort_pages(self.pages.values().cloned().collect(), self.index.as_ref()); | |||||
sorted_pages = populate_previous_and_next_pages(&sorted_pages); | sorted_pages = populate_previous_and_next_pages(&sorted_pages); | ||||
for page in &sorted_pages { | for page in &sorted_pages { | ||||
@@ -393,15 +407,26 @@ impl Site { | |||||
} | } | ||||
// And finally the index page | // And finally the index page | ||||
let mut context = Context::new(); | |||||
let mut rendered_index = false; | |||||
// Try to render the index as a paginated page first if needed | |||||
if let Some(ref i) = self.index { | |||||
if i.meta.is_paginated() { | |||||
self.render_paginated(&self.output_path, i)?; | |||||
rendered_index = true; | |||||
} | |||||
} | |||||
context.add("pages", &sorted_pages); | |||||
context.add("sections", &self.sections.values().collect::<Vec<&Section>>()); | |||||
context.add("config", &self.config); | |||||
context.add("current_url", &self.config.base_url); | |||||
context.add("current_path", &""); | |||||
let index = self.tera.render("index.html", &context)?; | |||||
create_file(public.join("index.html"), &self.inject_livereload(index))?; | |||||
// Otherwise render the default index page | |||||
if !rendered_index { | |||||
let mut context = Context::new(); | |||||
context.add("pages", &sorted_pages); | |||||
context.add("sections", &self.sections.values().collect::<Vec<&Section>>()); | |||||
context.add("config", &self.config); | |||||
context.add("current_url", &self.config.base_url); | |||||
context.add("current_path", &""); | |||||
let index = self.tera.render("index.html", &context)?; | |||||
create_file(public.join("index.html"), &self.inject_livereload(index))?; | |||||
} | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
@@ -422,6 +447,7 @@ impl Site { | |||||
self.copy_static_directory() | self.copy_static_directory() | ||||
} | } | ||||
/// Renders robots.txt | |||||
fn render_robots(&self) -> Result<()> { | fn render_robots(&self) -> Result<()> { | ||||
create_file( | create_file( | ||||
self.output_path.join("robots.txt"), | self.output_path.join("robots.txt"), | ||||
@@ -580,8 +606,46 @@ impl Site { | |||||
} | } | ||||
} | } | ||||
let output = section.render_html(&self.tera, &self.config)?; | |||||
create_file(output_path.join("index.html"), &self.inject_livereload(output))?; | |||||
if section.meta.is_paginated() { | |||||
self.render_paginated(&output_path, section)?; | |||||
} else { | |||||
let output = section.render_html(&self.tera, &self.config)?; | |||||
create_file(output_path.join("index.html"), &self.inject_livereload(output))?; | |||||
} | |||||
} | |||||
Ok(()) | |||||
} | |||||
/// Renders a list of pages when the section/index is wanting pagination. | |||||
fn render_paginated(&self, output_path: &Path, section: &Section) -> Result<()> { | |||||
let paginate_path = match section.meta.paginate_path { | |||||
Some(ref s) => s.clone(), | |||||
None => unreachable!() | |||||
}; | |||||
// this will sort too many times! | |||||
// TODO: make sorting happen once for everything so we don't need to sort all the time | |||||
let sorted_pages = if section.is_index() { | |||||
sort_pages(self.pages.values().cloned().collect(), self.index.as_ref()).0 | |||||
} else { | |||||
sort_pages(section.pages.clone(), Some(section)).0 | |||||
}; | |||||
let paginator = Paginator::new(&sorted_pages, section); | |||||
for (i, pager) in paginator.pagers.iter().enumerate() { | |||||
let folder_path = output_path.join(&paginate_path); | |||||
let page_path = folder_path.join(&format!("{}", i + 1)); | |||||
create_directory(&folder_path)?; | |||||
create_directory(&page_path)?; | |||||
let output = paginator.render_pager(pager, self)?; | |||||
if i > 0 { | |||||
create_file(page_path.join("index.html"), &self.inject_livereload(output))?; | |||||
} else { | |||||
create_file(output_path.join("index.html"), &self.inject_livereload(output))?; | |||||
create_file(page_path.join("index.html"), &render_alias(§ion.permalink, &self.tera)?)?; | |||||
} | |||||
} | } | ||||
Ok(()) | Ok(()) | ||||
@@ -0,0 +1,8 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<link rel="canonical" href="{{ url | safe }}" /> | |||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> | |||||
<meta http-equiv="refresh" content="0;url={{ url | safe }}" /> | |||||
</head> | |||||
</html> |
@@ -1,4 +1,3 @@ | |||||
+++ | +++ | ||||
title = "Home" | |||||
description = "" | |||||
title = "Index" | |||||
+++ | +++ |
@@ -0,0 +1,33 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="{{ config.language_code }}"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<meta name="apple-mobile-web-app-capable" content="yes"> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
<meta name="description" content="{{ config.description }}"> | |||||
<meta name="author" content="{{ config.extra.author.name }}"> | |||||
<link href="https://fonts.googleapis.com/css?family=Fira+Mono|Fira+Sans|Merriweather" rel="stylesheet"> | |||||
<link href="{{ config.base_url }}/site.css" rel="stylesheet"> | |||||
<title>{{ config.title }}</title> | |||||
</head> | |||||
<body> | |||||
<div class="content"> | |||||
{% block content %} | |||||
<div class="list-posts"> | |||||
{% for page in paginator.pages %} | |||||
<article> | |||||
<h3 class="post__title"><a href="{{ page.permalink }}">{{ page.title }}</a></h3> | |||||
</article> | |||||
{% endfor %} | |||||
{% if paginator.previous %}has_prev{% endif %} | |||||
{% if paginator.next %}has_next{% endif %} | |||||
Num pages: {{ paginator.pagers | length }} | |||||
Current index: {{ paginator.current_index }} | |||||
First: {{ paginator.first | safe }} | |||||
Last: {{ paginator.last | safe }} | |||||
</div> | |||||
{% endblock content %} | |||||
</div> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,17 @@ | |||||
{% extends "index.html" %} | |||||
{% block content %} | |||||
{% for page in paginator.pages %} | |||||
{{page.title}} | |||||
{% endfor %} | |||||
{% for pager in paginator.pagers %} | |||||
{{pager.index}}: {{pager.path | safe }} | |||||
{% endfor %} | |||||
Num pages: {{ paginator.pages | length }} | |||||
Page size: {{ paginator.paginate_by }} | |||||
Current index: {{ paginator.current_index }} | |||||
First: {{ paginator.first | safe }} | |||||
Last: {{ paginator.last | safe }} | |||||
{% if paginator.previous %}has_prev{% endif%} | |||||
{% if paginator.next %}has_next{% endif%} | |||||
{% endblock content %} |
@@ -43,7 +43,6 @@ fn test_can_parse_site() { | |||||
// And that the sections are correct | // And that the sections are correct | ||||
let posts_section = &site.sections[&posts_path]; | let posts_section = &site.sections[&posts_path]; | ||||
assert_eq!(posts_section.subsections.len(), 1); | assert_eq!(posts_section.subsections.len(), 1); | ||||
//println!("{:#?}", posts_section.pages); | |||||
assert_eq!(posts_section.pages.len(), 4); | assert_eq!(posts_section.pages.len(), 4); | ||||
let tutorials_section = &site.sections[&posts_path.join("tutorials")]; | let tutorials_section = &site.sections[&posts_path.join("tutorials")]; | ||||
@@ -82,6 +81,7 @@ macro_rules! file_contains { | |||||
let mut file = File::open(&path).unwrap(); | let mut file = File::open(&path).unwrap(); | ||||
let mut s = String::new(); | let mut s = String::new(); | ||||
file.read_to_string(&mut s).unwrap(); | file.read_to_string(&mut s).unwrap(); | ||||
println!("{}", s); | |||||
s.contains($text) | s.contains($text) | ||||
} | } | ||||
} | } | ||||
@@ -287,3 +287,98 @@ fn test_can_build_site_and_insert_anchor_links() { | |||||
// anchor link inserted | // anchor link inserted | ||||
assert!(file_contains!(public, "posts/something-else/index.html", "<h1 id=\"title\"><a class=\"anchor\" href=\"#title\"")); | assert!(file_contains!(public, "posts/something-else/index.html", "<h1 id=\"title\"><a class=\"anchor\" href=\"#title\"")); | ||||
} | } | ||||
#[test] | |||||
fn test_can_build_site_with_pagination_for_section() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
path.push("test_site"); | |||||
let mut site = Site::new(&path, "config.toml").unwrap(); | |||||
site.load().unwrap(); | |||||
for section in site.sections.values_mut(){ | |||||
section.meta.paginate_by = Some(2); | |||||
section.meta.template = Some("section_paginated.html".to_string()); | |||||
} | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
let public = &tmp_dir.path().join("public"); | |||||
site.set_output_path(&public); | |||||
site.build().unwrap(); | |||||
assert!(Path::new(&public).exists()); | |||||
assert!(file_exists!(public, "index.html")); | |||||
assert!(file_exists!(public, "sitemap.xml")); | |||||
assert!(file_exists!(public, "robots.txt")); | |||||
assert!(file_exists!(public, "a-fixed-url/index.html")); | |||||
assert!(file_exists!(public, "posts/python/index.html")); | |||||
assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html")); | |||||
assert!(file_exists!(public, "posts/with-assets/index.html")); | |||||
// Sections | |||||
assert!(file_exists!(public, "posts/index.html")); | |||||
// And pagination! | |||||
assert!(file_exists!(public, "posts/page/1/index.html")); | |||||
// should redirect to posts/ | |||||
assert!(file_contains!( | |||||
public, | |||||
"posts/page/1/index.html", | |||||
"http-equiv=\"refresh\" content=\"0;url=https://replace-this-with-your-url.com/posts\"" | |||||
)); | |||||
assert!(file_contains!(public, "posts/index.html", "Num pages: 2")); | |||||
assert!(file_contains!(public, "posts/index.html", "Page size: 2")); | |||||
assert!(file_contains!(public, "posts/index.html", "Current index: 1")); | |||||
assert!(file_contains!(public, "posts/index.html", "has_next")); | |||||
assert!(file_contains!(public, "posts/index.html", "First: https://replace-this-with-your-url.com/posts")); | |||||
assert!(file_contains!(public, "posts/index.html", "Last: https://replace-this-with-your-url.com/posts/page/2")); | |||||
assert_eq!(file_contains!(public, "posts/index.html", "has_prev"), false); | |||||
assert!(file_exists!(public, "posts/page/2/index.html")); | |||||
assert!(file_contains!(public, "posts/page/2/index.html", "Num pages: 2")); | |||||
assert!(file_contains!(public, "posts/page/2/index.html", "Page size: 2")); | |||||
assert!(file_contains!(public, "posts/page/2/index.html", "Current index: 2")); | |||||
assert!(file_contains!(public, "posts/page/2/index.html", "has_prev")); | |||||
assert_eq!(file_contains!(public, "posts/page/2/index.html", "has_next"), false); | |||||
assert!(file_contains!(public, "posts/page/2/index.html", "First: https://replace-this-with-your-url.com/posts")); | |||||
assert!(file_contains!(public, "posts/page/2/index.html", "Last: https://replace-this-with-your-url.com/posts/page/2")); | |||||
} | |||||
#[test] | |||||
fn test_can_build_site_with_pagination_for_index() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
path.push("test_site"); | |||||
let mut site = Site::new(&path, "config.toml").unwrap(); | |||||
site.load().unwrap(); | |||||
let mut index = site.index.unwrap(); | |||||
index.meta.paginate_by = Some(2); | |||||
index.meta.template = Some("index_paginated.html".to_string()); | |||||
site.index = Some(index); | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
let public = &tmp_dir.path().join("public"); | |||||
site.set_output_path(&public); | |||||
site.build().unwrap(); | |||||
assert!(Path::new(&public).exists()); | |||||
assert!(file_exists!(public, "index.html")); | |||||
assert!(file_exists!(public, "sitemap.xml")); | |||||
assert!(file_exists!(public, "robots.txt")); | |||||
assert!(file_exists!(public, "a-fixed-url/index.html")); | |||||
assert!(file_exists!(public, "posts/python/index.html")); | |||||
assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html")); | |||||
assert!(file_exists!(public, "posts/with-assets/index.html")); | |||||
// And pagination! | |||||
assert!(file_exists!(public, "page/1/index.html")); | |||||
// should redirect to index | |||||
assert!(file_contains!( | |||||
public, | |||||
"page/1/index.html", | |||||
"http-equiv=\"refresh\" content=\"0;url=https://replace-this-with-your-url.com/\"" | |||||
)); | |||||
assert!(file_contains!(public, "index.html", "Num pages: 2")); | |||||
assert!(file_contains!(public, "index.html", "Current index: 1")); | |||||
assert!(file_contains!(public, "index.html", "has_next")); | |||||
assert!(file_contains!(public, "index.html", "First: https://replace-this-with-your-url.com/")); | |||||
assert!(file_contains!(public, "index.html", "Last: https://replace-this-with-your-url.com/page/2")); | |||||
assert_eq!(file_contains!(public, "index.html", "has_prev"), false); | |||||
} |