Browse Source

Load multi-languages pages/sections

index-subcmd
Vincent Prouillet 5 years ago
parent
commit
1d06324a65
18 changed files with 199 additions and 34 deletions
  1. +1
    -0
      .gitignore
  2. +7
    -1
      components/library/src/content/file_info.rs
  3. +14
    -9
      components/library/src/library.rs
  4. +45
    -24
      components/site/src/lib.rs
  5. +46
    -0
      components/site/tests/site_i18n.rs
  6. +19
    -0
      test_site_i18n/config.toml
  7. +5
    -0
      test_site_i18n/content/base.fr.md
  8. +5
    -0
      test_site_i18n/content/base.md
  9. +4
    -0
      test_site_i18n/content/blog/_index.fr.md
  10. +4
    -0
      test_site_i18n/content/blog/_index.md
  11. +7
    -0
      test_site_i18n/content/blog/fixed-slug.fr.md
  12. +12
    -0
      test_site_i18n/content/blog/fixed-slug.md
  13. +5
    -0
      test_site_i18n/content/blog/not-in-frend.md
  14. +6
    -0
      test_site_i18n/content/blog/something.fr.md
  15. +6
    -0
      test_site_i18n/content/blog/something.md
  16. +5
    -0
      test_site_i18n/content/blog/with-assets/index.fr.md
  17. +5
    -0
      test_site_i18n/content/blog/with-assets/index.md
  18. +3
    -0
      test_site_i18n/templates/index.html

+ 1
- 0
.gitignore View File

@@ -1,6 +1,7 @@
target target
.idea/ .idea/
test_site/public test_site/public
test_site_i18n/public
docs/public docs/public


small-blog small-blog


+ 7
- 1
components/library/src/content/file_info.rs View File

@@ -31,6 +31,8 @@ pub fn find_content_components<P: AsRef<Path>>(path: P) -> Vec<String> {
pub struct FileInfo { pub struct FileInfo {
/// The full path to the .md file /// The full path to the .md file
pub path: PathBuf, 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 /// 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 /// Doesn't contain the language if there was one in the filename
pub name: String, pub name: String,
@@ -68,6 +70,7 @@ impl FileInfo {
} }


FileInfo { FileInfo {
filename: file_path.file_name().unwrap().to_string_lossy().to_string(),
path: file_path, path: file_path,
// We don't care about grand parent for pages // We don't care about grand parent for pages
grand_parent: None, grand_parent: None,
@@ -79,6 +82,7 @@ impl FileInfo {
} }


pub fn new_section(path: &Path) -> FileInfo { pub fn new_section(path: &Path) -> FileInfo {
let file_path = path.to_path_buf();
let parent = path.parent().unwrap().to_path_buf(); let parent = path.parent().unwrap().to_path_buf();
let name = path.file_stem().unwrap().to_string_lossy().to_string(); let name = path.file_stem().unwrap().to_string_lossy().to_string();
let components = find_content_components(path); let components = find_content_components(path);
@@ -90,7 +94,8 @@ impl FileInfo {
let grand_parent = parent.parent().map(|p| p.to_path_buf()); let grand_parent = parent.parent().map(|p| p.to_path_buf());


FileInfo { FileInfo {
path: path.to_path_buf(),
filename: file_path.file_name().unwrap().to_string_lossy().to_string(),
path: file_path,
parent, parent,
grand_parent, grand_parent,
name, name,
@@ -136,6 +141,7 @@ impl Default for FileInfo {
path: PathBuf::new(), path: PathBuf::new(),
parent: PathBuf::new(), parent: PathBuf::new(),
grand_parent: None, grand_parent: None,
filename: String::new(),
name: String::new(), name: String::new(),
components: vec![], components: vec![],
relative: String::new(), relative: String::new(),


+ 14
- 9
components/library/src/library.rs View File

@@ -80,14 +80,12 @@ impl Library {
/// Find out the direct subsections of each subsection if there are some /// Find out the direct subsections of each subsection if there are some
/// as well as the pages for each section /// as well as the pages for each section
pub fn populate_sections(&mut self) { pub fn populate_sections(&mut self) {
let (root_path, index_path) = self
let root_path= self
.sections .sections
.values() .values()
.find(|s| s.is_index()) .find(|s| s.is_index())
.map(|s| (s.file.parent.clone(), s.file.path.clone()))
.map(|s| s.file.parent.clone())
.unwrap(); .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 // 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 ancestors: HashMap<PathBuf, Vec<_>> = HashMap::new();
let mut subsections: 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 { if let Some(ref grand_parent) = section.file.grand_parent {
subsections subsections
.entry(grand_parent.join("_index.md"))
// Using the original filename to work for multi-lingual sections
.entry(grand_parent.join(&section.file.filename))
.or_insert_with(|| vec![]) .or_insert_with(|| vec![])
.push(section.file.path.clone()); .push(section.file.path.clone());
} }
@@ -111,6 +110,7 @@ impl Library {
} }


let mut path = root_path.clone(); let mut path = root_path.clone();
let root_key = self.paths_to_sections[&root_path.join(&section.file.filename)];
// Index section is the first ancestor of every single section // Index section is the first ancestor of every single section
let mut parents = vec![root_key]; let mut parents = vec![root_key];
for component in &section.file.components { for component in &section.file.components {
@@ -119,7 +119,7 @@ impl Library {
if path == section.file.parent { if path == section.file.parent {
continue; 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(&section.file.filename)) {
parents.push(*section_key); parents.push(*section_key);
} }
} }
@@ -127,7 +127,12 @@ impl Library {
} }


for (key, page) in &mut self.pages { 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) { while let Some(section_key) = self.paths_to_sections.get(&parent_section_path) {
let parent_is_transparent; let parent_is_transparent;
// We need to get a reference to a section later so keep the scope of borrowing small // We need to get a reference to a section later so keep the scope of borrowing small
@@ -158,9 +163,9 @@ impl Library {
break; 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() { 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, None => break,
} }
} }


+ 45
- 24
components/site/src/lib.rs View File

@@ -147,9 +147,14 @@ impl Site {
Ok(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 /// We avoid the port the server is going to use as it's not bound yet
@@ -184,7 +189,7 @@ impl Site {
.unwrap() .unwrap()
.filter_map(|e| e.ok()) .filter_map(|e| e.ok())
.filter(|e| !e.as_path().file_name().unwrap().to_str().unwrap().starts_with('.')) .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()); self.library = Library::new(page_entries.len(), section_entries.len());


@@ -219,26 +224,37 @@ impl Site {
self.add_section(s, false)?; 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 // 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: \ "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 \ either turn off the search in the config or remote `in_search_index = true` from the \
section front-matter." 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(); let mut pages_insert_anchors = HashMap::new();
@@ -246,7 +262,7 @@ impl Site {
let p = page?; let p = page?;
pages_insert_anchors.insert( pages_insert_anchors.insert(
p.file.path.clone(), 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)?; self.add_page(p, false)?;
} }
@@ -274,7 +290,7 @@ impl Site {
for (_, p) in self.library.pages() { for (_, p) in self.library.pages() {
pages_insert_anchors.insert( pages_insert_anchors.insert(
p.file.path.clone(), 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>> { pub fn add_page(&mut self, mut page: Page, render: bool) -> Result<Option<Page>> {
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone()); self.permalinks.insert(page.file.relative.clone(), page.permalink.clone());
if render { 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)?; page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?;
} }
let prev = self.library.remove_page(&page.file.path); 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`. /// Finds the insert_anchor for the parent section of the directory at `path`.
/// Defaults to `AnchorInsert::None` if no parent section found /// 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, Some(s) => s.meta.insert_anchor_links,
None => InsertAnchor::None, None => InsertAnchor::None,
} }


+ 46
- 0
components/site/tests/site_i18n.rs View File

@@ -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()));
}
}

+ 19
- 0
test_site_i18n/config.toml View File

@@ -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

+ 5
- 0
test_site_i18n/content/base.fr.md View File

@@ -0,0 +1,5 @@
+++
title = "Une page"
+++

Une page en Français

+ 5
- 0
test_site_i18n/content/base.md View File

@@ -0,0 +1,5 @@
+++
title = "A page"
+++

A page in english

+ 4
- 0
test_site_i18n/content/blog/_index.fr.md View File

@@ -0,0 +1,4 @@
+++
sort_by = "date"
insert_anchors = "right"
+++

+ 4
- 0
test_site_i18n/content/blog/_index.md View File

@@ -0,0 +1,4 @@
+++
sort_by = "date"
insert_anchors = "left"
+++

+ 7
- 0
test_site_i18n/content/blog/fixed-slug.fr.md View File

@@ -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

+ 12
- 0
test_site_i18n/content/blog/fixed-slug.md View File

@@ -0,0 +1,12 @@
+++
title = "Fixed slug"
slug = "something-else"
date = 2017-01-01
+++

A simple page with a slug defined

# Title

Hey


+ 5
- 0
test_site_i18n/content/blog/not-in-frend.md View File

@@ -0,0 +1,5 @@
+++
date = 2018-08-19
+++

Something not translated

+ 6
- 0
test_site_i18n/content/blog/something.fr.md View File

@@ -0,0 +1,6 @@
+++
title = "Quelque chose"
date = 2018-10-09
+++

Un article

+ 6
- 0
test_site_i18n/content/blog/something.md View File

@@ -0,0 +1,6 @@
+++
title = "Something"
date = 2018-10-09
+++

A blog post

+ 5
- 0
test_site_i18n/content/blog/with-assets/index.fr.md View File

@@ -0,0 +1,5 @@
+++
date = 2018-11-10
+++

Avec des fichiers

+ 5
- 0
test_site_i18n/content/blog/with-assets/index.md View File

@@ -0,0 +1,5 @@
+++
date = 2018-11-10
+++

With assets

+ 3
- 0
test_site_i18n/templates/index.html View File

@@ -0,0 +1,3 @@
{% for page in section.pages %}
{{page.title}}
{% endfor %}

Loading…
Cancel
Save