diff --git a/src/main.rs b/src/main.rs index 00c4d92..f1692a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 didn’t 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>> { - let mut vec: Vec> = Vec::new(); +fn load_sources(sources: &[String], load_default_sources: bool) -> anyhow::Result { + 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 { @@ -132,32 +128,14 @@ fn get_sysroot() -> Option { .map(|s| s.trim().into()) } -/// Find the documentation for an item with the given name (exact matches only). -fn find_doc( - sources: &[Box], - name: &doc::Name, - ty: Option, -) -> anyhow::Result> { - 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], - name: &doc::Name, -) -> anyhow::Result> { +fn search_doc(sources: &source::Sources, name: &doc::Name) -> anyhow::Result> { 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], + sources: &source::Sources, name: &doc::Name, ) -> anyhow::Result> { - let indexes = sources - .iter() - .filter_map(|s| s.load_index().transpose()) - .collect::>>()?; - let mut items = indexes - .iter() - .map(|i| i.find(name)) - .collect::>() - .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()); }); } } diff --git a/src/source.rs b/src/source.rs index ab5cda9..ea1cafd 100644 --- a/src/source.rs +++ b/src/source.rs @@ -22,6 +22,9 @@ pub trait Source { fn load_index(&self) -> anyhow::Result>; } +/// A collection of sources. +pub struct Sources(Vec>); + /// 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>) -> 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, + ) -> anyhow::Result> { + 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> { + let indexes = self + .0 + .iter() + .filter_map(|s| s.load_index().transpose()) + .collect::>>()?; + let mut items = indexes + .iter() + .map(|i| i.find(name)) + .collect::>() + .concat(); + items.sort_unstable(); + items.dedup(); + Ok(items) + } +} + impl DirSource { fn new(path: path::PathBuf) -> Self { log::info!("Created directory source at '{}'", path.display()); diff --git a/src/viewer/mod.rs b/src/viewer/mod.rs index 4d8bc14..79c6f4b 100644 --- a/src/viewer/mod.rs +++ b/src/viewer/mod.rs @@ -15,14 +15,14 @@ use crate::source; pub trait Viewer: fmt::Debug { fn open( &self, - sources: Vec>, + sources: source::Sources, args: args::ViewerArgs, doc: &doc::Doc, ) -> anyhow::Result<()>; fn open_examples( &self, - sources: Vec>, + sources: source::Sources, args: args::ViewerArgs, doc: &doc::Doc, examples: Vec, diff --git a/src/viewer/text/mod.rs b/src/viewer/text/mod.rs index 989cb0d..9798148 100644 --- a/src/viewer/text/mod.rs +++ b/src/viewer/text/mod.rs @@ -45,7 +45,7 @@ impl TextViewer { impl viewer::Viewer for TextViewer { fn open( &self, - _sources: Vec>, + _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>, + _sources: source::Sources, args: args::ViewerArgs, doc: &doc::Doc, examples: Vec, diff --git a/src/viewer/tui/mod.rs b/src/viewer/tui/mod.rs index bf67aed..abc2a5c 100644 --- a/src/viewer/tui/mod.rs +++ b/src/viewer/tui/mod.rs @@ -28,7 +28,7 @@ impl TuiViewer { fn render( &self, - sources: Vec>, + 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>, + 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>, + sources: source::Sources, args: args::ViewerArgs, doc: &doc::Doc, examples: Vec, @@ -70,16 +70,13 @@ impl viewer::Viewer for TuiViewer { } pub struct Context { - pub sources: Vec>, + pub sources: source::Sources, pub args: args::ViewerArgs, pub highlighter: Option, } impl Context { - pub fn new( - sources: Vec>, - args: args::ViewerArgs, - ) -> anyhow::Result { + pub fn new(sources: source::Sources, args: args::ViewerArgs) -> anyhow::Result { let highlighter = utils::get_highlighter(&args)?; Ok(Context { sources, @@ -197,7 +194,7 @@ fn create_backend() -> anyhow::Result> { } fn create_cursive( - sources: Vec>, + sources: source::Sources, args: args::ViewerArgs, ) -> anyhow::Result { 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], - ty: Option, - name: &doc::Fqn, -) -> anyhow::Result { - 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();