Skip to content

Commit

Permalink
Add traits for generating markdown docs (#1462)
Browse files Browse the repository at this point in the history
commit-id:9f0f272d

---

**Stack**:
- #1464
- #1463
- #1462⚠️ *Part of a stack created by [spr](https://github.com/ejoffe/spr). Do
not merge manually using the UI - doing so may have unexpected results.*
  • Loading branch information
piotmag769 authored Jul 18, 2024
1 parent ebccab9 commit e89c7bd
Show file tree
Hide file tree
Showing 3 changed files with 374 additions and 1 deletion.
157 changes: 156 additions & 1 deletion extensions/scarb-doc/src/docs_generation.rs
Original file line number Diff line number Diff line change
@@ -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<String>;
fn signature(&self) -> &Option<String>;
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<String> {
&self.item_data.doc
}

fn signature(&self) -> &Option<String> {
&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);
1 change: 1 addition & 0 deletions extensions/scarb-doc/src/docs_generation/markdown.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod book_toml;
mod traits;
217 changes: 217 additions & 0 deletions extensions/scarb-doc/src/docs_generation/markdown/traits.rs
Original file line number Diff line number Diff line change
@@ -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<T> TopLevelMarkdownDocItem for T where T: MarkdownDocItem + TopLevelDocItem {}

pub trait MarkdownDocItem: DocItem {
fn generate_markdown(&self, header_level: usize) -> String;
}

impl<T> 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
}

0 comments on commit e89c7bd

Please sign in to comment.