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.
This commit is contained in:
Robin Krahl 2020-07-17 22:48:05 +02:00
parent ab1d655d39
commit c16d7f7d60
3 changed files with 100 additions and 17 deletions

View File

@ -14,6 +14,7 @@ pub struct Crate {
#[derive(Clone, Debug, PartialEq)]
pub struct Item {
pub path: path::PathBuf,
pub member: Option<String>,
pub name: String,
}
@ -31,8 +32,9 @@ impl Crate {
pub fn find_item(&self, item: &[&str]) -> anyhow::Result<Option<Item>> {
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<Item> {
@ -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<Item> {
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<String>) -> Self {
Item { path, member, name }
}
pub fn load_doc(&self) -> anyhow::Result<Doc> {
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<Item> {
// 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
}
}
}

View File

@ -83,6 +83,7 @@ fn find_doc(sources: &[Box<dyn source::Source>], 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();
}
}

View File

@ -37,16 +37,9 @@ pub fn find_item<P: AsRef<path::Path>>(path: P, item: &str) -> anyhow::Result<Op
Ok(item)
}
pub fn parse_doc<P: AsRef<path::Path>>(path: P) -> anyhow::Result<doc::Doc> {
pub fn find_member<P: AsRef<path::Path>>(path: P, item: &str) -> anyhow::Result<bool> {
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<P: AsRef<path::Path>>(path: P) -> anyhow::Result<doc::Doc> {
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<P: AsRef<path::Path>>(
path: P,
item: &str,
name: &str,
) -> anyhow::Result<doc::Doc> {
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<Option<kuchiki::NodeDataRef<kuchiki::ElementData>>> {
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<String> {
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!(
"<span class=\"in-band\">\
@ -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!(
"<code id=\"as_node.v\">\
pub fn <a class=\"fnname\" href=\"#method.as_node\">as_node</a>(&amp;self) \
-&gt; &amp;<a class=\"struct\" href=\"../kuchiki/struct.NodeRef.html\" \
title=\"struct kuchiki::NodeRef\">NodeRef</a></code>",
&doc.definition.unwrap()
);
assert!(doc.description.is_some());
}
}