Add error info to fzf.

This commit is contained in:
Csonka Mihaly 2020-03-21 19:01:58 +01:00
parent a4e2311263
commit 1c22e11e09
3 changed files with 68 additions and 51 deletions

View file

@ -110,14 +110,15 @@ fn prompt_with_suggestions(
let (output, _) = fzf::call(opts, |stdin| {
stdin
.write_all(suggestions.as_bytes())
.expect("Could not write to fzf's stdin");
None
});
.context("Could not write to fzf's stdin")?;
Ok(None)
})
.context("fzf was unable to prompt with suggestions")?;
Ok(output)
}
fn prompt_without_suggestions(variable_name: &str) -> String {
fn prompt_without_suggestions(variable_name: &str) -> Result<String, Error> {
let opts = FzfOpts {
autoselect: false,
prompt: Some(display::variable_prompt(variable_name)),
@ -125,9 +126,10 @@ fn prompt_without_suggestions(variable_name: &str) -> String {
..Default::default()
};
let (output, _) = fzf::call(opts, |_stdin| None);
let (output, _) = fzf::call(opts, |_stdin| Ok(None))
.context("fzf was unable to prompt without suggestions")?;
output
Ok(output)
}
fn replace_variables_from_snippet(
@ -135,7 +137,7 @@ fn replace_variables_from_snippet(
tags: &str,
variables: VariableMap,
config: &Config,
) -> String {
) -> Result<String, Error> {
let mut interpolated_snippet = String::from(snippet);
let mut values: HashMap<String, String> = HashMap::new();
@ -146,15 +148,16 @@ fn replace_variables_from_snippet(
let value = values
.get(variable_name)
.map(|s| s.to_string())
.unwrap_or_else(|| {
.ok_or_else(|| anyhow!(format!("No value for variable {}", variable_name)))
.or_else(|_| {
variables
.get(&tags, &variable_name)
.ok_or_else(|| anyhow!("No suggestions"))
.and_then(|suggestion| {
prompt_with_suggestions(variable_name, &config, suggestion, &values)
})
.unwrap_or_else(|_| prompt_without_suggestions(variable_name))
});
.or_else(|_| prompt_without_suggestions(variable_name))
})?;
values.insert(variable_name.to_string(), value.clone());
@ -162,7 +165,7 @@ fn replace_variables_from_snippet(
interpolated_snippet.replacen(bracketed_variable_name, value.as_str(), 1);
}
interpolated_snippet
Ok(interpolated_snippet)
}
fn with_new_lines(txt: String) -> String {
@ -174,20 +177,24 @@ pub fn main(variant: Variant, config: Config, contains_key: bool) -> Result<(),
let opts = gen_core_fzf_opts(variant, &config).context("Failed to generate fzf options")?;
let (raw_selection, variables) = fzf::call(opts, |stdin| {
Some(
Ok(Some(
parser::read_all(&config, stdin)
.expect("Failed to read all variables intended for fzf"),
)
});
.context("Failed to parse variables intended for fzf")?,
))
})
.context("Failed getting selection and variables from fzf")?;
let (key, tags, snippet) = extract_from_selections(&raw_selection[..], contains_key);
let interpolated_snippet = with_new_lines(replace_variables_from_snippet(
snippet,
tags,
variables.expect("No variables received from fzf"),
&config,
));
let interpolated_snippet = with_new_lines(
replace_variables_from_snippet(
snippet,
tags,
variables.expect("No variables received from fzf"),
&config,
)
.context("Failed to replace variables from snippet")?,
);
// copy to clipboard
if key == "ctrl-y" {

View file

@ -30,9 +30,10 @@ pub fn browse() -> Result<(), Error> {
let (repo, _) = fzf::call(opts, |stdin| {
stdin
.write_all(repos.as_bytes())
.expect("Unable to prompt featured repositories");
None
});
.context("Unable to prompt featured repositories")?;
Ok(None)
})
.context("Failed to get repo URL from fzf")?;
filesystem::remove_dir(&repo_path_str)?;
@ -76,9 +77,10 @@ pub fn add(uri: String) -> Result<(), Error> {
let (files, _) = fzf::call(opts, |stdin| {
stdin
.write_all(all_files.as_bytes())
.expect("Unable to prompt cheats to import");
None
});
.context("Unable to prompt cheats to import")?;
Ok(None)
})
.context("Failed to get cheatsheet files from fzf")?;
for f in files.split('\n') {
let from = format!("{}/{}", tmp_path_str, f).replace("./", "");

View file

@ -1,21 +1,20 @@
use crate::display;
use crate::structures::cheat::VariableMap;
use crate::structures::fzf::{Opts, SuggestionType};
use anyhow::Context;
use anyhow::Error;
use std::process;
use std::process::{Command, Stdio};
fn get_column(text: String, column: Option<u8>, delimiter: Option<&str>) -> String {
if let Some(c) = column {
let mut result = String::from("");
let re = regex::Regex::new(delimiter.unwrap_or(r"\s\s+")).unwrap();
let re = regex::Regex::new(delimiter.unwrap_or(r"\s\s+")).expect("Invalid regex");
for line in text.split('\n') {
if (&line).is_empty() {
continue;
}
let mut parts = re.split(line);
for _ in 0..(c - 1) {
parts.next().unwrap();
}
let mut parts = re.split(line).skip((c - 1) as usize);
if !result.is_empty() {
result.push('\n');
}
@ -27,9 +26,9 @@ fn get_column(text: String, column: Option<u8>, delimiter: Option<&str>) -> Stri
}
}
pub fn call<F>(opts: Opts, stdin_fn: F) -> (String, Option<VariableMap>)
pub fn call<F>(opts: Opts, stdin_fn: F) -> Result<(String, Option<VariableMap>), Error>
where
F: Fn(&mut process::ChildStdin) -> Option<VariableMap>,
F: Fn(&mut process::ChildStdin) -> Result<Option<VariableMap>, Error>,
{
let mut fzf_command = Command::new("fzf");
@ -117,13 +116,18 @@ where
}
};
let stdin = child.stdin.as_mut().unwrap();
let result_map = stdin_fn(stdin);
let stdin = child
.stdin
.as_mut()
.ok_or_else(|| anyhow!("Unable to acquire stdin of fzf"))?;
let result_map = stdin_fn(stdin).context("Failed to pass data to fzf")?;
let out = child.wait_with_output().unwrap();
let out = child.wait_with_output().context("Failed to wait for fzf")?;
let text = match out.status.code() {
Some(0) | Some(1) | Some(2) => String::from_utf8(out.stdout).unwrap(),
Some(0) | Some(1) | Some(2) => {
String::from_utf8(out.stdout).context("Invalid utf8 received from fzf")?
}
Some(130) => process::exit(130),
_ => {
let err = String::from_utf8(out.stderr)
@ -133,17 +137,21 @@ where
};
let out = get_column(
parse_output_single(text, opts.suggestion_type),
parse_output_single(text, opts.suggestion_type)?,
opts.column,
opts.delimiter.as_deref(),
);
(out, result_map)
Ok((out, result_map))
}
fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> String {
match suggestion_type {
SuggestionType::SingleSelection => text.lines().next().unwrap().to_string(),
fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> Result<String, Error> {
Ok(match suggestion_type {
SuggestionType::SingleSelection => text
.lines()
.next()
.context("Not sufficient data for single selection")?
.to_string(),
SuggestionType::MultipleSelections
| SuggestionType::Disabled
| SuggestionType::SnippetSelection => {
@ -175,7 +183,7 @@ fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> Str
_ => "".to_string(),
}
}
}
})
}
#[cfg(test)]
@ -185,49 +193,49 @@ mod tests {
#[test]
fn test_parse_output1() {
let text = "palo\n".to_string();
let output = parse_output_single(text, SuggestionType::SingleSelection);
let output = parse_output_single(text, SuggestionType::SingleSelection).unwrap();
assert_eq!(output, "palo");
}
#[test]
fn test_parse_output2() {
let text = "\nenter\npalo".to_string();
let output = parse_output_single(text, SuggestionType::SingleRecommendation);
let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
assert_eq!(output, "palo");
}
#[test]
fn test_parse_recommendation_output_1() {
let text = "\nenter\npalo".to_string();
let output = parse_output_single(text, SuggestionType::SingleRecommendation);
let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
assert_eq!(output, "palo");
}
#[test]
fn test_parse_recommendation_output_2() {
let text = "p\nenter\npalo".to_string();
let output = parse_output_single(text, SuggestionType::SingleRecommendation);
let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
assert_eq!(output, "palo");
}
#[test]
fn test_parse_recommendation_output_3() {
let text = "peter\nenter\n".to_string();
let output = parse_output_single(text, SuggestionType::SingleRecommendation);
let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
assert_eq!(output, "peter");
}
#[test]
fn test_parse_output3() {
let text = "p\ntab\npalo".to_string();
let output = parse_output_single(text, SuggestionType::SingleRecommendation);
let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
assert_eq!(output, "p");
}
#[test]
fn test_parse_snippet_request() {
let text = "enter\nssh login to a server and forward to ssh key (d… ssh -A <user>@<server> ssh login to a server and forward to ssh key (dangerous but usefull for bastion hosts) ssh -A <user>@<server> \n".to_string();
let output = parse_output_single(text, SuggestionType::SnippetSelection);
let output = parse_output_single(text, SuggestionType::SnippetSelection).unwrap();
assert_eq!(output, "enter\nssh login to a server and forward to ssh key (d… ssh -A <user>@<server> ssh login to a server and forward to ssh key (dangerous but usefull for bastion hosts) ssh -A <user>@<server> ");
}
}