diff --git a/extensions/scarb-doc/src/docs_generation.rs b/extensions/scarb-doc/src/docs_generation.rs index 577affa97..5c54a32cb 100644 --- a/extensions/scarb-doc/src/docs_generation.rs +++ b/extensions/scarb-doc/src/docs_generation.rs @@ -1 +1,156 @@ -mod markdown; +#![allow(dead_code)] +use crate::types::{ + Constant, Crate, Enum, ExternFunction, ExternType, FreeFunction, Impl, ImplAlias, ImplConstant, + ImplFunction, ImplType, Member, Module, Struct, Trait, TraitConstant, TraitFunction, TraitType, + TypeAlias, Variant, +}; + +pub mod markdown; + +#[derive(Default)] +struct TopLevelItems<'a> { + pub modules: Vec<&'a Module>, + pub constants: Vec<&'a Constant>, + pub free_functions: Vec<&'a FreeFunction>, + pub structs: Vec<&'a Struct>, + pub enums: Vec<&'a Enum>, + pub type_aliases: Vec<&'a TypeAlias>, + pub impl_aliases: Vec<&'a ImplAlias>, + pub traits: Vec<&'a Trait>, + pub impls: Vec<&'a Impl>, + pub extern_types: Vec<&'a ExternType>, + pub extern_functions: Vec<&'a ExternFunction>, +} + +fn collect_all_top_level_items(crate_: &Crate) -> TopLevelItems { + let mut top_level_items = TopLevelItems::default(); + + top_level_items.modules.push(&crate_.root_module); + + collect_all_top_level_items_internal(&mut top_level_items, &crate_.root_module); + top_level_items +} + +fn collect_all_top_level_items_internal<'a, 'b>( + top_level_items: &'a mut TopLevelItems<'b>, + module: &'b Module, +) where + 'b: 'a, +{ + let Module { + module_id: _module_id, + item_data: _item_data, + submodules, + constants, + free_functions, + structs, + enums, + type_aliases, + impl_aliases, + traits, + impls, + extern_types, + extern_functions, + } = &module; + + top_level_items.modules.extend(submodules); + top_level_items.constants.extend(constants); + top_level_items.free_functions.extend(free_functions); + top_level_items.structs.extend(structs); + top_level_items.enums.extend(enums); + top_level_items.type_aliases.extend(type_aliases); + top_level_items.impl_aliases.extend(impl_aliases); + top_level_items.traits.extend(traits); + top_level_items.impls.extend(impls); + top_level_items.extern_types.extend(extern_types); + top_level_items.extern_functions.extend(extern_functions); + + for module in submodules { + collect_all_top_level_items_internal(top_level_items, module); + } +} + +// Trait for items with no descendants. +// Used to enforce constraints on generic implementations of traits like `MarkdownDocItem`. +trait PrimitiveDocItem: DocItem {} + +impl PrimitiveDocItem for Constant {} +impl PrimitiveDocItem for ExternFunction {} +impl PrimitiveDocItem for ExternType {} +impl PrimitiveDocItem for FreeFunction {} +impl PrimitiveDocItem for ImplAlias {} +impl PrimitiveDocItem for ImplConstant {} +impl PrimitiveDocItem for ImplFunction {} +impl PrimitiveDocItem for ImplType {} +impl PrimitiveDocItem for Member {} +impl PrimitiveDocItem for TraitConstant {} +impl PrimitiveDocItem for TraitFunction {} +impl PrimitiveDocItem for TraitType {} +impl PrimitiveDocItem for TypeAlias {} +impl PrimitiveDocItem for Variant {} + +// Trait for items that have their own documentation page. +// Used to enforce constraints on generic implementations of traits like `TopLevelMarkdownDocItem`. +trait TopLevelDocItem: DocItem {} + +impl TopLevelDocItem for Constant {} +impl TopLevelDocItem for Enum {} +impl TopLevelDocItem for ExternFunction {} +impl TopLevelDocItem for ExternType {} +impl TopLevelDocItem for FreeFunction {} +impl TopLevelDocItem for Impl {} +impl TopLevelDocItem for ImplAlias {} +impl TopLevelDocItem for Module {} +impl TopLevelDocItem for Struct {} +impl TopLevelDocItem for Trait {} +impl TopLevelDocItem for TypeAlias {} + +// Wrapper trait over a documentable item to hide implementation details of the item type. +trait DocItem { + fn name(&self) -> &str; + fn doc(&self) -> &Option; + fn signature(&self) -> &Option; + fn full_path(&self) -> &str; +} + +macro_rules! impl_doc_item { + ($t:ty) => { + impl DocItem for $t { + fn name(&self) -> &str { + &self.item_data.name + } + + fn doc(&self) -> &Option { + &self.item_data.doc + } + + fn signature(&self) -> &Option { + &self.item_data.signature + } + + fn full_path(&self) -> &str { + &self.item_data.full_path + } + } + }; +} + +impl_doc_item!(Constant); +impl_doc_item!(Enum); +impl_doc_item!(ExternFunction); +impl_doc_item!(ExternType); +impl_doc_item!(FreeFunction); +impl_doc_item!(Impl); +impl_doc_item!(ImplAlias); +impl_doc_item!(ImplConstant); +impl_doc_item!(ImplFunction); +impl_doc_item!(ImplType); +impl_doc_item!(Member); +impl_doc_item!(Module); +impl_doc_item!(Struct); +impl_doc_item!(Trait); +impl_doc_item!(TraitConstant); +impl_doc_item!(TraitType); +impl_doc_item!(TraitFunction); +impl_doc_item!(TypeAlias); +impl_doc_item!(Variant); diff --git a/extensions/scarb-doc/src/docs_generation/markdown.rs b/extensions/scarb-doc/src/docs_generation/markdown.rs index a4e89b61d..57c0f65fd 100644 --- a/extensions/scarb-doc/src/docs_generation/markdown.rs +++ b/extensions/scarb-doc/src/docs_generation/markdown.rs @@ -1 +1,2 @@ mod book_toml; +mod traits; diff --git a/extensions/scarb-doc/src/docs_generation/markdown/traits.rs b/extensions/scarb-doc/src/docs_generation/markdown/traits.rs new file mode 100644 index 000000000..43eb80a0a --- /dev/null +++ b/extensions/scarb-doc/src/docs_generation/markdown/traits.rs @@ -0,0 +1,217 @@ +use itertools::Itertools; +use std::fmt::Write; + +use crate::docs_generation::{DocItem, PrimitiveDocItem, TopLevelDocItem}; +use crate::types::{Enum, Impl, Module, Struct, Trait}; + +pub trait TopLevelMarkdownDocItem: MarkdownDocItem + TopLevelDocItem { + fn filename(&self) -> String { + format!("{}.md", self.full_path().replace("::", "-")) + } + + fn md_ref(&self) -> String { + format!("[{}](./{})", self.name(), self.filename()) + } + + fn generate_markdown_list_item(&self) -> String { + format!("- {}\n", self.md_ref()) + } +} + +impl TopLevelMarkdownDocItem for T where T: MarkdownDocItem + TopLevelDocItem {} + +pub trait MarkdownDocItem: DocItem { + fn generate_markdown(&self, header_level: usize) -> String; +} + +impl MarkdownDocItem for T +where + T: PrimitiveDocItem, +{ + fn generate_markdown(&self, header_level: usize) -> String { + generate_markdown_from_item_data(self, header_level) + } +} + +impl MarkdownDocItem for Enum { + fn generate_markdown(&self, header_level: usize) -> String { + let mut markdown = generate_markdown_from_item_data(self, header_level); + + markdown += &generate_markdown_for_subitems(&self.variants, "Variants", header_level); + + markdown + } +} + +impl MarkdownDocItem for Impl { + fn generate_markdown(&self, header_level: usize) -> String { + let mut markdown = generate_markdown_from_item_data(self, header_level); + + markdown += + &generate_markdown_for_subitems(&self.impl_constants, "Impl Constants", header_level); + markdown += + &generate_markdown_for_subitems(&self.impl_functions, "Impl Functions", header_level); + markdown += &generate_markdown_for_subitems(&self.impl_types, "Impl Types", header_level); + + markdown + } +} + +impl MarkdownDocItem for Module { + fn generate_markdown(&self, header_level: usize) -> String { + let mut markdown = generate_markdown_from_item_data(self, header_level); + + markdown += &generate_markdown_list_for_top_level_subitems( + &self.submodules.iter().collect_vec(), + "Submodules", + header_level + 1, + ); + markdown += &generate_markdown_list_for_top_level_subitems( + &self.constants.iter().collect_vec(), + "Constants", + header_level + 1, + ); + markdown += &generate_markdown_list_for_top_level_subitems( + &self.free_functions.iter().collect_vec(), + "Free functions", + header_level + 1, + ); + markdown += &generate_markdown_list_for_top_level_subitems( + &self.structs.iter().collect_vec(), + "Structs", + header_level + 1, + ); + markdown += &generate_markdown_list_for_top_level_subitems( + &self.enums.iter().collect_vec(), + "Enums", + header_level + 1, + ); + markdown += &generate_markdown_list_for_top_level_subitems( + &self.type_aliases.iter().collect_vec(), + "Type aliases", + header_level + 1, + ); + markdown += &generate_markdown_list_for_top_level_subitems( + &self.impl_aliases.iter().collect_vec(), + "Impl aliases", + header_level + 1, + ); + markdown += &generate_markdown_list_for_top_level_subitems( + &self.traits.iter().collect_vec(), + "Traits", + header_level + 1, + ); + markdown += &generate_markdown_list_for_top_level_subitems( + &self.impls.iter().collect_vec(), + "Impls", + header_level + 1, + ); + markdown += &generate_markdown_list_for_top_level_subitems( + &self.extern_types.iter().collect_vec(), + "Extern types", + header_level + 1, + ); + markdown += &generate_markdown_list_for_top_level_subitems( + &self.extern_functions.iter().collect_vec(), + "Extern functions", + header_level + 1, + ); + + markdown + } +} + +impl MarkdownDocItem for Struct { + fn generate_markdown(&self, header_level: usize) -> String { + let mut markdown = generate_markdown_from_item_data(self, header_level); + + markdown += &generate_markdown_for_subitems(&self.members, "Members", header_level); + + markdown + } +} + +impl MarkdownDocItem for Trait { + fn generate_markdown(&self, header_level: usize) -> String { + let mut markdown = generate_markdown_from_item_data(self, header_level); + + markdown += + &generate_markdown_for_subitems(&self.trait_constants, "Trait Constants", header_level); + markdown += + &generate_markdown_for_subitems(&self.trait_functions, "Trait Functions", header_level); + markdown += &generate_markdown_for_subitems(&self.trait_types, "Trait Types", header_level); + + markdown + } +} + +pub fn generate_markdown_list_for_top_level_subitems( + subitems: &[&impl TopLevelMarkdownDocItem], + name: &str, + header_level: usize, +) -> String { + let mut markdown = String::new(); + + if !subitems.is_empty() { + let header = str::repeat("#", header_level); + + writeln!(&mut markdown, "{header} {name}\n").unwrap(); + for item in subitems { + writeln!(&mut markdown, "{}", item.generate_markdown_list_item()).unwrap(); + } + } + + markdown +} + +fn generate_markdown_for_subitems( + subitems: &[impl MarkdownDocItem + PrimitiveDocItem], + name: &str, + header_level: usize, +) -> String { + let mut markdown = String::new(); + + if !subitems.is_empty() { + let header = str::repeat("#", header_level + 1); + + writeln!(&mut markdown, "{header} {name}\n").unwrap(); + for item in subitems { + writeln!( + &mut markdown, + "{}", + item.generate_markdown(header_level + 2) + ) + .unwrap(); + } + } + + markdown +} + +fn generate_markdown_from_item_data(doc_item: &dyn DocItem, header_level: usize) -> String { + let mut markdown = String::new(); + + let header = str::repeat("#", header_level); + + writeln!(&mut markdown, "{header} {}\n", doc_item.name()).unwrap(); + + if let Some(doc) = doc_item.doc() { + writeln!(&mut markdown, "{doc}\n").unwrap(); + } + + writeln!( + &mut markdown, + "Fully qualified path: `{}`\n", + doc_item.full_path() + ) + .unwrap(); + + if let Some(sig) = &doc_item.signature() { + if !sig.is_empty() { + // TODO(#1457) add cairo support to mdbook + writeln!(&mut markdown, "```rust\n{sig}\n```\n").unwrap(); + } + } + + markdown +}