From 34fff8d1e757bcf6efa425ee436530258c622997 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Fri, 6 Jul 2018 21:56:32 +0200 Subject: [PATCH] feat(doc): Make all types link to their definition Closes #552 --- Cargo.lock | 20 +++--- base/src/types/mod.rs | 6 +- base/src/types/pretty_print.rs | 35 ++++++++-- base/tests/types.rs | 2 +- doc/src/doc/index.html | 2 +- doc/src/doc/module.html | 6 +- doc/src/lib.rs | 118 ++++++++++++++++++++++++++------- doc/tests/doc.rs | 5 +- 8 files changed, 143 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c9062462e..b5b6126658 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,7 +539,7 @@ dependencies = [ "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "ordered-float 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pretty 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", @@ -570,7 +570,7 @@ dependencies = [ "gluon_parser 0.8.1", "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pretty 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rpds 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -618,7 +618,7 @@ dependencies = [ "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pretty 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", @@ -639,7 +639,7 @@ dependencies = [ "gluon_base 0.8.1", "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pretty 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -658,7 +658,7 @@ dependencies = [ "lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "ordered-float 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pretty 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -711,7 +711,7 @@ dependencies = [ "lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "mopa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "pretty 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1156,7 +1156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pretty" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "typed-arena 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2063,10 +2063,6 @@ name = "xdg" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[patch.unused]] -name = "pretty" -version = "0.5.3-alpha.0" - [metadata] "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" @@ -2178,7 +2174,7 @@ version = "0.5.3-alpha.0" "checksum phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2261d544c2bb6aa3b10022b0be371b9c7c64f762ef28c6f5d4f1ef6d97b5930" "checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -"checksum pretty 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "64cab899056cb51a7a889fc3575bd07ee70374d33345031695007b52e909cff6" +"checksum pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f60c0d9f6fc88ecdd245d90c1920ff76a430ab34303fc778d33b1d0a4c3bf6d3" "checksum pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a029430f0d744bc3d15dd474d591bed2402b645d024583082b9f63bb936dac6" "checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7" "checksum proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "effdb53b25cdad54f8f48843d67398f7ef2e14f12c1b4cb4effc549a6462a4d6" diff --git a/base/src/types/mod.rs b/base/src/types/mod.rs index faf3e37f96..3fdbf2e7cd 100644 --- a/base/src/types/mod.rs +++ b/base/src/types/mod.rs @@ -1275,7 +1275,7 @@ impl ArcType { top(self).pretty(&Printer::new(arena, &())) } - pub fn display(&self, width: usize) -> TypeFormatter { + pub fn display(&self, width: usize) -> TypeFormatter { TypeFormatter::new(self).width(width) } } @@ -1907,8 +1907,8 @@ where // This should not be displayed normally as it should only exist in `ExtendRow` // which handles `EmptyRow` explicitly Type::EmptyRow => arena.text("EmptyRow"), - Type::Ident(ref id) => arena.text(id.as_ref()), - Type::Alias(ref alias) => arena.text(alias.name.as_ref()), + Type::Ident(ref id) => printer.symbol(id), + Type::Alias(ref alias) => printer.symbol(&alias.name), }; match **typ { Type::App(..) | Type::ExtendRow { .. } | Type::Variant(..) | Type::Function(..) => doc, diff --git a/base/src/types/pretty_print.rs b/base/src/types/pretty_print.rs index 5385e1ed1f..625271ccae 100644 --- a/base/src/types/pretty_print.rs +++ b/base/src/types/pretty_print.rs @@ -76,29 +76,32 @@ impl From for Filter { } } -pub struct TypeFormatter<'a, I, T> +pub struct TypeFormatter<'a, I, T, A> where I: 'a, T: 'a, + A: 'a, { width: usize, typ: &'a T, filter: &'a Fn(&I) -> Filter, + annotate_symbol: &'a Fn(&I) -> Option, _marker: PhantomData, } -impl<'a, I, T> TypeFormatter<'a, I, T> { +impl<'a, I, T, A> TypeFormatter<'a, I, T, A> { pub fn new(typ: &'a T) -> Self { TypeFormatter { width: 80, typ: typ, filter: &|_| Filter::Retain, + annotate_symbol: &|_| None, _marker: PhantomData, } } } -impl<'a, I, T> TypeFormatter<'a, I, T> { +impl<'a, I, T, A> TypeFormatter<'a, I, T, A> { pub fn width(mut self, width: usize) -> Self { self.width = width; self @@ -109,7 +112,12 @@ impl<'a, I, T> TypeFormatter<'a, I, T> { self } - pub fn pretty(&self, arena: &'a Arena<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> + pub fn annotate_symbol(mut self, annotate_symbol: &'a Fn(&I) -> Option) -> Self { + self.annotate_symbol = annotate_symbol; + self + } + + pub fn pretty(&self, arena: &'a Arena<'a, A>) -> DocBuilder<'a, Arena<'a, A>, A> where T: Deref> + HasSpan + Commented + 'a, I: AsRef, @@ -120,19 +128,21 @@ impl<'a, I, T> TypeFormatter<'a, I, T> { arena, source: &(), filter: self.filter, + annotate_symbol: self.annotate_symbol, }) } - pub fn build(&self, arena: &'a Arena<'a, A>, source: &'a Source) -> Printer<'a, I, A> { + pub fn build(&self, arena: &'a Arena<'a, A>, source: &'a Source) -> Printer<'a, I, A> { Printer { arena, source, filter: self.filter, + annotate_symbol: self.annotate_symbol, } } } -impl<'a, I, T> fmt::Display for TypeFormatter<'a, I, T> +impl<'a, I, T> fmt::Display for TypeFormatter<'a, I, T, ()> where T: Deref> + HasSpan + Commented + 'a, I: AsRef, @@ -156,6 +166,7 @@ pub struct Printer<'a, I: 'a, A: 'a> { pub arena: &'a Arena<'a, A>, pub source: &'a Source, filter: &'a Fn(&I) -> Filter, + annotate_symbol: &'a Fn(&I) -> Option, } impl<'a, I, A> Printer<'a, I, A> { @@ -164,6 +175,7 @@ impl<'a, I, A> Printer<'a, I, A> { arena, source, filter: &|_| Filter::Retain, + annotate_symbol: &|_| None, } } @@ -171,6 +183,17 @@ impl<'a, I, A> Printer<'a, I, A> { (self.filter)(field) } + pub fn symbol(&self, symbol: &'a I) -> DocBuilder<'a, Arena<'a, A>, A> + where + I: AsRef, + { + let doc = self.arena.text(symbol.as_ref()); + match (self.annotate_symbol)(symbol) { + Some(ann) => doc.annotate(ann), + None => doc, + } + } + pub fn space_before(&self, pos: BytePos) -> DocBuilder<'a, Arena<'a, A>, A> { let (doc, comments) = self.comments_before_(pos); if let Doc::Nil = doc.1 { diff --git a/base/tests/types.rs b/base/tests/types.rs index 40c4e2da9e..29d6cebed8 100644 --- a/base/tests/types.rs +++ b/base/tests/types.rs @@ -315,7 +315,7 @@ fn break_record() { ), ], ); - let arena = Arena::new(); + let arena = Arena::<()>::new(); let source = &(); let printer = pretty_print::Printer::new(&arena, source); let typ = arena diff --git a/doc/src/doc/index.html b/doc/src/doc/index.html index bcfa8ee8c8..2d0bf331c1 100644 --- a/doc/src/doc/index.html +++ b/doc/src/doc/index.html @@ -24,7 +24,7 @@

Modules

{{#each modules}} -
{{name}} + {{name}} {{markdown_first_paragraph comment}} diff --git a/doc/src/doc/module.html b/doc/src/doc/module.html index d8769d2f0c..9026317c3f 100644 --- a/doc/src/doc/module.html +++ b/doc/src/doc/module.html @@ -20,7 +20,7 @@ @@ -36,7 +36,7 @@

{{name}}{{#each args}} {{name}}{{/each}} = {{type~}} +
type {{name}}{{#each args}} {{name}}{{/each}} = {{{type~}}}
                     

{{markdown comment}}
@@ -52,7 +52,7 @@

let {{name}}
                     {{~#each args~}}
                         {{~#if implicit}} ?{{name}}{{else}} {{name}}{{~/if~}}
-                    {{~/each}} : {{type~}}
+                    {{~/each}} : {{{type~}}}
                 

{{markdown comment}}
diff --git a/doc/src/lib.rs b/doc/src/lib.rs index b078c1566a..7ef9fcaa87 100644 --- a/doc/src/lib.rs +++ b/doc/src/lib.rs @@ -24,6 +24,7 @@ extern crate gluon; use std::fs::{create_dir_all, File}; use std::io::{self, Read, Write}; use std::path::{Path, PathBuf}; +use std::result::Result as StdResult; use failure::ResultExt; @@ -37,6 +38,7 @@ use pretty::{Arena, DocAllocator}; use gluon::base::filename_to_module; use gluon::base::metadata::Metadata; +use gluon::base::symbol::Symbol; use gluon::base::types::{ArcType, ArgType, Type}; use gluon::check::metadata::metadata; use gluon::{Compiler, Thread}; @@ -72,9 +74,54 @@ pub struct Field { pub comment: String, } -fn print_type(typ: &ArcType) -> String { +struct SymbolLinkRenderer { + escaped: String, + un_escaped: String, +} + +impl SymbolLinkRenderer { + fn flush_to_escaped(&mut self) { + self.escaped + .push_str(&handlebars::html_escape(&self.un_escaped)); + self.un_escaped.clear(); + } + fn finish(mut self) -> String { + self.escaped + .push_str(&handlebars::html_escape(&self.un_escaped)); + self.escaped + } +} + +impl pretty::Render for SymbolLinkRenderer { + type Error = (); + fn write_str(&mut self, s: &str) -> StdResult { + self.un_escaped.push_str(s); + Ok(s.len()) + } +} + +impl pretty::RenderAnnotated for SymbolLinkRenderer { + fn push_annotation(&mut self, annotation: &String) -> StdResult<(), Self::Error> { + self.flush_to_escaped(); + self.escaped + .push_str(&format!(r#""#, annotation)); + Ok(()) + } + fn pop_annotation(&mut self) -> StdResult<(), Self::Error> { + self.flush_to_escaped(); + self.escaped.push_str(""); + Ok(()) + } +} + +fn print_type(current_module: &str, typ: &ArcType) -> String { + let annotate_symbol = + |symbol: &Symbol| Some(symbol_link(false, current_module, symbol.as_ref())); let arena = Arena::new(); - let mut doc = typ.pretty(&arena); + let mut doc = typ + .display(80) + .annotate_symbol(&annotate_symbol) + .pretty(&arena); match **typ { Type::Record(_) => (), Type::Variant(_) => doc = arena.newline().append(doc).nest(4), @@ -82,10 +129,16 @@ fn print_type(typ: &ArcType) -> String { doc = doc.nest(4); } } - doc.group().1.pretty(80).to_string() + + let mut renderer = SymbolLinkRenderer { + un_escaped: String::new(), + escaped: String::new(), + }; + doc.group().1.render_raw(80, &mut renderer).unwrap(); + renderer.finish() } -pub fn record(typ: &ArcType, meta: &Metadata) -> Record { +pub fn record(current_module: &str, typ: &ArcType, meta: &Metadata) -> Record { Record { types: typ .type_field_iter() @@ -100,7 +153,7 @@ pub fn record(typ: &ArcType, meta: &Metadata) -> Record { name: gen.id.to_string(), }) .collect(), - typ: print_type(&field.typ.unresolved_type().remove_forall()), + typ: print_type(current_module, &field.typ.unresolved_type().remove_forall()), comment: meta .module .get(AsRef::::as_ref(&field.name)) @@ -127,7 +180,7 @@ pub fn record(typ: &ArcType, meta: &Metadata) -> Record { .collect() }) .unwrap_or_default(), - typ: print_type(&field.typ), + typ: print_type(current_module, &field.typ), comment: meta_opt .and_then(|meta| meta.comment.as_ref().map(|s| &s.content[..])) @@ -150,13 +203,44 @@ pub struct TemplateModule<'a> { const INDEX_TEMPLATE: &str = "index"; const MODULE_TEMPLATE: &str = "module"; +fn symbol_link(index: bool, current_module: &str, param: &str) -> String { + let skipped = if index { 0 } else { 1 }; + + let parts: Vec<_> = param.split('.').collect(); + let (typ, module_parts) = parts.split_last().unwrap(); + + format!( + "{}{}.html#type.{}", + current_module + .split('.') + .skip(skipped) + .map(|_| "../") + .format(""), + module_parts.iter().format("/"), + typ + ) +} + +fn module_link(index: bool, current_module: &str, param: &str) -> String { + let skipped = if index { 0 } else { 1 }; + format!( + "{}{}.html", + current_module + .split('.') + .skip(skipped) + .map(|_| "../") + .format(""), + param.replace(".", "/") + ) +} + fn handlebars() -> Result { let mut reg = Handlebars::new(); reg.register_template_string(INDEX_TEMPLATE, include_str!("doc/index.html"))?; reg.register_template_string(MODULE_TEMPLATE, include_str!("doc/module.html"))?; - fn symbol_link( + fn module_link_helper( h: &Helper, _: &Handlebars, rc: &mut RenderContext, @@ -168,23 +252,11 @@ fn handlebars() -> Result { .to_string(); let param = String::deserialize(h.param(0).unwrap().value())?; - let skipped = if rc.get_root_template_name().map(|s| &s[..]) == Some(INDEX_TEMPLATE) { - 0 - } else { - 1 - }; - out.write(&format!( - "{}{}.html", - current_module - .split('.') - .skip(skipped) - .map(|_| "../") - .format(""), - param.replace(".", "/") - ))?; + let index = rc.get_root_template_name().map(|s| &s[..]) == Some(INDEX_TEMPLATE); + out.write(&module_link(index, current_module, ¶m))?; Ok(()) } - reg.register_helper("symbol_link", Box::new(symbol_link)); + reg.register_helper("module_link", Box::new(module_link_helper)); fn breadcrumbs( h: &Helper, @@ -359,9 +431,9 @@ pub fn generate_for_path_(thread: &Thread, path: &Path, out_path: &Path) -> Resu .to_string(); let module = Module { + record: record(&name, &typ, &meta), name, comment, - record: record(&typ, &meta), }; directories diff --git a/doc/tests/doc.rs b/doc/tests/doc.rs index f5e840df4f..96d2f71724 100644 --- a/doc/tests/doc.rs +++ b/doc/tests/doc.rs @@ -1,5 +1,6 @@ extern crate gluon; extern crate gluon_doc as doc; +extern crate handlebars; use gluon::check::metadata::metadata; use gluon::{Compiler, RootedThread}; @@ -23,7 +24,7 @@ let test x = x .unwrap(); let (meta, _) = metadata(&*vm.get_env(), &expr); - let out = doc::record(&typ, &meta); + let out = doc::record("basic", &typ, &meta); assert_eq!( out, doc::Record { @@ -34,7 +35,7 @@ let test x = x implicit: false, name: "x".to_string(), }], - typ: "forall a . a -> a".to_string(), + typ: handlebars::html_escape("forall a . a -> a"), comment: "This is the test function".to_string(), }], }