Better module hierarchy and style for generated docs.
This commit also reverse the order of the sections in the sidebar such that modules are now placed last.
This commit is contained in:
parent
8b869c0a32
commit
0ff12b9246
|
@ -24,6 +24,8 @@ use std::{
|
|||
const MAX_COLUMNS: isize = 999;
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
mod link_tree;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct DocFile {
|
||||
pub path: PathBuf,
|
||||
|
@ -39,8 +41,7 @@ struct ModuleTemplate<'a> {
|
|||
module_name: String,
|
||||
project_name: &'a str,
|
||||
project_version: &'a str,
|
||||
modules_prefix: String,
|
||||
modules: &'a Vec<DocLink>,
|
||||
modules: &'a [DocLink],
|
||||
functions: Vec<DocFunction>,
|
||||
types: Vec<DocType>,
|
||||
constants: Vec<DocConstant>,
|
||||
|
@ -66,8 +67,7 @@ struct PageTemplate<'a> {
|
|||
page_title: &'a str,
|
||||
project_name: &'a str,
|
||||
project_version: &'a str,
|
||||
modules_prefix: String,
|
||||
modules: &'a Vec<DocLink>,
|
||||
modules: &'a [DocLink],
|
||||
content: String,
|
||||
source: &'a DocLink,
|
||||
timestamp: &'a str,
|
||||
|
@ -81,6 +81,7 @@ impl<'a> PageTemplate<'a> {
|
|||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
struct DocLink {
|
||||
indent: usize,
|
||||
name: String,
|
||||
path: String,
|
||||
}
|
||||
|
@ -89,6 +90,10 @@ impl DocLink {
|
|||
pub fn is_empty(&self) -> bool {
|
||||
self.name.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_separator(&self) -> bool {
|
||||
self.path.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate documentation files for a given project.
|
||||
|
@ -98,10 +103,11 @@ impl DocLink {
|
|||
/// across multiple modules.
|
||||
pub fn generate_all(root: &Path, config: &Config, modules: Vec<&CheckedModule>) -> Vec<DocFile> {
|
||||
let timestamp = new_timestamp();
|
||||
let (modules_prefix, modules_links) = generate_modules_links(&modules);
|
||||
let modules_links = generate_modules_links(&modules);
|
||||
|
||||
let source = match &config.repository {
|
||||
None => DocLink {
|
||||
indent: 0,
|
||||
name: String::new(),
|
||||
path: String::new(),
|
||||
},
|
||||
|
@ -110,6 +116,7 @@ pub fn generate_all(root: &Path, config: &Config, modules: Vec<&CheckedModule>)
|
|||
project,
|
||||
platform,
|
||||
}) => DocLink {
|
||||
indent: 0,
|
||||
name: format!("{user}/{project}"),
|
||||
path: format!("https://{platform}.com/{user}/{project}"),
|
||||
},
|
||||
|
@ -119,13 +126,7 @@ pub fn generate_all(root: &Path, config: &Config, modules: Vec<&CheckedModule>)
|
|||
let mut search_indexes: Vec<SearchIndex> = vec![];
|
||||
|
||||
for module in &modules {
|
||||
let (indexes, file) = generate_module(
|
||||
config,
|
||||
module,
|
||||
(&modules_prefix, &modules_links),
|
||||
&source,
|
||||
×tamp,
|
||||
);
|
||||
let (indexes, file) = generate_module(config, module, &modules_links, &source, ×tamp);
|
||||
if !indexes.is_empty() {
|
||||
search_indexes.extend(indexes);
|
||||
output_files.push(file);
|
||||
|
@ -136,7 +137,7 @@ pub fn generate_all(root: &Path, config: &Config, modules: Vec<&CheckedModule>)
|
|||
output_files.push(generate_readme(
|
||||
root,
|
||||
config,
|
||||
(&modules_prefix, &modules_links),
|
||||
&modules_links,
|
||||
&source,
|
||||
×tamp,
|
||||
));
|
||||
|
@ -147,7 +148,7 @@ pub fn generate_all(root: &Path, config: &Config, modules: Vec<&CheckedModule>)
|
|||
fn generate_module(
|
||||
config: &Config,
|
||||
module: &CheckedModule,
|
||||
(modules_prefix, modules): (&str, &Vec<DocLink>),
|
||||
modules: &[DocLink],
|
||||
source: &DocLink,
|
||||
timestamp: &Duration,
|
||||
) -> (Vec<SearchIndex>, DocFile) {
|
||||
|
@ -197,7 +198,6 @@ fn generate_module(
|
|||
aiken_version: VERSION,
|
||||
breadcrumbs: to_breadcrumbs(&module.name),
|
||||
documentation: render_markdown(&module.ast.docs.iter().join("\n")),
|
||||
modules_prefix: modules_prefix.to_string(),
|
||||
modules,
|
||||
project_name: &config.name.repo.to_string(),
|
||||
page_title: &format!("{} - {}", module.name, config.name),
|
||||
|
@ -279,7 +279,7 @@ fn generate_static_assets(search_indexes: Vec<SearchIndex>) -> Vec<DocFile> {
|
|||
fn generate_readme(
|
||||
root: &Path,
|
||||
config: &Config,
|
||||
(modules_prefix, modules): (&str, &Vec<DocLink>),
|
||||
modules: &[DocLink],
|
||||
source: &DocLink,
|
||||
timestamp: &Duration,
|
||||
) -> DocFile {
|
||||
|
@ -290,7 +290,6 @@ fn generate_readme(
|
|||
let template = PageTemplate {
|
||||
aiken_version: VERSION,
|
||||
breadcrumbs: ".",
|
||||
modules_prefix: modules_prefix.to_string(),
|
||||
modules,
|
||||
project_name: &config.name.repo.to_string(),
|
||||
page_title: &config.name.to_string(),
|
||||
|
@ -306,46 +305,30 @@ fn generate_readme(
|
|||
}
|
||||
}
|
||||
|
||||
fn generate_modules_links(modules: &[&CheckedModule]) -> (String, Vec<DocLink>) {
|
||||
let non_empty_modules = modules.iter().filter(|module| {
|
||||
module.ast.definitions.iter().any(|def| {
|
||||
matches!(
|
||||
def,
|
||||
Definition::Fn(Function { public: true, .. })
|
||||
| Definition::DataType(DataType { public: true, .. })
|
||||
| Definition::TypeAlias(TypeAlias { public: true, .. })
|
||||
| Definition::ModuleConstant(ModuleConstant { public: true, .. })
|
||||
)
|
||||
fn generate_modules_links(modules: &[&CheckedModule]) -> Vec<DocLink> {
|
||||
let non_empty_modules = modules
|
||||
.iter()
|
||||
.filter(|module| {
|
||||
module.ast.definitions.iter().any(|def| {
|
||||
matches!(
|
||||
def,
|
||||
Definition::Fn(Function { public: true, .. })
|
||||
| Definition::DataType(DataType { public: true, .. })
|
||||
| Definition::TypeAlias(TypeAlias { public: true, .. })
|
||||
| Definition::ModuleConstant(ModuleConstant { public: true, .. })
|
||||
)
|
||||
})
|
||||
})
|
||||
});
|
||||
.sorted_by(|a, b| a.name.cmp(&b.name))
|
||||
.collect_vec();
|
||||
|
||||
let mut links = link_tree::LinkTree::default();
|
||||
|
||||
let mut modules_links = vec![];
|
||||
for module in non_empty_modules {
|
||||
let module_path = [&module.name.clone(), ".html"].concat();
|
||||
modules_links.push(DocLink {
|
||||
path: module_path,
|
||||
name: module.name.to_string().clone(),
|
||||
});
|
||||
links.insert(module.name.as_str());
|
||||
}
|
||||
modules_links.sort();
|
||||
|
||||
let prefix = if modules_links.len() > 1 {
|
||||
let prefix = find_modules_prefix(&modules_links);
|
||||
|
||||
for module in &mut modules_links {
|
||||
let name = module.name.strip_prefix(&prefix).unwrap_or_default();
|
||||
module.name = name.strip_prefix('/').unwrap_or(name).to_string();
|
||||
if module.name == String::new() {
|
||||
module.name = "/".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
prefix
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
(prefix, modules_links)
|
||||
links.to_vec()
|
||||
}
|
||||
|
||||
#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
|
@ -608,141 +591,6 @@ fn new_timestamp() -> Duration {
|
|||
.expect("get current timestamp")
|
||||
}
|
||||
|
||||
fn find_modules_prefix(modules: &[DocLink]) -> String {
|
||||
do_find_modules_prefix("", modules)
|
||||
}
|
||||
|
||||
fn do_find_modules_prefix(current_prefix: &str, modules: &[DocLink]) -> String {
|
||||
let prefix = modules
|
||||
.iter()
|
||||
.fold(None, |previous_prefix, module| {
|
||||
let name = module.name.strip_prefix(current_prefix).unwrap_or_default();
|
||||
let name = if name.starts_with('/') {
|
||||
name.strip_prefix('/').unwrap_or_default()
|
||||
} else {
|
||||
name
|
||||
};
|
||||
|
||||
let prefix = name.split('/').next().unwrap_or_default().to_string();
|
||||
|
||||
match previous_prefix {
|
||||
None if prefix != module.name => Some(prefix),
|
||||
Some(..) if Some(prefix) == previous_prefix => previous_prefix,
|
||||
_ => Some(String::new()),
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if prefix.is_empty() {
|
||||
current_prefix.to_string()
|
||||
} else {
|
||||
let mut current_prefix = current_prefix.to_owned();
|
||||
if !current_prefix.is_empty() {
|
||||
current_prefix.push('/');
|
||||
}
|
||||
current_prefix.push_str(&prefix);
|
||||
do_find_modules_prefix(¤t_prefix, modules)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_modules_prefix_test() {
|
||||
assert_eq!(find_modules_prefix(&[]), "".to_string());
|
||||
|
||||
assert_eq!(
|
||||
find_modules_prefix(&[DocLink {
|
||||
name: "aiken/list".to_string(),
|
||||
path: String::new()
|
||||
}]),
|
||||
"aiken/list".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
find_modules_prefix(&[DocLink {
|
||||
name: "my_module".to_string(),
|
||||
path: String::new()
|
||||
}]),
|
||||
"".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
find_modules_prefix(&[
|
||||
DocLink {
|
||||
name: "aiken/list".to_string(),
|
||||
path: String::new()
|
||||
},
|
||||
DocLink {
|
||||
name: "aiken/bytearray".to_string(),
|
||||
path: String::new(),
|
||||
}
|
||||
]),
|
||||
"aiken".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
find_modules_prefix(&[
|
||||
DocLink {
|
||||
name: "aiken/list".to_string(),
|
||||
path: String::new()
|
||||
},
|
||||
DocLink {
|
||||
name: "foo/bytearray".to_string(),
|
||||
path: String::new(),
|
||||
}
|
||||
]),
|
||||
"".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_modules_prefix_test_2() {
|
||||
assert_eq!(
|
||||
find_modules_prefix(&[
|
||||
DocLink {
|
||||
name: "aiken/trees/bst".to_string(),
|
||||
path: String::new()
|
||||
},
|
||||
DocLink {
|
||||
name: "aiken/trees/mt".to_string(),
|
||||
path: String::new(),
|
||||
}
|
||||
]),
|
||||
"aiken/trees".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
find_modules_prefix(&[
|
||||
DocLink {
|
||||
name: "aiken/trees/bst".to_string(),
|
||||
path: String::new()
|
||||
},
|
||||
DocLink {
|
||||
name: "aiken/trees/mt".to_string(),
|
||||
path: String::new(),
|
||||
},
|
||||
DocLink {
|
||||
name: "aiken/sequences".to_string(),
|
||||
path: String::new(),
|
||||
}
|
||||
]),
|
||||
"aiken".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
find_modules_prefix(&[
|
||||
DocLink {
|
||||
name: "aiken".to_string(),
|
||||
path: String::new()
|
||||
},
|
||||
DocLink {
|
||||
name: "aiken/prelude".to_string(),
|
||||
path: String::new(),
|
||||
}
|
||||
]),
|
||||
"".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
fn to_breadcrumbs(path: &str) -> String {
|
||||
let breadcrumbs = path
|
||||
.strip_prefix('/')
|
||||
|
|
|
@ -0,0 +1,506 @@
|
|||
use super::DocLink;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
/// A custom tree structure to help constructing the links in the sidebar for documentation.
|
||||
/// The goal is to end up generating a vector of pre-constructed elements that are simple to handle
|
||||
/// in the HTML template, but still enforces a visual hierarchy that helps readability of modules.
|
||||
///
|
||||
/// So for example the following:
|
||||
/// - aiken/cbor
|
||||
/// - aiken/list
|
||||
/// - aiken/math
|
||||
/// - aiken/math/rational
|
||||
/// - aiken/primitive
|
||||
/// - aiken/primitive/bytearray
|
||||
/// - aiken/primitive/integer
|
||||
/// - cardano/asset
|
||||
/// - cardano/certificate
|
||||
///
|
||||
/// is nicely turned into:
|
||||
///
|
||||
/// aiken
|
||||
/// /cbor
|
||||
/// /list
|
||||
/// /math
|
||||
/// /rational
|
||||
/// /primitive
|
||||
/// /bytearray
|
||||
/// /integer
|
||||
///
|
||||
/// cardano
|
||||
/// /asset
|
||||
/// /certificate
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
|
||||
pub(crate) enum LinkTree {
|
||||
Empty,
|
||||
|
||||
Leaf {
|
||||
value: String,
|
||||
},
|
||||
|
||||
Node {
|
||||
prefix: String,
|
||||
separator: bool,
|
||||
children: Vec<Rc<RefCell<LinkTree>>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for LinkTree {
|
||||
/// The intended way of creating a new empty LinkTree.
|
||||
fn default() -> LinkTree {
|
||||
LinkTree::Empty
|
||||
}
|
||||
}
|
||||
|
||||
impl LinkTree {
|
||||
/// Convert a LinkTree into a sequence of DocLinks, ready to be displayed at the right
|
||||
/// indentation level.
|
||||
pub fn to_vec(&self) -> Vec<DocLink> {
|
||||
self.do_to_vec(&[])
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, module: &str) {
|
||||
/// Strip prefix and ensures to remove any leading slash "/" as well.
|
||||
fn strip_prefix(source: &str, prefix: &str) -> String {
|
||||
let result = source.strip_prefix(prefix).unwrap();
|
||||
if result.starts_with("/") {
|
||||
result.strip_prefix("/").unwrap().to_string()
|
||||
} else {
|
||||
result.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
match self {
|
||||
LinkTree::Empty => {
|
||||
*self = LinkTree::Leaf {
|
||||
value: module.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
LinkTree::Leaf {
|
||||
value: ref mut leaf,
|
||||
..
|
||||
} => {
|
||||
let (prefix, value) = if let Some(prefix) = common_prefix(module, leaf) {
|
||||
*leaf = strip_prefix(leaf, &prefix);
|
||||
let value = strip_prefix(module, &prefix);
|
||||
(prefix, value)
|
||||
} else {
|
||||
(String::new(), module.to_string())
|
||||
};
|
||||
|
||||
// When `prefix == module`, we are in the case where we try to insert a parent
|
||||
// (e.g. `aiken/math`) into a sub-leaf (e.g. `aiken/math/rational`). So `self`
|
||||
// must become a child node of our newly created parent.
|
||||
if prefix == module {
|
||||
let children = vec![self.clone().into_ref()];
|
||||
*self = LinkTree::Node {
|
||||
// Holds a value, so separator = false
|
||||
separator: false,
|
||||
children,
|
||||
prefix,
|
||||
};
|
||||
// If `leaf.is_empty()`, we are in the case where we are inserting a sub-leaf
|
||||
// (e.g. `aiken/math/rational`) into a parent (e.g. `aiken/math`); so much that
|
||||
// we've run out of path segments to follow down. So `self` can turn into a node
|
||||
// that contains that new leaf.
|
||||
} else if leaf.is_empty() {
|
||||
let children = vec![LinkTree::Leaf { value }.into_ref()];
|
||||
*self = LinkTree::Node {
|
||||
// Holds a value, so separator = false
|
||||
separator: false,
|
||||
children,
|
||||
prefix,
|
||||
};
|
||||
// Otherwise, neither one is a child of the other, so we can nest them under a node
|
||||
// with the corresponding (possibly empty) prefix.
|
||||
} else {
|
||||
let mut children =
|
||||
vec![self.clone().into_ref(), LinkTree::Leaf { value }.into_ref()];
|
||||
|
||||
children.sort_by(|a, b| a.borrow().path().cmp(b.borrow().path()));
|
||||
|
||||
*self = LinkTree::Node {
|
||||
// This node is a 'separator' because it doesn't
|
||||
// hold any value. It is just an intersection point.
|
||||
separator: true,
|
||||
children,
|
||||
prefix,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
LinkTree::Node {
|
||||
ref prefix,
|
||||
ref mut children,
|
||||
..
|
||||
} => {
|
||||
// When `module.starts_with(prefix)` is true, it means that the module being
|
||||
// inserted belong to our sub-tree. We do not know *where* exactly though, so we
|
||||
// have to find whether there's any child that continues the path. If node, we can
|
||||
// add it to our children.
|
||||
if module.starts_with(prefix) {
|
||||
let module = strip_prefix(module, prefix);
|
||||
|
||||
for child in children.iter_mut() {
|
||||
if common_prefix(child.borrow().path(), module.as_str()).is_some() {
|
||||
return child.borrow_mut().insert(module.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
children.push(
|
||||
LinkTree::Leaf {
|
||||
value: module.to_string(),
|
||||
}
|
||||
.into_ref(),
|
||||
);
|
||||
|
||||
children.sort_by(|a, b| a.borrow().path().cmp(b.borrow().path()));
|
||||
// Otherwise, we make it a neighbor that shares no common prefix.
|
||||
} else {
|
||||
let mut children = vec![
|
||||
self.clone().into_ref(),
|
||||
LinkTree::Leaf {
|
||||
value: module.to_string(),
|
||||
}
|
||||
.into_ref(),
|
||||
];
|
||||
|
||||
children.sort_by(|a, b| a.borrow().path().cmp(b.borrow().path()));
|
||||
|
||||
*self = LinkTree::Node {
|
||||
// This node is a 'separator' because it doesn't
|
||||
// hold any value. It is just an intersection point.
|
||||
separator: true,
|
||||
prefix: String::new(),
|
||||
children,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_to_vec(&self, path: &[&str]) -> Vec<DocLink> {
|
||||
let mk_path = |value: &str| {
|
||||
[
|
||||
path.join("/").as_str(),
|
||||
if path.is_empty() { "" } else { "/" },
|
||||
value,
|
||||
".html",
|
||||
]
|
||||
.concat()
|
||||
};
|
||||
|
||||
match self {
|
||||
LinkTree::Empty => vec![],
|
||||
|
||||
LinkTree::Leaf { value } => {
|
||||
let last_ix = value.split("/").count();
|
||||
let module_path = mk_path(value);
|
||||
value
|
||||
.split("/")
|
||||
.enumerate()
|
||||
.map(|(offset, segment)| {
|
||||
if offset == last_ix - 1 {
|
||||
DocLink {
|
||||
indent: path.len() + offset,
|
||||
name: segment.to_string(),
|
||||
path: module_path.to_string(),
|
||||
}
|
||||
} else {
|
||||
DocLink {
|
||||
indent: path.len() + offset,
|
||||
name: segment.to_string(),
|
||||
path: String::new(),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
LinkTree::Node {
|
||||
children,
|
||||
prefix,
|
||||
separator,
|
||||
} => {
|
||||
let mut links = if prefix.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![DocLink {
|
||||
indent: path.len(),
|
||||
name: prefix.clone(),
|
||||
path: if *separator {
|
||||
String::new()
|
||||
} else {
|
||||
mk_path(prefix)
|
||||
},
|
||||
}]
|
||||
};
|
||||
|
||||
let mut next = vec![];
|
||||
for segment in path {
|
||||
next.push(*segment);
|
||||
}
|
||||
if !prefix.is_empty() {
|
||||
next.push(prefix);
|
||||
}
|
||||
|
||||
links.extend(
|
||||
children
|
||||
.iter()
|
||||
.flat_map(|child| child.borrow().do_to_vec(&next[..])),
|
||||
);
|
||||
|
||||
links
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_ref(self) -> Rc<RefCell<LinkTree>> {
|
||||
Rc::new(RefCell::new(self))
|
||||
}
|
||||
|
||||
fn path(&self) -> &str {
|
||||
match self {
|
||||
LinkTree::Empty => "",
|
||||
LinkTree::Leaf { ref value, .. } => value.as_str(),
|
||||
LinkTree::Node { ref prefix, .. } => prefix.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_tree_1() {
|
||||
let mut tree = LinkTree::default();
|
||||
tree.insert("foo");
|
||||
assert_eq!(
|
||||
tree.to_vec(),
|
||||
vec![DocLink {
|
||||
indent: 0,
|
||||
name: "foo".to_string(),
|
||||
path: "foo.html".to_string(),
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_tree_2() {
|
||||
let mut tree = LinkTree::default();
|
||||
tree.insert("foo");
|
||||
tree.insert("bar");
|
||||
assert_eq!(
|
||||
tree.to_vec(),
|
||||
vec![
|
||||
DocLink {
|
||||
indent: 0,
|
||||
name: "bar".to_string(),
|
||||
path: "bar.html".to_string(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 0,
|
||||
name: "foo".to_string(),
|
||||
path: "foo.html".to_string(),
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_tree_3() {
|
||||
let mut tree = LinkTree::default();
|
||||
tree.insert("aiken/list");
|
||||
tree.insert("aiken/bytearray");
|
||||
assert_eq!(
|
||||
tree.to_vec(),
|
||||
vec![
|
||||
DocLink {
|
||||
indent: 0,
|
||||
name: "aiken".to_string(),
|
||||
path: String::new(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 1,
|
||||
name: "bytearray".to_string(),
|
||||
path: "aiken/bytearray.html".to_string(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 1,
|
||||
name: "list".to_string(),
|
||||
path: "aiken/list.html".to_string(),
|
||||
},
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_tree_4() {
|
||||
let mut tree = LinkTree::default();
|
||||
tree.insert("aiken/cbor");
|
||||
tree.insert("aiken/math/rational");
|
||||
tree.insert("aiken/math");
|
||||
tree.insert("cardano/foo");
|
||||
assert_eq!(
|
||||
tree.to_vec(),
|
||||
vec![
|
||||
DocLink {
|
||||
indent: 0,
|
||||
name: "aiken".to_string(),
|
||||
path: String::new(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 1,
|
||||
name: "cbor".to_string(),
|
||||
path: "aiken/cbor.html".to_string(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 1,
|
||||
name: "math".to_string(),
|
||||
path: "aiken/math.html".to_string(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 2,
|
||||
name: "rational".to_string(),
|
||||
path: "aiken/math/rational.html".to_string(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 0,
|
||||
name: "cardano/foo".to_string(),
|
||||
path: "cardano/foo.html".to_string(),
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_tree_5() {
|
||||
let mut tree = LinkTree::default();
|
||||
tree.insert("cardano/foo");
|
||||
tree.insert("cardano");
|
||||
tree.insert("aiken/cbor");
|
||||
tree.insert("aiken/math");
|
||||
tree.insert("aiken/math/rational");
|
||||
assert_eq!(
|
||||
tree.to_vec(),
|
||||
vec![
|
||||
DocLink {
|
||||
indent: 0,
|
||||
name: "aiken".to_string(),
|
||||
path: String::new(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 1,
|
||||
name: "cbor".to_string(),
|
||||
path: "aiken/cbor.html".to_string(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 1,
|
||||
name: "math".to_string(),
|
||||
path: "aiken/math.html".to_string(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 2,
|
||||
name: "rational".to_string(),
|
||||
path: "aiken/math/rational.html".to_string(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 0,
|
||||
name: "cardano".to_string(),
|
||||
path: "cardano.html".to_string(),
|
||||
},
|
||||
DocLink {
|
||||
indent: 1,
|
||||
name: "foo".to_string(),
|
||||
path: "cardano/foo.html".to_string(),
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
/// Find the common module prefix between two module path, if any.
|
||||
///
|
||||
/// ```rust
|
||||
/// assert_eq!(
|
||||
/// common_prefix("foo", "foo"),
|
||||
/// Some("foo".to_string()),
|
||||
/// )
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// common_prefix("aiken/list", "aiken/bytearray"),
|
||||
/// Some("aiken".to_string()),
|
||||
/// )
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// common_prefix("aiken/list", "cardano/asset"),
|
||||
/// None,
|
||||
/// )
|
||||
/// ```
|
||||
pub fn common_prefix(left: &str, right: &str) -> Option<String> {
|
||||
let mut prefix = vec![];
|
||||
|
||||
for (left, right) in left.split('/').zip(right.split('/')) {
|
||||
if !left.is_empty() && left == right {
|
||||
prefix.push(left);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if prefix.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(prefix.join("/"))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_prefix_1() {
|
||||
assert_eq!(common_prefix("", ""), None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_prefix_2() {
|
||||
assert_eq!(common_prefix("foo", "bar"), None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_prefix_3() {
|
||||
assert_eq!(common_prefix("foo", "foo"), Some("foo".to_string()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_prefix_4() {
|
||||
assert_eq!(common_prefix("foo", ""), None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_prefix_5() {
|
||||
assert_eq!(
|
||||
common_prefix("foo/bar", "foo/bar"),
|
||||
Some("foo/bar".to_string())
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_prefix_6() {
|
||||
assert_eq!(
|
||||
common_prefix("foo/bar", "foo/bar/baz"),
|
||||
Some("foo/bar".to_string())
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_prefix_7() {
|
||||
assert_eq!(
|
||||
common_prefix("foo/bar", "foo/wow/baz"),
|
||||
Some("foo".to_string())
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_prefix_8() {
|
||||
assert_eq!(
|
||||
common_prefix("foo/bar/baz", "foo/wow/baz"),
|
||||
Some("foo".to_string())
|
||||
)
|
||||
}
|
|
@ -495,7 +495,7 @@ impl Prng {
|
|||
/// Obtain a Prng back from a fuzzer execution. As a reminder, fuzzers have the following
|
||||
/// signature:
|
||||
///
|
||||
/// type Fuzzer<a> = fn(Prng) -> Option<(Prng, a)>
|
||||
/// `type Fuzzer<a> = fn(Prng) -> Option<(Prng, a)>`
|
||||
///
|
||||
/// In nominal scenarios (i.e. when the fuzzer is made from a seed and evolve pseudo-randomly),
|
||||
/// it cannot yield 'None'. When replayed however, we can't easily guarantee that the changes
|
||||
|
@ -507,8 +507,10 @@ impl Prng {
|
|||
fn as_prng(cst: &PlutusData) -> Prng {
|
||||
if let PlutusData::Constr(Constr { tag, fields, .. }) = cst {
|
||||
if *tag == 121 + Prng::SEEDED {
|
||||
if let [PlutusData::BoundedBytes(bytes), PlutusData::BoundedBytes(choices)] =
|
||||
&fields[..]
|
||||
if let [
|
||||
PlutusData::BoundedBytes(bytes),
|
||||
PlutusData::BoundedBytes(choices),
|
||||
] = &fields[..]
|
||||
{
|
||||
return Prng::Seeded {
|
||||
choices: choices.to_vec(),
|
||||
|
@ -1087,9 +1089,11 @@ impl TryFrom<TypedExpr> for Assertion<TypedExpr> {
|
|||
final_else,
|
||||
..
|
||||
} => {
|
||||
if let [IfBranch {
|
||||
condition, body, ..
|
||||
}] = &branches[..]
|
||||
if let [
|
||||
IfBranch {
|
||||
condition, body, ..
|
||||
},
|
||||
] = &branches[..]
|
||||
{
|
||||
let then_is_true = match body {
|
||||
TypedExpr::Var {
|
||||
|
@ -1509,13 +1513,14 @@ mod test {
|
|||
}
|
||||
"#});
|
||||
|
||||
assert!(prop
|
||||
.run::<()>(
|
||||
assert!(
|
||||
prop.run::<()>(
|
||||
42,
|
||||
PropertyTest::DEFAULT_MAX_SUCCESS,
|
||||
&PlutusVersion::default()
|
||||
)
|
||||
.is_success());
|
||||
.is_success()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -186,23 +186,26 @@
|
|||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% block sidebar_content %}{% endblock %}
|
||||
|
||||
<h2>Modules</h2>
|
||||
{% if !modules_prefix.is_empty() %}
|
||||
<h3 class="modules-prefix">{{ modules_prefix }}/</h3>
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% for module in modules %}
|
||||
<li><a href="{{ breadcrumbs }}/{{ module.path }}">
|
||||
{% if self.is_current_module(module) %}
|
||||
<strong>{{ module.name }}</strong>
|
||||
{% if module.is_separator() %}
|
||||
<li data-indent="{{ module.indent }}"><span>{{ module.name }}</span></li>
|
||||
{% else %}
|
||||
{{ module.name }}
|
||||
{% if self.is_current_module(module) %}
|
||||
<li data-indent="{{ module.indent }}" data-current><a href="{{ breadcrumbs }}/{{ module.path }}">
|
||||
<strong>{{ module.name }}</strong>
|
||||
</a></li>
|
||||
{% else %}
|
||||
<li data-indent="{{ module.indent }}"><a href="{{ breadcrumbs }}/{{ module.path }}">
|
||||
{{ module.name }}
|
||||
</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% block sidebar_content %}{% endblock %}
|
||||
</nav>
|
||||
|
||||
<main class="content">
|
||||
|
@ -276,9 +279,35 @@
|
|||
el.prepend(a);
|
||||
});
|
||||
</script>
|
||||
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||
<script src="https://unpkg.com/tippy.js@6"></script>
|
||||
<script src="{{ breadcrumbs }}/js/lunr.min.js?v={{ aiken_version }}"></script>
|
||||
<script src="{{ breadcrumbs }}/js/index.js?v={{ timestamp }}"></script>
|
||||
<!-- Load the search index using JSONP to avoid CORS issues -->
|
||||
<script src="{{ breadcrumbs }}/search-data.js?v={{ timestamp }}"></script>
|
||||
<script>
|
||||
void function() {
|
||||
if (typeof tippy !== "undefined") {
|
||||
const overflowed = Array.from(document
|
||||
.querySelectorAll('.sidebar li:not([data-indent])'))
|
||||
.filter(x => x.offsetWidth < x.scrollWidth);
|
||||
|
||||
tippy(overflowed, {
|
||||
arrow: true,
|
||||
placement: 'right',
|
||||
content: (el) => el.children[0]?.innerText,
|
||||
});
|
||||
|
||||
tippy('.sidebar li[data-indent] a', {
|
||||
arrow: true,
|
||||
placement: 'right',
|
||||
content: (el) => el
|
||||
.getAttribute('href')
|
||||
.replaceAll(/\.?\.\//g, '')
|
||||
.replace('.html', ''),
|
||||
});
|
||||
}
|
||||
}();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
--search-width: 680px;
|
||||
--header-height: 60px;
|
||||
--hash-offset: calc(var(--header-height) * 1.67);
|
||||
--sidebar-width: 240px;
|
||||
--sidebar-width: 260px;
|
||||
--gap: 24px;
|
||||
--small-gap: calc(var(--gap) / 2);
|
||||
--tiny-gap: calc(var(--small-gap) / 2);
|
||||
|
@ -60,6 +60,15 @@ html {
|
|||
scroll-padding-top: var(--hash-offset);
|
||||
}
|
||||
|
||||
.tippy-box {
|
||||
background-color: var(--color-text);
|
||||
color: var(--color-background);
|
||||
}
|
||||
|
||||
.tippy-arrow {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited {
|
||||
color: var(--color-link);
|
||||
|
@ -303,10 +312,7 @@ p code {
|
|||
font-size: 0.95rem;
|
||||
max-height: calc(100vh - var(--header-height));
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding-top: var(--gap);
|
||||
padding-bottom: var(--gap);
|
||||
padding-left: var(--gap);
|
||||
padding: var(--gap) var(--small-gap);
|
||||
position: fixed;
|
||||
top: var(--header-height);
|
||||
transition: transform 0.5s ease;
|
||||
|
@ -315,6 +321,8 @@ p code {
|
|||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.sidebar h2 {
|
||||
|
@ -340,17 +348,9 @@ p code {
|
|||
.sidebar li {
|
||||
line-height: 1.2;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.sidebar ul li a > strong {
|
||||
font-weight: 900;
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.sidebar ul li a > strong::before {
|
||||
font-size: 0.75em;
|
||||
content: 'ᐅ ';
|
||||
padding-right: 0.1rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar .sidebar-toggle {
|
||||
|
@ -1059,3 +1059,57 @@ body.theme-dark {
|
|||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar li[data-indent] {
|
||||
margin-bottom: unset;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
color: var(--color-background-accent);
|
||||
}
|
||||
|
||||
.sidebar li[data-indent] span,
|
||||
.sidebar li[data-indent] a {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
left: -0.5rem;
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.sidebar li[data-indent] a:hover,
|
||||
.sidebar li[data-current] a,
|
||||
.sidebar li[data-current] a:visited {
|
||||
padding-right: 0.25rem;
|
||||
text-decoration: none;
|
||||
color: var(--color-text-accent);
|
||||
background:
|
||||
linear-gradient(
|
||||
115deg,
|
||||
transparent 0.5rem,
|
||||
var(--color-background-accent) 0.5rem
|
||||
);
|
||||
}
|
||||
|
||||
.sidebar li[data-indent="0"] {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.sidebar li[data-indent="0"]:not(first-child) {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar li[data-indent]::before {
|
||||
content: '/';
|
||||
font-size: 1.05em;
|
||||
font-family: monospace;
|
||||
padding-left: 0.25rem;
|
||||
letter-spacing: -0.1rem;
|
||||
padding-left: 4rem;
|
||||
display: flex;
|
||||
color: var(--color-background-accent);
|
||||
}
|
||||
|
||||
.sidebar li[data-indent="0"]::before { display: none; }
|
||||
.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; }
|
||||
|
|
Loading…
Reference in New Issue