Move item lookup logic into Sources struct

This match adds the Sources struct that is a wrapper for a vector of
Source structs.  It now contains the lookup logic that was previously
contained in functions in main.rs.
This commit is contained in:
Robin Krahl 2020-10-12 00:28:51 +02:00
parent 2c1b91462e
commit 44b1405595
No known key found for this signature in database
GPG Key ID: 8E9B0870524F69D8
5 changed files with 89 additions and 103 deletions

View File

@ -9,9 +9,8 @@
//! and the `source` module. Per default, we look for documentation in the directory
//! `share/doc/rust{,-doc}/html` relative to the Rust installation path (`rustc --print sysroot`
//! or `usr`) and in `./target/doc`.
//! 2. We try to look up the given keyword in all acailable sources, see the `find_doc` function
//! and the `source` module for the lookup logic and the `doc` module for the loaded
//! documentation.
//! 2. We try to look up the given keyword in all acailable sources, see the and the `source`
//! module for the lookup logic and the `doc` module for the loaded documentation.
//! 3. If we didnt find a match in the previous step, we load the search index from the
//! `search-index.js` file for all sources and try to find a matching item. If we find one, we
//! open the documentation for that item as in step 2. See the `search_doc` function and the
@ -52,7 +51,7 @@ fn main() -> anyhow::Result<()> {
let args = args::Args::load()?;
let sources = load_sources(&args.source_paths, !args.no_default_sources)?;
let doc = if let Some(doc) = find_doc(&sources, &args.keyword, None)? {
let doc = if let Some(doc) = sources.find(&args.keyword, None)? {
Some(doc)
} else if !args.no_search {
search_doc(&sources, &args.keyword)?
@ -80,11 +79,8 @@ fn main() -> anyhow::Result<()> {
}
/// Load all sources given as a command-line argument and, if enabled, the default sources.
fn load_sources(
sources: &[String],
load_default_sources: bool,
) -> anyhow::Result<Vec<Box<dyn source::Source>>> {
let mut vec: Vec<Box<dyn source::Source>> = Vec::new();
fn load_sources(sources: &[String], load_default_sources: bool) -> anyhow::Result<source::Sources> {
let mut vec = Vec::new();
if load_default_sources {
for path in get_default_sources() {
@ -106,7 +102,7 @@ fn load_sources(
// The last source should be searched first --> reverse source vector
vec.reverse();
Ok(vec)
Ok(source::Sources::new(vec))
}
fn get_default_sources() -> Vec<path::PathBuf> {
@ -132,32 +128,14 @@ fn get_sysroot() -> Option<path::PathBuf> {
.map(|s| s.trim().into())
}
/// Find the documentation for an item with the given name (exact matches only).
fn find_doc(
sources: &[Box<dyn source::Source>],
name: &doc::Name,
ty: Option<doc::ItemType>,
) -> anyhow::Result<Option<doc::Doc>> {
let fqn = name.clone().into();
for source in sources {
if let Some(doc) = source.find_doc(&fqn, ty)? {
return Ok(Some(doc));
}
}
log::info!("Could not find item '{}'", fqn);
Ok(None)
}
/// Use the search index to find the documentation for an item that partially matches the given
/// keyword.
fn search_doc(
sources: &[Box<dyn source::Source>],
name: &doc::Name,
) -> anyhow::Result<Option<doc::Doc>> {
fn search_doc(sources: &source::Sources, name: &doc::Name) -> anyhow::Result<Option<doc::Doc>> {
if let Some(item) = search_item(sources, name)? {
use anyhow::Context;
let doc = find_doc(sources, &item.name, Some(item.ty))?
let doc = sources
.find(&item.name, Some(item.ty))?
.with_context(|| format!("Could not find documentation for {}", &item.name))?;
Ok(Some(doc))
} else {
@ -171,21 +149,10 @@ fn search_doc(
/// Use the search index to find an item that partially matches the given keyword.
fn search_item(
sources: &[Box<dyn source::Source>],
sources: &source::Sources,
name: &doc::Name,
) -> anyhow::Result<Option<index::IndexItem>> {
let indexes = sources
.iter()
.filter_map(|s| s.load_index().transpose())
.collect::<anyhow::Result<Vec<_>>>()?;
let mut items = indexes
.iter()
.map(|i| i.find(name))
.collect::<Vec<_>>()
.concat();
items.sort_unstable();
items.dedup();
let items = sources.search(name)?;
if items.is_empty() {
Err(anyhow::anyhow!(
"Could not find documentation for {}",
@ -213,7 +180,7 @@ fn select_item(
name
);
println!("Found mulitple matches for {} select one of:", name);
println!("Found multiple matches for {} select one of:", name);
println!();
let width = (items.len() + 1).to_string().len();
for (i, item) in items.iter().enumerate() {
@ -240,35 +207,28 @@ mod tests {
#[test]
fn test_find_doc() {
with_rustdoc("*", |_, path| {
let sources = vec![source::get_source(path).unwrap()];
let sources = source::Sources::new(vec![source::get_source(path).unwrap()]);
assert!(
super::find_doc(&sources, &"kuchiki".to_owned().into(), None)
.unwrap()
.is_some()
);
assert!(
super::find_doc(&sources, &"kuchiki::NodeRef".to_owned().into(), None)
.unwrap()
.is_some()
);
assert!(super::find_doc(
&sources,
&"kuchiki::NodeDataRef::as_node".to_owned().into(),
None
)
.unwrap()
.is_some());
assert!(
super::find_doc(&sources, &"kuchiki::traits".to_owned().into(), None)
.unwrap()
.is_some()
);
assert!(
super::find_doc(&sources, &"kachiki".to_owned().into(), None)
.unwrap()
.is_none()
);
assert!(sources
.find(&"kuchiki".to_owned().into(), None)
.unwrap()
.is_some());
assert!(sources
.find(&"kuchiki::NodeRef".to_owned().into(), None)
.unwrap()
.is_some());
assert!(sources
.find(&"kuchiki::NodeDataRef::as_node".to_owned().into(), None)
.unwrap()
.is_some());
assert!(sources
.find(&"kuchiki::traits".to_owned().into(), None)
.unwrap()
.is_some());
assert!(sources
.find(&"kachiki".to_owned().into(), None)
.unwrap()
.is_none());
});
}
}

View File

@ -22,6 +22,9 @@ pub trait Source {
fn load_index(&self) -> anyhow::Result<Option<index::Index>>;
}
/// A collection of sources.
pub struct Sources(Vec<Box<dyn Source>>);
/// Local directory containing documentation data.
///
/// The directory must contain documentation for one or more crates in subdirectories. Suitable
@ -32,6 +35,45 @@ pub struct DirSource {
path: path::PathBuf,
}
impl Sources {
pub fn new(sources: Vec<Box<dyn Source>>) -> Sources {
Sources(sources)
}
/// Find the documentation for an item with the given name (exact matches only).
pub fn find(
&self,
name: &doc::Name,
ty: Option<doc::ItemType>,
) -> anyhow::Result<Option<doc::Doc>> {
let fqn = name.clone().into();
for source in &self.0 {
if let Some(doc) = source.find_doc(&fqn, ty)? {
return Ok(Some(doc));
}
}
log::info!("Could not find item '{}'", fqn);
Ok(None)
}
/// Use the search index to find an item that partially matches the given keyword.
pub fn search(&self, name: &doc::Name) -> anyhow::Result<Vec<index::IndexItem>> {
let indexes = self
.0
.iter()
.filter_map(|s| s.load_index().transpose())
.collect::<anyhow::Result<Vec<_>>>()?;
let mut items = indexes
.iter()
.map(|i| i.find(name))
.collect::<Vec<_>>()
.concat();
items.sort_unstable();
items.dedup();
Ok(items)
}
}
impl DirSource {
fn new(path: path::PathBuf) -> Self {
log::info!("Created directory source at '{}'", path.display());

View File

@ -15,14 +15,14 @@ use crate::source;
pub trait Viewer: fmt::Debug {
fn open(
&self,
sources: Vec<Box<dyn source::Source>>,
sources: source::Sources,
args: args::ViewerArgs,
doc: &doc::Doc,
) -> anyhow::Result<()>;
fn open_examples(
&self,
sources: Vec<Box<dyn source::Source>>,
sources: source::Sources,
args: args::ViewerArgs,
doc: &doc::Doc,
examples: Vec<doc::Example>,

View File

@ -45,7 +45,7 @@ impl TextViewer {
impl viewer::Viewer for TextViewer {
fn open(
&self,
_sources: Vec<Box<dyn source::Source>>,
_sources: source::Sources,
args: args::ViewerArgs,
doc: &doc::Doc,
) -> anyhow::Result<()> {
@ -54,7 +54,7 @@ impl viewer::Viewer for TextViewer {
fn open_examples(
&self,
_sources: Vec<Box<dyn source::Source>>,
_sources: source::Sources,
args: args::ViewerArgs,
doc: &doc::Doc,
examples: Vec<doc::Example>,

View File

@ -28,7 +28,7 @@ impl TuiViewer {
fn render<F>(
&self,
sources: Vec<Box<dyn source::Source>>,
sources: source::Sources,
args: args::ViewerArgs,
doc: &doc::Doc,
f: F,
@ -49,7 +49,7 @@ impl TuiViewer {
impl viewer::Viewer for TuiViewer {
fn open(
&self,
sources: Vec<Box<dyn source::Source>>,
sources: source::Sources,
args: args::ViewerArgs,
doc: &doc::Doc,
) -> anyhow::Result<()> {
@ -58,7 +58,7 @@ impl viewer::Viewer for TuiViewer {
fn open_examples(
&self,
sources: Vec<Box<dyn source::Source>>,
sources: source::Sources,
args: args::ViewerArgs,
doc: &doc::Doc,
examples: Vec<doc::Example>,
@ -70,16 +70,13 @@ impl viewer::Viewer for TuiViewer {
}
pub struct Context {
pub sources: Vec<Box<dyn source::Source>>,
pub sources: source::Sources,
pub args: args::ViewerArgs,
pub highlighter: Option<utils::Highlighter>,
}
impl Context {
pub fn new(
sources: Vec<Box<dyn source::Source>>,
args: args::ViewerArgs,
) -> anyhow::Result<Context> {
pub fn new(sources: source::Sources, args: args::ViewerArgs) -> anyhow::Result<Context> {
let highlighter = utils::get_highlighter(&args)?;
Ok(Context {
sources,
@ -197,7 +194,7 @@ fn create_backend() -> anyhow::Result<Box<dyn cursive::backend::Backend>> {
}
fn create_cursive(
sources: Vec<Box<dyn source::Source>>,
sources: source::Sources,
args: args::ViewerArgs,
) -> anyhow::Result<cursive::Cursive> {
use cursive::event::{Event, Key};
@ -264,26 +261,13 @@ fn handle_link(s: &mut cursive::Cursive, doc_name: &doc::Fqn, doc_ty: doc::ItemT
}
}
fn find_doc(
sources: &[Box<dyn source::Source>],
ty: Option<doc::ItemType>,
name: &doc::Fqn,
) -> anyhow::Result<doc::Doc> {
for source in sources {
if let Some(doc) = source.find_doc(name, ty)? {
return Ok(doc);
}
}
Err(anyhow::anyhow!(
"Could not find documentation for item: {}",
name
))
}
fn open_link(s: &mut cursive::Cursive, link: ResolvedLink) -> anyhow::Result<()> {
match link {
ResolvedLink::Doc(ty, name) => {
let doc = find_doc(&context(s).sources, ty, &name)?;
let doc = context(s)
.sources
.find(&name, ty)?
.with_context(|| format!("Could not find documentation for item: {}", name))?;
let mut renderer = context(s).create_renderer(&doc);
renderer.render_doc(&doc).unwrap();
let view = renderer.into_view();