| @@ -1,6 +1,7 @@ | |||
| target | |||
| .idea/ | |||
| test_site/public | |||
| test_site_i18n/public | |||
| docs/public | |||
| small-blog | |||
| @@ -31,6 +31,8 @@ pub fn find_content_components<P: AsRef<Path>>(path: P) -> Vec<String> { | |||
| pub struct FileInfo { | |||
| /// The full path to the .md file | |||
| pub path: PathBuf, | |||
| /// The on-disk filename, will differ from the `name` when there is a language code in it | |||
| pub filename: String, | |||
| /// The name of the .md file without the extension, always `_index` for sections | |||
| /// Doesn't contain the language if there was one in the filename | |||
| pub name: String, | |||
| @@ -68,6 +70,7 @@ impl FileInfo { | |||
| } | |||
| FileInfo { | |||
| filename: file_path.file_name().unwrap().to_string_lossy().to_string(), | |||
| path: file_path, | |||
| // We don't care about grand parent for pages | |||
| grand_parent: None, | |||
| @@ -79,6 +82,7 @@ impl FileInfo { | |||
| } | |||
| pub fn new_section(path: &Path) -> FileInfo { | |||
| let file_path = path.to_path_buf(); | |||
| let parent = path.parent().unwrap().to_path_buf(); | |||
| let name = path.file_stem().unwrap().to_string_lossy().to_string(); | |||
| let components = find_content_components(path); | |||
| @@ -90,7 +94,8 @@ impl FileInfo { | |||
| let grand_parent = parent.parent().map(|p| p.to_path_buf()); | |||
| FileInfo { | |||
| path: path.to_path_buf(), | |||
| filename: file_path.file_name().unwrap().to_string_lossy().to_string(), | |||
| path: file_path, | |||
| parent, | |||
| grand_parent, | |||
| name, | |||
| @@ -136,6 +141,7 @@ impl Default for FileInfo { | |||
| path: PathBuf::new(), | |||
| parent: PathBuf::new(), | |||
| grand_parent: None, | |||
| filename: String::new(), | |||
| name: String::new(), | |||
| components: vec![], | |||
| relative: String::new(), | |||
| @@ -80,14 +80,12 @@ impl Library { | |||
| /// Find out the direct subsections of each subsection if there are some | |||
| /// as well as the pages for each section | |||
| pub fn populate_sections(&mut self) { | |||
| let (root_path, index_path) = self | |||
| let root_path= self | |||
| .sections | |||
| .values() | |||
| .find(|s| s.is_index()) | |||
| .map(|s| (s.file.parent.clone(), s.file.path.clone())) | |||
| .map(|s| s.file.parent.clone()) | |||
| .unwrap(); | |||
| let root_key = self.paths_to_sections[&index_path]; | |||
| // We are going to get both the ancestors and grandparents for each section in one go | |||
| let mut ancestors: HashMap<PathBuf, Vec<_>> = HashMap::new(); | |||
| let mut subsections: HashMap<PathBuf, Vec<_>> = HashMap::new(); | |||
| @@ -99,7 +97,8 @@ impl Library { | |||
| if let Some(ref grand_parent) = section.file.grand_parent { | |||
| subsections | |||
| .entry(grand_parent.join("_index.md")) | |||
| // Using the original filename to work for multi-lingual sections | |||
| .entry(grand_parent.join(§ion.file.filename)) | |||
| .or_insert_with(|| vec![]) | |||
| .push(section.file.path.clone()); | |||
| } | |||
| @@ -111,6 +110,7 @@ impl Library { | |||
| } | |||
| let mut path = root_path.clone(); | |||
| let root_key = self.paths_to_sections[&root_path.join(§ion.file.filename)]; | |||
| // Index section is the first ancestor of every single section | |||
| let mut parents = vec![root_key]; | |||
| for component in §ion.file.components { | |||
| @@ -119,7 +119,7 @@ impl Library { | |||
| if path == section.file.parent { | |||
| continue; | |||
| } | |||
| if let Some(section_key) = self.paths_to_sections.get(&path.join("_index.md")) { | |||
| if let Some(section_key) = self.paths_to_sections.get(&path.join(§ion.file.filename)) { | |||
| parents.push(*section_key); | |||
| } | |||
| } | |||
| @@ -127,7 +127,12 @@ impl Library { | |||
| } | |||
| for (key, page) in &mut self.pages { | |||
| let mut parent_section_path = page.file.parent.join("_index.md"); | |||
| let parent_filename = if let Some(ref lang) = page.lang { | |||
| format!("_index.{}.md", lang) | |||
| } else { | |||
| "_index.md".to_string() | |||
| }; | |||
| let mut parent_section_path = page.file.parent.join(&parent_filename); | |||
| while let Some(section_key) = self.paths_to_sections.get(&parent_section_path) { | |||
| let parent_is_transparent; | |||
| // We need to get a reference to a section later so keep the scope of borrowing small | |||
| @@ -158,9 +163,9 @@ impl Library { | |||
| break; | |||
| } | |||
| // We've added `_index.md` so if we are here so we need to go up twice | |||
| // We've added `_index(.{LANG})?.md` so if we are here so we need to go up twice | |||
| match parent_section_path.clone().parent().unwrap().parent() { | |||
| Some(parent) => parent_section_path = parent.join("_index.md"), | |||
| Some(parent) => parent_section_path = parent.join(&parent_filename), | |||
| None => break, | |||
| } | |||
| } | |||
| @@ -147,9 +147,14 @@ impl Site { | |||
| Ok(site) | |||
| } | |||
| /// The index section is ALWAYS at that path | |||
| pub fn index_section_path(&self) -> PathBuf { | |||
| self.content_path.join("_index.md") | |||
| /// The index sections are ALWAYS at those paths | |||
| /// There are one index section for the basic language + 1 per language | |||
| fn index_section_paths(&self) -> Vec<(PathBuf, Option<String>)> { | |||
| let mut res = vec![(self.content_path.join("_index.md"), None)]; | |||
| for language in &self.config.languages { | |||
| res.push((self.content_path.join(format!("_index.{}.md", language.code)), Some(language.code.clone()))); | |||
| } | |||
| res | |||
| } | |||
| /// We avoid the port the server is going to use as it's not bound yet | |||
| @@ -184,7 +189,7 @@ impl Site { | |||
| .unwrap() | |||
| .filter_map(|e| e.ok()) | |||
| .filter(|e| !e.as_path().file_name().unwrap().to_str().unwrap().starts_with('.')) | |||
| .partition(|entry| entry.as_path().file_name().unwrap() == "_index.md"); | |||
| .partition(|entry| entry.as_path().file_name().unwrap().to_str().unwrap().starts_with("_index.")); | |||
| self.library = Library::new(page_entries.len(), section_entries.len()); | |||
| @@ -219,26 +224,37 @@ impl Site { | |||
| self.add_section(s, false)?; | |||
| } | |||
| // Insert a default index section if necessary so we don't need to create | |||
| // Insert a default index section for each language if necessary so we don't need to create | |||
| // a _index.md to render the index page at the root of the site | |||
| let index_path = self.index_section_path(); | |||
| if let Some(ref index_section) = self.library.get_section(&index_path) { | |||
| if self.config.build_search_index && !index_section.meta.in_search_index { | |||
| bail!( | |||
| for (index_path, lang) in self.index_section_paths() { | |||
| if let Some(ref index_section) = self.library.get_section(&index_path) { | |||
| if self.config.build_search_index && !index_section.meta.in_search_index { | |||
| bail!( | |||
| "You have enabled search in the config but disabled it in the index section: \ | |||
| either turn off the search in the config or remote `in_search_index = true` from the \ | |||
| section front-matter." | |||
| ) | |||
| ) | |||
| } | |||
| } | |||
| // Not in else because of borrow checker | |||
| if !self.library.contains_section(&index_path) { | |||
| let mut index_section = Section::default(); | |||
| index_section.file.parent = self.content_path.clone(); | |||
| index_section.file.name = "_index".to_string(); | |||
| index_section.file.filename = index_path.file_name().unwrap().to_string_lossy().to_string(); | |||
| if let Some(ref l) = lang { | |||
| index_section.permalink = self.config.make_permalink(l); | |||
| let filename = format!("_index.{}.md", l); | |||
| index_section.file.path = self.content_path.join(&filename); | |||
| index_section.file.relative = filename; | |||
| index_section.lang = Some(l.clone()); | |||
| } else { | |||
| index_section.permalink = self.config.make_permalink(""); | |||
| index_section.file.path = self.content_path.join("_index.md"); | |||
| index_section.file.relative = "_index.md".to_string(); | |||
| } | |||
| self.library.insert_section(index_section); | |||
| } | |||
| } | |||
| // Not in else because of borrow checker | |||
| if !self.library.contains_section(&index_path) { | |||
| let mut index_section = Section::default(); | |||
| index_section.permalink = self.config.make_permalink(""); | |||
| index_section.file.path = self.content_path.join("_index.md"); | |||
| index_section.file.parent = self.content_path.clone(); | |||
| index_section.file.relative = "_index.md".to_string(); | |||
| self.library.insert_section(index_section); | |||
| } | |||
| let mut pages_insert_anchors = HashMap::new(); | |||
| @@ -246,7 +262,7 @@ impl Site { | |||
| let p = page?; | |||
| pages_insert_anchors.insert( | |||
| p.file.path.clone(), | |||
| self.find_parent_section_insert_anchor(&p.file.parent.clone()), | |||
| self.find_parent_section_insert_anchor(&p.file.parent.clone(), &p.lang), | |||
| ); | |||
| self.add_page(p, false)?; | |||
| } | |||
| @@ -274,7 +290,7 @@ impl Site { | |||
| for (_, p) in self.library.pages() { | |||
| pages_insert_anchors.insert( | |||
| p.file.path.clone(), | |||
| self.find_parent_section_insert_anchor(&p.file.parent.clone()), | |||
| self.find_parent_section_insert_anchor(&p.file.parent.clone(), &p.lang), | |||
| ); | |||
| } | |||
| @@ -337,7 +353,7 @@ impl Site { | |||
| pub fn add_page(&mut self, mut page: Page, render: bool) -> Result<Option<Page>> { | |||
| self.permalinks.insert(page.file.relative.clone(), page.permalink.clone()); | |||
| if render { | |||
| let insert_anchor = self.find_parent_section_insert_anchor(&page.file.parent); | |||
| let insert_anchor = self.find_parent_section_insert_anchor(&page.file.parent, &page.lang); | |||
| page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?; | |||
| } | |||
| let prev = self.library.remove_page(&page.file.path); | |||
| @@ -363,8 +379,13 @@ impl Site { | |||
| /// Finds the insert_anchor for the parent section of the directory at `path`. | |||
| /// Defaults to `AnchorInsert::None` if no parent section found | |||
| pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf) -> InsertAnchor { | |||
| match self.library.get_section(&parent_path.join("_index.md")) { | |||
| pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf, lang: &Option<String>) -> InsertAnchor { | |||
| let parent = if let Some(ref l) = lang { | |||
| parent_path.join(format!("_index.{}.md", l)) | |||
| } else { | |||
| parent_path.join("_index.md") | |||
| }; | |||
| match self.library.get_section(&parent) { | |||
| Some(s) => s.meta.insert_anchor_links, | |||
| None => InsertAnchor::None, | |||
| } | |||
| @@ -0,0 +1,46 @@ | |||
| extern crate site; | |||
| use std::env; | |||
| use site::Site; | |||
| #[test] | |||
| fn can_parse_multilingual_site() { | |||
| let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||
| path.push("test_site_i18n"); | |||
| let mut site = Site::new(&path, "config.toml").unwrap(); | |||
| site.load().unwrap(); | |||
| assert_eq!(site.library.pages().len(), 9); | |||
| assert_eq!(site.library.sections().len(), 4); | |||
| // default index sections | |||
| let default_index_section = site.library.get_section(&path.join("content").join("_index.md")).unwrap(); | |||
| assert_eq!(default_index_section.pages.len(), 1); | |||
| assert!(default_index_section.ancestors.is_empty()); | |||
| let fr_index_section = site.library.get_section(&path.join("content").join("_index.fr.md")).unwrap(); | |||
| assert_eq!(fr_index_section.pages.len(), 1); | |||
| assert!(fr_index_section.ancestors.is_empty()); | |||
| // blog sections get only their own language pages | |||
| let blog_path = path.join("content").join("blog"); | |||
| let default_blog = site.library.get_section(&blog_path.join("_index.md")).unwrap(); | |||
| assert_eq!(default_blog.subsections.len(), 0); | |||
| assert_eq!(default_blog.pages.len(), 4); | |||
| assert_eq!(default_blog.ancestors, vec![*site.library.get_section_key(&default_index_section.file.path).unwrap()]); | |||
| for key in &default_blog.pages { | |||
| let page = site.library.get_page_by_key(*key); | |||
| assert_eq!(page.lang, None); | |||
| } | |||
| let fr_blog = site.library.get_section(&blog_path.join("_index.fr.md")).unwrap(); | |||
| assert_eq!(fr_blog.subsections.len(), 0); | |||
| assert_eq!(fr_blog.pages.len(), 3); | |||
| assert_eq!(fr_blog.ancestors, vec![*site.library.get_section_key(&fr_index_section.file.path).unwrap()]); | |||
| for key in &fr_blog.pages { | |||
| let page = site.library.get_page_by_key(*key); | |||
| assert_eq!(page.lang, Some("fr".to_string())); | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| # The URL the site will be built for | |||
| base_url = "https://example.com" | |||
| # Whether to automatically compile all Sass files in the sass directory | |||
| compile_sass = false | |||
| # Whether to do syntax highlighting | |||
| # Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola | |||
| highlight_code = false | |||
| # Whether to build a search index to be used later on by a JavaScript library | |||
| build_search_index = false | |||
| languages = [ | |||
| {code = "fr"}, | |||
| ] | |||
| [extra] | |||
| # Put all your custom variables here | |||
| @@ -0,0 +1,5 @@ | |||
| +++ | |||
| title = "Une page" | |||
| +++ | |||
| Une page en Français | |||
| @@ -0,0 +1,5 @@ | |||
| +++ | |||
| title = "A page" | |||
| +++ | |||
| A page in english | |||
| @@ -0,0 +1,4 @@ | |||
| +++ | |||
| sort_by = "date" | |||
| insert_anchors = "right" | |||
| +++ | |||
| @@ -0,0 +1,4 @@ | |||
| +++ | |||
| sort_by = "date" | |||
| insert_anchors = "left" | |||
| +++ | |||
| @@ -0,0 +1,7 @@ | |||
| +++ | |||
| title = "Un slug fix" | |||
| slug = "something-else" | |||
| date = 2017-01-01 | |||
| +++ | |||
| Une page qui definit son slug dans le front-matter | |||
| @@ -0,0 +1,12 @@ | |||
| +++ | |||
| title = "Fixed slug" | |||
| slug = "something-else" | |||
| date = 2017-01-01 | |||
| +++ | |||
| A simple page with a slug defined | |||
| # Title | |||
| Hey | |||
| @@ -0,0 +1,5 @@ | |||
| +++ | |||
| date = 2018-08-19 | |||
| +++ | |||
| Something not translated | |||
| @@ -0,0 +1,6 @@ | |||
| +++ | |||
| title = "Quelque chose" | |||
| date = 2018-10-09 | |||
| +++ | |||
| Un article | |||
| @@ -0,0 +1,6 @@ | |||
| +++ | |||
| title = "Something" | |||
| date = 2018-10-09 | |||
| +++ | |||
| A blog post | |||
| @@ -0,0 +1,5 @@ | |||
| +++ | |||
| date = 2018-11-10 | |||
| +++ | |||
| Avec des fichiers | |||
| @@ -0,0 +1,5 @@ | |||
| +++ | |||
| date = 2018-11-10 | |||
| +++ | |||
| With assets | |||
| @@ -0,0 +1,3 @@ | |||
| {% for page in section.pages %} | |||
| {{page.title}} | |||
| {% endfor %} | |||