Skip to main content

unlab_gpu/
doc.rs

1//
2// Copyright (c) 2025-2026 Ɓukasz Szpakowski
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7//
8//! A documentation module.
9use std::fs::File;
10use std::fs::create_dir;
11use std::fs::write;
12use std::io;
13use std::io::BufWriter;
14use std::io::Write;
15use std::path;
16use std::path::Path;
17use std::path::PathBuf;
18use std::sync::Arc;
19use std::sync::RwLock;
20use std::sync::RwLockReadGuard;
21use markdown::CompileOptions;
22use markdown::Options;
23use markdown::ParseOptions;
24use crate::error::*;
25use crate::mod_node::*;
26use crate::parser::*;
27use crate::tree::*;
28use crate::utils::*;
29
30/// A trait of documentation iterator.
31///
32/// A documenentation iterator is a normal iterator with a documentation collection. A parser 
33/// collects documentation by this interator and then documentation is stored in a documentation
34/// module
35pub trait DocIterator: Iterator
36{
37    /// Takes the documentation.
38    fn take_doc(&mut self) -> Option<String>;
39}
40
41/// A structure of documentation iterator.
42///
43/// This structure represents the documentation iterator that is dummy and doesn't take the
44/// documentation.
45pub struct DocIter<T: Iterator>
46{
47    iter: T,
48}
49
50impl<T: Iterator> DocIter<T>
51{
52    /// Create a documentation iterator.
53    pub fn new(iter: T) -> Self
54    { DocIter { iter, } }
55
56    /// Returns the immutable iterator.
57    pub fn iter(&self) -> &T
58    { &self.iter }
59    
60    /// Returns the mutable iterator.
61    pub fn iter_mut(&mut self) -> &mut T
62    { &mut self.iter }
63}
64
65impl<T: Iterator> Iterator for DocIter<T>
66{
67    type Item = T::Item;
68    
69    fn next(&mut self) -> Option<Self::Item>
70    { self.iter.next() }
71}
72
73impl<T: Iterator> DocIterator for DocIter<T>
74{
75    fn take_doc(&mut self) -> Option<String>
76    { None }
77}
78
79/// An enumeration of argument of built-in function.
80#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
81pub enum BuiltinFunArg
82{
83    /// An Argument.
84    Arg(String),
85    /// An optional argument.
86    OptArg(String),
87    /// An optional arguments.
88    DotDotDot,
89}
90
91/// A signature structure.
92///
93/// The signature specifies whether a variable is a normal variable, a function, or a built-in
94/// function. If the variable is the function or the built-in funciton, the signature also
95/// specifies which arguments are passed to the function or the built-in function.
96#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
97pub enum Sig
98{
99    /// A normal variable.
100    Var,
101    /// A function.
102    Fun(Vec<String>),
103    /// A built-in function.
104    BuiltinFun(Vec<BuiltinFunArg>),
105}
106
107/// A structure of documentation tree.
108///
109/// The documentation tree is a half result of documentation generation. The documentation tree
110/// has two module nodes which are a root modules of signature and a root module of documentation.
111/// These modules are used to finally generate a documentation.
112#[derive(Clone, Debug)]
113pub struct DocTree
114{
115    sig_root_mod: Arc<RwLock<ModNode<Sig, ()>>>,
116    doc_root_mod: Arc<RwLock<ModNode<String, Option<String>>>>,
117}
118
119impl DocTree
120{
121    /// Creates a documentation tree.
122    pub fn new(sig_root_mod: Arc<RwLock<ModNode<Sig, ()>>>, doc_root_mod: Arc<RwLock<ModNode<String, Option<String>>>>) -> Self
123    { DocTree { sig_root_mod, doc_root_mod, } }
124    
125    /// Returns the root module of signature.
126    pub fn sig_root_mod(&self) -> &Arc<RwLock<ModNode<Sig, ()>>>
127    { &self.sig_root_mod }
128
129    /// Returns the root module of documentation.
130    pub fn doc_root_mod(&self) -> &Arc<RwLock<ModNode<String, Option<String>>>>
131    { &self.doc_root_mod }
132    
133    /// Locks the reader-writer locks of root modules with shared read access.
134    pub fn read(&self) -> Result<DocTreeReadGuard<'_>>
135    {
136        let sig_root_mod_g = rw_lock_read(&*self.sig_root_mod)?;
137        let doc_root_mod_g = rw_lock_read(&*self.doc_root_mod)?;
138        Ok(DocTreeReadGuard::new(sig_root_mod_g, doc_root_mod_g))
139    }
140}
141
142/// A structure of guard of documentation tree.
143#[derive(Debug)]
144pub struct DocTreeReadGuard<'a>
145{
146    sig_root_mod_g: RwLockReadGuard<'a, ModNode<Sig, ()>>,
147    doc_root_mod_g: RwLockReadGuard<'a, ModNode<String, Option<String>>>,
148}
149
150impl<'a> DocTreeReadGuard<'a>
151{
152    fn new(sig_root_mod_g: RwLockReadGuard<'a, ModNode<Sig, ()>>, doc_root_mod_g: RwLockReadGuard<'a, ModNode<String, Option<String>>>) -> Self
153    { DocTreeReadGuard { sig_root_mod_g, doc_root_mod_g, } }
154
155    /// Returns the description of root module.
156    pub fn desc(&self) -> Option<&String>
157    { 
158        match self.doc_root_mod_g.value() {
159            Some(desc) => Some(desc),
160            None => None,
161        }
162    }
163    
164    /// Returns the subtrees of the documentation tree.
165    pub fn subtrees(&self) -> Vec<(&String, DocTree)>
166    { self.sig_root_mod_g.mods().iter().map(|(id, sm)| self.doc_root_mod_g.mod1(id).map(|dm| (id, DocTree::new(sm.clone(), dm.clone())))).flatten().collect() }
167    
168    /// Returns the signatures with the optional descriptions.
169    pub fn var_desc_pairs(&self) -> Vec<(&String, (&Sig, Option<&String>))>
170    { self.sig_root_mod_g.vars().iter().map(|(id, s)| (id, (s, self.doc_root_mod_g.var(id)))).collect() }
171}
172
173#[derive(Clone, Debug)]
174struct DocTreeEnv
175{
176    sig_root_mod: Arc<RwLock<ModNode<Sig, ()>>>,
177    sig_current_mod: Arc<RwLock<ModNode<Sig, ()>>>,
178    doc_root_mod: Arc<RwLock<ModNode<String, Option<String>>>>,
179    doc_current_mod: Arc<RwLock<ModNode<String, Option<String>>>>,
180}
181
182impl DocTreeEnv
183{
184    fn new(doc_root_mod: Arc<RwLock<ModNode<String, Option<String>>>>) -> Self
185    {
186        let sig_root_mod: Arc<RwLock<ModNode<Sig, ()>>> = Arc::new(RwLock::new(ModNode::new(())));
187        DocTreeEnv {
188            sig_root_mod: sig_root_mod.clone(),
189            sig_current_mod: sig_root_mod,
190            doc_root_mod: doc_root_mod.clone(),
191            doc_current_mod: doc_root_mod,
192        }
193    }
194}
195
196/// A structure of generator of documentation tree.
197///
198/// The generator of documentation tree takes a root module of documentation and then generates
199/// a documentation tree. A root module of signature are generated from the syntax tree while
200/// a generation of documentation tree. Applications of built-in function of running with
201/// documentation allows to documention generation from other files than the main file of
202/// library. By defaylt, the built-in function of running with documentation is `runwithdoc`.
203#[derive(Clone, Debug)]
204pub struct DocTreeGen
205{
206    env: DocTreeEnv,
207    script_dir: PathBuf,
208    run_with_doc_ident: String,
209}
210
211impl DocTreeGen
212{
213    /// Creates a generator of documentation tree with the path to script directory and the
214    /// identifier of built-in function of running with documentation.
215    ///
216    /// Also, this method takes the root module of documentation that can have the module
217    /// descriptions and the variable description.
218    pub fn new_with_script_dir_and_run_with_doc_ident(doc_root_mod: Arc<RwLock<ModNode<String, Option<String>>>>, script_dir: PathBuf, run_with_doc_ident: String) -> Self
219    { DocTreeGen { env: DocTreeEnv::new(doc_root_mod), script_dir, run_with_doc_ident, } }
220
221    /// Creates a generator of documentation tree with the path to script directory.
222    ///
223    /// See [`new_with_script_dir_and_run_with_doc_ident`](Self::new_with_script_dir_and_run_with_doc_ident).
224    pub fn new_with_script_dir(doc_root_mod: Arc<RwLock<ModNode<String, Option<String>>>>, script_dir: PathBuf) -> Self
225    { Self::new_with_script_dir_and_run_with_doc_ident(doc_root_mod, script_dir, String::from("runwithdoc")) }
226
227    /// Creates a generator of documentation tree.
228    ///
229    /// See [`new_with_script_dir_and_run_with_doc_ident`](Self::new_with_script_dir_and_run_with_doc_ident).
230    pub fn new(doc_root_mod: Arc<RwLock<ModNode<String, Option<String>>>>) -> Self
231    { Self::new_with_script_dir(doc_root_mod, PathBuf::from(".")) }
232
233    /// Returns the path to script directory.
234    pub fn script_dir(&self) -> &Path
235    { self.script_dir.as_path() }
236    
237    
238    /// Returns the identifier of built-in function of running with documentation.
239    pub fn run_with_doc_ident(&self) -> &str
240    { self.run_with_doc_ident.as_str() }
241
242    /// Generates a documentation tree from the syntax tree.
243    pub fn generate(&mut self, tree: &Tree) -> Result<DocTree>
244    {
245        self.generate_for_tree(tree)?;
246        Ok(DocTree::new(self.env.sig_root_mod.clone(), self.env.doc_root_mod.clone()))
247    }
248
249    fn generate_for_tree(&mut self, tree: &Tree) -> Result<()>
250    {
251        match tree {
252            Tree(nodes) => self.generate_for_nodes(nodes.as_slice()),
253        }
254    }
255    
256    fn generate_for_node(&mut self, node: &Node, script_names: &mut Vec<String>) -> Result<()>
257    {
258        match node {
259            Node::Def(def) => self.generate_for_def(&**def),
260            Node::Stat(stat) => self.generate_for_stat(&**stat, script_names),
261        }
262    }
263
264    fn generate_for_nodes(&mut self, nodes: &[Node]) -> Result<()>
265    {
266        let mut script_names: Vec<String> = Vec::new();
267        for node in nodes {
268            self.generate_for_node(node, &mut script_names)?;
269        }
270        for script_name in &script_names {
271            let mut path_buf = PathBuf::from(self.script_dir.clone());
272            path_buf.push(script_name.replace('/', path::MAIN_SEPARATOR_STR).as_str());
273            let tree = parse_with_doc_root_mod_and_doc_current_mod(path_buf, Some(self.env.doc_root_mod.clone()), Some(self.env.doc_current_mod.clone()))?;
274            self.generate_for_tree(&tree)?;
275        }
276        Ok(())
277    }
278
279    fn generate_for_def(&mut self, def: &Def) -> Result<()>
280    {
281        match def {
282            Def::Mod(ident, mod1, _) => {
283                match &**mod1 {
284                    Mod(nodes) => {
285                        let new_sig_mod: Arc<RwLock<ModNode<Sig, ()>>> = Arc::new(RwLock::new(ModNode::new(())));
286                        ModNode::add_mod(&self.env.sig_current_mod, ident.clone(), new_sig_mod.clone())?;
287                        self.env.sig_current_mod = new_sig_mod;
288                        self.env.doc_current_mod = {
289                            let doc_current_mod_g = rw_lock_read(&*self.env.doc_current_mod)?;
290                            match doc_current_mod_g.mod1(ident) {
291                                Some(doc_mod) => doc_mod.clone(),
292                                None => return Err(Error::NoDocMod),
293                            }
294                        };
295                        self.generate_for_nodes(nodes.as_slice())?;
296                        let doc_parent = {
297                            let doc_current_mod_g = rw_lock_read(&*self.env.doc_current_mod)?;
298                            doc_current_mod_g.parent()
299                        };
300                        match doc_parent {
301                            Some(doc_parent) => self.env.doc_current_mod = doc_parent,
302                            None => (),
303                        }
304                        let sig_parent = {
305                            let sig_current_mod_g = rw_lock_read(&*self.env.sig_current_mod)?;
306                            sig_current_mod_g.parent()
307                        };
308                        match sig_parent {
309                            Some(sig_parent) => self.env.sig_current_mod = sig_parent,
310                            None => (),
311                        }
312                        
313                    },
314                }
315            },
316            Def::Fun(ident, fun, _) => {
317                match &**fun {
318                    Fun(args, _) => {
319                        let mut sig_current_mod_g = rw_lock_write(&*self.env.sig_current_mod)?;
320                        sig_current_mod_g.add_var(ident.clone(), Sig::Fun(args.iter().map(|a| a.0.clone()).collect()));
321                    },
322                }
323            },
324        }
325        Ok(())
326    }
327
328    fn generate_for_stat(&mut self, stat: &Stat, script_names: &mut Vec<String>) -> Result<()>
329    {
330        match stat {
331            Stat::Expr(expr, _) => {
332                match &**expr {
333                    Expr::App(expr2, exprs, _) => {
334                        let is_run_with_doc = match &**expr2 {
335                            Expr::Var(Name::Abs(idents, ident), _) => idents.is_empty() && ident == &self.run_with_doc_ident,
336                            Expr::Var(Name::Rel(_, _), _) => false,
337                            Expr::Var(Name::Var(ident), _) => {
338                                if ident == &self.run_with_doc_ident {
339                                    let sig_current_mod_g = rw_lock_read(&self.env.sig_current_mod)?;
340                                    !sig_current_mod_g.has_var(&self.run_with_doc_ident)
341                                } else {
342                                    false
343                                }
344                            },
345                            _ => false,
346                        };
347                        if is_run_with_doc {
348                            match exprs.first() {
349                                Some(expr3) => {
350                                    match &**expr3 {
351                                        Expr::Lit(Lit::String(s), _) => script_names.push(s.clone()),
352                                        _ => (),
353                                    }
354                                },
355                                None => (),
356                            }
357                        }
358                    },
359                    _ => (),
360                }
361            },
362            Stat::Assign(expr, _, _) => {
363                let pair = match &**expr {
364                    Expr::Var(Name::Abs(idents, ident), _) => {
365                        match ModNode::mod_from(&self.env.sig_root_mod, idents.as_slice(), false)? {
366                            Some(tmp_sig_mod) => Some((tmp_sig_mod, ident.clone())),
367                            None => None,
368                        }
369                    },
370                    Expr::Var(Name::Rel(idents, ident), _) => {
371                        match ModNode::mod_from(&self.env.sig_current_mod, idents.as_slice(), true)? {
372                            Some(tmp_sig_mod) => Some((tmp_sig_mod, ident.clone())),
373                            None => {
374                                match ModNode::mod_from(&self.env.sig_root_mod, idents.as_slice(), false)? {
375                                    Some(tmp_sig_mod) => Some((tmp_sig_mod, ident.clone())),
376                                    None => None,
377                                }
378                            },
379                        }
380                    },
381                    Expr::Var(Name::Var(ident), _) => Some((self.env.sig_current_mod.clone(), ident.clone())),
382                    _ => None,
383                };
384                match pair {
385                    Some((sig_mod, ident)) => {
386                        let mut sig_mod_g = rw_lock_write(&*sig_mod)?;
387                        sig_mod_g.add_var(ident, Sig::Var);
388                    },
389                    None => (),
390                }
391            },
392            _ => (),
393        }
394        Ok(())
395    }
396}
397
398/// Generates a documentation tree for the script directory.
399///
400/// See [`DocTreeGen::new_with_script_dir_and_run_with_doc_ident`] and [`DocTreeGen::generate`].
401pub fn generate_doc_tree<P: AsRef<Path>>(script_dir: P) -> Result<DocTree>
402{
403    let mut lib_file = PathBuf::from(script_dir.as_ref());
404    lib_file.push("lib.un");
405    let doc_root_mod: Arc<RwLock<ModNode<String, Option<String>>>> = Arc::new(RwLock::new(ModNode::new(None)));
406    let tree = parse_with_doc_root_mod(lib_file, Some(doc_root_mod.clone()))?;
407    let mut doc_tree_gen = DocTreeGen::new_with_script_dir(doc_root_mod, PathBuf::from(script_dir.as_ref()));
408    doc_tree_gen.generate(&tree)
409}
410
411fn str_to_html(s: &str) -> String
412{ s.replace('&', "&amp;").replace('<', "&lt;").replace(">", "&gt;") }
413
414fn idents_to_string(idents: &[String]) -> String
415{
416    let mut s = String::new();
417    let mut is_first = true;
418    for ident in idents {
419        if !is_first {
420            s.push_str("::");
421        }
422        s.push_str(ident.as_str());
423        is_first = false;
424    }
425    s
426}
427
428/// A strucuture of documentation generator.
429///
430/// The documentation generator generates a documentation in a documentation directory of library.
431/// The documentation tree is used to a documentation generation by the documentation generator.
432#[derive(Clone, Debug)]
433pub struct DocGen
434{
435    lib_name: String,
436    lib_doc_dir: PathBuf,
437}
438
439impl DocGen
440{
441    /// Creates a documentation generator.
442    pub fn new<P: AsRef<Path>>(doc_dir: PathBuf, lib_path: P) -> Self
443    {
444        let lib_name = lib_path.as_ref().to_string_lossy().into_owned().replace(path::MAIN_SEPARATOR, "/");
445        let mut lib_doc_dir = doc_dir;
446        lib_doc_dir.push(lib_path);
447        DocGen { lib_name, lib_doc_dir, }
448    }
449    
450    /// Returns the library name.
451    pub fn lib_name(&self) -> &str
452    { self.lib_name.as_str() }
453
454    /// Returns the path to the documentation directory of library.
455    pub fn lib_doc_dir(&self) -> &Path
456    { self.lib_doc_dir.as_path() }
457    
458    fn idents_to_html(idents: &[String]) -> String
459    {
460        let mut s = String::new();
461        let mut is_first = true;
462        for ident in idents {
463            if !is_first {
464                s.push_str("::");
465            }
466            if is_first {
467                s.push_str(format!("<span class=\"keyword\">{}</span>", ident).as_str());
468            } else {
469                s.push_str(format!("<span class=\"mod\">{}</span>", ident).as_str());
470            }
471            is_first = false;
472        }
473        s
474    }
475
476    fn str_to_href(s: &str, depth: usize) -> String
477    {
478        let mut url = String::new();
479        for _ in 0..depth {
480            url.push_str("../");
481        }
482        url.push_str(str_to_url_name(s, true).as_str());
483        url
484    }
485
486    fn ident_and_sig_to_html(ident: &str, sig: &Sig) -> String
487    {
488        let mut html = String::new();
489        html.push_str("<h3 class=\"sig\">");
490        match sig {
491            Sig::Var => (),
492            Sig::Fun(_) | Sig::BuiltinFun(_) => html.push_str("<span class=\"keyword\">function</span> "),
493        }
494        html.push_str(format!("<a href=\"#var.{}\" class=\"var\">{}</a>", ident, ident).as_str());
495        match sig {
496            Sig::Var => (),
497            Sig::Fun(args) => {
498                html.push('(');
499                let mut is_first = true;
500                for arg in args {
501                    if !is_first {
502                        html.push_str(", ");
503                    }
504                    html.push_str(format!("<span class=\"arg\">{}</span>", arg).as_str());
505                    is_first = false;
506                }
507                html.push(')');
508            },
509            Sig::BuiltinFun(args) => {
510                html.push('(');
511                let mut is_first = true;
512                for arg in args {
513                    if !is_first {
514                        html.push_str(", ");
515                    }
516                    match arg {
517                        BuiltinFunArg::Arg(ident) => html.push_str(format!("<span class=\"arg\">{}</span>", ident).as_str()),
518                        BuiltinFunArg::OptArg(ident) => html.push_str(format!("<span class=\"arg\">{}</span>?", ident).as_str()),
519                        BuiltinFunArg::DotDotDot => html.push_str("..."),
520                    }
521                    is_first = false;
522                }
523                html.push(')');
524            },
525        }
526        html.push_str("</h3>");
527        html
528    }
529    
530    fn markdown_to_html(s: &str) -> Result<String>
531    {
532        match latex2mathml::replace(s) {
533            Ok(t) => {
534                let options = Options {
535                    parse: ParseOptions::gfm(),
536                    compile: CompileOptions {
537                        allow_dangerous_html: true,
538                        gfm_tagfilter: true,
539                        ..CompileOptions::default()
540                    },
541                    ..Options::default()
542                };
543                match markdown::to_html_with_options(t.as_str(), &options) {
544                    Ok(u) => Ok(u),
545                    Err(msg) => Err(Error::Markdown(format!("{}", msg))),
546                }
547            },
548            Err(err) => Err(Error::Latex2mathml(Box::new(err))),
549        }
550    }
551    
552    fn io_res_generate_html_file<P: AsRef<Path>>(&self, path: P, idents: &[String], content: &str, depth: usize) -> io::Result<()>
553    {
554        let file = File::create(path)?;
555        let mut w = BufWriter::new(file);
556        writeln!(&mut w, "<!DOCTYPE html>")?;
557        writeln!(&mut w, "<html>")?;
558        writeln!(&mut w, "<head>")?;
559        writeln!(&mut w, "<meta charset=\"utf-8\" />")?;
560        writeln!(&mut w, "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />")?;
561        writeln!(&mut w, "<link rel=\"stylesheet\" href=\"{}\" />", Self::str_to_href("styles.css", depth))?;
562        write!(&mut w, "<title>")?;
563        if !idents.is_empty() {
564            write!(&mut w, "{} in ", idents_to_string(idents))?;
565        }
566        write!(&mut w, "{} - Unlab", str_to_html(self.lib_name.as_str()))?;
567        writeln!(&mut w, "</title>")?;
568        writeln!(&mut w, "</head>")?;
569        writeln!(&mut w, "<body>")?;
570        writeln!(&mut w, "<header>")?;
571        writeln!(&mut w, "<h1><a href=\"{}\">{}</a></h1>", Self::str_to_href("index.html", depth), str_to_html(self.lib_name.as_str()))?;
572        writeln!(&mut w, "</header>")?;
573        match idents.first() {
574            Some(first_ident) => {
575                writeln!(&mut w, "<nav>")?;
576                let mut mod_path = String::from(first_ident);
577                write!(&mut w, "<h2><a href=\"{}\" class=\"keyword\">{}</a>", Self::str_to_href(format!("{}.html", mod_path).as_str(), depth), first_ident)?;
578                for ident in &idents[1..] {
579                    mod_path.push('/');
580                    mod_path.push_str(ident.as_str());
581                    write!(&mut w, "::")?;
582                    write!(&mut w, "<a href=\"{}\" class=\"mod\">{}</a>", Self::str_to_href(format!("{}.html", mod_path).as_str(), depth), ident)?;
583                }
584                writeln!(&mut w, "</h2>")?;
585                writeln!(&mut w, "</nav>")?;
586            },
587            None => writeln!(&mut w, "<nav></nav>")?,
588        }
589        writeln!(&mut w, "<div id=\"left\"></div>")?;
590        writeln!(&mut w, "<main>")?;
591        write!(&mut w, "{}", content)?;
592        writeln!(&mut w, "</main>")?;
593        writeln!(&mut w, "<div id=\"right\"></div>")?;
594        writeln!(&mut w, "</body>")?;
595        writeln!(&mut w, "</html>")?;
596        Ok(())
597    }
598
599    fn generate_mod_doc(&self, doc_tree: &DocTree, idents: &[String], path: &Path, mod_path: &str, index_content: &mut String, depth: usize) -> Result<()>
600    {
601        index_content.push_str(format!("<li class=\"mod-list-item\"><a href=\"{}\">{}</a></li>\n", Self::str_to_href(format!("{}.html", mod_path).as_str(), 0), Self::idents_to_html(idents)).as_str());
602        let doc_tree_g = doc_tree.read()?;
603        let mut subtrees = doc_tree_g.subtrees();
604        if !subtrees.is_empty() {
605            match create_dir(path) {
606                Ok(()) => (),
607                Err(err) => return Err(Error::Io(err)),
608            }
609            subtrees.sort_by(|p1, p2| p1.0.cmp(p2.0));
610            for (ident, subtree) in subtrees {
611                let mut new_idents = idents.to_vec();
612                new_idents.push(ident.clone());
613                let mut new_path = PathBuf::from(path);
614                new_path.push(ident);
615                let mut new_mod_path = String::from(mod_path);
616                new_mod_path.push('/');
617                new_mod_path.push_str(ident);
618                self.generate_mod_doc(&subtree, new_idents.as_slice(), new_path.as_path(), new_mod_path.as_str(), index_content, depth + 1)?;
619            }
620        }
621        let mut mod_content = String::new();
622        mod_content.push_str("<section id=\"mod\" class=\"mod-section\">\n");
623        match doc_tree_g.desc() {
624            Some(desc) => mod_content.push_str(format!("<div class=\"mod-desc desc\">\n{}</div>\n", Self::markdown_to_html(desc.as_str())?).as_str()),
625            None => (),
626        }
627        mod_content.push_str("</section>\n");
628        let mut var_desc_pairs = doc_tree_g.var_desc_pairs();
629        var_desc_pairs.sort_by(|p1, p2| p1.0.cmp(p2.0));
630        for (ident, (sig, desc))  in var_desc_pairs {
631            mod_content.push_str("<hr />\n");
632            mod_content.push_str(format!("<section id=\"var.{}\" class=\"var-section\">\n", ident).as_str());
633            mod_content.push_str(format!("{}\n", Self::ident_and_sig_to_html(ident, sig)).as_str());
634            match desc {
635                Some(desc) => mod_content.push_str(format!("<div class=\"var-desc desc\">\n{}</div>\n", Self::markdown_to_html(desc.as_str())?).as_str()),
636                None => (),
637            }
638            mod_content.push_str("</section>\n");
639        }
640        match self.io_res_generate_html_file(path.with_extension("html"), idents, mod_content.as_str(), depth) {
641            Ok(()) => (),
642            Err(err) => return Err(Error::Io(err)),
643        }
644        Ok(())
645    }
646
647    /// Generates a documnetation from the documentation tree.
648    pub fn generate(&self, doc_tree: &DocTree) -> Result<()>
649    {
650        let mut styles_path_buf = self.lib_doc_dir.clone();
651        styles_path_buf.push("styles.css");
652        match write(styles_path_buf, include_str!("styles.css")) {
653            Ok(()) => (),
654            Err(err) => return Err(Error::Io(err)),
655        }
656        let mut path_buf = self.lib_doc_dir.clone();
657        path_buf.push("root");
658        let mut index_content = String::new();
659        index_content.push_str("<ul class=\"mod-list\">\n");
660        self.generate_mod_doc(doc_tree, &[String::from("root")], path_buf.as_path(), "root", &mut index_content, 0)?;
661        index_content.push_str("</ul>\n");
662        let mut index_path_buf = self.lib_doc_dir.clone();
663        index_path_buf.push("index.html");
664        match self.io_res_generate_html_file(index_path_buf, &[], index_content.as_str(), 0) {
665            Ok(()) => (),
666            Err(err) => return Err(Error::Io(err)),
667        }
668        Ok(())
669    }
670}
671
672/// Generates a documentation in the documentation directory for the library directory and the
673/// library path.
674///
675/// See [`generate_doc_tree`], [`DocGen::new`], and [`DocGen::generate`].
676pub fn generate_doc<P: AsRef<Path>, Q: AsRef<Path>, R: AsRef<Path>>(lib_dir: P, doc_dir: Q, lib_path: R) -> Result<()>
677{
678    let mut script_dir = PathBuf::from(lib_dir.as_ref());
679    script_dir.push(lib_path.as_ref());
680    let doc_tree = generate_doc_tree(script_dir)?;
681    let doc_gen = DocGen::new(PathBuf::from(doc_dir.as_ref()), lib_path.as_ref());
682    doc_gen.generate(&doc_tree)
683}
684
685#[cfg(test)]
686mod tests;