From c16d7f7d601f738b46b22ac3cbebff886347504d Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 17 Jul 2020 22:48:05 +0200 Subject: [PATCH] Add support for item members Previously, we only supported documentation for modules and top-level items. With this patch, we also display the documentation for members of an item, typically methods of a struct or trait. --- src/doc.rs | 39 +++++++++++++++++++++++---- src/main.rs | 3 ++- src/parser.rs | 75 +++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/doc.rs b/src/doc.rs index 444420a..1266315 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -14,6 +14,7 @@ pub struct Crate { #[derive(Clone, Debug, PartialEq)] pub struct Item { pub path: path::PathBuf, + pub member: Option, pub name: String, } @@ -31,8 +32,9 @@ impl Crate { pub fn find_item(&self, item: &[&str]) -> anyhow::Result> { let name = item.join("::"); + // TODO: add crate to name? parser::find_item(self.path.join("all.html"), &name) - .map(|o| o.map(|s| Item::new(name, self.path.join(path::PathBuf::from(s))))) + .map(|o| o.map(|s| Item::new(name, self.path.join(path::PathBuf::from(s)), None))) } pub fn find_module(&self, item: &[&str]) -> Option { @@ -41,7 +43,17 @@ impl Crate { .join(path::PathBuf::from(item.join("/"))) .join("index.html"); if path.is_file() { - Some(Item::new(item.join("::"), path)) + Some(Item::new(item.join("::"), path, None)) + } else { + None + } + } + + pub fn find_member(&self, item: &[&str]) -> Option { + if let Some((last, elements)) = item.split_last() { + // TODO: error + let parent = self.find_item(elements).unwrap(); + parent.and_then(|i| i.find_member(last)) } else { None } @@ -49,12 +61,29 @@ impl Crate { } impl Item { - pub fn new(name: String, path: path::PathBuf) -> Self { - Item { path, name } + pub fn new(name: String, path: path::PathBuf, member: Option) -> Self { + Item { path, member, name } } pub fn load_doc(&self) -> anyhow::Result { - parser::parse_doc(&self.path) + if let Some(member) = &self.member { + parser::parse_member_doc(&self.path, &self.name, member) + } else { + parser::parse_item_doc(&self.path) + } + } + + pub fn find_member(&self, name: &str) -> Option { + // TODO: error handling + if parser::find_member(&self.path, name).unwrap() { + Some(Item::new( + self.name.clone(), + self.path.clone(), + Some(name.to_owned()), + )) + } else { + None + } } } diff --git a/src/main.rs b/src/main.rs index e7646ee..385931c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,6 +83,7 @@ fn find_doc(sources: &[Box], keyword: &str) -> anyhow::Resul let item = crate_ .find_item(&parts[1..])? .or_else(|| crate_.find_module(&parts[1..])) + .or_else(|| crate_.find_member(&parts[1..])) .with_context(|| format!("Could not find the item {}", keyword))?; item.load_doc() } @@ -119,8 +120,8 @@ mod tests { super::find_doc(&sources, "kuchiki").unwrap(); super::find_doc(&sources, "kuchiki::NodeRef").unwrap(); + super::find_doc(&sources, "kuchiki::NodeDataRef::as_node").unwrap(); super::find_doc(&sources, "kuchiki::traits").unwrap(); super::find_doc(&sources, "kachiki").unwrap_err(); - super::find_doc(&sources, "kuchiki::NodeDataRef::as_node").unwrap_err(); } } diff --git a/src/parser.rs b/src/parser.rs index 13f51e0..1f0dc00 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -37,16 +37,9 @@ pub fn find_item>(path: P, item: &str) -> anyhow::Result>(path: P) -> anyhow::Result { +pub fn find_member>(path: P, item: &str) -> anyhow::Result { let document = parse_file(path)?; - let heading = select_first(&document, ".fqn .in-band")?.context("Could not find heading")?; - let definition = select_first(&document, ".docblock.type-decl")?; - let description = select_first(&document, ".docblock:not(.type-decl)")?; - - let mut doc = doc::Doc::new(get_html(heading.as_node())?); - doc.description = description.map(|n| get_html(n.as_node())).transpose()?; - doc.definition = definition.map(|n| get_html(n.as_node())).transpose()?; - Ok(doc) + Ok(get_member(&document, item)?.is_some()) } fn select( @@ -66,6 +59,49 @@ fn select_first( select(element, selector).map(|mut i| i.next()) } +pub fn parse_item_doc>(path: P) -> anyhow::Result { + let document = parse_file(path)?; + let heading = select_first(&document, ".fqn .in-band")?.context("Could not find heading")?; + let definition = select_first(&document, ".docblock.type-decl")?; + let description = select_first(&document, ".docblock:not(.type-decl)")?; + + let mut doc = doc::Doc::new(get_html(heading.as_node())?); + doc.description = description.map(|n| get_html(n.as_node())).transpose()?; + doc.definition = definition.map(|n| get_html(n.as_node())).transpose()?; + Ok(doc) +} + +pub fn parse_member_doc>( + path: P, + item: &str, + name: &str, +) -> anyhow::Result { + let document = parse_file(path)?; + let member = + get_member(&document, name)?.with_context(|| format!("Could not find member {}", name))?; + let heading = member + .as_node() + .parent() + .with_context(|| format!("The member {} does not have a parent", name))?; + let docblock = heading.next_sibling(); + + let mut doc = doc::Doc::new(format!("{}::{}", item, name)); + doc.definition = Some(get_html(member.as_node())?); + doc.description = docblock.map(|n| get_html(&n)).transpose()?; + Ok(doc) +} + +fn get_member( + document: &kuchiki::NodeRef, + name: &str, +) -> anyhow::Result>> { + document + .select(&format!("#{}\\.v", name)) + .ok() + .context("Could not select member by id") + .map(|mut i| i.next()) +} + fn get_attribute(element: &kuchiki::ElementData, name: &str) -> Option { element.attributes.borrow().get(name).map(ToOwned::to_owned) } @@ -90,10 +126,10 @@ mod tests { } #[test] - fn test_parse_doc_struct() { + fn test_parse_item_doc() { let path = crate::tests::ensure_docs(); let path = path.join("kuchiki").join("struct.NodeRef.html"); - let doc = super::parse_doc(&path).unwrap(); + let doc = super::parse_item_doc(&path).unwrap(); assert_eq!( "\ @@ -105,4 +141,21 @@ mod tests { assert!(doc.definition.is_some()); assert!(doc.description.is_some()); } + + #[test] + fn test_parse_member_doc() { + let path = crate::tests::ensure_docs(); + let path = path.join("kuchiki").join("struct.NodeDataRef.html"); + let doc = super::parse_member_doc(&path, "kuchiki::NodeDataRef", "as_node").unwrap(); + + assert_eq!("kuchiki::NodeDataRef::as_node", &doc.title); + assert_eq!( + "\ + pub fn as_node(&self) \ + -> &NodeRef", + &doc.definition.unwrap() + ); + assert!(doc.description.is_some()); + } }