You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lib.rs 32KB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
6 years ago
7 years ago
6 years ago
7 years ago
6 years ago
6 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  1. extern crate glob;
  2. extern crate rayon;
  3. extern crate serde;
  4. extern crate tera;
  5. #[macro_use]
  6. extern crate serde_derive;
  7. extern crate sass_rs;
  8. #[macro_use]
  9. extern crate errors;
  10. extern crate config;
  11. extern crate front_matter;
  12. extern crate imageproc;
  13. extern crate library;
  14. extern crate search;
  15. extern crate templates;
  16. extern crate utils;
  17. #[cfg(test)]
  18. extern crate tempfile;
  19. use std::collections::HashMap;
  20. use std::fs::{copy, create_dir_all, remove_dir_all};
  21. use std::path::{Path, PathBuf};
  22. use std::sync::{Arc, Mutex};
  23. use glob::glob;
  24. use rayon::prelude::*;
  25. use sass_rs::{compile_file, Options as SassOptions, OutputStyle};
  26. use tera::{Context, Tera};
  27. use config::{get_config, Config};
  28. use errors::{Result, ResultExt};
  29. use front_matter::InsertAnchor;
  30. use library::{
  31. find_taxonomies, sort_actual_pages_by_date, Library, Page, Paginator, Section, Taxonomy,
  32. };
  33. use templates::{global_fns, render_redirect_template, ZOLA_TERA};
  34. use utils::fs::{copy_directory, create_directory, create_file, ensure_directory_exists};
  35. use utils::net::get_available_port;
  36. use utils::templates::{render_template, rewrite_theme_paths};
  37. /// The sitemap only needs links and potentially date so we trim down
  38. /// all pages to only that
  39. #[derive(Debug, Serialize)]
  40. struct SitemapEntry {
  41. permalink: String,
  42. date: Option<String>,
  43. }
  44. impl SitemapEntry {
  45. pub fn new(permalink: String, date: Option<String>) -> SitemapEntry {
  46. SitemapEntry { permalink, date }
  47. }
  48. }
  49. #[derive(Debug)]
  50. pub struct Site {
  51. /// The base path of the zola site
  52. pub base_path: PathBuf,
  53. /// The parsed config for the site
  54. pub config: Config,
  55. pub tera: Tera,
  56. imageproc: Arc<Mutex<imageproc::Processor>>,
  57. // the live reload port to be used if there is one
  58. pub live_reload: Option<u16>,
  59. pub output_path: PathBuf,
  60. content_path: PathBuf,
  61. pub static_path: PathBuf,
  62. pub taxonomies: Vec<Taxonomy>,
  63. /// A map of all .md files (section and pages) and their permalink
  64. /// We need that if there are relative links in the content that need to be resolved
  65. pub permalinks: HashMap<String, String>,
  66. /// Contains all pages and sections of the site
  67. pub library: Library,
  68. }
  69. impl Site {
  70. /// Parse a site at the given path. Defaults to the current dir
  71. /// Passing in a path is only used in tests
  72. pub fn new<P: AsRef<Path>>(path: P, config_file: &str) -> Result<Site> {
  73. let path = path.as_ref();
  74. let mut config = get_config(path, config_file);
  75. config.load_extra_syntaxes(path)?;
  76. let tpl_glob =
  77. format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*.*ml");
  78. // Only parsing as we might be extending templates from themes and that would error
  79. // as we haven't loaded them yet
  80. let mut tera = Tera::parse(&tpl_glob).chain_err(|| "Error parsing templates")?;
  81. if let Some(theme) = config.theme.clone() {
  82. // Grab data from the extra section of the theme
  83. config.merge_with_theme(&path.join("themes").join(&theme).join("theme.toml"))?;
  84. // Test that the templates folder exist for that theme
  85. let theme_path = path.join("themes").join(&theme);
  86. if !theme_path.join("templates").exists() {
  87. bail!("Theme `{}` is missing a templates folder", theme);
  88. }
  89. let theme_tpl_glob = format!(
  90. "{}/{}",
  91. path.to_string_lossy().replace("\\", "/"),
  92. format!("themes/{}/templates/**/*.*ml", theme)
  93. );
  94. let mut tera_theme =
  95. Tera::parse(&theme_tpl_glob).chain_err(|| "Error parsing templates from themes")?;
  96. rewrite_theme_paths(&mut tera_theme, &theme);
  97. // TODO: same as below
  98. if theme_path.join("templates").join("robots.txt").exists() {
  99. tera_theme
  100. .add_template_file(theme_path.join("templates").join("robots.txt"), None)?;
  101. }
  102. tera_theme.build_inheritance_chains()?;
  103. tera.extend(&tera_theme)?;
  104. }
  105. tera.extend(&ZOLA_TERA)?;
  106. // the `extend` above already does it but hey
  107. tera.build_inheritance_chains()?;
  108. // TODO: Tera doesn't use globset right now so we can load the robots.txt as part
  109. // of the glob above, therefore we load it manually if it exists.
  110. if path.join("templates").join("robots.txt").exists() {
  111. tera.add_template_file(path.join("templates").join("robots.txt"), Some("robots.txt"))?;
  112. }
  113. let content_path = path.join("content");
  114. let static_path = path.join("static");
  115. let imageproc =
  116. imageproc::Processor::new(content_path.clone(), &static_path, &config.base_url);
  117. let site = Site {
  118. base_path: path.to_path_buf(),
  119. config,
  120. tera,
  121. imageproc: Arc::new(Mutex::new(imageproc)),
  122. live_reload: None,
  123. output_path: path.join("public"),
  124. content_path,
  125. static_path,
  126. taxonomies: Vec::new(),
  127. permalinks: HashMap::new(),
  128. // We will allocate it properly later on
  129. library: Library::new(0, 0),
  130. };
  131. Ok(site)
  132. }
  133. /// The index section is ALWAYS at that path
  134. pub fn index_section_path(&self) -> PathBuf {
  135. self.content_path.join("_index.md")
  136. }
  137. /// We avoid the port the server is going to use as it's not bound yet
  138. /// when calling this function and we could end up having tried to bind
  139. /// both http and websocket server to the same port
  140. pub fn enable_live_reload(&mut self, port_to_avoid: u16) {
  141. self.live_reload = get_available_port(port_to_avoid);
  142. }
  143. /// Get all the orphan (== without section) pages in the site
  144. pub fn get_all_orphan_pages(&self) -> Vec<&Page> {
  145. self.library.get_all_orphan_pages()
  146. }
  147. pub fn set_base_url(&mut self, base_url: String) {
  148. let mut imageproc = self.imageproc.lock().unwrap();
  149. imageproc.set_base_url(&base_url);
  150. self.config.base_url = base_url;
  151. }
  152. pub fn set_output_path<P: AsRef<Path>>(&mut self, path: P) {
  153. self.output_path = path.as_ref().to_path_buf();
  154. }
  155. /// Reads all .md files in the `content` directory and create pages/sections
  156. /// out of them
  157. pub fn load(&mut self) -> Result<()> {
  158. let base_path = self.base_path.to_string_lossy().replace("\\", "/");
  159. let content_glob = format!("{}/{}", base_path, "content/**/*.md");
  160. let (section_entries, page_entries): (Vec<_>, Vec<_>) = glob(&content_glob)
  161. .unwrap()
  162. .filter_map(|e| e.ok())
  163. .filter(|e| !e.as_path().file_name().unwrap().to_str().unwrap().starts_with('.'))
  164. .partition(|entry| entry.as_path().file_name().unwrap() == "_index.md");
  165. self.library = Library::new(page_entries.len(), section_entries.len());
  166. let sections = {
  167. let config = &self.config;
  168. section_entries
  169. .into_par_iter()
  170. .map(|entry| {
  171. let path = entry.as_path();
  172. Section::from_file(path, config)
  173. })
  174. .collect::<Vec<_>>()
  175. };
  176. let pages = {
  177. let config = &self.config;
  178. page_entries
  179. .into_par_iter()
  180. .map(|entry| {
  181. let path = entry.as_path();
  182. Page::from_file(path, config)
  183. })
  184. .collect::<Vec<_>>()
  185. };
  186. // Kinda duplicated code for add_section/add_page but necessary to do it that
  187. // way because of the borrow checker
  188. for section in sections {
  189. let s = section?;
  190. self.add_section(s, false)?;
  191. }
  192. // Insert a default index section if necessary so we don't need to create
  193. // a _index.md to render the index page at the root of the site
  194. let index_path = self.index_section_path();
  195. if let Some(ref index_section) = self.library.get_section(&index_path) {
  196. if self.config.build_search_index && !index_section.meta.in_search_index {
  197. bail!(
  198. "You have enabled search in the config but disabled it in the index section: \
  199. either turn off the search in the config or remote `in_search_index = true` from the \
  200. section front-matter."
  201. )
  202. }
  203. }
  204. // Not in else because of borrow checker
  205. if !self.library.contains_section(&index_path) {
  206. let mut index_section = Section::default();
  207. index_section.permalink = self.config.make_permalink("");
  208. index_section.file.path = self.content_path.join("_index.md");
  209. index_section.file.parent = self.content_path.clone();
  210. index_section.file.relative = "_index.md".to_string();
  211. self.library.insert_section(index_section);
  212. }
  213. let mut pages_insert_anchors = HashMap::new();
  214. for page in pages {
  215. let p = page?;
  216. pages_insert_anchors.insert(
  217. p.file.path.clone(),
  218. self.find_parent_section_insert_anchor(&p.file.parent.clone()),
  219. );
  220. self.add_page(p, false)?;
  221. }
  222. self.register_early_global_fns();
  223. self.populate_sections();
  224. self.render_markdown()?;
  225. self.populate_taxonomies()?;
  226. self.register_tera_global_fns();
  227. Ok(())
  228. }
  229. /// Render the markdown of all pages/sections
  230. /// Used in a build and in `serve` if a shortcode has changed
  231. pub fn render_markdown(&mut self) -> Result<()> {
  232. // Another silly thing needed to not borrow &self in parallel and
  233. // make the borrow checker happy
  234. let permalinks = &self.permalinks;
  235. let tera = &self.tera;
  236. let config = &self.config;
  237. // This is needed in the first place because of silly borrow checker
  238. let mut pages_insert_anchors = HashMap::new();
  239. for (_, p) in self.library.pages() {
  240. pages_insert_anchors.insert(
  241. p.file.path.clone(),
  242. self.find_parent_section_insert_anchor(&p.file.parent.clone()),
  243. );
  244. }
  245. self.library
  246. .pages_mut()
  247. .values_mut()
  248. .collect::<Vec<_>>()
  249. .par_iter_mut()
  250. .map(|page| {
  251. let insert_anchor = pages_insert_anchors[&page.file.path];
  252. page.render_markdown(permalinks, tera, config, insert_anchor)
  253. })
  254. .collect::<Result<()>>()?;
  255. self.library
  256. .sections_mut()
  257. .values_mut()
  258. .collect::<Vec<_>>()
  259. .par_iter_mut()
  260. .map(|section| section.render_markdown(permalinks, tera, config))
  261. .collect::<Result<()>>()?;
  262. Ok(())
  263. }
  264. /// Adds global fns that are to be available to shortcodes while rendering markdown
  265. pub fn register_early_global_fns(&mut self) {
  266. self.tera.register_function(
  267. "get_url",
  268. global_fns::make_get_url(self.permalinks.clone(), self.config.clone()),
  269. );
  270. self.tera.register_function(
  271. "resize_image",
  272. global_fns::make_resize_image(self.imageproc.clone()),
  273. );
  274. }
  275. pub fn register_tera_global_fns(&mut self) {
  276. self.tera.register_function("trans", global_fns::make_trans(self.config.clone()));
  277. self.tera.register_function("get_page", global_fns::make_get_page(&self.library));
  278. self.tera.register_function("get_section", global_fns::make_get_section(&self.library));
  279. self.tera.register_function(
  280. "get_taxonomy",
  281. global_fns::make_get_taxonomy(&self.taxonomies, &self.library),
  282. );
  283. self.tera.register_function(
  284. "get_taxonomy_url",
  285. global_fns::make_get_taxonomy_url(&self.taxonomies),
  286. );
  287. self.tera.register_function(
  288. "load_data",
  289. global_fns::make_load_data(self.content_path.clone(), self.base_path.clone()),
  290. );
  291. }
  292. /// Add a page to the site
  293. /// The `render` parameter is used in the serve command, when rebuilding a page.
  294. /// If `true`, it will also render the markdown for that page
  295. /// Returns the previous page struct if there was one at the same path
  296. pub fn add_page(&mut self, mut page: Page, render: bool) -> Result<Option<Page>> {
  297. self.permalinks.insert(page.file.relative.clone(), page.permalink.clone());
  298. if render {
  299. let insert_anchor = self.find_parent_section_insert_anchor(&page.file.parent);
  300. page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?;
  301. }
  302. let prev = self.library.remove_page(&page.file.path);
  303. self.library.insert_page(page);
  304. Ok(prev)
  305. }
  306. /// Add a section to the site
  307. /// The `render` parameter is used in the serve command, when rebuilding a page.
  308. /// If `true`, it will also render the markdown for that page
  309. /// Returns the previous section struct if there was one at the same path
  310. pub fn add_section(&mut self, mut section: Section, render: bool) -> Result<Option<Section>> {
  311. self.permalinks.insert(section.file.relative.clone(), section.permalink.clone());
  312. if render {
  313. section.render_markdown(&self.permalinks, &self.tera, &self.config)?;
  314. }
  315. let prev = self.library.remove_section(&section.file.path);
  316. self.library.insert_section(section);
  317. Ok(prev)
  318. }
  319. /// Finds the insert_anchor for the parent section of the directory at `path`.
  320. /// Defaults to `AnchorInsert::None` if no parent section found
  321. pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf) -> InsertAnchor {
  322. match self.library.get_section(&parent_path.join("_index.md")) {
  323. Some(s) => s.meta.insert_anchor_links,
  324. None => InsertAnchor::None,
  325. }
  326. }
  327. /// Find out the direct subsections of each subsection if there are some
  328. /// as well as the pages for each section
  329. pub fn populate_sections(&mut self) {
  330. self.library.populate_sections();
  331. }
  332. /// Find all the tags and categories if it's asked in the config
  333. pub fn populate_taxonomies(&mut self) -> Result<()> {
  334. if self.config.taxonomies.is_empty() {
  335. return Ok(());
  336. }
  337. self.taxonomies = find_taxonomies(&self.config, &self.library)?;
  338. Ok(())
  339. }
  340. /// Inject live reload script tag if in live reload mode
  341. fn inject_livereload(&self, html: String) -> String {
  342. if let Some(port) = self.live_reload {
  343. return html.replace(
  344. "</body>",
  345. &format!(
  346. r#"<script src="/livereload.js?port={}&mindelay=10"></script></body>"#,
  347. port
  348. ),
  349. );
  350. }
  351. html
  352. }
  353. /// Copy the main `static` folder and the theme `static` folder if a theme is used
  354. pub fn copy_static_directories(&self) -> Result<()> {
  355. // The user files will overwrite the theme files
  356. if let Some(ref theme) = self.config.theme {
  357. copy_directory(
  358. &self.base_path.join("themes").join(theme).join("static"),
  359. &self.output_path,
  360. )?;
  361. }
  362. // We're fine with missing static folders
  363. if self.static_path.exists() {
  364. copy_directory(&self.static_path, &self.output_path)?;
  365. }
  366. Ok(())
  367. }
  368. pub fn num_img_ops(&self) -> usize {
  369. let imageproc = self.imageproc.lock().unwrap();
  370. imageproc.num_img_ops()
  371. }
  372. pub fn process_images(&self) -> Result<()> {
  373. let mut imageproc = self.imageproc.lock().unwrap();
  374. imageproc.prune()?;
  375. imageproc.do_process()
  376. }
  377. /// Deletes the `public` directory if it exists
  378. pub fn clean(&self) -> Result<()> {
  379. if self.output_path.exists() {
  380. // Delete current `public` directory so we can start fresh
  381. remove_dir_all(&self.output_path).chain_err(|| "Couldn't delete output directory")?;
  382. }
  383. Ok(())
  384. }
  385. /// Renders a single content page
  386. pub fn render_page(&self, page: &Page) -> Result<()> {
  387. ensure_directory_exists(&self.output_path)?;
  388. // Copy the nesting of the content directory if we have sections for that page
  389. let mut current_path = self.output_path.to_path_buf();
  390. for component in page.path.split('/') {
  391. current_path.push(component);
  392. if !current_path.exists() {
  393. create_directory(&current_path)?;
  394. }
  395. }
  396. // Make sure the folder exists
  397. create_directory(&current_path)?;
  398. // Finally, create a index.html file there with the page rendered
  399. let output = page.render_html(&self.tera, &self.config, &self.library)?;
  400. create_file(&current_path.join("index.html"), &self.inject_livereload(output))?;
  401. // Copy any asset we found previously into the same directory as the index.html
  402. for asset in &page.assets {
  403. let asset_path = asset.as_path();
  404. copy(&asset_path, &current_path.join(asset_path.file_name().unwrap()))?;
  405. }
  406. Ok(())
  407. }
  408. /// Deletes the `public` directory and builds the site
  409. pub fn build(&self) -> Result<()> {
  410. self.clean()?;
  411. // Render aliases first to allow overwriting
  412. self.render_aliases()?;
  413. self.render_sections()?;
  414. self.render_orphan_pages()?;
  415. self.render_sitemap()?;
  416. if self.config.generate_rss {
  417. self.render_rss_feed(self.library.pages_values(), None)?;
  418. }
  419. self.render_404()?;
  420. self.render_robots()?;
  421. self.render_taxonomies()?;
  422. if let Some(ref theme) = self.config.theme {
  423. let theme_path = self.base_path.join("themes").join(theme);
  424. if theme_path.join("sass").exists() {
  425. self.compile_sass(&theme_path)?;
  426. }
  427. }
  428. if self.config.compile_sass {
  429. self.compile_sass(&self.base_path)?;
  430. }
  431. self.process_images()?;
  432. self.copy_static_directories()?;
  433. if self.config.build_search_index {
  434. self.build_search_index()?;
  435. }
  436. Ok(())
  437. }
  438. pub fn build_search_index(&self) -> Result<()> {
  439. // index first
  440. create_file(
  441. &self.output_path.join(&format!("search_index.{}.js", self.config.default_language)),
  442. &format!(
  443. "window.searchIndex = {};",
  444. search::build_index(&self.config.default_language, &self.library)?
  445. ),
  446. )?;
  447. // then elasticlunr.min.js
  448. create_file(&self.output_path.join("elasticlunr.min.js"), search::ELASTICLUNR_JS)?;
  449. Ok(())
  450. }
  451. pub fn compile_sass(&self, base_path: &Path) -> Result<()> {
  452. ensure_directory_exists(&self.output_path)?;
  453. let sass_path = {
  454. let mut sass_path = PathBuf::from(base_path);
  455. sass_path.push("sass");
  456. sass_path
  457. };
  458. let mut options = SassOptions::default();
  459. options.output_style = OutputStyle::Compressed;
  460. let mut compiled_paths = self.compile_sass_glob(&sass_path, "scss", &options.clone())?;
  461. options.indented_syntax = true;
  462. compiled_paths.extend(self.compile_sass_glob(&sass_path, "sass", &options)?);
  463. compiled_paths.sort();
  464. for window in compiled_paths.windows(2) {
  465. if window[0].1 == window[1].1 {
  466. bail!(
  467. "SASS path conflict: \"{}\" and \"{}\" both compile to \"{}\"",
  468. window[0].0.display(),
  469. window[1].0.display(),
  470. window[0].1.display(),
  471. );
  472. }
  473. }
  474. Ok(())
  475. }
  476. fn compile_sass_glob(
  477. &self,
  478. sass_path: &Path,
  479. extension: &str,
  480. options: &SassOptions,
  481. ) -> Result<Vec<(PathBuf, PathBuf)>> {
  482. let glob_string = format!("{}/**/*.{}", sass_path.display(), extension);
  483. let files = glob(&glob_string)
  484. .unwrap()
  485. .filter_map(|e| e.ok())
  486. .filter(|entry| {
  487. !entry.as_path().file_name().unwrap().to_string_lossy().starts_with('_')
  488. })
  489. .collect::<Vec<_>>();
  490. let mut compiled_paths = Vec::new();
  491. for file in files {
  492. let css = compile_file(&file, options.clone())?;
  493. let path_inside_sass = file.strip_prefix(&sass_path).unwrap();
  494. let parent_inside_sass = path_inside_sass.parent();
  495. let css_output_path = self.output_path.join(path_inside_sass).with_extension("css");
  496. if parent_inside_sass.is_some() {
  497. create_dir_all(&css_output_path.parent().unwrap())?;
  498. }
  499. create_file(&css_output_path, &css)?;
  500. compiled_paths.push((path_inside_sass.to_owned(), css_output_path));
  501. }
  502. Ok(compiled_paths)
  503. }
  504. pub fn render_aliases(&self) -> Result<()> {
  505. ensure_directory_exists(&self.output_path)?;
  506. for (_, page) in self.library.pages() {
  507. for alias in &page.meta.aliases {
  508. let mut output_path = self.output_path.to_path_buf();
  509. let mut split = alias.split('/').collect::<Vec<_>>();
  510. // If the alias ends with an html file name, use that instead of mapping
  511. // as a path containing an `index.html`
  512. let page_name = match split.pop() {
  513. Some(part) if part.ends_with(".html") => part,
  514. Some(part) => {
  515. split.push(part);
  516. "index.html"
  517. }
  518. None => "index.html",
  519. };
  520. for component in split {
  521. output_path.push(&component);
  522. if !output_path.exists() {
  523. create_directory(&output_path)?;
  524. }
  525. }
  526. create_file(
  527. &output_path.join(page_name),
  528. &render_redirect_template(&page.permalink, &self.tera)?,
  529. )?;
  530. }
  531. }
  532. Ok(())
  533. }
  534. /// Renders 404.html
  535. pub fn render_404(&self) -> Result<()> {
  536. ensure_directory_exists(&self.output_path)?;
  537. let mut context = Context::new();
  538. context.insert("config", &self.config);
  539. create_file(
  540. &self.output_path.join("404.html"),
  541. &render_template("404.html", &self.tera, &context, &self.config.theme)?,
  542. )
  543. }
  544. /// Renders robots.txt
  545. pub fn render_robots(&self) -> Result<()> {
  546. ensure_directory_exists(&self.output_path)?;
  547. let mut context = Context::new();
  548. context.insert("config", &self.config);
  549. create_file(
  550. &self.output_path.join("robots.txt"),
  551. &render_template("robots.txt", &self.tera, &context, &self.config.theme)?,
  552. )
  553. }
  554. /// Renders all taxonomies with at least one non-draft post
  555. pub fn render_taxonomies(&self) -> Result<()> {
  556. // TODO: make parallel?
  557. for taxonomy in &self.taxonomies {
  558. self.render_taxonomy(taxonomy)?;
  559. }
  560. Ok(())
  561. }
  562. fn render_taxonomy(&self, taxonomy: &Taxonomy) -> Result<()> {
  563. if taxonomy.items.is_empty() {
  564. return Ok(());
  565. }
  566. ensure_directory_exists(&self.output_path)?;
  567. let output_path = self.output_path.join(&taxonomy.kind.name);
  568. let list_output = taxonomy.render_all_terms(&self.tera, &self.config, &self.library)?;
  569. create_directory(&output_path)?;
  570. create_file(&output_path.join("index.html"), &self.inject_livereload(list_output))?;
  571. taxonomy
  572. .items
  573. .par_iter()
  574. .map(|item| {
  575. if taxonomy.kind.rss {
  576. self.render_rss_feed(
  577. item.pages.iter().map(|p| self.library.get_page_by_key(*p)).collect(),
  578. Some(&PathBuf::from(format!("{}/{}", taxonomy.kind.name, item.slug))),
  579. )?;
  580. }
  581. if taxonomy.kind.is_paginated() {
  582. self.render_paginated(
  583. &output_path,
  584. &Paginator::from_taxonomy(&taxonomy, item, &self.library),
  585. )
  586. } else {
  587. let single_output =
  588. taxonomy.render_term(item, &self.tera, &self.config, &self.library)?;
  589. let path = output_path.join(&item.slug);
  590. create_directory(&path)?;
  591. create_file(&path.join("index.html"), &self.inject_livereload(single_output))
  592. }
  593. })
  594. .collect::<Result<()>>()
  595. }
  596. /// What it says on the tin
  597. pub fn render_sitemap(&self) -> Result<()> {
  598. ensure_directory_exists(&self.output_path)?;
  599. let mut context = Context::new();
  600. let mut pages = self
  601. .library
  602. .pages_values()
  603. .iter()
  604. .filter(|p| !p.is_draft())
  605. .map(|p| {
  606. let date = match p.meta.date {
  607. Some(ref d) => Some(d.to_string()),
  608. None => None,
  609. };
  610. SitemapEntry::new(p.permalink.clone(), date)
  611. })
  612. .collect::<Vec<_>>();
  613. pages.sort_by(|a, b| a.permalink.cmp(&b.permalink));
  614. context.insert("pages", &pages);
  615. let mut sections = self
  616. .library
  617. .sections_values()
  618. .iter()
  619. .map(|s| SitemapEntry::new(s.permalink.clone(), None))
  620. .collect::<Vec<_>>();
  621. sections.sort_by(|a, b| a.permalink.cmp(&b.permalink));
  622. context.insert("sections", &sections);
  623. let mut taxonomies = vec![];
  624. for taxonomy in &self.taxonomies {
  625. let name = &taxonomy.kind.name;
  626. let mut terms = vec![];
  627. terms.push(SitemapEntry::new(self.config.make_permalink(name), None));
  628. for item in &taxonomy.items {
  629. terms.push(SitemapEntry::new(
  630. self.config.make_permalink(&format!("{}/{}", &name, item.slug)),
  631. None,
  632. ));
  633. }
  634. terms.sort_by(|a, b| a.permalink.cmp(&b.permalink));
  635. taxonomies.push(terms);
  636. }
  637. context.insert("taxonomies", &taxonomies);
  638. context.insert("config", &self.config);
  639. let sitemap = &render_template("sitemap.xml", &self.tera, &context, &self.config.theme)?;
  640. create_file(&self.output_path.join("sitemap.xml"), sitemap)?;
  641. Ok(())
  642. }
  643. /// Renders a RSS feed for the given path and at the given path
  644. /// If both arguments are `None`, it will render only the RSS feed for the whole
  645. /// site at the root folder.
  646. pub fn render_rss_feed(
  647. &self,
  648. all_pages: Vec<&Page>,
  649. base_path: Option<&PathBuf>,
  650. ) -> Result<()> {
  651. ensure_directory_exists(&self.output_path)?;
  652. let mut context = Context::new();
  653. let mut pages = all_pages
  654. .into_iter()
  655. .filter(|p| p.meta.date.is_some() && !p.is_draft())
  656. .collect::<Vec<_>>();
  657. // Don't generate a RSS feed if none of the pages has a date
  658. if pages.is_empty() {
  659. return Ok(());
  660. }
  661. pages.par_sort_unstable_by(sort_actual_pages_by_date);
  662. context.insert("last_build_date", &pages[0].meta.date.clone().map(|d| d.to_string()));
  663. // limit to the last n elements if the limit is set; otherwise use all.
  664. let num_entries = self.config.rss_limit.unwrap_or(pages.len());
  665. let p = pages
  666. .iter()
  667. .take(num_entries)
  668. .map(|x| x.to_serialized_basic(&self.library))
  669. .collect::<Vec<_>>();
  670. context.insert("pages", &p);
  671. context.insert("config", &self.config);
  672. let rss_feed_url = if let Some(ref base) = base_path {
  673. self.config.make_permalink(&base.join("rss.xml").to_string_lossy().replace('\\', "/"))
  674. } else {
  675. self.config.make_permalink("rss.xml")
  676. };
  677. context.insert("feed_url", &rss_feed_url);
  678. let feed = &render_template("rss.xml", &self.tera, &context, &self.config.theme)?;
  679. if let Some(ref base) = base_path {
  680. let mut output_path = self.output_path.clone().to_path_buf();
  681. for component in base.components() {
  682. output_path.push(component);
  683. if !output_path.exists() {
  684. create_directory(&output_path)?;
  685. }
  686. }
  687. create_file(&output_path.join("rss.xml"), feed)?;
  688. } else {
  689. create_file(&self.output_path.join("rss.xml"), feed)?;
  690. }
  691. Ok(())
  692. }
  693. /// Renders a single section
  694. pub fn render_section(&self, section: &Section, render_pages: bool) -> Result<()> {
  695. ensure_directory_exists(&self.output_path)?;
  696. let public = self.output_path.clone();
  697. let mut output_path = public.to_path_buf();
  698. for component in &section.file.components {
  699. output_path.push(component);
  700. if !output_path.exists() {
  701. create_directory(&output_path)?;
  702. }
  703. }
  704. // Copy any asset we found previously into the same directory as the index.html
  705. for asset in &section.assets {
  706. let asset_path = asset.as_path();
  707. copy(&asset_path, &output_path.join(asset_path.file_name().unwrap()))?;
  708. }
  709. if render_pages {
  710. section
  711. .pages
  712. .par_iter()
  713. .map(|k| self.render_page(self.library.get_page_by_key(*k)))
  714. .collect::<Result<()>>()?;
  715. }
  716. if !section.meta.render {
  717. return Ok(());
  718. }
  719. if let Some(ref redirect_to) = section.meta.redirect_to {
  720. let permalink = self.config.make_permalink(redirect_to);
  721. create_file(
  722. &output_path.join("index.html"),
  723. &render_redirect_template(&permalink, &self.tera)?,
  724. )?;
  725. return Ok(());
  726. }
  727. if section.meta.is_paginated() {
  728. self.render_paginated(&output_path, &Paginator::from_section(&section, &self.library))?;
  729. } else {
  730. let output = section.render_html(&self.tera, &self.config, &self.library)?;
  731. create_file(&output_path.join("index.html"), &self.inject_livereload(output))?;
  732. }
  733. Ok(())
  734. }
  735. /// Used only on reload
  736. pub fn render_index(&self) -> Result<()> {
  737. self.render_section(
  738. &self.library.get_section(&self.content_path.join("_index.md")).unwrap(),
  739. false,
  740. )
  741. }
  742. /// Renders all sections
  743. pub fn render_sections(&self) -> Result<()> {
  744. self.library
  745. .sections_values()
  746. .into_par_iter()
  747. .map(|s| self.render_section(s, true))
  748. .collect::<Result<()>>()
  749. }
  750. /// Renders all pages that do not belong to any sections
  751. pub fn render_orphan_pages(&self) -> Result<()> {
  752. ensure_directory_exists(&self.output_path)?;
  753. for page in self.get_all_orphan_pages() {
  754. self.render_page(page)?;
  755. }
  756. Ok(())
  757. }
  758. /// Renders a list of pages when the section/index is wanting pagination.
  759. pub fn render_paginated(&self, output_path: &Path, paginator: &Paginator) -> Result<()> {
  760. ensure_directory_exists(&self.output_path)?;
  761. let folder_path = output_path.join(&paginator.paginate_path);
  762. create_directory(&folder_path)?;
  763. paginator
  764. .pagers
  765. .par_iter()
  766. .map(|pager| {
  767. let page_path = folder_path.join(&format!("{}", pager.index));
  768. create_directory(&page_path)?;
  769. let output =
  770. paginator.render_pager(pager, &self.config, &self.tera, &self.library)?;
  771. if pager.index > 1 {
  772. create_file(&page_path.join("index.html"), &self.inject_livereload(output))?;
  773. } else {
  774. create_file(&output_path.join("index.html"), &self.inject_livereload(output))?;
  775. create_file(
  776. &page_path.join("index.html"),
  777. &render_redirect_template(&paginator.permalink, &self.tera)?,
  778. )?;
  779. }
  780. Ok(())
  781. })
  782. .collect::<Result<()>>()
  783. }
  784. }