Improve error handling for cheatsh and tldr (#766)

Fixes #695 and #703
This commit is contained in:
Denis Isidoro 2022-07-29 18:53:18 -03:00 committed by GitHub
parent d68c4437a1
commit 8b78d54863
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 98 additions and 114 deletions

View file

@ -1,26 +1,22 @@
use crate::parser::Parser;
use crate::prelude::*; use crate::prelude::*;
use std::process::Command;
use crate::structures::fetcher;
use std::process::{self, Command};
fn map_line(line: &str) -> String { fn map_line(line: &str) -> String {
line.trim().trim_end_matches(':').to_string() line.trim().trim_end_matches(':').to_string()
} }
fn lines(query: &str, markdown: &str) -> impl Iterator<Item = Result<String>> { fn as_lines(query: &str, markdown: &str) -> Vec<String> {
format!( format!(
"% {}, cheat.sh "% {}, cheat.sh
{}", {}",
query, markdown query, markdown
) )
.lines() .lines()
.map(|line| Ok(map_line(line))) .map(map_line)
.collect::<Vec<Result<String>>>() .collect()
.into_iter()
} }
pub fn fetch(query: &str) -> Result<String> { pub fn call(query: &str) -> Result<Vec<String>> {
let args = ["-qO-", &format!("cheat.sh/{}", query)]; let args = ["-qO-", &format!("cheat.sh/{}", query)];
let child = Command::new("wget") let child = Command::new("wget")
@ -32,19 +28,34 @@ pub fn fetch(query: &str) -> Result<String> {
let child = match child { let child = match child {
Ok(x) => x, Ok(x) => x,
Err(_) => { Err(_) => {
eprintln!( let msg = "navi was unable to call wget.
"navi was unable to call wget. Make sure wget is correctly installed.";
Make sure wget is correctly installed." return Err(anyhow!(msg));
);
process::exit(34)
} }
}; };
let out = child.wait_with_output().context("Failed to wait for wget")?; let out = child.wait_with_output().context("Failed to wait for wget")?;
if let Some(0) = out.status.code() { if let Some(0) = out.status.code() {
let stdout = out.stdout;
let plain_bytes = strip_ansi_escapes::strip(&stdout)?;
let markdown = String::from_utf8(plain_bytes).context("Output is invalid utf8")?;
if markdown.starts_with("Unknown topic.") {
let msg = format!(
"`{}` not found in cheatsh.
Output:
{}
",
&query, markdown,
);
return Err(anyhow!(msg));
}
let lines = as_lines(query, &markdown);
Ok(lines)
} else { } else {
eprintln!( let msg = format!(
"Failed to call: "Failed to call:
wget {} wget {}
@ -58,43 +69,6 @@ Error:
String::from_utf8(out.stdout).unwrap_or_else(|_e| "Unable to get output message".to_string()), String::from_utf8(out.stdout).unwrap_or_else(|_e| "Unable to get output message".to_string()),
String::from_utf8(out.stderr).unwrap_or_else(|_e| "Unable to get error message".to_string()) String::from_utf8(out.stderr).unwrap_or_else(|_e| "Unable to get error message".to_string())
); );
process::exit(35) Err(anyhow!(msg))
}
let stdout = out.stdout;
let plain_bytes = strip_ansi_escapes::strip(&stdout)?;
String::from_utf8(plain_bytes).context("Output is invalid utf8")
}
pub struct Fetcher {
query: String,
}
impl Fetcher {
pub fn new(query: String) -> Self {
Self { query }
}
}
impl fetcher::Fetcher for Fetcher {
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
let cheat = &fetch(&self.query)?;
if cheat.starts_with("Unknown topic.") {
eprintln!(
"`{}` not found in cheatsh.
Output:
{}
",
&self.query, cheat
);
process::exit(35)
}
parser.read_lines(lines(&self.query, cheat), "cheat.sh", None)?;
Ok(true)
} }
} }

View file

@ -1,6 +1,4 @@
use crate::parser::Parser;
use crate::prelude::*; use crate::prelude::*;
use crate::structures::fetcher;
use std::process::{self, Command, Stdio}; use std::process::{self, Command, Stdio};
lazy_static! { lazy_static! {
@ -42,19 +40,18 @@ fn convert_tldr(line: &str) -> String {
} }
} }
fn markdown_lines(query: &str, markdown: &str) -> impl Iterator<Item = Result<String>> { fn markdown_lines(query: &str, markdown: &str) -> Vec<String> {
format!( format!(
"% {}, tldr "% {}, tldr
{}", {}",
query, markdown query, markdown
) )
.lines() .lines()
.map(|line| Ok(convert_tldr(line))) .map(convert_tldr)
.collect::<Vec<Result<String>>>() .collect()
.into_iter()
} }
pub fn fetch(query: &str) -> Result<String> { pub fn call(query: &str) -> Result<Vec<String>> {
let args = [query, "--markdown"]; let args = [query, "--markdown"];
let child = Command::new("tldr") let child = Command::new("tldr")
@ -67,7 +64,7 @@ pub fn fetch(query: &str) -> Result<String> {
let child = match child { let child = match child {
Ok(x) => x, Ok(x) => x,
Err(_) => { Err(_) => {
eprintln!( let msg = format!(
"navi was unable to call tldr. "navi was unable to call tldr.
Make sure tldr is correctly installed. Make sure tldr is correctly installed.
Refer to https://github.com/tldr-pages/tldr for more info. Refer to https://github.com/tldr-pages/tldr for more info.
@ -77,15 +74,20 @@ Note:
", ",
VERSION_DISCLAIMER VERSION_DISCLAIMER
); );
process::exit(34) return Err(anyhow!(msg));
} }
}; };
let out = child.wait_with_output().context("Failed to wait for tldr")?; let out = child.wait_with_output().context("Failed to wait for tldr")?;
if let Some(0) = out.status.code() { if let Some(0) = out.status.code() {
let stdout = out.stdout;
let markdown = String::from_utf8(stdout).context("Output is invalid utf8")?;
let lines = markdown_lines(query, &markdown);
Ok(lines)
} else { } else {
eprintln!( let msg = format!(
"Failed to call: "Failed to call:
tldr {} tldr {}
@ -103,30 +105,8 @@ If you are already using a supported version you can ignore this message.
args.join(" "), args.join(" "),
String::from_utf8(out.stdout).unwrap_or_else(|_e| "Unable to get output message".to_string()), String::from_utf8(out.stdout).unwrap_or_else(|_e| "Unable to get output message".to_string()),
String::from_utf8(out.stderr).unwrap_or_else(|_e| "Unable to get error message".to_string()), String::from_utf8(out.stderr).unwrap_or_else(|_e| "Unable to get error message".to_string()),
VERSION_DISCLAIMER VERSION_DISCLAIMER,
); );
process::exit(35) Err(anyhow!(msg))
}
let stdout = out.stdout;
String::from_utf8(stdout).context("Output is invalid utf8")
}
pub struct Fetcher {
query: String,
}
impl Fetcher {
pub fn new(query: String) -> Self {
Self { query }
}
}
impl fetcher::Fetcher for Fetcher {
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
let markdown = fetch(&self.query)?;
parser.read_lines(markdown_lines(&self.query, &markdown), "markdown", None)?;
Ok(true)
} }
} }

View file

@ -1,20 +1,23 @@
mod actor; mod actor;
mod extractor; mod extractor;
use crate::clients::cheatsh;
use crate::config::Source;
use crate::filesystem;
use crate::finder::structures::Opts as FinderOpts; use crate::finder::structures::Opts as FinderOpts;
use crate::parser::Parser; use crate::parser::Parser;
use crate::prelude::*; use crate::prelude::*;
use crate::structures::fetcher::{Fetcher, StaticFetcher};
use crate::welcome; use crate::welcome;
pub fn main() -> Result<()> { pub fn init(fetcher: Box<dyn Fetcher>) -> Result<()> {
let config = &CONFIG; let config = &CONFIG;
let opts = FinderOpts::snippet_default(); let opts = FinderOpts::snippet_default();
// let fetcher = config.fetcher();
let (raw_selection, (variables, files)) = config let (raw_selection, (variables, files)) = config
.finder() .finder()
.call(opts, |writer| { .call(opts, |writer| {
let fetcher = config.fetcher();
let mut parser = Parser::new(writer, true); let mut parser = Parser::new(writer, true);
let found_something = fetcher let found_something = fetcher
@ -32,10 +35,38 @@ pub fn main() -> Result<()> {
let extractions = extractor::extract_from_selections(&raw_selection, config.best_match()); let extractions = extractor::extract_from_selections(&raw_selection, config.best_match());
if extractions.is_err() { if extractions.is_err() {
return main(); return init(fetcher);
} }
actor::act(extractions, files, variables)?; actor::act(extractions, files, variables)?;
Ok(()) Ok(())
} }
pub fn get_fetcher() -> Result<Box<dyn Fetcher>> {
match CONFIG.source() {
Source::Cheats(query) => {
let lines = cheatsh::call(&query)?;
let fetcher = Box::new(StaticFetcher::new(lines));
Ok(fetcher)
}
Source::Tldr(query) => {
let lines = cheatsh::call(&query)?;
let fetcher = Box::new(StaticFetcher::new(lines));
Ok(fetcher)
}
Source::Filesystem(path) => {
let fetcher = Box::new(filesystem::Fetcher::new(path));
Ok(fetcher)
}
Source::Welcome => {
let fetcher = Box::new(welcome::Fetcher::new());
Ok(fetcher)
}
}
}
pub fn main() -> Result<()> {
let fetcher = get_fetcher()?;
init(fetcher)
}

View file

@ -1,3 +1,4 @@
use crate::commands::core::get_fetcher;
use crate::common::shell::{self, ShellSpawnError}; use crate::common::shell::{self, ShellSpawnError};
use crate::finder::structures::Opts as FinderOpts; use crate::finder::structures::Opts as FinderOpts;
use crate::parser::Parser; use crate::parser::Parser;
@ -8,7 +9,7 @@ pub fn main() -> Result<()> {
let config = &CONFIG; let config = &CONFIG;
let _opts = FinderOpts::snippet_default(); let _opts = FinderOpts::snippet_default();
let fetcher = config.fetcher(); let fetcher = get_fetcher()?;
let hash: u64 = 2087294461664323320; let hash: u64 = 2087294461664323320;
let mut buf = vec![]; let mut buf = vec![];
@ -50,7 +51,7 @@ pub fn main() -> Result<()> {
pub fn _main0() -> Result<()> { pub fn _main0() -> Result<()> {
let config = &CONFIG; let config = &CONFIG;
let fetcher = config.fetcher(); let fetcher = get_fetcher()?;
let mut stdout = io::stdout(); let mut stdout = io::stdout();
let mut writer: Box<&mut dyn Write> = Box::new(&mut stdout); let mut writer: Box<&mut dyn Write> = Box::new(&mut stdout);

View file

@ -2,15 +2,8 @@ mod cli;
mod env; mod env;
mod yaml; mod yaml;
use crate::clients::cheatsh;
use crate::clients::tldr;
use crate::commands::func::Func; use crate::commands::func::Func;
use crate::config::Source;
use crate::filesystem;
use crate::finder::FinderChoice; use crate::finder::FinderChoice;
use crate::structures::fetcher::Fetcher;
use crate::welcome;
pub use cli::*; pub use cli::*;
use crossterm::style::Color; use crossterm::style::Color;
use env::EnvConfig; use env::EnvConfig;
@ -62,15 +55,6 @@ impl Config {
} }
} }
pub fn fetcher(&self) -> Box<dyn Fetcher> {
match self.source() {
Source::Cheats(query) => Box::new(cheatsh::Fetcher::new(query)),
Source::Tldr(query) => Box::new(tldr::Fetcher::new(query)),
Source::Filesystem(path) => Box::new(filesystem::Fetcher::new(path)),
Source::Welcome => Box::new(welcome::Fetcher::new()),
}
}
pub fn path(&self) -> Option<String> { pub fn path(&self) -> Option<String> {
self.clap self.clap
.path .path

View file

@ -8,3 +8,20 @@ pub trait Fetcher {
vec![] vec![]
} }
} }
pub struct StaticFetcher {
lines: Vec<String>,
}
impl StaticFetcher {
pub fn new(lines: Vec<String>) -> Self {
Self { lines }
}
}
impl Fetcher for StaticFetcher {
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
parser.read_lines(self.lines.clone().into_iter().map(Ok), "static", None)?;
Ok(true)
}
}

View file

@ -4,12 +4,9 @@ use crate::structures::fetcher;
pub fn populate_cheatsheet(parser: &mut Parser) -> Result<()> { pub fn populate_cheatsheet(parser: &mut Parser) -> Result<()> {
let cheatsheet = include_str!("../docs/navi.cheat"); let cheatsheet = include_str!("../docs/navi.cheat");
let lines = cheatsheet.split('\n').into_iter().map(|s| Ok(s.to_string()));
parser.read_lines( parser.read_lines(lines, "welcome", None)?;
cheatsheet.split('\n').into_iter().map(|s| Ok(s.to_string())),
"welcome",
None,
)?;
Ok(()) Ok(())
} }