diff --git a/Cargo.lock b/Cargo.lock index d65c133..f451f2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,6 +477,7 @@ dependencies = [ "anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", "html2text 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "kuchiki 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "markup5ever 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "pager 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 901e845..555356e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ license = "MIT" anyhow = "1.0.31" html2text = "0.1.12" kuchiki = "0.8.0" +markup5ever = "0.10.0" pager = "0.15.0" serde_json = "1.0.56" serde_repr = "0.1.6" diff --git a/src/doc.rs b/src/doc.rs index 20cb411..3bc987d 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -245,6 +245,37 @@ impl ItemType { } } + pub fn class(&self) -> &str { + match self { + ItemType::Module => "module", + ItemType::ExternCrate => "extern-crate", + ItemType::Import => "import", + ItemType::Struct => "struct", + ItemType::Enum => "enum", + ItemType::Function => "function", + ItemType::Typedef => "typedef", + ItemType::Static => "static", + ItemType::Trait => "trait", + ItemType::Impl => "impl", + ItemType::TyMethod => "required-method", + ItemType::Method => "method", + ItemType::StructField => "field", + ItemType::Variant => "variant", + ItemType::Macro => "macro", + ItemType::Primitive => "primitive", + ItemType::AssocType => "associated-type", + ItemType::Constant => "constant", + ItemType::AssocConst => "associated-const", + ItemType::Union => "union", + ItemType::ForeignType => "foreign-type", + ItemType::Keyword => "keyword", + ItemType::OpaqueTy => "opaque-type", + ItemType::ProcAttribute => "proc-attribute", + ItemType::ProcDerive => "proc-derive", + ItemType::TraitAlias => "trait-alias", + } + } + pub fn group_name(&self) -> &str { match self { ItemType::Module => "Modules", diff --git a/src/parser.rs b/src/parser.rs index c2736b5..744d626 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -47,7 +47,7 @@ pub fn find_member>( .as_node() .parent() .context("Member element does not have a parent")?; - if let Some(parent_id) = get_attribute(parent.as_element().unwrap(), "id") { + if let Some(parent_id) = get_node_attribute(&parent, "id") { let item_type: doc::ItemType = parent_id.splitn(2, '.').next().unwrap().parse()?; return Ok(Some(doc::Item::new( name.clone(), @@ -84,6 +84,12 @@ pub fn parse_item_doc(item: &doc::Item) -> anyhow::Result { let mut doc = doc::Doc::new(item.name.clone(), item.ty); doc.description = description.map(|n| get_html(n.as_node())).transpose()?; doc.definition = definition.map(|n| get_html(n.as_node())).transpose()?; + + let (ty, groups) = get_variants(&document, item)?; + if !groups.is_empty() { + doc.groups.push((ty, groups)); + } + Ok(doc) } @@ -135,6 +141,52 @@ pub fn parse_member_doc(item: &doc::Item) -> anyhow::Result { Ok(doc) } +fn get_variants( + document: &kuchiki::NodeRef, + parent: &doc::Item, +) -> anyhow::Result<(doc::ItemType, Vec)> { + let ty = doc::ItemType::Variant; + let mut variants: Vec = Vec::new(); + let heading = select_first(document, &format!("#{}", ty.group_id()))?; + + let mut next = heading.and_then(|n| next_sibling_element(n.as_node())); + let mut name: Option = None; + while let Some(element) = &next { + if is_element(element, markup5ever::local_name!("div")) { + if has_class(element, ty.class()) { + if let Some(name) = &name { + variants.push(doc::Doc::new(parent.name.child(name), ty)); + } + name = get_node_attribute(element, "id") + .and_then(|s| s.splitn(2, '.').nth(1).map(ToOwned::to_owned)); + } else if has_class(element, "docblock") { + if let Some(name) = &name { + let mut doc = doc::Doc::new(parent.name.child(name), ty); + // TODO: use inner_html() instead + doc.description = Some(element.text_contents()); + variants.push(doc); + } + name = None; + } + + next = element.next_sibling(); + } else { + if let Some(name) = &name { + variants.push(doc::Doc::new(parent.name.child(name), ty)); + } + next = None; + } + } + + let mut groups: Vec = Vec::new(); + if !variants.is_empty() { + let mut group = doc::MemberGroup::new(); + group.members = variants; + groups.push(group); + } + Ok((ty, groups)) +} + fn get_members( document: &kuchiki::NodeRef, parent: &doc::Item, @@ -168,6 +220,34 @@ fn get_attribute(element: &kuchiki::ElementData, name: &str) -> Option { element.attributes.borrow().get(name).map(ToOwned::to_owned) } +fn get_node_attribute(node: &kuchiki::NodeRef, name: &str) -> Option { + node.as_element().and_then(|e| get_attribute(e, name)) +} + +fn next_sibling_element(node: &kuchiki::NodeRef) -> Option { + let mut next = node.next_sibling(); + while let Some(node) = &next { + if node.as_element().is_some() { + break; + } + next = node.next_sibling(); + } + next +} + +fn is_element(node: &kuchiki::NodeRef, name: markup5ever::LocalName) -> bool { + node.as_element() + .map(|e| e.name.local == name) + .unwrap_or(false) +} + +fn has_class(node: &kuchiki::NodeRef, class: &str) -> bool { + node.as_element() + .and_then(|e| get_attribute(&e, "class")) + .map(|a| a.split(' ').any(|s| s == class)) + .unwrap_or(false) +} + fn get_html(node: &kuchiki::NodeRef) -> anyhow::Result { let mut vec: Vec = Vec::new(); node.serialize(&mut vec)?;