1use 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
30pub trait DocIterator: Iterator
36{
37 fn take_doc(&mut self) -> Option<String>;
39}
40
41pub struct DocIter<T: Iterator>
46{
47 iter: T,
48}
49
50impl<T: Iterator> DocIter<T>
51{
52 pub fn new(iter: T) -> Self
54 { DocIter { iter, } }
55
56 pub fn iter(&self) -> &T
58 { &self.iter }
59
60 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#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
81pub enum BuiltinFunArg
82{
83 Arg(String),
85 OptArg(String),
87 DotDotDot,
89}
90
91#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
97pub enum Sig
98{
99 Var,
101 Fun(Vec<String>),
103 BuiltinFun(Vec<BuiltinFunArg>),
105}
106
107#[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 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 pub fn sig_root_mod(&self) -> &Arc<RwLock<ModNode<Sig, ()>>>
127 { &self.sig_root_mod }
128
129 pub fn doc_root_mod(&self) -> &Arc<RwLock<ModNode<String, Option<String>>>>
131 { &self.doc_root_mod }
132
133 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#[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 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 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 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#[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 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 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 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 pub fn script_dir(&self) -> &Path
235 { self.script_dir.as_path() }
236
237
238 pub fn run_with_doc_ident(&self) -> &str
240 { self.run_with_doc_ident.as_str() }
241
242 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
398pub 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('&', "&").replace('<', "<").replace(">", ">") }
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#[derive(Clone, Debug)]
433pub struct DocGen
434{
435 lib_name: String,
436 lib_doc_dir: PathBuf,
437}
438
439impl DocGen
440{
441 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 pub fn lib_name(&self) -> &str
452 { self.lib_name.as_str() }
453
454 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 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
672pub 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;