Support data-types in documentation.

This commit is contained in:
KtorZ 2022-12-16 19:37:15 +01:00
parent 1f3f769b53
commit d2c6d27545
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
7 changed files with 228 additions and 57 deletions

View File

@ -130,14 +130,14 @@ impl TypedDataType {
tipo: tipo.clone(), tipo: tipo.clone(),
doc: None, doc: None,
}], }],
documentation: None, doc: None,
sugar: false, sugar: false,
}, },
RecordConstructor { RecordConstructor {
location: Span::empty(), location: Span::empty(),
name: "None".to_string(), name: "None".to_string(),
arguments: vec![], arguments: vec![],
documentation: None, doc: None,
sugar: false, sugar: false,
}, },
], ],
@ -352,7 +352,7 @@ pub struct RecordConstructor<T> {
pub location: Span, pub location: Span,
pub name: String, pub name: String,
pub arguments: Vec<RecordConstructorArg<T>>, pub arguments: Vec<RecordConstructorArg<T>>,
pub documentation: Option<String>, pub doc: Option<String>,
pub sugar: bool, pub sugar: bool,
} }

View File

@ -1172,37 +1172,93 @@ impl<'comments> Formatter<'comments> {
.append("}") .append("}")
} }
pub fn docs_opaque_custom_type<'a>( pub fn docs_data_type<'a, A>(
&mut self,
name: &'a str,
args: &'a [String],
constructors: &'a [RecordConstructor<A>],
location: &'a Span,
) -> Document<'a> {
self.pop_empty_lines(location.start);
let mut is_sugar = false;
(if args.is_empty() {
name.to_doc()
} else {
name.to_doc()
.append(wrap_generics(args.iter().map(|e| e.to_doc())))
.group()
})
.append(" {")
.append(if constructors.len() == 1 && constructors[0].sugar {
is_sugar = true;
self.record_constructor(&constructors[0])
} else {
concat(constructors.iter().map(|c| {
if self.pop_empty_lines(c.location.start) {
lines(2)
} else {
line()
}
.append(self.record_constructor(c))
.nest(INDENT)
.group()
}))
})
.append(if is_sugar { nil() } else { line() })
.append("}")
}
pub fn docs_opaque_data_type<'a>(
&mut self, &mut self,
public: bool,
name: &'a str, name: &'a str,
args: &'a [String], args: &'a [String],
location: &'a Span, location: &'a Span,
) -> Document<'a> { ) -> Document<'a> {
self.pop_empty_lines(location.start); self.pop_empty_lines(location.start);
pub_(public) if args.is_empty() {
.to_doc()
.append("opaque type ")
.append(if args.is_empty() {
name.to_doc() name.to_doc()
} else { } else {
name.to_doc() name.to_doc()
.append(wrap_args(args.iter().map(|e| (e.to_doc(), false)))) .append(wrap_args(args.iter().map(|e| (e.to_doc(), false))))
}) }
}
pub fn docs_type_alias<'a>(
&mut self,
name: &'a str,
args: &'a [String],
typ: &'a Annotation,
) -> Document<'a> {
let head = name.to_doc();
let head = if args.is_empty() {
head
} else {
head.append(wrap_generics(args.iter().map(|e| e.to_doc())).group())
};
head.append(" = ")
.append(self.annotation(typ).group().nest(INDENT))
}
pub fn docs_record_constructor<'a, A>(
&mut self,
constructor: &'a RecordConstructor<A>,
) -> Document<'a> {
constructor.name.to_doc()
} }
pub fn docs_fn_signature<'a>( pub fn docs_fn_signature<'a>(
&mut self, &mut self,
public: bool,
name: &'a str, name: &'a str,
args: &'a [TypedArg], args: &'a [TypedArg],
return_type: Arc<Type>, return_type: Arc<Type>,
) -> Document<'a> { ) -> Document<'a> {
let mut printer = tipo::pretty::Printer::new(); let mut printer = tipo::pretty::Printer::new();
name.to_doc()
pub_(public)
.append("fn ")
.append(name)
.append(self.docs_fn_args(args, &mut printer)) .append(self.docs_fn_args(args, &mut printer))
.append(" -> ".to_doc()) .append(" -> ".to_doc())
.append(printer.print(&return_type)) .append(printer.print(&return_type))

View File

@ -157,7 +157,7 @@ pub fn data_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = Parse
location: span, location: span,
arguments: arguments.unwrap_or_default(), arguments: arguments.unwrap_or_default(),
name, name,
documentation: None, doc: None,
sugar: false, sugar: false,
}) })
.repeated() .repeated()
@ -167,7 +167,7 @@ pub fn data_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = Parse
vec![ast::RecordConstructor { vec![ast::RecordConstructor {
location: span, location: span,
arguments, arguments,
documentation: None, doc: None,
name: String::from("_replace"), name: String::from("_replace"),
sugar: true, sugar: true,
}] }]

View File

@ -130,14 +130,14 @@ fn custom_type() {
doc: None, doc: None,
}, },
], ],
documentation: None, doc: None,
sugar: false, sugar: false,
}, },
ast::RecordConstructor { ast::RecordConstructor {
location: Span::new((), 34..38), location: Span::new((), 34..38),
name: "None".to_string(), name: "None".to_string(),
arguments: vec![], arguments: vec![],
documentation: None, doc: None,
sugar: false, sugar: false,
}, },
ast::RecordConstructor { ast::RecordConstructor {
@ -169,7 +169,7 @@ fn custom_type() {
doc: None, doc: None,
}, },
], ],
documentation: None, doc: None,
sugar: false, sugar: false,
}, },
], ],
@ -208,7 +208,7 @@ fn opaque_type() {
tipo: (), tipo: (),
doc: None, doc: None,
}], }],
documentation: None, doc: None,
sugar: true, sugar: true,
}], }],
doc: None, doc: None,

View File

@ -288,7 +288,7 @@ fn infer_definition(
location, location,
name, name,
arguments: args, arguments: args,
documentation, doc,
sugar, sugar,
}| { }| {
let preregistered_fn = environment let preregistered_fn = environment
@ -330,7 +330,7 @@ fn infer_definition(
location, location,
name, name,
arguments: args, arguments: args,
documentation, doc,
sugar, sugar,
} }
}, },

View File

@ -1,7 +1,8 @@
use crate::{config::Config, module::CheckedModule}; use crate::{config::Config, module::CheckedModule};
use aiken_lang::{ use aiken_lang::{
ast::{Definition, TypedDefinition}, ast::{Definition, RecordConstructor, RecordConstructorArg, TypedDefinition},
format, format,
tipo::Type,
}; };
use askama::Template; use askama::Template;
use itertools::Itertools; use itertools::Itertools;
@ -10,6 +11,7 @@ use serde::Serialize;
use serde_json as json; use serde_json as json;
use std::{ use std::{
path::PathBuf, path::PathBuf,
sync::Arc,
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
@ -75,24 +77,26 @@ struct SearchIndex {
/// across multiple modules. /// across multiple modules.
pub fn generate_all(root: &PathBuf, config: &Config, modules: Vec<&CheckedModule>) -> Vec<DocFile> { pub fn generate_all(root: &PathBuf, config: &Config, modules: Vec<&CheckedModule>) -> Vec<DocFile> {
let timestamp = new_timestamp(); let timestamp = new_timestamp();
let modules_links = generate_modules_links(&modules);
let mut output_files: Vec<DocFile> = vec![]; let mut output_files: Vec<DocFile> = vec![];
let mut search_indexes: Vec<SearchIndex> = vec![]; let mut search_indexes: Vec<SearchIndex> = vec![];
for module in &modules { for module in &modules {
let (indexes, file) = generate_module(config, module, &timestamp); let (indexes, file) = generate_module(config, module, &modules_links, &timestamp);
search_indexes.extend(indexes); search_indexes.extend(indexes);
output_files.push(file); output_files.push(file);
} }
output_files.extend(generate_static_assets(search_indexes)); output_files.extend(generate_static_assets(search_indexes));
output_files.push(generate_readme(root, config, modules, &timestamp)); output_files.push(generate_readme(root, config, &modules_links, &timestamp));
output_files output_files
} }
fn generate_module( fn generate_module(
config: &Config, config: &Config,
module: &CheckedModule, module: &CheckedModule,
modules: &Vec<DocLink>,
timestamp: &Duration, timestamp: &Duration,
) -> (Vec<SearchIndex>, DocFile) { ) -> (Vec<SearchIndex>, DocFile) {
let mut search_indexes = vec![]; let mut search_indexes = vec![];
@ -116,11 +120,44 @@ fn generate_module(
}); });
// Types // Types
let types: Vec<DocType> = module
.ast
.definitions
.iter()
.flat_map(DocType::from_definition)
.sorted()
.collect();
types.iter().for_each(|type_info| {
let constructors = type_info
.constructors
.iter()
.map(|constructor| {
let arguments = constructor
.arguments
.iter()
.map(|argument| format!("{}\n{}", argument.label, argument.documentation))
.join("\n");
format!(
"{}\n{}\n{}",
constructor.definition, constructor.documentation, arguments
)
})
.join("\n");
search_indexes.push(SearchIndex {
doc: module.name.to_string(),
title: type_info.name.to_string(),
content: format!(
"{}\n{}\n{}",
type_info.definition, type_info.documentation, constructors,
),
url: format!("{}.html#{}", module.name, type_info.name),
})
});
// Constants // Constants
// TODO
// Template // Module
search_indexes.push(SearchIndex { search_indexes.push(SearchIndex {
doc: module.name.to_string(), doc: module.name.to_string(),
title: module.name.to_string(), title: module.name.to_string(),
@ -133,13 +170,13 @@ fn generate_module(
breadcrumbs: to_breadcrumbs(&module.name), breadcrumbs: to_breadcrumbs(&module.name),
links: &vec![], links: &vec![],
documentation: render_markdown(&module.ast.docs.iter().join("\n")), documentation: render_markdown(&module.ast.docs.iter().join("\n")),
modules: &vec![], modules,
project_name: &config.name, project_name: &config.name,
page_title: &format!("{} - {}", module.name, config.name), page_title: &format!("{} - {}", module.name, config.name),
module_name: module.name.clone(), module_name: module.name.clone(),
project_version: &config.version.to_string(), project_version: &config.version.to_string(),
functions, functions,
types: vec![], types,
constants: vec![], constants: vec![],
timestamp: timestamp.as_secs().to_string(), timestamp: timestamp.as_secs().to_string(),
}; };
@ -208,28 +245,18 @@ fn generate_static_assets(search_indexes: Vec<SearchIndex>) -> Vec<DocFile> {
fn generate_readme( fn generate_readme(
root: &PathBuf, root: &PathBuf,
config: &Config, config: &Config,
modules: Vec<&CheckedModule>, modules: &Vec<DocLink>,
timestamp: &Duration, timestamp: &Duration,
) -> DocFile { ) -> DocFile {
let path = PathBuf::from("index.html"); let path = PathBuf::from("index.html");
let content = std::fs::read_to_string(root.join("README.md")).unwrap_or_default(); let content = std::fs::read_to_string(root.join("README.md")).unwrap_or_default();
let mut modules_links = vec![];
for module in modules {
let module_path = [&module.name.clone(), ".html"].concat();
modules_links.push(DocLink {
path: module_path,
name: module.name.to_string().clone(),
});
}
modules_links.sort();
let template = PageTemplate { let template = PageTemplate {
aiken_version: VERSION, aiken_version: VERSION,
breadcrumbs: ".", breadcrumbs: ".",
links: &vec![], links: &vec![],
modules: &modules_links, modules,
project_name: &config.name, project_name: &config.name,
page_title: &config.name, page_title: &config.name,
project_version: &config.version.to_string(), project_version: &config.version.to_string(),
@ -243,6 +270,19 @@ fn generate_readme(
} }
} }
fn generate_modules_links(modules: &Vec<&CheckedModule>) -> Vec<DocLink> {
let mut modules_links = vec![];
for module in modules {
let module_path = [&module.name.clone(), ".html"].concat();
modules_links.push(DocLink {
path: module_path,
name: module.name.to_string().clone(),
});
}
modules_links.sort();
modules_links
}
#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(PartialEq, Eq, PartialOrd, Ord)]
struct DocFunction { struct DocFunction {
name: String, name: String,
@ -263,13 +303,12 @@ impl DocFunction {
.unwrap_or_default(), .unwrap_or_default(),
signature: format::Formatter::new() signature: format::Formatter::new()
.docs_fn_signature( .docs_fn_signature(
true,
&func_def.name, &func_def.name,
&func_def.arguments, &func_def.arguments,
func_def.return_type.clone(), func_def.return_type.clone(),
) )
.to_pretty_string(MAX_COLUMNS), .to_pretty_string(MAX_COLUMNS),
source_url: "TODO: source_url".to_string(), source_url: "#todo".to_string(),
}), }),
_ => None, _ => None,
} }
@ -281,7 +320,6 @@ struct DocConstant {
name: String, name: String,
definition: String, definition: String,
documentation: String, documentation: String,
text_documentation: String,
source_url: String, source_url: String,
} }
@ -291,22 +329,99 @@ struct DocType {
definition: String, definition: String,
documentation: String, documentation: String,
constructors: Vec<DocTypeConstructor>, constructors: Vec<DocTypeConstructor>,
text_documentation: String,
source_url: String, source_url: String,
} }
impl DocType {
fn from_definition(def: &TypedDefinition) -> Option<Self> {
match def {
Definition::TypeAlias(info) if info.public => Some(DocType {
name: info.alias.clone(),
definition: format::Formatter::new()
.docs_type_alias(&info.alias, &info.parameters, &info.annotation)
.to_pretty_string(MAX_COLUMNS),
documentation: info.doc.as_deref().map(render_markdown).unwrap_or_default(),
constructors: vec![],
source_url: "#todo".to_string(),
}),
Definition::DataType(info) if info.public && !info.opaque => Some(DocType {
name: info.name.clone(),
definition: format::Formatter::new()
.docs_data_type(
&info.name,
&info.parameters,
&info.constructors,
&info.location,
)
.to_pretty_string(MAX_COLUMNS),
documentation: info.doc.as_deref().map(render_markdown).unwrap_or_default(),
constructors: info
.constructors
.iter()
.map(DocTypeConstructor::from_record_constructor)
.collect(),
source_url: "#todo".to_string(),
}),
Definition::DataType(info) if info.public && info.opaque => Some(DocType {
name: info.name.clone(),
definition: format::Formatter::new()
.docs_opaque_data_type(&info.name, &info.parameters, &info.location)
.to_pretty_string(MAX_COLUMNS),
documentation: info.doc.as_deref().map(render_markdown).unwrap_or_default(),
constructors: vec![],
source_url: "#todo".to_string(),
}),
_ => None,
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
struct DocTypeConstructor { struct DocTypeConstructor {
definition: String, definition: String,
documentation: String, documentation: String,
text_documentation: String,
arguments: Vec<DocTypeConstructorArg>, arguments: Vec<DocTypeConstructorArg>,
} }
impl DocTypeConstructor {
fn from_record_constructor(constructor: &RecordConstructor<Arc<Type>>) -> Self {
DocTypeConstructor {
definition: format::Formatter::new()
.docs_record_constructor(constructor)
.to_pretty_string(MAX_COLUMNS),
documentation: constructor
.doc
.as_deref()
.map(render_markdown)
.unwrap_or_default(),
arguments: constructor
.arguments
.iter()
.filter_map(DocTypeConstructorArg::from_record_constructor_arg)
.collect(),
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
struct DocTypeConstructorArg { struct DocTypeConstructorArg {
name: String, label: String,
doc: String, documentation: String,
}
impl DocTypeConstructorArg {
fn from_record_constructor_arg(arg: &RecordConstructorArg<Arc<Type>>) -> Option<Self> {
match &arg.label {
None => None,
Some(label) => Some(DocTypeConstructorArg {
label: label.clone(),
documentation: arg.doc.as_deref().map(render_markdown).unwrap_or_default(),
}),
}
}
} }
// ------ Extra Helpers // ------ Extra Helpers

View File

@ -83,10 +83,10 @@
<li> <li>
<div class="constructor-argument-item"> <div class="constructor-argument-item">
<p class="constructor-argument-label"> <p class="constructor-argument-label">
<i>{{ argument.name }}</i> <i>{{ argument.label }}</i>
</p> </p>
<div class="constructor-argument-doc"> <div class="constructor-argument-doc">
{{ argument.doc|safe }} {{ argument.documentation|safe }}
</div> </div>
</div> </div>
</li> </li>