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:
parent
ab1d655d39
commit
c16d7f7d60
39
src/doc.rs
39
src/doc.rs
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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>(&self) \
|
||||
-> &<a class=\"struct\" href=\"../kuchiki/struct.NodeRef.html\" \
|
||||
title=\"struct kuchiki::NodeRef\">NodeRef</a></code>",
|
||||
&doc.definition.unwrap()
|
||||
);
|
||||
assert!(doc.description.is_some());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user