List members in module documentation

This patch adds support for listing the module members on its
documentation page.  This is quite easy as the items are displayed in a
single table per item type.
This commit is contained in:
Robin Krahl 2020-07-20 11:48:29 +02:00
parent 3d4fac0d5c
commit d0c73d2523
No known key found for this signature in database
GPG Key ID: 8E9B0870524F69D8
5 changed files with 111 additions and 9 deletions

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
use std::fmt;
use std::path;
use crate::parser;
@ -23,6 +24,7 @@ pub struct Doc {
pub title: String,
pub description: Option<String>,
pub definition: Option<String>,
pub members: Vec<(String, Vec<Doc>)>,
}
impl Crate {
@ -31,10 +33,9 @@ impl Crate {
}
pub fn find_item(&self, item: &[&str]) -> anyhow::Result<Option<Item>> {
let name = item.join("::");
// TODO: add crate to name?
let (name, full_name) = self.get_names(item);
parser::find_item(self.path.join("all.html"), &name)
.map(|o| o.map(|s| Item::new(name, self.path.join(path::PathBuf::from(s)), None)))
.map(|o| o.map(|s| Item::new(full_name, self.path.join(path::PathBuf::from(s)), None)))
}
pub fn find_module(&self, item: &[&str]) -> Option<Item> {
@ -43,7 +44,8 @@ impl Crate {
.join(path::PathBuf::from(item.join("/")))
.join("index.html");
if path.is_file() {
Some(Item::new(item.join("::"), path, None))
let (_, full_name) = self.get_names(item);
Some(Item::new(full_name, path, None))
} else {
None
}
@ -58,6 +60,16 @@ impl Crate {
None
}
}
fn get_names(&self, item: &[&str]) -> (String, String) {
let name = item.join("::");
let full_name = if item.is_empty() {
self.name.clone()
} else {
format!("{}::{}", &self.name, &name)
};
(name, full_name)
}
}
impl Item {
@ -69,7 +81,7 @@ impl Item {
if let Some(member) = &self.member {
parser::parse_member_doc(&self.path, &self.name, member)
} else {
parser::parse_item_doc(&self.path)
parser::parse_item_doc(&self.path, &self.name)
}
}
@ -95,3 +107,13 @@ impl Doc {
}
}
}
impl fmt::Display for Doc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(description) = &self.description {
write!(f, "{}: {}", &self.title, description)
} else {
write!(f, "{}", &self.title)
}
}
}

View File

@ -126,7 +126,7 @@ impl Index {
// Skip associated types (== item type 16)
continue;
}
let full_path = match item.parent {
Some(idx) => {
let parent = &data.paths[idx].1;

View File

@ -59,7 +59,23 @@ 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> {
const ITEM_MEMBERS: &[(&str, &str)] = &[
("Extern Crates", "extern-crates"),
("Imports", "imports"),
("Primitives", "primitives"),
("Modules", "modules"),
("Macros", "macros"),
("Structs", "structs"),
("Enums", "enums"),
("Constants", "constants"),
("Statics", "statics"),
("Traits", "traits"),
("Functions", "functions"),
("Typedefs", "typedefs"),
("Unions", "unions"),
];
pub fn parse_item_doc<P: AsRef<path::Path>>(path: P, name: &str) -> 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")?;
@ -68,6 +84,12 @@ pub fn parse_item_doc<P: AsRef<path::Path>>(path: P) -> anyhow::Result<doc::Doc>
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()?;
for (heading, id) in ITEM_MEMBERS {
let members = get_members(&document, name, id)?;
if !members.is_empty() {
doc.members.push((heading.to_string(), members));
}
}
Ok(doc)
}
@ -91,6 +113,23 @@ pub fn parse_member_doc<P: AsRef<path::Path>>(
Ok(doc)
}
fn get_members(
document: &kuchiki::NodeRef,
base_name: &str,
id: &str,
) -> anyhow::Result<Vec<doc::Doc>> {
let mut members: Vec<doc::Doc> = Vec::new();
if let Some(table) = select_first(document, &format!("#{} + table", id))? {
// On module pages, the members are listed in tables
let items = select(table.as_node(), "td:first-child :first-child")?;
for item in items {
let name = format!("{}::{}", base_name, get_html(item.as_node())?);
members.push(doc::Doc::new(name))
}
}
Ok(members)
}
fn get_member(
document: &kuchiki::NodeRef,
name: &str,
@ -129,7 +168,7 @@ mod tests {
fn test_parse_item_doc() {
let path = crate::tests::ensure_docs();
let path = path.join("kuchiki").join("struct.NodeRef.html");
let doc = super::parse_item_doc(&path).unwrap();
let doc = super::parse_item_doc(&path, "kuchiki::NodeRef").unwrap();
assert_eq!(
"<span class=\"in-band\">\

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
use std::fmt;
use std::io::{self, Write};
use html2text::render::text_renderer;
@ -26,6 +27,13 @@ impl RichViewer {
self.print_heading(&doc.title, 1)?;
self.print_opt(doc.definition.as_deref())?;
self.print_opt(doc.description.as_deref())?;
for (heading, items) in &doc.members {
if !items.is_empty() {
writeln!(io::stdout())?;
self.print_heading(heading, 2)?;
self.print_list(items)?;
}
}
Ok(())
}
@ -58,6 +66,15 @@ impl RichViewer {
write!(io::stdout(), "{}", termion::style::Reset)
}
fn print_list(&self, items: &[impl fmt::Display]) -> io::Result<()> {
let html = items
.iter()
.map(|i| format!("<li>{}</li>", i))
.collect::<Vec<_>>()
.join("");
self.print(&format!("<ul>{}</ul>", html))
}
fn render_string(&self, ts: &RichString) -> io::Result<()> {
let start_style = get_style(ts, get_start_style);
let end_style = get_style(ts, get_end_style);

View File

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
use std::fmt;
use html2text::render::text_renderer;
use crate::doc;
@ -35,15 +37,37 @@ impl TextViewer {
self.print(s);
}
}
fn print_heading(&self, s: &str, level: usize) {
let prefix = "#".repeat(level);
print!("{} ", prefix);
self.print(s);
}
fn print_list(&self, items: &[impl fmt::Display]) {
let html = items
.iter()
.map(|i| format!("<li>{}</li>", i))
.collect::<Vec<_>>()
.join("");
self.print(&format!("<ul>{}</ul>", html));
}
}
impl viewer::Viewer for TextViewer {
fn open(&self, doc: &doc::Doc) -> anyhow::Result<()> {
viewer::spawn_pager();
self.print(&doc.title);
self.print_heading(&doc.title, 1);
self.print_opt(doc.definition.as_deref());
self.print_opt(doc.description.as_deref());
for (heading, items) in &doc.members {
if !items.is_empty() {
println!();
self.print_heading(heading, 2);
self.print_list(items);
}
}
Ok(())
}
}