@@ -3,118 +3,103 @@ use super::CustomComponent;
33use crate :: custom_component_renderer:: error:: Result ;
44use lol_html:: html_content:: ContentType ;
55use lol_html:: { element, RewriteStrSettings } ;
6+ use mdbook:: renderer:: RenderContext ;
7+ use serde_json:: to_value;
8+ use std:: collections:: HashMap ;
69use std:: fs;
710use std:: io:: { Read , Write } ;
811use std:: path:: { Path , PathBuf } ;
9-
10- use std:: collections:: BTreeMap ;
11-
12- use serde:: Deserialize ;
13-
14- /// Configuration specific to the i18n-helpers component.
15- #[ derive( Deserialize , Debug ) ]
16- pub struct I18nConfiguration {
17- pub languages : BTreeMap < String , String > ,
18- pub default_language : Option < String > ,
19- #[ serde( default ) ]
20- pub translate_all_languages : bool ,
21- }
12+ use std:: sync:: Arc ;
2213
2314pub struct RenderingContext < ' a > {
2415 pub path : PathBuf ,
25- pub language : String ,
26- pub i18_config : & ' a I18nConfiguration ,
27- pub language_to_rendered_path : BTreeMap < String , PathBuf > ,
16+ pub language : Option < String > ,
17+ pub serialized_ctx : & ' a serde_json :: Value ,
18+ pub ctx : & ' a RenderContext ,
2819}
2920
3021impl < ' a > RenderingContext < ' a > {
3122 fn new (
3223 path : PathBuf ,
33- book_dir : PathBuf ,
34- language : String ,
35- i18_config : & ' a I18nConfiguration ,
24+ language : Option < String > ,
25+ serialized_ctx : & ' a serde_json :: Value ,
26+ ctx : & ' a RenderContext ,
3627 ) -> Result < Self > {
37- let html_dir = book_dir. join ( "html" ) ;
38- let mut language_to_rendered_path: BTreeMap < String , PathBuf > = BTreeMap :: new ( ) ;
39- for identifier in i18_config. languages . keys ( ) {
40- let mut relative_path = path. strip_prefix ( & html_dir) ?. to_owned ( ) ;
41- if let Ok ( without_lang) = relative_path. strip_prefix ( & language) {
42- relative_path = without_lang. to_owned ( ) ;
43- }
44- if Some ( identifier) != i18_config. default_language . as_ref ( ) {
45- relative_path = Path :: new ( identifier) . join ( relative_path) . to_owned ( ) ;
46- }
47- language_to_rendered_path. insert (
48- identifier. clone ( ) ,
49- Path :: new ( "/" ) . join ( relative_path) . to_owned ( ) ,
50- ) ;
51- }
5228 Ok ( RenderingContext {
5329 path,
5430 language,
55- i18_config ,
56- language_to_rendered_path ,
31+ serialized_ctx ,
32+ ctx ,
5733 } )
5834 }
5935}
6036
6137pub ( crate ) struct BookDirectoryRenderer {
62- book : mdbook :: MDBook ,
63- book_dir : PathBuf ,
38+ ctx : Arc < RenderContext > ,
39+ serialized_ctx : serde_json :: Value ,
6440 components : Vec < CustomComponent > ,
65- languages_paths : BTreeMap < String , PathBuf > ,
6641}
6742
6843impl BookDirectoryRenderer {
69- pub ( crate ) fn new ( book : mdbook:: MDBook , book_dir : PathBuf ) -> BookDirectoryRenderer {
70- let default_language = config. default_language . clone ( ) ;
71- let languages_paths = config
72- . languages
73- . keys ( )
74- . filter ( |language| {
75- default_language. is_none ( ) || * language != default_language. as_ref ( ) . unwrap ( )
76- } )
77- . map ( |language| ( language. clone ( ) , book_dir. join ( "html" ) . join ( language) ) )
78- . collect :: < BTreeMap < String , PathBuf > > ( ) ;
79- BookDirectoryRenderer {
80- config,
81- book,
82- languages_paths,
83- book_dir,
44+ pub ( crate ) fn new ( ctx : RenderContext ) -> Result < BookDirectoryRenderer > {
45+ Ok ( BookDirectoryRenderer {
46+ serialized_ctx : serde_json:: to_value ( & ctx) ?,
47+ ctx : Arc :: new ( ctx) ,
8448 components : Vec :: new ( ) ,
85- }
49+ } )
8650 }
8751
8852 pub ( crate ) fn render_book ( & mut self ) -> Result < ( ) > {
89- let html_dir = self . book_dir . join ( "html" ) ;
90- if !html_dir. is_dir ( ) {
53+ let dest_dir = & self
54+ . ctx
55+ . destination
56+ . parent ( )
57+ . ok_or_else ( || {
58+ RendererError :: InvalidPath ( format ! (
59+ "Destination directory {:?} has no parent" ,
60+ self . ctx. destination
61+ ) )
62+ } ) ?
63+ . to_owned ( ) ;
64+ if !dest_dir. is_dir ( ) {
9165 return Err ( RendererError :: InvalidPath ( format ! (
9266 "{:?} is not a directory" ,
93- self . book_dir
67+ dest_dir
9468 ) ) ) ;
9569 }
96- self . render_book_directory ( & html_dir )
70+ self . render_book_directory ( & dest_dir )
9771 }
9872
99- pub ( crate ) fn add_component ( & mut self , component : CustomComponent ) {
100- self . components . push ( component) ;
73+ fn create_get_context_function ( & self ) -> impl tera:: Function {
74+ let ctx_rx = Arc :: clone ( & self . ctx ) ;
75+ move |args : & HashMap < String , serde_json:: value:: Value > | -> tera:: Result < tera:: Value > {
76+ let key = args
77+ . get ( "key" )
78+ . ok_or_else ( || tera:: Error :: from ( format ! ( "No key argument provided" ) ) ) ?
79+ . as_str ( )
80+ . ok_or_else ( || {
81+ tera:: Error :: from ( format ! ( "Key has invalid type, expected string" ) )
82+ } ) ?;
83+ let value = ctx_rx
84+ . config
85+ . get ( key)
86+ . ok_or_else ( || tera:: Error :: from ( format ! ( "Could not find key {key} in config" ) ) ) ?;
87+ let value = to_value ( value) ?;
88+ Ok ( value)
89+ }
10190 }
10291
103- fn extract_language_from_path ( & self , path : & Path ) -> String {
104- for ( language, language_path) in & self . languages_paths {
105- if path. starts_with ( language_path) {
106- return language. clone ( ) ;
107- }
108- }
109- self . config . default_language . clone ( ) . unwrap_or_default ( )
92+ pub ( crate ) fn add_component ( & mut self , mut component : CustomComponent ) {
93+ component. register_function ( "get_context" , self . create_get_context_function ( ) ) ;
94+ self . components . push ( component) ;
11095 }
11196
11297 fn render_components ( & mut self , file_content : & str , path : & Path ) -> Result < String > {
11398 let rendering_context = RenderingContext :: new (
11499 path. to_owned ( ) ,
115- self . book_dir . clone ( ) ,
116- self . extract_language_from_path ( path ) ,
117- & self . config ,
100+ self . ctx . config . book . language . clone ( ) ,
101+ & self . serialized_ctx ,
102+ & self . ctx ,
118103 ) ?;
119104 let custom_components_handlers = self
120105 . components
@@ -192,11 +177,11 @@ mod tests {
192177 [output.html]
193178 curly-quotes = true
194179
195- [output.i18n-helpers ]
180+ [output.i18n]
196181 default_language = "en"
197182 translate_all_languages = false
198183
199- [output.i18n-helpers .languages]
184+ [output.i18n.languages]
200185 "en" = "English"
201186 "es" = "Spanish (Español)"
202187 "ko" = "Korean (한국어)"
@@ -222,25 +207,23 @@ mod tests {
222207 std:: fs:: write ( dir. path ( ) . join ( "src/SUMMARY.md" ) , "" )
223208 . expect ( "Failed to write initial SUMMARY.md" ) ;
224209
225- let mut languages = BTreeMap :: new ( ) ;
226- languages. insert ( String :: from ( "en" ) , String :: from ( "English" ) ) ;
227- languages. insert ( String :: from ( "fr" ) , String :: from ( "French" ) ) ;
228- let mock_config = I18nConfiguration {
229- languages,
230- default_language : Some ( String :: from ( "en" ) ) ,
231- translate_all_languages : true ,
232- } ;
233- let mdbook = mdbook:: MDBook :: load ( & dir. path ( ) ) . expect ( "Failed to load book" ) ;
234-
235- let mut renderer = BookDirectoryRenderer :: new ( mock_config, mdbook, dir. path ( ) . to_owned ( ) ) ;
210+ let mdbook = mdbook:: MDBook :: load ( dir. path ( ) ) . expect ( "Failed to load mdbook" ) ;
211+ let ctx = RenderContext :: new (
212+ dir. path ( ) ,
213+ mdbook. book ,
214+ mdbook. config ,
215+ dir. path ( ) . join ( "i18n-helpers" ) ,
216+ ) ;
217+
218+ let mut renderer = BookDirectoryRenderer :: new ( ctx) . expect ( "Failed to create renderer" ) ;
236219 renderer. add_component ( standard_templates:: create_language_picker_component ( ) ) ;
237220 renderer. render_book ( ) . expect ( "Failed to render book" ) ;
238221
239222 let mut output = String :: new ( ) ;
240223 let mut file = File :: open ( dir. path ( ) . join ( "html/test.html" ) ) . unwrap ( ) ;
241224 file. read_to_string ( & mut output) . unwrap ( ) ;
242225
243- const EXPECTED : & str = "<html><body><button id=\" language-toggle0\" class=\" icon-button\" type=\" button\" \n title=\" Change language\" aria-label=\" Change language\" \n aria-haspopup=\" true\" aria-expanded=\" false\" \n aria-controls=\" language-list0\" >\n <i class=\" fa fa-globe\" ></i>\n </button>\n <ul id=\" language-list0\" class=\" theme-popup\" aria-label=\" Languages\" \n role=\" menu\" style=\" left: auto; right: 10px;\" >\n \n <li role=\" none\" >\n <a id=\" en\" \n href=\" /test.html\" \n style=\" color: inherit;\" >\n <button role=\" menuitem\" class=\" theme theme-selected \" >\n English\n </button>\n </a>\n </li>\n \n <li role=\" none\" >\n <a id=\" fr\" \n href=\" /fr/test.html\" \n style=\" color: inherit;\" >\n <button role=\" menuitem\" class=\" theme \" >\n French\n </button>\n </a>\n </li>\n \n </ul>\n \n <script>\n let langToggle = document.getElementById(\" language-toggle0\" );\n let langList = document.getElementById(\" language-list0\" );\n \n langToggle.addEventListener(\" click\" , (event) => {{\n langList.style.display = langList.style.display == \" block\" ? \" none\" : \" block\" ;\n }});\n \n </script>\n \n <style>\n [dir=rtl] #language-list0 {\n left: 10px;\n right: auto;\n }\n \n </style>\n </html>" ;
226+ const EXPECTED : & str = "<html><body><button id=\" language-toggle0\" class=\" icon-button\" type=\" button\" \n title=\" Change language\" aria-label=\" Change language\" \n aria-haspopup=\" true\" aria-expanded=\" false\" \n aria-controls=\" language-list0\" >\n <i class=\" fa fa-globe\" ></i>\n </button>\n <ul id=\" language-list0\" class=\" theme-popup\" aria-label=\" Languages\" \n role=\" menu\" style=\" left: auto; right: 10px;\" >\n \n <li role=\" none\" >\n <a id=\" en\" \n href=\" /test.html\" \n style=\" color: inherit;\" >\n <button role=\" menuitem\" class=\" theme theme-selected \" >\n English\n </button>\n </a>\n </li>\n \n <li role=\" none\" >\n <a id=\" es\" \n href=\" /es/test.html\" \n style=\" color: inherit;\" >\n <button role=\" menuitem\" class=\" theme \" >\n Spanish (Español)\n </button>\n </a>\n </li>\n \n <li role=\" none\" >\n <a id=\" ko\" \n href=\" /ko/test.html\" \n style=\" color: inherit;\" >\n <button role=\" menuitem\" class=\" theme \" >\n Korean (한국어)\n </button>\n </a>\n </li>\n \n <li role=\" none\" >\n <a id=\" pt-BR\" \n href=\" /pt-BR/test.html\" \n style=\" color: inherit;\" >\n <button role=\" menuitem\" class=\" theme \" >\n Brazilian Portuguese (Português do Brasil)\n </button>\n </a>\n </li>\n \n </ul>\n \n <script>\n let langToggle = document.getElementById(\" language-toggle0\" );\n let langList = document.getElementById(\" language-list0\" );\n \n langToggle.addEventListener(\" click\" , (event) => {{\n langList.style.display = langList.style.display == \" block\" ? \" none\" : \" block\" ;\n }});\n \n </script>\n \n <style>\n [dir=rtl] #language-list0 {\n left: 10px;\n right: auto;\n }\n \n </style>\n </html>" ;
244227
245228 assert_eq ! ( output, EXPECTED ) ;
246229 }
0 commit comments