From 1d199936f64b7a35be2f12f1a2cf3e0a9581e6a4 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Tue, 6 Mar 2018 00:01:36 +0100 Subject: [PATCH] feat(doc): Add documentation generation Adds the `doc` module to `gluon` which contains functions to generate markdown from a module or a directory of modules. Also exports this functionality through the `doc` subcommand --- Cargo.lock | 32 ++++++++++ Cargo.toml | 1 + repl/src/main.rs | 12 ++-- src/compiler_pipeline.rs | 6 +- src/doc.rs | 125 +++++++++++++++++++++++++-------------- std/list.glu | 2 +- 6 files changed, 125 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57dbed7906..ceb3c15ce2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,6 +341,25 @@ dependencies = [ "backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "failure" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "filetime" version = "0.1.15" @@ -415,6 +434,7 @@ dependencies = [ "curl 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "gluon_base 0.7.1", @@ -1354,6 +1374,15 @@ dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "synstructure" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "take" version = "0.1.0" @@ -1758,6 +1787,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum encode_unicode 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "28d65f1f5841ef7c6792861294b72beda34c664deb8be27970f36c306b7da1ce" "checksum env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0561146661ae44c579e993456bc76d11ce1e0c7d745e57b2fa7146b6e49fa2ad" "checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" +"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82" +"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b" "checksum filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f" "checksum fixedbitset 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "85cb8fec437468d86dc7c83ca7cfc933341d561873275f22dd5eedefa63a6478" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" @@ -1861,6 +1892,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum syn 0.12.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8c5bc2d6ff27891209efa5f63e9de78648d7801f085e4653701a692ce938d6fd" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" "checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e" "checksum tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0" diff --git a/Cargo.toml b/Cargo.toml index d9a7254e60..9e9786677b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ either = "1.0.0" itertools = "0.7.0" futures = "0.1.11" walkdir = "1" +failure = { version = "0.1", features = ["backtrace"] } serde = { version = "1.0.0", optional = true } serde_state = { version = "0.4.0", optional = true } diff --git a/repl/src/main.rs b/repl/src/main.rs index bc9ce39cf6..a9e338c516 100644 --- a/repl/src/main.rs +++ b/repl/src/main.rs @@ -118,7 +118,8 @@ fn run() -> std::result::Result<(), Box> { ) (@subcommand doc => (about: "Documents gluon source code") - (@arg INPUT: ... "Documents the file or directory") + (@arg INPUT: +required "Documents the file or directory") + (@arg OUTPUT: +required "Outputs the documentation to this directory") ) (@arg INPUT: ... "Executes each file as a gluon program") ).get_matches(); @@ -149,11 +150,10 @@ fn run() -> std::result::Result<(), Box> { fmt_stdio()?; } } else if let Some(fmt_matches) = matches.subcommand_matches("doc") { - if let Some(args) = fmt_matches.values_of("INPUT") { - for arg in args { - gluon::doc::generate_for_path(&new_vm(), arg, "doc")?; - } - } + let input = fmt_matches.value_of("INPUT").expect("INPUT"); + let output = fmt_matches.value_of("OUTPUT").expect("OUTPUT"); + gluon::doc::generate_for_path(&new_vm(), input, output) + .map_err(|err| format!("{}\n{}", err, err.backtrace()))?; } else if matches.is_present("REPL") { repl::run()?; } else if let Some(args) = matches.values_of("INPUT") { diff --git a/src/compiler_pipeline.rs b/src/compiler_pipeline.rs index f10c27caa0..76d776a8e4 100644 --- a/src/compiler_pipeline.rs +++ b/src/compiler_pipeline.rs @@ -118,9 +118,9 @@ impl<'s> MacroExpandable for &'s mut SpannedExpr { compiler: &mut Compiler, macros: &mut MacroExpander, file: &str, - _expr_str: &str, + expr_str: &str, ) -> SalvageResult> { - if compiler.implicit_prelude { + if compiler.implicit_prelude && !expr_str.starts_with("//@NO-IMPLICIT-PRELUDE") { compiler.include_implicit_prelude(macros.vm.global_env().type_cache(), file, self); } macros.run(self); @@ -138,7 +138,7 @@ impl MacroExpandable for SpannedExpr { file: &str, expr_str: &str, ) -> SalvageResult> { - if compiler.implicit_prelude { + if compiler.implicit_prelude && !expr_str.starts_with("//@NO-IMPLICIT-PRELUDE") { compiler.include_implicit_prelude(macros.vm.global_env().type_cache(), file, &mut self); } let prev_errors = mem::replace(&mut macros.errors, Errors::new()); diff --git a/src/doc.rs b/src/doc.rs index dce5abd409..09efa8810c 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -1,58 +1,73 @@ +extern crate failure; extern crate walkdir; -use std::fmt; -use std::fs::File; -use std::io::{self, Read, Write}; +use std::fmt::{self, Write as WriteFmt}; +use std::fs::{create_dir_all, File}; +use std::io::{Read, Write}; use std::path::Path; +use self::failure::ResultExt; + +use base::filename_to_module; use base::types::ArcType; use base::metadata::Metadata; use check::metadata::metadata; -use {new_vm, Compiler, Thread}; +use {Compiler, Thread}; -quick_error! { - #[derive(Debug)] - pub enum Error { - Io(err: io::Error) { - description(err.description()) - display("{}", err) - from() - } - Fmt(err: fmt::Error) { - description(err.description()) - display("{}", err) - from() - } - WalkDir(err: walkdir::Error) { - description(err.description()) - display("{}", err) - from() - } +pub type Error = failure::Error; +pub type Result = ::std::result::Result; + +fn generate_field( + out: &mut W, + field_name: &str, + typ: &ArcType, + comment: Option<&str>, +) -> Result<()> +where + W: ?Sized + fmt::Write, +{ + writeln!(out, "### {}", field_name)?; + + writeln!(out, "```gluon")?; + writeln!(out, "{}", typ)?; + writeln!(out, "```")?; + + if let Some(comment) = comment { + writeln!(out, "{}", comment)?; } + writeln!(out)?; + Ok(()) } -pub type Result = ::std::result::Result; - pub fn generate(out: &mut W, typ: &ArcType, meta: &Metadata) -> Result<()> where W: ?Sized + fmt::Write, { - for field in typ.row_iter() { + if typ.type_field_iter().next().is_some() { + writeln!(out, "## Types")?; + writeln!(out)?; + } + for field in typ.type_field_iter() { let field_name: &str = field.name.as_ref(); - let meta = meta.module.get(field_name).unwrap(); - let field_type = &field.typ; - - writeln!(out, "## {}", field_name)?; - - writeln!(out, "```gluon")?; - writeln!(out, "{}", field_type)?; - writeln!(out, "```")?; + let field_type = &field.typ.unresolved_type(); + let comment = meta.module + .get(field_name) + .and_then(|meta| meta.comment.as_ref().map(|s| &s[..])); + generate_field(out, field_name, field_type, comment)?; + } - if let Some(ref comment) = meta.comment { - writeln!(out, "{}", comment)?; - } + if typ.row_iter().next().is_some() { + writeln!(out, "## Values")?; + writeln!(out)?; + } - generate(out, &field.typ, meta)?; + for field in typ.row_iter() { + let field_name: &str = field.name.as_ref(); + let field_type = &field.typ; + let comment = meta.module + .get(field_name) + .and_then(|meta| meta.comment.as_ref().map(|s| &s[..])); + generate_field(out, field_name, field_type, comment)?; } Ok(()) } @@ -64,21 +79,45 @@ where { for entry in walkdir::WalkDir::new(path) { let entry = entry?; - if !entry.file_type().is_file() { + if !entry.file_type().is_file() + || entry.path().extension().and_then(|ext| ext.to_str()) != Some("glu") + { continue; } - let mut input = File::open(entry.path())?; + let mut input = File::open(&*entry.path()).with_context(|err| { + format!( + "Unable to open gluon file `{}`: {}", + entry.path().display(), + err + ) + })?; let mut content = String::new(); input.read_to_string(&mut content)?; - let (expr, typ) = Compiler::new() - .typecheck_str(thread, "basic", &content, None) - .unwrap(); + let (expr, typ) = Compiler::new().typecheck_str(thread, "basic", &content, None)?; let (meta, _) = metadata(&*thread.get_env(), &expr); let mut out = String::new(); + if let Some(module_path) = entry.path().to_str() { + writeln!(out, "# {}", filename_to_module(module_path))?; + writeln!(out)?; + } generate(&mut out, &typ, &meta)?; - let mut doc_file = File::create(out_path.as_ref().join(entry.path().with_extension("md")))?; + + create_dir_all( + out_path + .as_ref() + .join(entry.path().parent().unwrap_or(Path::new(""))), + )?; + + let out_path = out_path.as_ref().join(entry.path().with_extension("md")); + let mut doc_file = File::create(&*out_path).with_context(|err| { + format!( + "Unable to open output file `{}`: {}", + out_path.display(), + err + ) + })?; doc_file.write_all(out.as_bytes())?; } Ok(()) diff --git a/std/list.glu b/std/list.glu index acaf162d09..e8c6337620 100644 --- a/std/list.glu +++ b/std/list.glu @@ -99,7 +99,7 @@ let monad : Monad List = { applicative = applicative, flat_map } -let show ?d: [Show a] -> Show (List a) = +let show ?d : [Show a] -> Show (List a) = let (++) = string.semigroup.append {