Merge pull request #1261 from rtfeldman/module-level-documentation

Detatched Docs
This commit is contained in:
Richard Feldman 2021-05-03 07:52:19 -04:00 committed by GitHub
commit 84c46b94e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 144 additions and 69 deletions

View file

@ -1,8 +1,42 @@
interface Str interface Str
exposes [ Str, decimal, split, isEmpty, startsWith, endsWith, contains, anyGraphemes, allGraphemes, join, joinWith, padGraphemesStart, padGraphemesEnd, graphemes, reverseGraphemes, isCaseInsensitiveEq, isCaseInsensitiveNeq, walkGraphemes, isCapitalized, isAllUppercase, isAllLowercase, toUtf8, toUtf16, toUtf32, trim, walkUtf8, walkUtf16, walkUtf32, walkRevUtf8, walkRevUtf16, walkRevUtf32 ] exposes
[
Str,
decimal,
split,
isEmpty,
startsWith,
endsWith,
contains,
anyGraphemes,
allGraphemes,
join,
joinWith,
padGraphemesStart,
padGraphemesEnd,
graphemes,
reverseGraphemes,
isCaseInsensitiveEq,
isCaseInsensitiveNeq,
walkGraphemes,
isCapitalized,
isAllUppercase,
isAllLowercase,
toUtf8,
toUtf16,
toUtf32,
trim,
walkUtf8,
walkUtf16,
walkUtf32,
walkRevUtf8,
walkRevUtf16,
walkRevUtf32
]
imports [] imports []
## # Types
## # Types
##
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks ## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
## to the basics. ## to the basics.
## ##

View file

@ -1,9 +1,11 @@
use crate::docs::DocEntry::DetatchedDoc;
use crate::docs::TypeAnnotation::{Apply, BoundVariable, Record, TagUnion}; use crate::docs::TypeAnnotation::{Apply, BoundVariable, Record, TagUnion};
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_module::ident::ModuleName; use roc_module::ident::ModuleName;
use roc_module::symbol::IdentIds; use roc_module::symbol::IdentIds;
use roc_parse::ast; use roc_parse::ast;
use roc_parse::ast::AssignedField; use roc_parse::ast::CommentOrNewline;
use roc_parse::ast::{AssignedField, Def};
use roc_region::all::Located; use roc_region::all::Located;
// Documentation generation requirements // Documentation generation requirements
@ -19,12 +21,17 @@ pub struct Documentation {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ModuleDocumentation { pub struct ModuleDocumentation {
pub name: String, pub name: String,
pub docs: String,
pub entries: Vec<DocEntry>, pub entries: Vec<DocEntry>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DocEntry { pub enum DocEntry {
DocDef(DocDef),
DetatchedDoc(String),
}
#[derive(Debug, Clone)]
pub struct DocDef {
pub name: String, pub name: String,
pub type_vars: Vec<String>, pub type_vars: Vec<String>,
pub type_annotation: Option<TypeAnnotation>, pub type_annotation: Option<TypeAnnotation>,
@ -76,17 +83,40 @@ pub fn generate_module_docs<'a>(
parsed_defs parsed_defs
.iter() .iter()
.fold((vec![], None), |(acc, maybe_comments_after), def| { .fold((vec![], None), |(acc, maybe_comments_after), def| {
generate_module_doc(exposed_ident_ids, acc, maybe_comments_after, &def.value) generate_entry_doc(exposed_ident_ids, acc, maybe_comments_after, &def.value)
}); });
ModuleDocumentation { ModuleDocumentation {
name: module_name.as_str().to_string(), name: module_name.as_str().to_string(),
docs: "".to_string(),
entries, entries,
} }
} }
fn generate_module_doc<'a>( fn detatched_docs_from_comments_and_new_lines<'a>(
comments_or_new_lines: &'a [roc_parse::ast::CommentOrNewline<'a>],
) -> Vec<String> {
let mut detatched_docs: Vec<String> = Vec::new();
let mut docs = String::new();
for comment_or_new_line in comments_or_new_lines.iter() {
match comment_or_new_line {
CommentOrNewline::DocComment(doc_str) => {
docs.push_str(doc_str);
docs.push('\n');
}
CommentOrNewline::LineComment(_) | CommentOrNewline::Newline => {
detatched_docs.push(docs.clone());
docs = String::new();
}
}
}
detatched_docs
}
fn generate_entry_doc<'a>(
exposed_ident_ids: &'a IdentIds, exposed_ident_ids: &'a IdentIds,
mut acc: Vec<DocEntry>, mut acc: Vec<DocEntry>,
before_comments_or_new_lines: Option<&'a [roc_parse::ast::CommentOrNewline<'a>]>, before_comments_or_new_lines: Option<&'a [roc_parse::ast::CommentOrNewline<'a>]>,
@ -95,58 +125,62 @@ fn generate_module_doc<'a>(
Vec<DocEntry>, Vec<DocEntry>,
Option<&'a [roc_parse::ast::CommentOrNewline<'a>]>, Option<&'a [roc_parse::ast::CommentOrNewline<'a>]>,
) { ) {
use roc_parse::ast::Def::*;
use roc_parse::ast::Pattern; use roc_parse::ast::Pattern;
match def { match def {
SpaceBefore(sub_def, comments_or_new_lines) => { Def::SpaceBefore(sub_def, comments_or_new_lines) => {
// Comments before a definition are attached to the current defition // Comments before a definition are attached to the current defition
generate_module_doc(exposed_ident_ids, acc, Some(comments_or_new_lines), sub_def)
for detatched_doc in detatched_docs_from_comments_and_new_lines(comments_or_new_lines) {
acc.push(DetatchedDoc(detatched_doc));
} }
SpaceAfter(sub_def, comments_or_new_lines) => { generate_entry_doc(exposed_ident_ids, acc, Some(comments_or_new_lines), sub_def)
}
Def::SpaceAfter(sub_def, comments_or_new_lines) => {
let (new_acc, _) = let (new_acc, _) =
// If there are comments before, attach to this definition // If there are comments before, attach to this definition
generate_module_doc(exposed_ident_ids, acc, before_comments_or_new_lines, sub_def); generate_entry_doc(exposed_ident_ids, acc, before_comments_or_new_lines, sub_def);
// Comments after a definition are attached to the next definition // Comments after a definition are attached to the next definition
(new_acc, Some(comments_or_new_lines)) (new_acc, Some(comments_or_new_lines))
} }
Annotation(loc_pattern, _loc_ann) => match loc_pattern.value { Def::Annotation(loc_pattern, _loc_ann) => match loc_pattern.value {
Pattern::Identifier(identifier) => { Pattern::Identifier(identifier) => {
// Check if the definition is exposed // Check if the definition is exposed
if exposed_ident_ids if exposed_ident_ids
.get_id(&InlinableString::from(identifier)) .get_id(&InlinableString::from(identifier))
.is_some() .is_some()
{ {
let entry = DocEntry { let doc_def = DocDef {
name: identifier.to_string(), name: identifier.to_string(),
type_annotation: None, type_annotation: None,
type_vars: Vec::new(), type_vars: Vec::new(),
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs), docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
}; };
acc.push(entry); acc.push(DocEntry::DocDef(doc_def));
} }
(acc, None) (acc, None)
} }
_ => (acc, None), _ => (acc, None),
}, },
AnnotatedBody { ann_pattern, .. } => match ann_pattern.value { Def::AnnotatedBody { ann_pattern, .. } => match ann_pattern.value {
Pattern::Identifier(identifier) => { Pattern::Identifier(identifier) => {
// Check if the definition is exposed // Check if the definition is exposed
if exposed_ident_ids if exposed_ident_ids
.get_id(&InlinableString::from(identifier)) .get_id(&InlinableString::from(identifier))
.is_some() .is_some()
{ {
let entry = DocEntry { let doc_def = DocDef {
name: identifier.to_string(), name: identifier.to_string(),
type_annotation: None, type_annotation: None,
type_vars: Vec::new(), type_vars: Vec::new(),
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs), docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
}; };
acc.push(entry); acc.push(DocEntry::DocDef(doc_def));
} }
(acc, None) (acc, None)
} }
@ -154,7 +188,7 @@ fn generate_module_doc<'a>(
_ => (acc, None), _ => (acc, None),
}, },
Alias { name, vars, ann } => { Def::Alias { name, vars, ann } => {
let mut type_vars = Vec::new(); let mut type_vars = Vec::new();
for var in vars.iter() { for var in vars.iter() {
@ -163,22 +197,22 @@ fn generate_module_doc<'a>(
} }
} }
let entry = DocEntry { let doc_def = DocDef {
name: name.value.to_string(), name: name.value.to_string(),
type_annotation: type_to_docs(ann.value), type_annotation: type_to_docs(ann.value),
type_vars, type_vars,
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs), docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
}; };
acc.push(entry); acc.push(DocEntry::DocDef(doc_def));
(acc, None) (acc, None)
} }
Body(_, _) => (acc, None), Def::Body(_, _) => (acc, None),
Expect(c) => todo!("documentation for tests {:?}", c), Def::Expect(c) => todo!("documentation for tests {:?}", c),
NotYetImplemented(s) => todo!("{}", s), Def::NotYetImplemented(s) => todo!("{}", s),
} }
} }
@ -327,21 +361,20 @@ fn tag_to_doc(tag: ast::Tag) -> Option<Tag> {
fn comments_or_new_lines_to_docs<'a>( fn comments_or_new_lines_to_docs<'a>(
comments_or_new_lines: &'a [roc_parse::ast::CommentOrNewline<'a>], comments_or_new_lines: &'a [roc_parse::ast::CommentOrNewline<'a>],
) -> Option<String> { ) -> Option<String> {
use roc_parse::ast::CommentOrNewline::*;
let mut docs = String::new(); let mut docs = String::new();
for comment_or_new_line in comments_or_new_lines.iter() { for comment_or_new_line in comments_or_new_lines.iter() {
match comment_or_new_line { match comment_or_new_line {
DocComment(doc_str) => { CommentOrNewline::DocComment(doc_str) => {
docs.push_str(doc_str); docs.push_str(doc_str);
docs.push('\n'); docs.push('\n');
} }
Newline | LineComment(_) => { CommentOrNewline::Newline | CommentOrNewline::LineComment(_) => {
docs = String::new(); docs = String::new();
} }
} }
} }
if docs.is_empty() { if docs.is_empty() {
None None
} else { } else {

View file

@ -3516,7 +3516,6 @@ fn fabricate_effects_module<'a>(
// Should a effect module ever have a ModuleDocumentation? // Should a effect module ever have a ModuleDocumentation?
let module_docs = ModuleDocumentation { let module_docs = ModuleDocumentation {
name: String::from(name), name: String::from(name),
docs: String::from("idk fix this later"),
entries: Vec::new(), entries: Vec::new(),
}; };

View file

@ -1,7 +1,7 @@
extern crate pulldown_cmark; extern crate pulldown_cmark;
use roc_builtins::std::StdLib; use roc_builtins::std::StdLib;
use roc_can::builtins::builtin_defs_map; use roc_can::builtins::builtin_defs_map;
use roc_load::docs::TypeAnnotation; use roc_load::docs::{DocEntry, TypeAnnotation};
use roc_load::docs::{ModuleDocumentation, RecordField}; use roc_load::docs::{ModuleDocumentation, RecordField};
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
@ -86,25 +86,27 @@ fn render_main_content(module: &ModuleDocumentation) -> String {
.as_str(), .as_str(),
); );
buf.push_str(markdown_to_html(module.docs.clone()).as_str()); // buf.push_str(markdown_to_html(module.docs.clone()).as_str());
for entry in &module.entries { for entry in &module.entries {
match entry {
DocEntry::DocDef(doc_def) => {
let mut href = String::new(); let mut href = String::new();
href.push('#'); href.push('#');
href.push_str(entry.name.as_str()); href.push_str(doc_def.name.as_str());
let name = entry.name.as_str(); let name = doc_def.name.as_str();
let mut content = String::new(); let mut content = String::new();
content.push_str(html_node("a", vec![("href", href.as_str())], name).as_str()); content.push_str(html_node("a", vec![("href", href.as_str())], name).as_str());
for type_var in &entry.type_vars { for type_var in &doc_def.type_vars {
content.push(' '); content.push(' ');
content.push_str(type_var.as_str()); content.push_str(type_var.as_str());
} }
if let Some(type_ann) = &entry.type_annotation { if let Some(type_ann) = &doc_def.type_annotation {
content.push_str(" : "); content.push_str(" : ");
type_annotation_to_html(0, &mut content, &type_ann); type_annotation_to_html(0, &mut content, &type_ann);
} }
@ -118,10 +120,15 @@ fn render_main_content(module: &ModuleDocumentation) -> String {
.as_str(), .as_str(),
); );
if let Some(docs) = &entry.docs { if let Some(docs) = &doc_def.docs {
buf.push_str(markdown_to_html(docs.to_string()).as_str()); buf.push_str(markdown_to_html(docs.to_string()).as_str());
} }
} }
DocEntry::DetatchedDoc(docs) => {
buf.push_str(markdown_to_html(docs.to_string()).as_str());
}
};
}
buf buf
} }
@ -218,21 +225,23 @@ fn render_module_links(modules: &[ModuleDocumentation]) -> String {
let mut entries_buf = String::new(); let mut entries_buf = String::new();
for entry in &module.entries { for entry in &module.entries {
if let DocEntry::DocDef(doc_def) = entry {
let mut entry_href = String::new(); let mut entry_href = String::new();
entry_href.push_str(href.as_str()); entry_href.push_str(href.as_str());
entry_href.push('#'); entry_href.push('#');
entry_href.push_str(entry.name.as_str()); entry_href.push_str(doc_def.name.as_str());
entries_buf.push_str( entries_buf.push_str(
html_node( html_node(
"a", "a",
vec![("href", entry_href.as_str())], vec![("href", entry_href.as_str())],
entry.name.as_str(), doc_def.name.as_str(),
) )
.as_str(), .as_str(),
); );
} }
}
entries_buf entries_buf
}; };