Parse and display documentation section headers.
The idea is pretty simple, we'll just look for lines starting with Markdown heading sections, and render them in the documentation. They appear both in the sidebar, and within the generated docs themselves in between functions. This, coupled with the order preservation of the declaration in a module should make the generated docs significantly more elegant to organize and present.
This commit is contained in:
parent
0ff12b9246
commit
10c829edfa
|
@ -16,7 +16,7 @@ build = "build.rs"
|
|||
|
||||
[dependencies]
|
||||
aiken-lang = { path = "../aiken-lang", version = "1.0.31-alpha" }
|
||||
askama = "0.12.0"
|
||||
askama = { version = "0.12.0", features = ["urlencode"] }
|
||||
ciborium = "0.2.2"
|
||||
cryptoxide = "0.4.4"
|
||||
dirs = "4.0.0"
|
||||
|
|
|
@ -4,10 +4,11 @@ use crate::{
|
|||
};
|
||||
use aiken_lang::{
|
||||
ast::{
|
||||
DataType, Definition, Function, ModuleConstant, RecordConstructor, TypeAlias,
|
||||
DataType, Definition, Function, ModuleConstant, RecordConstructor, Span, TypeAlias,
|
||||
TypedDefinition,
|
||||
},
|
||||
format,
|
||||
parser::extra::Comment,
|
||||
tipo::Type,
|
||||
};
|
||||
use askama::Template;
|
||||
|
@ -42,7 +43,7 @@ struct ModuleTemplate<'a> {
|
|||
project_name: &'a str,
|
||||
project_version: &'a str,
|
||||
modules: &'a [DocLink],
|
||||
functions: Vec<DocFunction>,
|
||||
functions: Vec<Interspersed>,
|
||||
types: Vec<DocType>,
|
||||
constants: Vec<DocConstant>,
|
||||
documentation: String,
|
||||
|
@ -154,16 +155,62 @@ fn generate_module(
|
|||
) -> (Vec<SearchIndex>, DocFile) {
|
||||
let mut search_indexes = vec![];
|
||||
|
||||
// Section headers
|
||||
let mut section_headers = module
|
||||
.extra
|
||||
.comments
|
||||
.iter()
|
||||
.filter_map(|span| {
|
||||
let comment = Comment::from((span, module.code.as_str()))
|
||||
.content
|
||||
.trim_start();
|
||||
if comment.starts_with("#") {
|
||||
let trimmed = comment.trim_start_matches("#");
|
||||
let heading = comment.len() - trimmed.len();
|
||||
Some((
|
||||
span,
|
||||
DocSection {
|
||||
heading,
|
||||
title: trimmed.trim_start().to_string(),
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
// Functions
|
||||
let functions: Vec<DocFunction> = module
|
||||
let functions: Vec<(Span, DocFunction)> = module
|
||||
.ast
|
||||
.definitions
|
||||
.iter()
|
||||
.flat_map(DocFunction::from_definition)
|
||||
.collect();
|
||||
functions
|
||||
.iter()
|
||||
.for_each(|function| search_indexes.push(SearchIndex::from_function(module, function)));
|
||||
|
||||
functions.iter().for_each(|(_, function)| {
|
||||
search_indexes.push(SearchIndex::from_function(module, function))
|
||||
});
|
||||
|
||||
let no_functions = functions.is_empty();
|
||||
|
||||
let mut functions_and_headers = Vec::new();
|
||||
|
||||
for (span_fn, function) in functions {
|
||||
let mut to_remove = vec![];
|
||||
for (ix, (span_h, header)) in section_headers.iter().enumerate() {
|
||||
if span_h.start < span_fn.start {
|
||||
functions_and_headers.push(Interspersed::Section(header.clone()));
|
||||
to_remove.push(ix);
|
||||
}
|
||||
}
|
||||
|
||||
for ix in to_remove.iter().rev() {
|
||||
section_headers.remove(*ix);
|
||||
}
|
||||
|
||||
functions_and_headers.push(Interspersed::Function(function))
|
||||
}
|
||||
|
||||
// Types
|
||||
let types: Vec<DocType> = module
|
||||
|
@ -187,7 +234,7 @@ fn generate_module(
|
|||
.iter()
|
||||
.for_each(|constant| search_indexes.push(SearchIndex::from_constant(module, constant)));
|
||||
|
||||
let is_empty = functions.is_empty() && types.is_empty() && constants.is_empty();
|
||||
let is_empty = no_functions && types.is_empty() && constants.is_empty();
|
||||
|
||||
// Module
|
||||
if !is_empty {
|
||||
|
@ -203,7 +250,7 @@ fn generate_module(
|
|||
page_title: &format!("{} - {}", module.name, config.name),
|
||||
module_name: module.name.clone(),
|
||||
project_version: &config.version.to_string(),
|
||||
functions,
|
||||
functions: functions_and_headers,
|
||||
types,
|
||||
constants,
|
||||
source,
|
||||
|
@ -391,7 +438,19 @@ impl SearchIndex {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum Interspersed {
|
||||
Section(DocSection),
|
||||
Function(DocFunction),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
struct DocSection {
|
||||
heading: usize,
|
||||
title: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct DocFunction {
|
||||
name: String,
|
||||
signature: String,
|
||||
|
@ -401,9 +460,11 @@ struct DocFunction {
|
|||
}
|
||||
|
||||
impl DocFunction {
|
||||
fn from_definition(def: &TypedDefinition) -> Option<Self> {
|
||||
fn from_definition(def: &TypedDefinition) -> Option<(Span, Self)> {
|
||||
match def {
|
||||
Definition::Fn(func_def) if func_def.public => Some(DocFunction {
|
||||
Definition::Fn(func_def) if func_def.public => Some((
|
||||
func_def.location,
|
||||
DocFunction {
|
||||
name: func_def.name.clone(),
|
||||
documentation: func_def
|
||||
.doc
|
||||
|
@ -420,7 +481,8 @@ impl DocFunction {
|
|||
)
|
||||
.to_pretty_string(MAX_COLUMNS),
|
||||
source_url: "#todo".to_string(),
|
||||
}),
|
||||
},
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -290,11 +290,13 @@ p code {
|
|||
/* Module doc */
|
||||
|
||||
.module-name > a,
|
||||
.module-heading > a,
|
||||
.module-member-kind > a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.module-name > a:hover,
|
||||
.module-heading > a:hover,
|
||||
.module-member-kind > a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -328,6 +330,7 @@ p code {
|
|||
.sidebar h2 {
|
||||
margin: 0;
|
||||
color: var(--color-link-accent);
|
||||
font-size: 1.75em;
|
||||
}
|
||||
|
||||
.sidebar h3.modules-prefix {
|
||||
|
@ -407,6 +410,10 @@ body.drawer-open .label-closed {
|
|||
|
||||
.module-member-kind {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.module-heading,
|
||||
.module-member-kind {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
|
@ -1113,3 +1120,29 @@ body.theme-dark {
|
|||
.sidebar li[data-indent="1"]::before { padding-left: 1rem; }
|
||||
.sidebar li[data-indent="2"]::before { padding-left: 2rem; }
|
||||
.sidebar li[data-indent="3"]::before { padding-left: 3rem; }
|
||||
|
||||
.sidebar li[data-heading] {
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
color: var(--color-link-accent);
|
||||
}
|
||||
|
||||
.sidebar li[data-heading="1"] {
|
||||
margin-top: 1.5rem;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
.sidebar li[data-heading="2"] {
|
||||
margin-top: 1rem;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.sidebar li[data-heading="3"] {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.sidebar li[data-heading]:first-child,
|
||||
.sidebar li[data-heading] + li[data-heading] {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,14 @@
|
|||
{% if !functions.is_empty() %}
|
||||
<h2>Functions</h2>
|
||||
<ul>
|
||||
{% for function in functions %}
|
||||
{% for function_or_section in functions %}
|
||||
{% match function_or_section %}
|
||||
{% when Interspersed::Function with (function) %}
|
||||
<li><a href="#{{ function.name }}">{{ function.name }}</a></li>
|
||||
|
||||
{% when Interspersed::Section with (section) %}
|
||||
<li data-heading="{{ section.heading }}"><a href="#{{ section.title|urlencode }}">{{ section.title }}</a></li>
|
||||
{% endmatch %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
@ -118,7 +124,9 @@
|
|||
<h1 id="module-functions" class="module-member-kind">
|
||||
<a href="#module-functions">Functions</a>
|
||||
</h1>
|
||||
{% for function in functions %}
|
||||
{% for function_or_section in functions %}
|
||||
{% match function_or_section %}
|
||||
{% when Interspersed::Function with (function) %}
|
||||
<div class="member">
|
||||
<div class="member-name">
|
||||
<h2 id="{{ function.name }}"><pre class="hljs language-aiken">{{ function.signature }}</pre></h2>
|
||||
|
@ -132,6 +140,17 @@
|
|||
</div>
|
||||
<div class="rendered-markdown">{{ function.documentation|safe }}</div>
|
||||
</div>
|
||||
{% when Interspersed::Section with (section) %}
|
||||
{% if section.heading == 1 %}
|
||||
<h2 id="{{ section.title|urlencode }}" class="module-heading"><a href="#{{ section.title|urlencode }}">{{ section.title }}</a></h2>
|
||||
{% else if section.heading == 2 %}
|
||||
<h2 id="{{ section.title|urlencode }}" class="module-heading"><a href="#{{ section.title|urlencode }}">{{ section.title }}</a></h3>
|
||||
{% else if section.heading == 3 %}
|
||||
<h3 id="{{ section.title|urlencode }}" class="module-heading"><a href="#{{ section.title|urlencode }}">{{ section.title }}</a></h4>
|
||||
{% else %}
|
||||
<h4 id="{{ section.title|urlencode }}" class="module-heading"><a href="#{{ section.title|urlencode }}">{{ section.title }}</a></h5>
|
||||
{% endif %}
|
||||
{% endmatch %}
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in New Issue