diff --git a/forc-plugins/forc-doc/src/descriptor.rs b/forc-plugins/forc-doc/src/descriptor.rs index 967e75e4099..102fdaf42a1 100644 --- a/forc-plugins/forc-doc/src/descriptor.rs +++ b/forc-plugins/forc-doc/src/descriptor.rs @@ -1,7 +1,7 @@ //! Determine whether a [Declaration] is documentable. use crate::{ doc::{Document, ModuleInfo}, - render::{attrsmap_to_html_str, trim_fn_body, ContextType, ItemBody, ItemContext, ItemHeader}, + render::{trim_fn_body, ContextType, DocStrings, ItemBody, ItemContext, ItemHeader}, }; use anyhow::Result; use sway_core::{ @@ -50,8 +50,13 @@ impl Descriptor { Ok(Descriptor::NonDocumentable) } else { let item_name = struct_decl.name; - let attrs_opt = (!struct_decl.attributes.is_empty()) - .then(|| attrsmap_to_html_str(struct_decl.attributes)); + let (attrs_opt, raw_attributes) = match struct_decl.attributes.is_empty() { + true => (None, None), + false => ( + Some(struct_decl.attributes.to_html_string()), + Some(struct_decl.attributes.to_raw_string()), + ), + }; let context = (!struct_decl.fields.is_empty()) .then_some(ContextType::StructFields(struct_decl.fields)); @@ -72,6 +77,7 @@ impl Descriptor { attrs_opt, item_context: ItemContext { context }, }, + raw_attributes, })) } } @@ -81,8 +87,13 @@ impl Descriptor { Ok(Descriptor::NonDocumentable) } else { let item_name = enum_decl.name; - let attrs_opt = (!enum_decl.attributes.is_empty()) - .then(|| attrsmap_to_html_str(enum_decl.attributes)); + let (attrs_opt, raw_attributes) = match enum_decl.attributes.is_empty() { + true => (None, None), + false => ( + Some(enum_decl.attributes.to_html_string()), + Some(enum_decl.attributes.to_raw_string()), + ), + }; let context = (!enum_decl.variants.is_empty()) .then_some(ContextType::EnumVariants(enum_decl.variants)); @@ -103,6 +114,7 @@ impl Descriptor { attrs_opt, item_context: ItemContext { context }, }, + raw_attributes, })) } } @@ -112,8 +124,13 @@ impl Descriptor { Ok(Descriptor::NonDocumentable) } else { let item_name = trait_decl.name; - let attrs_opt = (!trait_decl.attributes.is_empty()) - .then(|| attrsmap_to_html_str(trait_decl.attributes)); + let (attrs_opt, raw_attributes) = match trait_decl.attributes.is_empty() { + true => (None, None), + false => ( + Some(trait_decl.attributes.to_html_string()), + Some(trait_decl.attributes.to_raw_string()), + ), + }; let context = (!trait_decl.interface_surface.is_empty()).then_some( ContextType::RequiredMethods( trait_decl.interface_surface.to_methods(decl_engine), @@ -137,14 +154,20 @@ impl Descriptor { attrs_opt, item_context: ItemContext { context }, }, + raw_attributes, })) } } AbiDeclaration(ref decl_id) => { let abi_decl = decl_engine.get_abi(decl_id.clone(), &decl_id.span())?; let item_name = abi_decl.name; - let attrs_opt = (!abi_decl.attributes.is_empty()) - .then(|| attrsmap_to_html_str(abi_decl.attributes)); + let (attrs_opt, raw_attributes) = match abi_decl.attributes.is_empty() { + true => (None, None), + false => ( + Some(abi_decl.attributes.to_html_string()), + Some(abi_decl.attributes.to_raw_string()), + ), + }; let context = (!abi_decl.interface_surface.is_empty()).then_some( ContextType::RequiredMethods( abi_decl.interface_surface.to_methods(decl_engine), @@ -166,6 +189,7 @@ impl Descriptor { attrs_opt, item_context: ItemContext { context }, }, + raw_attributes, })) } StorageDeclaration(ref decl_id) => { @@ -173,8 +197,13 @@ impl Descriptor { let item_name = sway_types::BaseIdent::new_no_trim( sway_types::span::Span::from_string(CONTRACT_STORAGE.to_string()), ); - let attrs_opt = (!storage_decl.attributes.is_empty()) - .then(|| attrsmap_to_html_str(storage_decl.attributes)); + let (attrs_opt, raw_attributes) = match storage_decl.attributes.is_empty() { + true => (None, None), + false => ( + Some(storage_decl.attributes.to_html_string()), + Some(storage_decl.attributes.to_raw_string()), + ), + }; let context = (!storage_decl.fields.is_empty()) .then_some(ContextType::StorageFields(storage_decl.fields)); @@ -195,6 +224,7 @@ impl Descriptor { attrs_opt, item_context: ItemContext { context }, }, + raw_attributes, })) } ImplTrait(ref decl_id) => { @@ -221,6 +251,7 @@ impl Descriptor { attrs_opt: None, // no attributes field item_context: ItemContext { context: None }, }, + raw_attributes: None, })) } FunctionDeclaration(ref decl_id) => { @@ -229,8 +260,13 @@ impl Descriptor { Ok(Descriptor::NonDocumentable) } else { let item_name = fn_decl.name; - let attrs_opt = (!fn_decl.attributes.is_empty()) - .then(|| attrsmap_to_html_str(fn_decl.attributes)); + let (attrs_opt, raw_attributes) = match fn_decl.attributes.is_empty() { + true => (None, None), + false => ( + Some(fn_decl.attributes.to_html_string()), + Some(fn_decl.attributes.to_raw_string()), + ), + }; Ok(Descriptor::Documentable(Document { module_info: module_info.clone(), @@ -249,6 +285,7 @@ impl Descriptor { attrs_opt, item_context: ItemContext { context: None }, }, + raw_attributes, })) } } @@ -258,8 +295,13 @@ impl Descriptor { Ok(Descriptor::NonDocumentable) } else { let item_name = const_decl.name; - let attrs_opt = (!const_decl.attributes.is_empty()) - .then(|| attrsmap_to_html_str(const_decl.attributes)); + let (attrs_opt, raw_attributes) = match const_decl.attributes.is_empty() { + true => (None, None), + false => ( + Some(const_decl.attributes.to_html_string()), + Some(const_decl.attributes.to_raw_string()), + ), + }; Ok(Descriptor::Documentable(Document { module_info: module_info.clone(), @@ -278,6 +320,7 @@ impl Descriptor { attrs_opt, item_context: ItemContext { context: None }, }, + raw_attributes, })) } } diff --git a/forc-plugins/forc-doc/src/doc.rs b/forc-plugins/forc-doc/src/doc.rs index 320927748bf..eea949a06cd 100644 --- a/forc-plugins/forc-doc/src/doc.rs +++ b/forc-plugins/forc-doc/src/doc.rs @@ -18,6 +18,7 @@ pub(crate) struct Document { pub(crate) module_info: ModuleInfo, pub(crate) item_header: ItemHeader, pub(crate) item_body: ItemBody, + pub(crate) raw_attributes: Option, } impl Document { /// Creates an HTML file name from the [Document]. @@ -44,6 +45,20 @@ impl Document { name: self.item_header.item_name.as_str().to_owned(), module_info: self.module_info.clone(), html_filename: self.html_filename(), + preview_opt: self.preview_opt(), + } + } + fn preview_opt(&self) -> Option { + match &self.raw_attributes { + Some(description) => { + if description.len() > 200 { + let (first, _) = description.split_at(75); + Some(format!("{}...", first.trim_end())) + } else { + Some(description.to_string()) + } + } + None => None, } } /// Gather [Documentation] from the [TyProgram]. diff --git a/forc-plugins/forc-doc/src/render.rs b/forc-plugins/forc-doc/src/render.rs index efbed9db765..abe2bf14741 100644 --- a/forc-plugins/forc-doc/src/render.rs +++ b/forc-plugins/forc-doc/src/render.rs @@ -133,6 +133,7 @@ impl RenderedDocumentation { name: location.clone(), module_info: doc.module_info.to_owned(), html_filename: INDEX_FILENAME.to_owned(), + preview_opt: None, }; match module_map.get_mut(parent_module) { Some(doc_links) => match doc_links.get_mut(&BlockTitle::Modules) { @@ -476,6 +477,7 @@ impl ItemContext { IDENTITY, field.name.as_str() ), + preview_opt: None, }) .collect(); links.insert(BlockTitle::Fields, doc_links); @@ -491,6 +493,7 @@ impl ItemContext { IDENTITY, field.name.as_str() ), + preview_opt: None, }) .collect(); links.insert(BlockTitle::Fields, doc_links); @@ -502,6 +505,7 @@ impl ItemContext { name: variant.name.as_str().to_string(), module_info: ModuleInfo::from_vec(vec![]), html_filename: format!("{}variant.{}", IDENTITY, variant.name.as_str()), + preview_opt: None, }) .collect(); links.insert(BlockTitle::Variants, doc_links); @@ -517,6 +521,7 @@ impl ItemContext { IDENTITY, method.name.as_str() ), + preview_opt: None, }) .collect(); links.insert(BlockTitle::RequiredMethods, doc_links); @@ -572,7 +577,7 @@ impl Renderable for TyStructField { } @ if !self.attributes.is_empty() { div(class="docblock") { - : Raw(attrsmap_to_html_str(self.attributes)); + : Raw(self.attributes.to_html_string()); } } } @@ -592,7 +597,7 @@ impl Renderable for TyStorageField { } @ if !self.attributes.is_empty() { div(class="docblock") { - : Raw(attrsmap_to_html_str(self.attributes)); + : Raw(self.attributes.to_html_string()); } } } @@ -611,7 +616,7 @@ impl Renderable for TyEnumVariant { } @ if !self.attributes.is_empty() { div(class="docblock") { - : Raw(attrsmap_to_html_str(self.attributes)); + : Raw(self.attributes.to_html_string()); } } } @@ -717,6 +722,7 @@ pub(crate) struct DocLink { pub(crate) name: String, pub(crate) module_info: ModuleInfo, pub(crate) html_filename: String, + pub(crate) preview_opt: Option, } #[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] struct DocLinks { @@ -731,14 +737,21 @@ impl Renderable for DocLinks { @ for (title, list_items) in self.links { @ if !list_items.is_empty() { h3(id=format!("{}", title.html_title_string())) { : title.as_str(); } - ul(class=format!("{} docblock", title.html_title_string())) { + div(class="item-table") { @ for item in list_items { - li { - a(href=item.module_info.to_file_path_string(&item.html_filename, item.module_info.project_name())) { - : item.module_info.to_path_literal_string( - &item.name, - item.module_info.project_name() - ); + div(class="item-row") { + div(class=format!("item-left {}-item", title.item_title_str())) { + a(href=item.module_info.to_file_path_string(&item.html_filename, item.module_info.project_name())) { + : item.module_info.to_path_literal_string( + &item.name, + item.module_info.project_name() + ); + } + } + @ if item.preview_opt.is_some() { + div(class="item-right docblock-short") { + : item.preview_opt.unwrap(); + } } } } @@ -752,17 +765,24 @@ impl Renderable for DocLinks { @ for (title, list_items) in self.links { @ if !list_items.is_empty() { h3(id=format!("{}", title.html_title_string())) { : title.as_str(); } - ul(class=format!("{} docblock", title.html_title_string())) { + div(class="item-table") { @ for item in list_items { - li { - a(href=item.module_info.to_file_path_string(&item.html_filename, item.module_info.project_name())) { - @ if title == BlockTitle::Modules { - : item.name; - } else { - : item.module_info.to_path_literal_string( - &item.name, - item.module_info.project_name() - ); + div(class="item-row") { + div(class=format!("item-left {}-item", title.item_title_str())) { + a(href=item.module_info.to_file_path_string(&item.html_filename, item.module_info.project_name())) { + @ if title == BlockTitle::Modules { + : item.name; + } else { + : item.module_info.to_path_literal_string( + &item.name, + item.module_info.project_name() + ); + } + } + } + @ if item.preview_opt.is_some() { + div(class="item-right docblock-short") { + : item.preview_opt.unwrap(); } } } @@ -777,14 +797,21 @@ impl Renderable for DocLinks { @ for (title, list_items) in self.links { @ if !list_items.is_empty() { h3(id=format!("{}", title.html_title_string())) { : title.as_str(); } - ul(class=format!("{} docblock", title.html_title_string())) { + div(class="item-table") { @ for item in list_items { - li { - a(href=item.module_info.to_file_path_string(&item.html_filename, item.module_info.location())) { - : item.module_info.to_path_literal_string( - &item.name, - item.module_info.location() - ); + div(class="item-row") { + div(class=format!("item-left {}-item", title.item_title_str())) { + a(href=item.module_info.to_file_path_string(&item.html_filename, item.module_info.location())) { + : item.module_info.to_path_literal_string( + &item.name, + item.module_info.location() + ); + } + } + @ if item.preview_opt.is_some() { + div(class="item-right docblock-short") { + : item.preview_opt.unwrap(); + } } } } @@ -1163,29 +1190,39 @@ impl Renderable for Sidebar { } } } +pub(crate) trait DocStrings { + fn to_html_string(&self) -> String; + fn to_raw_string(&self) -> String; +} /// Creates an HTML String from an [AttributesMap] -pub(crate) fn attrsmap_to_html_str(attributes: AttributesMap) -> String { - let attributes = attributes.get(&AttributeKind::DocComment); - let mut docs = String::new(); +impl DocStrings for AttributesMap { + fn to_html_string(&self) -> String { + let docs = self.to_raw_string(); - if let Some(vec_attrs) = attributes { - for ident in vec_attrs.iter().flat_map(|attribute| &attribute.args) { - writeln!(docs, "{}", ident.as_str()) - .expect("problem appending `ident.as_str()` to `docs` with `writeln` macro."); - } + let mut options = ComrakOptions::default(); + options.render.hardbreaks = true; + options.render.github_pre_lang = true; + options.extension.strikethrough = true; + options.extension.table = true; + options.extension.autolink = true; + options.extension.superscript = true; + options.extension.footnotes = true; + options.parse.smart = true; + options.parse.default_info_string = Some("sway".into()); + markdown_to_html(&format_docs(&docs), &options) } + fn to_raw_string(&self) -> String { + let attributes = self.get(&AttributeKind::DocComment); + let mut docs = String::new(); - let mut options = ComrakOptions::default(); - options.render.hardbreaks = true; - options.render.github_pre_lang = true; - options.extension.strikethrough = true; - options.extension.table = true; - options.extension.autolink = true; - options.extension.superscript = true; - options.extension.footnotes = true; - options.parse.smart = true; - options.parse.default_info_string = Some("sway".into()); - markdown_to_html(&format_docs(&docs), &options) + if let Some(vec_attrs) = attributes { + for ident in vec_attrs.iter().flat_map(|attribute| &attribute.args) { + writeln!(docs, "{}", ident.as_str()) + .expect("problem appending `ident.as_str()` to `docs` with `writeln` macro."); + } + } + docs + } } /// Takes a formatted String fn and returns only the function signature. pub(crate) fn trim_fn_body(f: String) -> String {