Skip to content

Commit

Permalink
feat: Adds Markdown formatting to item previews in forc doc (FuelLa…
Browse files Browse the repository at this point in the history
…bs#3869)

Closes FuelLabs#3868 

Adds markdown support and a few layers of logic to figure out where a
preview should end.
![Screen Shot 2023-01-23 at 5 18 57
PM](https://user-images.githubusercontent.com/57543709/214174091-74ea5fae-9ea4-435d-b4e5-16ccf5ce2ecd.png)

Co-authored-by: Joshua Batty <[email protected]>
  • Loading branch information
eureka-cpu and JoshuaBatty authored Jan 25, 2023
1 parent 1e384ca commit 0419c19
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 76 deletions.
91 changes: 28 additions & 63 deletions forc-plugins/forc-doc/src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,8 @@ impl Descriptor {
Ok(Descriptor::NonDocumentable)
} else {
let item_name = struct_decl.name;
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 attrs_opt = (!struct_decl.attributes.is_empty())
.then(|| struct_decl.attributes.to_html_string());
let context = (!struct_decl.fields.is_empty())
.then_some(ContextType::StructFields(struct_decl.fields));

Expand All @@ -74,10 +69,10 @@ impl Descriptor {
code_str: parse::parse_format::<sway_ast::ItemStruct>(
struct_decl.span.as_str(),
),
attrs_opt,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext { context },
},
raw_attributes,
raw_attributes: attrs_opt,
}))
}
}
Expand All @@ -87,13 +82,8 @@ impl Descriptor {
Ok(Descriptor::NonDocumentable)
} else {
let item_name = enum_decl.name;
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 attrs_opt = (!enum_decl.attributes.is_empty())
.then(|| enum_decl.attributes.to_html_string());
let context = (!enum_decl.variants.is_empty())
.then_some(ContextType::EnumVariants(enum_decl.variants));

Expand All @@ -111,10 +101,10 @@ impl Descriptor {
code_str: parse::parse_format::<sway_ast::ItemEnum>(
enum_decl.span.as_str(),
),
attrs_opt,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext { context },
},
raw_attributes,
raw_attributes: attrs_opt,
}))
}
}
Expand All @@ -124,13 +114,8 @@ impl Descriptor {
Ok(Descriptor::NonDocumentable)
} else {
let item_name = trait_decl.name;
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 attrs_opt = (!trait_decl.attributes.is_empty())
.then(|| trait_decl.attributes.to_html_string());
let context = (!trait_decl.interface_surface.is_empty()).then_some(
ContextType::RequiredMethods(
trait_decl.interface_surface.to_methods(decl_engine),
Expand All @@ -151,23 +136,18 @@ impl Descriptor {
code_str: parse::parse_format::<sway_ast::ItemTrait>(
trait_decl.span.as_str(),
),
attrs_opt,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext { context },
},
raw_attributes,
raw_attributes: attrs_opt,
}))
}
}
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, 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 attrs_opt =
(!abi_decl.attributes.is_empty()).then(|| abi_decl.attributes.to_html_string());
let context = (!abi_decl.interface_surface.is_empty()).then_some(
ContextType::RequiredMethods(
abi_decl.interface_surface.to_methods(decl_engine),
Expand All @@ -186,24 +166,19 @@ impl Descriptor {
ty_decl: ty_decl.clone(),
item_name,
code_str: parse::parse_format::<sway_ast::ItemAbi>(abi_decl.span.as_str()),
attrs_opt,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext { context },
},
raw_attributes,
raw_attributes: attrs_opt,
}))
}
StorageDeclaration(ref decl_id) => {
let storage_decl = decl_engine.get_storage(decl_id.clone(), &decl_id.span())?;
let item_name = sway_types::BaseIdent::new_no_trim(
sway_types::span::Span::from_string(CONTRACT_STORAGE.to_string()),
);
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 attrs_opt = (!storage_decl.attributes.is_empty())
.then(|| storage_decl.attributes.to_html_string());
let context = (!storage_decl.fields.is_empty())
.then_some(ContextType::StorageFields(storage_decl.fields));

Expand All @@ -221,10 +196,10 @@ impl Descriptor {
code_str: parse::parse_format::<sway_ast::ItemStorage>(
storage_decl.span.as_str(),
),
attrs_opt,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext { context },
},
raw_attributes,
raw_attributes: attrs_opt,
}))
}
ImplTrait(ref decl_id) => {
Expand Down Expand Up @@ -260,13 +235,8 @@ impl Descriptor {
Ok(Descriptor::NonDocumentable)
} else {
let item_name = fn_decl.name;
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()),
),
};
let attrs_opt = (!fn_decl.attributes.is_empty())
.then(|| fn_decl.attributes.to_html_string());

Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
Expand All @@ -282,10 +252,10 @@ impl Descriptor {
code_str: trim_fn_body(parse::parse_format::<sway_ast::ItemFn>(
fn_decl.span.as_str(),
)),
attrs_opt,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext { context: None },
},
raw_attributes,
raw_attributes: attrs_opt,
}))
}
}
Expand All @@ -295,13 +265,8 @@ impl Descriptor {
Ok(Descriptor::NonDocumentable)
} else {
let item_name = const_decl.name;
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()),
),
};
let attrs_opt = (!const_decl.attributes.is_empty())
.then(|| const_decl.attributes.to_html_string());

Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
Expand All @@ -317,10 +282,10 @@ impl Descriptor {
code_str: parse::parse_format::<sway_ast::ItemConst>(
const_decl.span.as_str(),
),
attrs_opt,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext { context: None },
},
raw_attributes,
raw_attributes: attrs_opt,
}))
}
}
Expand Down
38 changes: 28 additions & 10 deletions forc-plugins/forc-doc/src/doc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
descriptor::Descriptor,
render::{DocLink, ItemBody, ItemHeader, Renderable},
render::{split_at_markdown_header, DocLink, ItemBody, ItemHeader, Renderable},
};
use anyhow::Result;
use horrorshow::{box_html, RenderBox};
Expand Down Expand Up @@ -49,17 +49,35 @@ impl Document {
}
}
fn preview_opt(&self) -> Option<String> {
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())
const MAX_PREVIEW_CHARS: usize = 100;
const CLOSING_PARAGRAPH_TAG: &str = "</p>";

self.raw_attributes.as_ref().map(|description| {
let preview = split_at_markdown_header(description);
if preview.chars().count() > MAX_PREVIEW_CHARS
&& preview.contains(CLOSING_PARAGRAPH_TAG)
{
match preview.find(CLOSING_PARAGRAPH_TAG) {
Some(index) => {
// We add 1 here to get the index of the char after the closing tag.
// This ensures we retain the closing tag and don't break the html.
let (preview, _) =
preview.split_at(index + CLOSING_PARAGRAPH_TAG.len() + 1);
if preview.chars().count() > MAX_PREVIEW_CHARS && preview.contains('\n') {
match preview.find('\n') {
Some(index) => preview.split_at(index).0.to_string(),
None => unreachable!("Previous logic prevents this panic"),
}
} else {
preview.to_string()
}
}
None => unreachable!("Previous logic prevents this panic"),
}
} else {
preview.to_string()
}
None => None,
}
})
}
/// Gather [Documentation] from the [TyProgram].
pub(crate) fn from_ty_program(
Expand Down
34 changes: 31 additions & 3 deletions forc-plugins/forc-doc/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@ impl Renderable for DocLinks {
}
@ if item.preview_opt.is_some() {
div(class="item-right docblock-short") {
: item.preview_opt.unwrap();
: Raw(item.preview_opt.unwrap());
}
}
}
Expand Down Expand Up @@ -782,7 +782,7 @@ impl Renderable for DocLinks {
}
@ if item.preview_opt.is_some() {
div(class="item-right docblock-short") {
: item.preview_opt.unwrap();
: Raw(item.preview_opt.unwrap());
}
}
}
Expand Down Expand Up @@ -810,7 +810,7 @@ impl Renderable for DocLinks {
}
@ if item.preview_opt.is_some() {
div(class="item-right docblock-short") {
: item.preview_opt.unwrap();
: Raw(item.preview_opt.unwrap());
}
}
}
Expand Down Expand Up @@ -1231,3 +1231,31 @@ pub(crate) fn trim_fn_body(f: String) -> String {
None => f,
}
}

/// Checks if some raw html (rendered from markdown) contains a header.
/// If it does, it splits at the header and returns the slice that preceeded it.
pub(crate) fn split_at_markdown_header(raw_html: &str) -> &str {
const H1: &str = "<h1>";
const H2: &str = "<h2>";
const H3: &str = "<h3>";
const H4: &str = "<h4>";
const H5: &str = "<h5>";
if raw_html.contains(H1) {
let v: Vec<_> = raw_html.split(H1).collect();
v.first().expect("expected a non-empty str")
} else if raw_html.contains(H2) {
let v: Vec<_> = raw_html.split(H2).collect();
v.first().expect("expected a non-empty str")
} else if raw_html.contains(H3) {
let v: Vec<_> = raw_html.split(H3).collect();
v.first().expect("expected a non-empty str")
} else if raw_html.contains(H4) {
let v: Vec<_> = raw_html.split(H4).collect();
v.first().expect("expected a non-empty str")
} else if raw_html.contains(H5) {
let v: Vec<_> = raw_html.split(H5).collect();
v.first().expect("expected a non-empty str")
} else {
raw_html
}
}

0 comments on commit 0419c19

Please sign in to comment.