Skip to content

Commit

Permalink
Translate from renderer + LanguagePicker
Browse files Browse the repository at this point in the history
  • Loading branch information
sakex committed Sep 18, 2023
1 parent d428f65 commit 98cc45f
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 34 deletions.
163 changes: 148 additions & 15 deletions src/custom_component_renderer/book_directory_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,172 @@ use crate::custom_component_renderer::error::Result;
use scraper::{Html, Selector};
use std::fs;
use std::io::{Read, Write};
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::Command;

use std::collections::BTreeMap;

use serde::Deserialize;

/// Configuration specific to the i18n-helpers component.
#[derive(Deserialize, Debug)]
pub struct LanguagesConfiguration {
pub struct I18nConfiguration {
pub languages: BTreeMap<String, String>,
pub default_language: Option<String>,
}

/// Configuration from the book.toml file.
pub struct RendererConfiguration {
pub i18n: I18nConfiguration,
pub destination: PathBuf,
pub current_language: Option<String>,
pub root: PathBuf,
}

impl RendererConfiguration {
pub fn new(
i18n: I18nConfiguration,
destination: PathBuf,
current_language: Option<String>,
root: PathBuf,
) -> Self {
RendererConfiguration {
i18n,
destination,
current_language,
root,
}
}
}

pub struct RenderingContext {
pub path: PathBuf,
pub destination: PathBuf,
pub language: String,
}

impl RenderingContext {
fn new(path: PathBuf, destination: PathBuf, language: String) -> Self {
RenderingContext {
path,
destination,
language,
}
}

pub fn rendered_path(&self) -> String {
let mut relative_path = PathBuf::from(
self.path
.strip_prefix(&self.destination)
.expect("Invalid path"),
);
if let Ok(stripped) = relative_path.strip_prefix(&self.language) {
relative_path = stripped.to_owned();
}
String::from(
relative_path
.to_str()
.expect("Failed to convert path to rendered path"),
)
}
}

pub(crate) struct BookDirectoryRenderer {
config: LanguagesConfiguration,
config: RendererConfiguration,
components: Vec<Box<dyn Component>>,
languages_paths: BTreeMap<String, PathBuf>,
}

impl BookDirectoryRenderer {
pub(crate) fn new(config: LanguagesConfiguration) -> BookDirectoryRenderer {
pub(crate) fn new(config: RendererConfiguration) -> BookDirectoryRenderer {
let default_language = config.i18n.default_language.clone();
let languages_paths = config
.i18n
.languages
.keys()
.filter(|language| {
default_language.is_none() || *language != default_language.as_ref().unwrap()
})
.map(|language| {
(
language.clone(),
config.destination.join("html").join(language),
)
})
.collect::<BTreeMap<String, PathBuf>>();
BookDirectoryRenderer {
config,
languages_paths,
components: Vec::new(),
}
}

pub(crate) fn render_book(&mut self, path: &Path) -> Result<()> {
if !path.is_dir() {
pub fn translate(&self) -> Result<()> {
let default_language = &self.config.i18n.default_language;
for (identifier, _) in &self.config.i18n.languages {
if let Some(default_language) = default_language {
if default_language == identifier {
continue;
}
}

let destination = self.config.destination.as_path().display().to_string();
let book_folder = self.config.root.as_path().display().to_string();

Command::new("mdbook")
.arg("build")
.arg(&book_folder)
.arg("-d")
.arg(&format!("{destination}/{identifier}"))
.env("MDBOOK_BOOK__LANGUAGE", identifier)
.env(
"MDBOOK_OUTPUT__HTML__SITE_URL",
&format!("/comprehensive-rust/{identifier}/"),
)
.output()?;

std::fs::rename(
&format!("{destination}/{identifier}/html"),
&format!("{destination}/html/{identifier}"),
)?;
}

Ok(())
}

pub(crate) fn render_book(&mut self) -> Result<()> {
if !self.config.destination.is_dir() {
return Err(RendererError::InvalidPath(format!(
"{:?} is not a directory",
path
self.config.destination
)));
}
self.render_book_directory(path)
self.render_book_directory(&self.config.destination.clone())
}

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

fn render_components(&mut self, file_content: &str) -> Result<String> {
fn extract_language_from_path(&self, path: &Path) -> String {
for (language, language_path) in &self.languages_paths {
if path.starts_with(language_path) {
return language.clone();
}
}
self.config
.i18n
.default_language
.clone()
.unwrap_or_default()
}

fn render_components(&mut self, file_content: &str, path: &Path) -> Result<String> {
let path_buf = path.to_owned();
let destination = self.config.destination.join("html");
let language = self.extract_language_from_path(&path_buf);

let rendering_context = RenderingContext::new(path_buf, destination, language);
let mut document = Html::parse_document(file_content);
for custom_component in &mut self.components {
let mut node_ids = Vec::new();
Expand All @@ -56,7 +183,7 @@ impl BookDirectoryRenderer {
let tree = &mut document.tree;
for id in node_ids {
let dom_manipulator = NodeManipulator::new(tree, id);
custom_component.render(dom_manipulator, &self.config)?;
custom_component.render(dom_manipulator, &self.config, &rendering_context)?;
}
}
Ok(document.html())
Expand All @@ -71,7 +198,7 @@ impl BookDirectoryRenderer {
let mut file = fs::File::open(path)?;
file.read_to_string(&mut file_content)?;
}
let output_html = self.render_components(&file_content)?;
let output_html = self.render_components(&file_content, path)?;
let mut file = fs::File::create(path)?;
file.write_all(output_html.as_bytes())?;
Ok(())
Expand Down Expand Up @@ -109,14 +236,20 @@ mod tests {
let mut languages = BTreeMap::new();
languages.insert(String::from("en"), String::from("English"));
languages.insert(String::from("fr"), String::from("French"));
let mock_config = LanguagesConfiguration { languages };
let mock_config = RendererConfiguration::new(
I18nConfiguration {
languages,
default_language: Some(String::from("en")),
},
dir.path().to_owned(),
Some(String::from("en")),
dir.path().to_owned(),
);

let mut renderer = BookDirectoryRenderer::new(mock_config);
let test_component = Box::new(TestComponent::new());
renderer.add_component(test_component);
renderer
.render_book(dir.path())
.expect("Failed to render book");
renderer.render_book().expect("Failed to render book");

let mut output = String::new();
let mut file = File::open(dir.path().join("test.html")).unwrap();
Expand Down
10 changes: 8 additions & 2 deletions src/custom_component_renderer/component_trait.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use super::dom_manipulator::NodeManipulator;
use super::RenderingContext;
use crate::custom_component_renderer::error::Result;
use crate::LanguagesConfiguration;
use crate::RendererConfiguration;

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

fn render(&mut self, node: NodeManipulator<'_>, config: &LanguagesConfiguration) -> Result<()>;
fn render(
&mut self,
node: NodeManipulator<'_>,
config: &RendererConfiguration,
rendering_context: &RenderingContext,
) -> Result<()>;
}
70 changes: 65 additions & 5 deletions src/custom_component_renderer/dom_manipulator.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::error::RendererError;
use crate::custom_component_renderer::error::Result;
use ego_tree::iter::Children;
use ego_tree::{NodeId, NodeMut, NodeRef, Tree};
use markup5ever::{namespace_url, ns, Attribute, LocalName, QualName};
use scraper::node::{Element, Text};
use scraper::Node;
use scraper::{Html, Node};

pub struct NodeManipulator<'a> {
tree: &'a mut Tree<Node>,
Expand Down Expand Up @@ -46,14 +47,21 @@ impl<'a> NodeManipulator<'a> {
}
}

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

pub fn append_children(&mut self) -> &mut AppendChildrenBuilder {
/// Appends a sibling node and returns a reference to the new node.
pub fn append_sibling(&'a mut self, new_node: Node) -> Result<Self> {
let mut node = self.get_node_mut()?;
let inserted_id = node.insert_after(new_node).id();
Ok(Self::new(self.tree, inserted_id))
}

pub fn builder(&mut self) -> &mut AppendChildrenBuilder {
let builder = AppendChildrenBuilder::new(None);
self.append_children_builder = Some(builder);
self.append_children_builder.as_mut().unwrap()
Expand All @@ -75,13 +83,14 @@ impl<'a> NodeManipulator<'a> {
Ok(())
}

pub fn build_children(&'a mut self) -> Result<()> {
pub fn build_children(&mut self) -> Result<&mut Self> {
let builder = self.append_children_builder.take().ok_or_else(|| {
RendererError::InternalError(String::from(
"Missing children builder in build_children call",
))
})?;
self.build_children_impl(builder)
self.build_children_impl(builder)?;
Ok(self)
}

pub fn replace_with(mut self, new_node: Node) -> Result<Self> {
Expand All @@ -91,6 +100,42 @@ impl<'a> NodeManipulator<'a> {
let Self { tree, .. } = self;
Ok(Self::new(tree, inserted_id))
}

/// Adds the nodes in `to_add` as children to `target` recursively.
fn merge_trees(target: &mut NodeMut<Node>, to_add: Children<Node>) {
for node in to_add {
let mut inserted_node = target.append(node.value().clone());
Self::merge_trees(&mut inserted_node, node.children());
}
}

/// Replaces the node with the given HTML.
///
/// We need to recursively add the children of the HTML node to the parent of the node we are replacing.
pub fn replace_with_html(mut self, html: Html) -> Result<Self> {
let mut node = self.get_node_mut()?;
let html_root = html.tree.root();
let inserted_id = node.insert_after(html_root.value().clone()).id();
node.detach();
let mut new_node = self.tree.get_mut(inserted_id).ok_or_else(|| {
RendererError::InternalError(format!("Node with id {:?} does not exist", self.node_id))
})?;
Self::merge_trees(&mut new_node, html_root.children());
let Self { tree, .. } = self;
Ok(Self::new(tree, inserted_id))
}

/// Appends a sibling node from HTML.
pub fn append_sibling_html(&mut self, html: Html) -> Result<()> {
let mut node = self.get_node_mut()?;
let html_root = html.tree.root();
let inserted_id = node.insert_after(html_root.value().clone()).id();
let mut new_node = self.tree.get_mut(inserted_id).ok_or_else(|| {
RendererError::InternalError(format!("Node with id {:?} does not exist", self.node_id))
})?;
Self::merge_trees(&mut new_node, html_root.children());
Ok(())
}
}

pub struct AppendChildrenBuilder {
Expand All @@ -111,6 +156,21 @@ impl AppendChildrenBuilder {
self.children.push(new_builder);
self.children.last_mut().unwrap()
}

/// Adds the nodes in `to_add` as children to `target` recursively.
fn append_html_tree(&mut self, to_add: Children<Node>) {
for node in to_add {
let mut inserted_node = self.append_child(node.value().clone());
Self::append_html_tree(&mut inserted_node, node.children());
}
}

pub fn append_html(&mut self, html: &str) {
let parsed = Html::parse_fragment(html);
let mut newly_added = self.append_child(parsed.tree.root().value().clone());
let root_children = parsed.tree.root().children();
Self::append_html_tree(&mut newly_added, root_children);
}
}

pub struct NodeAttribute {
Expand Down
Loading

0 comments on commit 98cc45f

Please sign in to comment.