Skip to content

Commit 98cc45f

Browse files
committed
Translate from renderer + LanguagePicker
1 parent d428f65 commit 98cc45f

File tree

7 files changed

+380
-34
lines changed

7 files changed

+380
-34
lines changed

src/custom_component_renderer/book_directory_renderer.rs

Lines changed: 148 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,172 @@ use crate::custom_component_renderer::error::Result;
55
use scraper::{Html, Selector};
66
use std::fs;
77
use std::io::{Read, Write};
8-
use std::path::Path;
8+
use std::path::{Path, PathBuf};
9+
use std::process::Command;
910

1011
use std::collections::BTreeMap;
1112

1213
use serde::Deserialize;
1314

15+
/// Configuration specific to the i18n-helpers component.
1416
#[derive(Deserialize, Debug)]
15-
pub struct LanguagesConfiguration {
17+
pub struct I18nConfiguration {
1618
pub languages: BTreeMap<String, String>,
19+
pub default_language: Option<String>,
20+
}
21+
22+
/// Configuration from the book.toml file.
23+
pub struct RendererConfiguration {
24+
pub i18n: I18nConfiguration,
25+
pub destination: PathBuf,
26+
pub current_language: Option<String>,
27+
pub root: PathBuf,
28+
}
29+
30+
impl RendererConfiguration {
31+
pub fn new(
32+
i18n: I18nConfiguration,
33+
destination: PathBuf,
34+
current_language: Option<String>,
35+
root: PathBuf,
36+
) -> Self {
37+
RendererConfiguration {
38+
i18n,
39+
destination,
40+
current_language,
41+
root,
42+
}
43+
}
44+
}
45+
46+
pub struct RenderingContext {
47+
pub path: PathBuf,
48+
pub destination: PathBuf,
49+
pub language: String,
50+
}
51+
52+
impl RenderingContext {
53+
fn new(path: PathBuf, destination: PathBuf, language: String) -> Self {
54+
RenderingContext {
55+
path,
56+
destination,
57+
language,
58+
}
59+
}
60+
61+
pub fn rendered_path(&self) -> String {
62+
let mut relative_path = PathBuf::from(
63+
self.path
64+
.strip_prefix(&self.destination)
65+
.expect("Invalid path"),
66+
);
67+
if let Ok(stripped) = relative_path.strip_prefix(&self.language) {
68+
relative_path = stripped.to_owned();
69+
}
70+
String::from(
71+
relative_path
72+
.to_str()
73+
.expect("Failed to convert path to rendered path"),
74+
)
75+
}
1776
}
1877

1978
pub(crate) struct BookDirectoryRenderer {
20-
config: LanguagesConfiguration,
79+
config: RendererConfiguration,
2180
components: Vec<Box<dyn Component>>,
81+
languages_paths: BTreeMap<String, PathBuf>,
2282
}
2383

2484
impl BookDirectoryRenderer {
25-
pub(crate) fn new(config: LanguagesConfiguration) -> BookDirectoryRenderer {
85+
pub(crate) fn new(config: RendererConfiguration) -> BookDirectoryRenderer {
86+
let default_language = config.i18n.default_language.clone();
87+
let languages_paths = config
88+
.i18n
89+
.languages
90+
.keys()
91+
.filter(|language| {
92+
default_language.is_none() || *language != default_language.as_ref().unwrap()
93+
})
94+
.map(|language| {
95+
(
96+
language.clone(),
97+
config.destination.join("html").join(language),
98+
)
99+
})
100+
.collect::<BTreeMap<String, PathBuf>>();
26101
BookDirectoryRenderer {
27102
config,
103+
languages_paths,
28104
components: Vec::new(),
29105
}
30106
}
31107

32-
pub(crate) fn render_book(&mut self, path: &Path) -> Result<()> {
33-
if !path.is_dir() {
108+
pub fn translate(&self) -> Result<()> {
109+
let default_language = &self.config.i18n.default_language;
110+
for (identifier, _) in &self.config.i18n.languages {
111+
if let Some(default_language) = default_language {
112+
if default_language == identifier {
113+
continue;
114+
}
115+
}
116+
117+
let destination = self.config.destination.as_path().display().to_string();
118+
let book_folder = self.config.root.as_path().display().to_string();
119+
120+
Command::new("mdbook")
121+
.arg("build")
122+
.arg(&book_folder)
123+
.arg("-d")
124+
.arg(&format!("{destination}/{identifier}"))
125+
.env("MDBOOK_BOOK__LANGUAGE", identifier)
126+
.env(
127+
"MDBOOK_OUTPUT__HTML__SITE_URL",
128+
&format!("/comprehensive-rust/{identifier}/"),
129+
)
130+
.output()?;
131+
132+
std::fs::rename(
133+
&format!("{destination}/{identifier}/html"),
134+
&format!("{destination}/html/{identifier}"),
135+
)?;
136+
}
137+
138+
Ok(())
139+
}
140+
141+
pub(crate) fn render_book(&mut self) -> Result<()> {
142+
if !self.config.destination.is_dir() {
34143
return Err(RendererError::InvalidPath(format!(
35144
"{:?} is not a directory",
36-
path
145+
self.config.destination
37146
)));
38147
}
39-
self.render_book_directory(path)
148+
self.render_book_directory(&self.config.destination.clone())
40149
}
41150

42151
pub(crate) fn add_component(&mut self, component: Box<dyn Component>) {
43152
self.components.push(component);
44153
}
45154

46-
fn render_components(&mut self, file_content: &str) -> Result<String> {
155+
fn extract_language_from_path(&self, path: &Path) -> String {
156+
for (language, language_path) in &self.languages_paths {
157+
if path.starts_with(language_path) {
158+
return language.clone();
159+
}
160+
}
161+
self.config
162+
.i18n
163+
.default_language
164+
.clone()
165+
.unwrap_or_default()
166+
}
167+
168+
fn render_components(&mut self, file_content: &str, path: &Path) -> Result<String> {
169+
let path_buf = path.to_owned();
170+
let destination = self.config.destination.join("html");
171+
let language = self.extract_language_from_path(&path_buf);
172+
173+
let rendering_context = RenderingContext::new(path_buf, destination, language);
47174
let mut document = Html::parse_document(file_content);
48175
for custom_component in &mut self.components {
49176
let mut node_ids = Vec::new();
@@ -56,7 +183,7 @@ impl BookDirectoryRenderer {
56183
let tree = &mut document.tree;
57184
for id in node_ids {
58185
let dom_manipulator = NodeManipulator::new(tree, id);
59-
custom_component.render(dom_manipulator, &self.config)?;
186+
custom_component.render(dom_manipulator, &self.config, &rendering_context)?;
60187
}
61188
}
62189
Ok(document.html())
@@ -71,7 +198,7 @@ impl BookDirectoryRenderer {
71198
let mut file = fs::File::open(path)?;
72199
file.read_to_string(&mut file_content)?;
73200
}
74-
let output_html = self.render_components(&file_content)?;
201+
let output_html = self.render_components(&file_content, path)?;
75202
let mut file = fs::File::create(path)?;
76203
file.write_all(output_html.as_bytes())?;
77204
Ok(())
@@ -109,14 +236,20 @@ mod tests {
109236
let mut languages = BTreeMap::new();
110237
languages.insert(String::from("en"), String::from("English"));
111238
languages.insert(String::from("fr"), String::from("French"));
112-
let mock_config = LanguagesConfiguration { languages };
239+
let mock_config = RendererConfiguration::new(
240+
I18nConfiguration {
241+
languages,
242+
default_language: Some(String::from("en")),
243+
},
244+
dir.path().to_owned(),
245+
Some(String::from("en")),
246+
dir.path().to_owned(),
247+
);
113248

114249
let mut renderer = BookDirectoryRenderer::new(mock_config);
115250
let test_component = Box::new(TestComponent::new());
116251
renderer.add_component(test_component);
117-
renderer
118-
.render_book(dir.path())
119-
.expect("Failed to render book");
252+
renderer.render_book().expect("Failed to render book");
120253

121254
let mut output = String::new();
122255
let mut file = File::open(dir.path().join("test.html")).unwrap();
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
use super::dom_manipulator::NodeManipulator;
2+
use super::RenderingContext;
23
use crate::custom_component_renderer::error::Result;
3-
use crate::LanguagesConfiguration;
4+
use crate::RendererConfiguration;
45

56
pub trait Component {
67
/// Returns the identifier of the component. ie `<i18n-helpers />` -> `i18n-helpers`
78
fn identifier(&self) -> String;
89

9-
fn render(&mut self, node: NodeManipulator<'_>, config: &LanguagesConfiguration) -> Result<()>;
10+
fn render(
11+
&mut self,
12+
node: NodeManipulator<'_>,
13+
config: &RendererConfiguration,
14+
rendering_context: &RenderingContext,
15+
) -> Result<()>;
1016
}

src/custom_component_renderer/dom_manipulator.rs

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use super::error::RendererError;
22
use crate::custom_component_renderer::error::Result;
3+
use ego_tree::iter::Children;
34
use ego_tree::{NodeId, NodeMut, NodeRef, Tree};
45
use markup5ever::{namespace_url, ns, Attribute, LocalName, QualName};
56
use scraper::node::{Element, Text};
6-
use scraper::Node;
7+
use scraper::{Html, Node};
78

89
pub struct NodeManipulator<'a> {
910
tree: &'a mut Tree<Node>,
@@ -46,14 +47,21 @@ impl<'a> NodeManipulator<'a> {
4647
}
4748
}
4849

49-
/// Appends a child node and returns the id of the inserted node id.
50+
/// Appends a child node and returns a reference to the new node.
5051
pub fn append_child(&'a mut self, new_node: Node) -> Result<Self> {
5152
let mut node = self.get_node_mut()?;
5253
let inserted_id = node.append(new_node).id();
5354
Ok(Self::new(self.tree, inserted_id))
5455
}
5556

56-
pub fn append_children(&mut self) -> &mut AppendChildrenBuilder {
57+
/// Appends a sibling node and returns a reference to the new node.
58+
pub fn append_sibling(&'a mut self, new_node: Node) -> Result<Self> {
59+
let mut node = self.get_node_mut()?;
60+
let inserted_id = node.insert_after(new_node).id();
61+
Ok(Self::new(self.tree, inserted_id))
62+
}
63+
64+
pub fn builder(&mut self) -> &mut AppendChildrenBuilder {
5765
let builder = AppendChildrenBuilder::new(None);
5866
self.append_children_builder = Some(builder);
5967
self.append_children_builder.as_mut().unwrap()
@@ -75,13 +83,14 @@ impl<'a> NodeManipulator<'a> {
7583
Ok(())
7684
}
7785

78-
pub fn build_children(&'a mut self) -> Result<()> {
86+
pub fn build_children(&mut self) -> Result<&mut Self> {
7987
let builder = self.append_children_builder.take().ok_or_else(|| {
8088
RendererError::InternalError(String::from(
8189
"Missing children builder in build_children call",
8290
))
8391
})?;
84-
self.build_children_impl(builder)
92+
self.build_children_impl(builder)?;
93+
Ok(self)
8594
}
8695

8796
pub fn replace_with(mut self, new_node: Node) -> Result<Self> {
@@ -91,6 +100,42 @@ impl<'a> NodeManipulator<'a> {
91100
let Self { tree, .. } = self;
92101
Ok(Self::new(tree, inserted_id))
93102
}
103+
104+
/// Adds the nodes in `to_add` as children to `target` recursively.
105+
fn merge_trees(target: &mut NodeMut<Node>, to_add: Children<Node>) {
106+
for node in to_add {
107+
let mut inserted_node = target.append(node.value().clone());
108+
Self::merge_trees(&mut inserted_node, node.children());
109+
}
110+
}
111+
112+
/// Replaces the node with the given HTML.
113+
///
114+
/// We need to recursively add the children of the HTML node to the parent of the node we are replacing.
115+
pub fn replace_with_html(mut self, html: Html) -> Result<Self> {
116+
let mut node = self.get_node_mut()?;
117+
let html_root = html.tree.root();
118+
let inserted_id = node.insert_after(html_root.value().clone()).id();
119+
node.detach();
120+
let mut new_node = self.tree.get_mut(inserted_id).ok_or_else(|| {
121+
RendererError::InternalError(format!("Node with id {:?} does not exist", self.node_id))
122+
})?;
123+
Self::merge_trees(&mut new_node, html_root.children());
124+
let Self { tree, .. } = self;
125+
Ok(Self::new(tree, inserted_id))
126+
}
127+
128+
/// Appends a sibling node from HTML.
129+
pub fn append_sibling_html(&mut self, html: Html) -> Result<()> {
130+
let mut node = self.get_node_mut()?;
131+
let html_root = html.tree.root();
132+
let inserted_id = node.insert_after(html_root.value().clone()).id();
133+
let mut new_node = self.tree.get_mut(inserted_id).ok_or_else(|| {
134+
RendererError::InternalError(format!("Node with id {:?} does not exist", self.node_id))
135+
})?;
136+
Self::merge_trees(&mut new_node, html_root.children());
137+
Ok(())
138+
}
94139
}
95140

96141
pub struct AppendChildrenBuilder {
@@ -111,6 +156,21 @@ impl AppendChildrenBuilder {
111156
self.children.push(new_builder);
112157
self.children.last_mut().unwrap()
113158
}
159+
160+
/// Adds the nodes in `to_add` as children to `target` recursively.
161+
fn append_html_tree(&mut self, to_add: Children<Node>) {
162+
for node in to_add {
163+
let mut inserted_node = self.append_child(node.value().clone());
164+
Self::append_html_tree(&mut inserted_node, node.children());
165+
}
166+
}
167+
168+
pub fn append_html(&mut self, html: &str) {
169+
let parsed = Html::parse_fragment(html);
170+
let mut newly_added = self.append_child(parsed.tree.root().value().clone());
171+
let root_children = parsed.tree.root().children();
172+
Self::append_html_tree(&mut newly_added, root_children);
173+
}
114174
}
115175

116176
pub struct NodeAttribute {

0 commit comments

Comments
 (0)