mirror of
https://github.com/denisidoro/navi
synced 2024-11-21 19:13:07 +00:00
Fix Ctrl-O behavior when snippet has variables (#437)
This commit is contained in:
parent
8e324d5bd1
commit
ec6596fe76
21 changed files with 332 additions and 88 deletions
|
@ -1 +1 @@
|
||||||
max_width = 150
|
max_width = 110
|
||||||
|
|
|
@ -24,17 +24,34 @@ fn lines(query: &str, markdown: &str) -> impl Iterator<Item = Result<String, Err
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_all(query: &str, cheat: &str, stdin: &mut std::process::ChildStdin, writer: &mut dyn Writer) -> Result<Option<VariableMap>, Error> {
|
fn read_all(
|
||||||
|
query: &str,
|
||||||
|
cheat: &str,
|
||||||
|
stdin: &mut std::process::ChildStdin,
|
||||||
|
writer: &mut dyn Writer,
|
||||||
|
) -> Result<Option<VariableMap>, Error> {
|
||||||
let mut variables = VariableMap::new();
|
let mut variables = VariableMap::new();
|
||||||
let mut visited_lines = HashSet::new();
|
let mut visited_lines = HashSet::new();
|
||||||
parser::read_lines(lines(query, cheat), "cheat.sh", 0, &mut variables, &mut visited_lines, writer, stdin)?;
|
parser::read_lines(
|
||||||
|
lines(query, cheat),
|
||||||
|
"cheat.sh",
|
||||||
|
0,
|
||||||
|
&mut variables,
|
||||||
|
&mut visited_lines,
|
||||||
|
writer,
|
||||||
|
stdin,
|
||||||
|
)?;
|
||||||
Ok(Some(variables))
|
Ok(Some(variables))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch(query: &str) -> Result<String, Error> {
|
pub fn fetch(query: &str) -> Result<String, Error> {
|
||||||
let args = ["-qO-", &format!("cheat.sh/{}", query)];
|
let args = ["-qO-", &format!("cheat.sh/{}", query)];
|
||||||
|
|
||||||
let child = Command::new("wget").args(&args).stdin(Stdio::piped()).stdout(Stdio::piped()).spawn();
|
let child = Command::new("wget")
|
||||||
|
.args(&args)
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn();
|
||||||
|
|
||||||
let child = match child {
|
let child = match child {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
|
@ -85,7 +102,12 @@ impl Fetcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fetcher::Fetcher for Fetcher {
|
impl fetcher::Fetcher for Fetcher {
|
||||||
fn fetch(&self, stdin: &mut std::process::ChildStdin, writer: &mut dyn Writer, _files: &mut Vec<String>) -> Result<Option<VariableMap>, Error> {
|
fn fetch(
|
||||||
|
&self,
|
||||||
|
stdin: &mut std::process::ChildStdin,
|
||||||
|
writer: &mut dyn Writer,
|
||||||
|
_files: &mut Vec<String>,
|
||||||
|
) -> Result<Option<VariableMap>, Error> {
|
||||||
let cheat = fetch(&self.query)?;
|
let cheat = fetch(&self.query)?;
|
||||||
read_all(&self.query, &cheat, stdin, writer)
|
read_all(&self.query, &cheat, stdin, writer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,10 @@ use std::env;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
pub fn main(config: Config) -> Result<(), Error> {
|
pub fn main(config: Config) -> Result<(), Error> {
|
||||||
let mut child = Command::new("cat").stdin(Stdio::piped()).spawn().context("Unable to create child")?;
|
let mut child = Command::new("cat")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.context("Unable to create child")?;
|
||||||
let stdin = child.stdin.as_mut().context("Unable to get stdin")?;
|
let stdin = child.stdin.as_mut().context("Unable to get stdin")?;
|
||||||
let mut writer = display::alfred::Writer::new();
|
let mut writer = display::alfred::Writer::new();
|
||||||
|
|
||||||
|
@ -38,7 +41,12 @@ fn prompt_finder(suggestion: &Suggestion) -> Result<String, Error> {
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| BashSpawnError::new(suggestion_command, e))?;
|
.map_err(|e| BashSpawnError::new(suggestion_command, e))?;
|
||||||
|
|
||||||
let suggestions = String::from_utf8(child.wait_with_output().context("Failed to wait and collect output from bash")?.stdout)
|
let suggestions = String::from_utf8(
|
||||||
|
child
|
||||||
|
.wait_with_output()
|
||||||
|
.context("Failed to wait and collect output from bash")?
|
||||||
|
.stdout,
|
||||||
|
)
|
||||||
.context("Suggestions are invalid utf8")?;
|
.context("Suggestions are invalid utf8")?;
|
||||||
|
|
||||||
Ok(suggestions)
|
Ok(suggestions)
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::tldr;
|
||||||
use crate::welcome;
|
use crate::welcome;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use edit;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
@ -32,23 +32,40 @@ fn gen_core_finder_opts(config: &Config) -> Result<FinderOpts, Error> {
|
||||||
autoselect: config.autoselect(),
|
autoselect: config.autoselect(),
|
||||||
overrides: config.fzf_overrides.clone(),
|
overrides: config.fzf_overrides.clone(),
|
||||||
suggestion_type: SuggestionType::SnippetSelection,
|
suggestion_type: SuggestionType::SnippetSelection,
|
||||||
query: if config.get_best_match() { None } else { config.get_query() },
|
query: if config.get_best_match() {
|
||||||
filter: if config.get_best_match() { config.get_query() } else { None },
|
None
|
||||||
|
} else {
|
||||||
|
config.get_query()
|
||||||
|
},
|
||||||
|
filter: if config.get_best_match() {
|
||||||
|
config.get_query()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(opts)
|
Ok(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_from_selections(raw_snippet: &str, is_single: bool) -> Result<(&str, &str, &str, &str, Option<usize>), Error> {
|
fn extract_from_selections(
|
||||||
|
raw_snippet: &str,
|
||||||
|
is_single: bool,
|
||||||
|
) -> Result<(&str, &str, &str, &str, Option<usize>), Error> {
|
||||||
let mut lines = raw_snippet.split('\n');
|
let mut lines = raw_snippet.split('\n');
|
||||||
let key = if is_single {
|
let key = if is_single {
|
||||||
"enter"
|
"enter"
|
||||||
} else {
|
} else {
|
||||||
lines.next().context("Key was promised but not present in `selections`")?
|
lines
|
||||||
|
.next()
|
||||||
|
.context("Key was promised but not present in `selections`")?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut parts = lines.next().context("No more parts in `selections`")?.split(display::DELIMITER).skip(3);
|
let mut parts = lines
|
||||||
|
.next()
|
||||||
|
.context("No more parts in `selections`")?
|
||||||
|
.split(display::DELIMITER)
|
||||||
|
.skip(3);
|
||||||
|
|
||||||
let tags = parts.next().unwrap_or("");
|
let tags = parts.next().unwrap_or("");
|
||||||
let comment = parts.next().unwrap_or("");
|
let comment = parts.next().unwrap_or("");
|
||||||
|
@ -57,7 +74,12 @@ fn extract_from_selections(raw_snippet: &str, is_single: bool) -> Result<(&str,
|
||||||
Ok((key, tags, comment, snippet, file_index))
|
Ok((key, tags, comment, snippet, file_index))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt_finder(variable_name: &str, config: &Config, suggestion: Option<&Suggestion>, variable_count: usize) -> Result<String, Error> {
|
fn prompt_finder(
|
||||||
|
variable_name: &str,
|
||||||
|
config: &Config,
|
||||||
|
suggestion: Option<&Suggestion>,
|
||||||
|
variable_count: usize,
|
||||||
|
) -> Result<String, Error> {
|
||||||
env::remove_var(env_vars::PREVIEW_COLUMN);
|
env::remove_var(env_vars::PREVIEW_COLUMN);
|
||||||
env::remove_var(env_vars::PREVIEW_DELIMITER);
|
env::remove_var(env_vars::PREVIEW_DELIMITER);
|
||||||
env::remove_var(env_vars::PREVIEW_MAP);
|
env::remove_var(env_vars::PREVIEW_MAP);
|
||||||
|
@ -89,7 +111,12 @@ fn prompt_finder(variable_name: &str, config: &Config, suggestion: Option<&Sugge
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| BashSpawnError::new(suggestion_command, e))?;
|
.map_err(|e| BashSpawnError::new(suggestion_command, e))?;
|
||||||
|
|
||||||
let text = String::from_utf8(child.wait_with_output().context("Failed to wait and collect output from bash")?.stdout)
|
let text = String::from_utf8(
|
||||||
|
child
|
||||||
|
.wait_with_output()
|
||||||
|
.context("Failed to wait and collect output from bash")?
|
||||||
|
.stdout,
|
||||||
|
)
|
||||||
.context("Suggestions are invalid utf8")?;
|
.context("Suggestions are invalid utf8")?;
|
||||||
|
|
||||||
(text, suggestion_opts)
|
(text, suggestion_opts)
|
||||||
|
@ -138,7 +165,9 @@ NAVIEOF
|
||||||
let (output, _) = config
|
let (output, _) = config
|
||||||
.finder
|
.finder
|
||||||
.call(opts, &mut Vec::new(), |stdin, _| {
|
.call(opts, &mut Vec::new(), |stdin, _| {
|
||||||
stdin.write_all(suggestions.as_bytes()).context("Could not write to finder's stdin")?;
|
stdin
|
||||||
|
.write_all(suggestions.as_bytes())
|
||||||
|
.context("Could not write to finder's stdin")?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
})
|
})
|
||||||
.context("finder was unable to prompt with suggestions")?;
|
.context("finder was unable to prompt with suggestions")?;
|
||||||
|
@ -153,9 +182,17 @@ fn unique_result_count(results: &[&str]) -> usize {
|
||||||
vars.len()
|
vars.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_variables_from_snippet(snippet: &str, tags: &str, variables: VariableMap, config: &Config) -> Result<String, Error> {
|
fn replace_variables_from_snippet(
|
||||||
|
snippet: &str,
|
||||||
|
tags: &str,
|
||||||
|
variables: VariableMap,
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<String, Error> {
|
||||||
let mut interpolated_snippet = String::from(snippet);
|
let mut interpolated_snippet = String::from(snippet);
|
||||||
let variables_found: Vec<&str> = display::VAR_REGEX.find_iter(snippet).map(|m| m.as_str()).collect();
|
let variables_found: Vec<&str> = display::VAR_REGEX
|
||||||
|
.find_iter(snippet)
|
||||||
|
.map(|m| m.as_str())
|
||||||
|
.collect();
|
||||||
let variable_count = unique_result_count(&variables_found);
|
let variable_count = unique_result_count(&variables_found);
|
||||||
|
|
||||||
for bracketed_variable_name in variables_found {
|
for bracketed_variable_name in variables_found {
|
||||||
|
@ -167,7 +204,8 @@ fn replace_variables_from_snippet(snippet: &str, tags: &str, variables: Variable
|
||||||
e
|
e
|
||||||
} else if let Some(suggestion) = variables.get_suggestion(&tags, &variable_name) {
|
} else if let Some(suggestion) = variables.get_suggestion(&tags, &variable_name) {
|
||||||
let mut new_suggestion = suggestion.clone();
|
let mut new_suggestion = suggestion.clone();
|
||||||
new_suggestion.0 = replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone(), config)?;
|
new_suggestion.0 =
|
||||||
|
replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone(), config)?;
|
||||||
prompt_finder(variable_name, &config, Some(&new_suggestion), variable_count)?
|
prompt_finder(variable_name, &config, Some(&new_suggestion), variable_count)?
|
||||||
} else {
|
} else {
|
||||||
prompt_finder(variable_name, &config, None, variable_count)?
|
prompt_finder(variable_name, &config, None, variable_count)?
|
||||||
|
@ -214,14 +252,26 @@ pub fn main(config: Config) -> Result<(), Error> {
|
||||||
})
|
})
|
||||||
.context("Failed getting selection and variables from finder")?;
|
.context("Failed getting selection and variables from finder")?;
|
||||||
|
|
||||||
let (key, tags, comment, snippet, file_index) = extract_from_selections(&raw_selection, config.get_best_match())?;
|
let (key, tags, comment, snippet, file_index) =
|
||||||
|
extract_from_selections(&raw_selection, config.get_best_match())?;
|
||||||
|
|
||||||
|
if key == "ctrl-o" {
|
||||||
|
edit::edit_file(Path::new(&files[file_index.expect("No files found")]))
|
||||||
|
.expect("Cound not open file in external editor");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
env::set_var(env_vars::PREVIEW_INITIAL_SNIPPET, &snippet);
|
env::set_var(env_vars::PREVIEW_INITIAL_SNIPPET, &snippet);
|
||||||
env::set_var(env_vars::PREVIEW_TAGS, &tags);
|
env::set_var(env_vars::PREVIEW_TAGS, &tags);
|
||||||
env::set_var(env_vars::PREVIEW_COMMENT, &comment);
|
env::set_var(env_vars::PREVIEW_COMMENT, &comment);
|
||||||
|
|
||||||
let interpolated_snippet = display::with_new_lines(
|
let interpolated_snippet = display::with_new_lines(
|
||||||
replace_variables_from_snippet(snippet, tags, variables.expect("No variables received from finder"), &config)
|
replace_variables_from_snippet(
|
||||||
|
snippet,
|
||||||
|
tags,
|
||||||
|
variables.expect("No variables received from finder"),
|
||||||
|
&config,
|
||||||
|
)
|
||||||
.context("Failed to replace variables from snippet")?,
|
.context("Failed to replace variables from snippet")?,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -232,12 +282,11 @@ pub fn main(config: Config) -> Result<(), Error> {
|
||||||
Action::SAVE(filepath) => {
|
Action::SAVE(filepath) => {
|
||||||
fs::write(filepath, interpolated_snippet).context("Unable to save output")?;
|
fs::write(filepath, interpolated_snippet).context("Unable to save output")?;
|
||||||
}
|
}
|
||||||
Action::EXECUTE => {
|
Action::EXECUTE => match key {
|
||||||
if key == "ctrl-y" {
|
"ctrl-y" => {
|
||||||
clipboard::copy(interpolated_snippet)?;
|
clipboard::copy(interpolated_snippet)?;
|
||||||
} else if key == "ctrl-o" {
|
}
|
||||||
edit::edit_file(Path::new(&files[file_index.expect("No files found")])).expect("Cound not open file in external editor");
|
_ => {
|
||||||
} else {
|
|
||||||
Command::new("bash")
|
Command::new("bash")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(&interpolated_snippet[..])
|
.arg(&interpolated_snippet[..])
|
||||||
|
@ -246,7 +295,7 @@ pub fn main(config: Config) -> Result<(), Error> {
|
||||||
.wait()
|
.wait()
|
||||||
.context("bash was not running")?;
|
.context("bash was not running")?;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -12,6 +12,8 @@ pub enum Func {
|
||||||
pub fn main(func: &Func, args: Vec<String>) -> Result<(), Error> {
|
pub fn main(func: &Func, args: Vec<String>) -> Result<(), Error> {
|
||||||
match func {
|
match func {
|
||||||
Func::UrlOpen => url::open(args),
|
Func::UrlOpen => url::open(args),
|
||||||
Func::Welcome => handler::handle_config(config::config_from_iter("navi --path /tmp/navi/irrelevant".split(' ').collect())),
|
Func::Welcome => handler::handle_config(config::config_from_iter(
|
||||||
|
"navi --path /tmp/navi/irrelevant".split(' ').collect(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,11 @@ pub fn browse(finder: &FinderChoice) -> Result<(), Error> {
|
||||||
filesystem::create_dir(&repo_path_str)?;
|
filesystem::create_dir(&repo_path_str)?;
|
||||||
|
|
||||||
let (repo_url, _, _) = git::meta("denisidoro/cheats");
|
let (repo_url, _, _) = git::meta("denisidoro/cheats");
|
||||||
git::shallow_clone(repo_url.as_str(), &repo_path_str).with_context(|| format!("Failed to clone `{}`", repo_url))?;
|
git::shallow_clone(repo_url.as_str(), &repo_path_str)
|
||||||
|
.with_context(|| format!("Failed to clone `{}`", repo_url))?;
|
||||||
|
|
||||||
let repos = fs::read_to_string(format!("{}/featured_repos.txt", &repo_path_str)).context("Unable to fetch featured repositories")?;
|
let repos = fs::read_to_string(format!("{}/featured_repos.txt", &repo_path_str))
|
||||||
|
.context("Unable to fetch featured repositories")?;
|
||||||
|
|
||||||
let opts = FinderOpts {
|
let opts = FinderOpts {
|
||||||
column: Some(1),
|
column: Some(1),
|
||||||
|
@ -26,7 +28,9 @@ pub fn browse(finder: &FinderChoice) -> Result<(), Error> {
|
||||||
|
|
||||||
let (repo, _) = finder
|
let (repo, _) = finder
|
||||||
.call(opts, &mut Vec::new(), |stdin, _| {
|
.call(opts, &mut Vec::new(), |stdin, _| {
|
||||||
stdin.write_all(repos.as_bytes()).context("Unable to prompt featured repositories")?;
|
stdin
|
||||||
|
.write_all(repos.as_bytes())
|
||||||
|
.context("Unable to prompt featured repositories")?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
})
|
})
|
||||||
.context("Failed to get repo URL from finder")?;
|
.context("Failed to get repo URL from finder")?;
|
||||||
|
@ -45,7 +49,9 @@ pub fn ask_if_should_import_all(finder: &FinderChoice) -> Result<bool, Error> {
|
||||||
|
|
||||||
let (response, _) = finder
|
let (response, _) = finder
|
||||||
.call(opts, &mut Vec::new(), |stdin, _| {
|
.call(opts, &mut Vec::new(), |stdin, _| {
|
||||||
stdin.write_all(b"Yes\nNo").context("Unable to writer alternatives")?;
|
stdin
|
||||||
|
.write_all(b"Yes\nNo")
|
||||||
|
.context("Unable to writer alternatives")?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
})
|
})
|
||||||
.context("Unable to get response")?;
|
.context("Unable to get response")?;
|
||||||
|
@ -69,7 +75,8 @@ pub fn add(uri: String, finder: &FinderChoice) -> Result<(), Error> {
|
||||||
|
|
||||||
eprintln!("Cloning {} into {}...\n", &actual_uri, &tmp_path_str);
|
eprintln!("Cloning {} into {}...\n", &actual_uri, &tmp_path_str);
|
||||||
|
|
||||||
git::shallow_clone(actual_uri.as_str(), &tmp_path_str).with_context(|| format!("Failed to clone `{}`", actual_uri))?;
|
git::shallow_clone(actual_uri.as_str(), &tmp_path_str)
|
||||||
|
.with_context(|| format!("Failed to clone `{}`", actual_uri))?;
|
||||||
|
|
||||||
let all_files = filesystem::all_cheat_files(&tmp_path_str).join("\n");
|
let all_files = filesystem::all_cheat_files(&tmp_path_str).join("\n");
|
||||||
|
|
||||||
|
@ -86,7 +93,9 @@ pub fn add(uri: String, finder: &FinderChoice) -> Result<(), Error> {
|
||||||
} else {
|
} else {
|
||||||
let (files, _) = finder
|
let (files, _) = finder
|
||||||
.call(opts, &mut Vec::new(), |stdin, _| {
|
.call(opts, &mut Vec::new(), |stdin, _| {
|
||||||
stdin.write_all(all_files.as_bytes()).context("Unable to prompt cheats to import")?;
|
stdin
|
||||||
|
.write_all(all_files.as_bytes())
|
||||||
|
.context("Unable to prompt cheats to import")?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
})
|
})
|
||||||
.context("Failed to get cheatsheet files from finder")?;
|
.context("Failed to get cheatsheet files from finder")?;
|
||||||
|
|
|
@ -16,6 +16,8 @@ impl FileAnIssue {
|
||||||
where
|
where
|
||||||
SourceError: Into<anyhow::Error>,
|
SourceError: Into<anyhow::Error>,
|
||||||
{
|
{
|
||||||
FileAnIssue { source: source.into() }
|
FileAnIssue {
|
||||||
|
source: source.into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,9 @@ where
|
||||||
P: AsRef<Path> + Display + Copy,
|
P: AsRef<Path> + Display + Copy,
|
||||||
{
|
{
|
||||||
let file = File::open(filename).with_context(|| format!("Failed to open file {}", filename))?;
|
let file = File::open(filename).with_context(|| format!("Failed to open file {}", filename))?;
|
||||||
Ok(io::BufReader::new(file).lines().map(|line| line.map_err(Error::from)))
|
Ok(io::BufReader::new(file)
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.map_err(Error::from)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pathbuf_to_string(pathbuf: PathBuf) -> Result<String, Error> {
|
pub fn pathbuf_to_string(pathbuf: PathBuf) -> Result<String, Error> {
|
||||||
|
@ -37,10 +39,18 @@ pub fn pathbuf_to_string(pathbuf: PathBuf) -> Result<String, Error> {
|
||||||
fn follow_symlink(pathbuf: PathBuf) -> Result<PathBuf, Error> {
|
fn follow_symlink(pathbuf: PathBuf) -> Result<PathBuf, Error> {
|
||||||
fs::read_link(pathbuf.clone())
|
fs::read_link(pathbuf.clone())
|
||||||
.map(|o| {
|
.map(|o| {
|
||||||
let o_str = o.as_os_str().to_str().ok_or_else(|| InvalidPath(o.to_path_buf()))?;
|
let o_str = o
|
||||||
|
.as_os_str()
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| InvalidPath(o.to_path_buf()))?;
|
||||||
if o_str.starts_with('.') {
|
if o_str.starts_with('.') {
|
||||||
let parent = pathbuf.parent().ok_or_else(|| anyhow!("`{}` has no parent", pathbuf.display()))?;
|
let parent = pathbuf
|
||||||
let parent_str = parent.as_os_str().to_str().ok_or_else(|| InvalidPath(parent.to_path_buf()))?;
|
.parent()
|
||||||
|
.ok_or_else(|| anyhow!("`{}` has no parent", pathbuf.display()))?;
|
||||||
|
let parent_str = parent
|
||||||
|
.as_os_str()
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| InvalidPath(parent.to_path_buf()))?;
|
||||||
let path_str = format!("{}/{}", parent_str, o_str);
|
let path_str = format!("{}/{}", parent_str, o_str);
|
||||||
let p = PathBuf::from(path_str);
|
let p = PathBuf::from(path_str);
|
||||||
follow_symlink(p)
|
follow_symlink(p)
|
||||||
|
|
|
@ -3,7 +3,9 @@ use std::fmt::Debug;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref IS_FISH: bool = env::var("SHELL").unwrap_or_else(|_| "".to_string()).contains(&"fish");
|
pub static ref IS_FISH: bool = env::var("SHELL")
|
||||||
|
.unwrap_or_else(|_| "".to_string())
|
||||||
|
.contains(&"fish");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -3,7 +3,10 @@ use anyhow::Error;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
pub fn open(args: Vec<String>) -> Result<(), Error> {
|
pub fn open(args: Vec<String>) -> Result<(), Error> {
|
||||||
let url = args.into_iter().next().ok_or_else(|| anyhow!("No URL specified"))?;
|
let url = args
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("No URL specified"))?;
|
||||||
let code = r#"
|
let code = r#"
|
||||||
exst() {
|
exst() {
|
||||||
type "$1" &>/dev/null
|
type "$1" &>/dev/null
|
||||||
|
|
|
@ -6,7 +6,9 @@ pub struct Writer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn escape_for_json(txt: &str) -> String {
|
fn escape_for_json(txt: &str) -> String {
|
||||||
txt.replace('\\', "\\\\").replace('"', "“").replace(display::NEWLINE_ESCAPE_CHAR, " ")
|
txt.replace('\\', "\\\\")
|
||||||
|
.replace('"', "“")
|
||||||
|
.replace(display::NEWLINE_ESCAPE_CHAR, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_items_start(varname: Option<&str>) {
|
pub fn print_items_start(varname: Option<&str>) {
|
||||||
|
|
|
@ -19,7 +19,9 @@ pub fn with_new_lines(txt: String) -> String {
|
||||||
|
|
||||||
pub fn fix_newlines(txt: &str) -> String {
|
pub fn fix_newlines(txt: &str) -> String {
|
||||||
if txt.contains(NEWLINE_ESCAPE_CHAR) {
|
if txt.contains(NEWLINE_ESCAPE_CHAR) {
|
||||||
(*NEWLINE_REGEX).replace_all(txt.replace(LINE_SEPARATOR, " ").as_str(), "").to_string()
|
(*NEWLINE_REGEX)
|
||||||
|
.replace_all(txt.replace(LINE_SEPARATOR, " ").as_str(), "")
|
||||||
|
.to_string()
|
||||||
} else {
|
} else {
|
||||||
txt.to_string()
|
txt.to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,12 @@ pub fn parse_env_var<T: FromStr>(varname: &str) -> Option<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref TAG_COLOR: color::AnsiValue = color::AnsiValue(parse_env_var(env_vars::TAG_COLOR).unwrap_or(14));
|
pub static ref TAG_COLOR: color::AnsiValue =
|
||||||
pub static ref COMMENT_COLOR: color::AnsiValue = color::AnsiValue(parse_env_var(env_vars::COMMENT_COLOR).unwrap_or(4));
|
color::AnsiValue(parse_env_var(env_vars::TAG_COLOR).unwrap_or(14));
|
||||||
pub static ref SNIPPET_COLOR: color::AnsiValue = color::AnsiValue(parse_env_var(env_vars::SNIPPET_COLOR).unwrap_or(7));
|
pub static ref COMMENT_COLOR: color::AnsiValue =
|
||||||
|
color::AnsiValue(parse_env_var(env_vars::COMMENT_COLOR).unwrap_or(4));
|
||||||
|
pub static ref SNIPPET_COLOR: color::AnsiValue =
|
||||||
|
color::AnsiValue(parse_env_var(env_vars::SNIPPET_COLOR).unwrap_or(7));
|
||||||
pub static ref TAG_WIDTH_PERCENTAGE: u16 = parse_env_var(env_vars::TAG_WIDTH).unwrap_or(20);
|
pub static ref TAG_WIDTH_PERCENTAGE: u16 = parse_env_var(env_vars::TAG_WIDTH).unwrap_or(20);
|
||||||
pub static ref COMMENT_WIDTH_PERCENTAGE: u16 = parse_env_var(env_vars::COMMENT_WIDTH).unwrap_or(40);
|
pub static ref COMMENT_WIDTH_PERCENTAGE: u16 = parse_env_var(env_vars::COMMENT_WIDTH).unwrap_or(40);
|
||||||
}
|
}
|
||||||
|
@ -106,7 +109,10 @@ pub fn preview_var(selection: &str, query: &str, variable: &str) {
|
||||||
color = variable_color,
|
color = variable_color,
|
||||||
variable = variable_name,
|
variable = variable_name,
|
||||||
reset = reset,
|
reset = reset,
|
||||||
value = wrapped_by_map(&finder::get_column(value, column, delimiter.as_deref()), map.as_deref())
|
value = wrapped_by_map(
|
||||||
|
&finder::get_column(value, column, delimiter.as_deref()),
|
||||||
|
map.as_deref()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +151,10 @@ pub struct Writer {
|
||||||
impl Writer {
|
impl Writer {
|
||||||
pub fn new() -> Writer {
|
pub fn new() -> Writer {
|
||||||
let (tag_width, comment_width) = get_widths();
|
let (tag_width, comment_width) = get_widths();
|
||||||
display::terminal::Writer { tag_width, comment_width }
|
display::terminal::Writer {
|
||||||
|
tag_width,
|
||||||
|
comment_width,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,12 @@ pub fn cheat_paths(path: Option<String>) -> Result<String, Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_all(path: Option<String>, files: &mut Vec<String>, stdin: &mut std::process::ChildStdin, writer: &mut dyn Writer) -> Result<Option<VariableMap>, Error> {
|
pub fn read_all(
|
||||||
|
path: Option<String>,
|
||||||
|
files: &mut Vec<String>,
|
||||||
|
stdin: &mut std::process::ChildStdin,
|
||||||
|
writer: &mut dyn Writer,
|
||||||
|
) -> Result<Option<VariableMap>, Error> {
|
||||||
let mut variables = VariableMap::new();
|
let mut variables = VariableMap::new();
|
||||||
let mut found_something = false;
|
let mut found_something = false;
|
||||||
let mut visited_lines = HashSet::new();
|
let mut visited_lines = HashSet::new();
|
||||||
|
@ -75,7 +80,18 @@ pub fn read_all(path: Option<String>, files: &mut Vec<String>, stdin: &mut std::
|
||||||
for file in all_cheat_files(folder) {
|
for file in all_cheat_files(folder) {
|
||||||
let full_filename = format!("{}/{}", &folder, &file);
|
let full_filename = format!("{}/{}", &folder, &file);
|
||||||
files.push(full_filename.clone());
|
files.push(full_filename.clone());
|
||||||
if read_file(&full_filename, files.len()-1, &mut variables, &mut visited_lines, writer, stdin).is_ok() && !found_something {
|
let index = files.len() - 1;
|
||||||
|
if read_file(
|
||||||
|
&full_filename,
|
||||||
|
index,
|
||||||
|
&mut variables,
|
||||||
|
&mut visited_lines,
|
||||||
|
writer,
|
||||||
|
stdin,
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
&& !found_something
|
||||||
|
{
|
||||||
found_something = true
|
found_something = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,11 +115,23 @@ mod tests {
|
||||||
fn test_read_file() {
|
fn test_read_file() {
|
||||||
let path = "tests/cheats/ssh.cheat";
|
let path = "tests/cheats/ssh.cheat";
|
||||||
let mut variables = VariableMap::new();
|
let mut variables = VariableMap::new();
|
||||||
let mut child = Command::new("cat").stdin(Stdio::piped()).stdout(Stdio::null()).spawn().unwrap();
|
let mut child = Command::new("cat")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
let child_stdin = child.stdin.as_mut().unwrap();
|
let child_stdin = child.stdin.as_mut().unwrap();
|
||||||
let mut visited_lines: HashSet<u64> = HashSet::new();
|
let mut visited_lines: HashSet<u64> = HashSet::new();
|
||||||
let mut writer: Box<dyn Writer> = Box::new(display::terminal::Writer::new());
|
let mut writer: Box<dyn Writer> = Box::new(display::terminal::Writer::new());
|
||||||
read_file(path, 0, &mut variables, &mut visited_lines, &mut *writer, child_stdin).unwrap();
|
read_file(
|
||||||
|
path,
|
||||||
|
0,
|
||||||
|
&mut variables,
|
||||||
|
&mut visited_lines,
|
||||||
|
&mut *writer,
|
||||||
|
child_stdin,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let expected_suggestion = (
|
let expected_suggestion = (
|
||||||
r#" echo -e "$(whoami)\nroot" "#.to_string(),
|
r#" echo -e "$(whoami)\nroot" "#.to_string(),
|
||||||
Some(FinderOpts {
|
Some(FinderOpts {
|
||||||
|
|
|
@ -5,5 +5,10 @@ use crate::structures::cheat::VariableMap;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
|
|
||||||
pub trait Fetcher {
|
pub trait Fetcher {
|
||||||
fn fetch(&self, stdin: &mut std::process::ChildStdin, writer: &mut dyn Writer, files: &mut Vec<String>) -> Result<Option<VariableMap>, Error>;
|
fn fetch(
|
||||||
|
&self,
|
||||||
|
stdin: &mut std::process::ChildStdin,
|
||||||
|
writer: &mut dyn Writer,
|
||||||
|
files: &mut Vec<String>,
|
||||||
|
) -> Result<Option<VariableMap>, Error>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
pub use crate::common::filesystem::{create_dir, exe_string, pathbuf_to_string, remove_dir, InvalidPath, UnreadableDir};
|
pub use crate::common::filesystem::{
|
||||||
|
create_dir, exe_string, pathbuf_to_string, remove_dir, InvalidPath, UnreadableDir,
|
||||||
|
};
|
||||||
use crate::display::Writer;
|
use crate::display::Writer;
|
||||||
use crate::fetcher;
|
use crate::fetcher;
|
||||||
pub use crate::fetcher::filesystem::{all_cheat_files, default_cheat_pathbuf, read_all};
|
pub use crate::fetcher::filesystem::{all_cheat_files, default_cheat_pathbuf, read_all};
|
||||||
|
@ -21,7 +23,12 @@ impl Fetcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fetcher::Fetcher for Fetcher {
|
impl fetcher::Fetcher for Fetcher {
|
||||||
fn fetch(&self, stdin: &mut std::process::ChildStdin, writer: &mut dyn Writer, files: &mut Vec<String>) -> Result<Option<VariableMap>, Error> {
|
fn fetch(
|
||||||
|
&self,
|
||||||
|
stdin: &mut std::process::ChildStdin,
|
||||||
|
writer: &mut dyn Writer,
|
||||||
|
files: &mut Vec<String>,
|
||||||
|
) -> Result<Option<VariableMap>, Error> {
|
||||||
read_all(self.path.clone(), files, stdin, writer)
|
read_all(self.path.clone(), files, stdin, writer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,12 @@ pub enum FinderChoice {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Finder {
|
pub trait Finder {
|
||||||
fn call<F>(&self, opts: Opts, files: &mut Vec<String>, stdin_fn: F) -> Result<(String, Option<VariableMap>), Error>
|
fn call<F>(
|
||||||
|
&self,
|
||||||
|
opts: Opts,
|
||||||
|
files: &mut Vec<String>,
|
||||||
|
stdin_fn: F,
|
||||||
|
) -> Result<(String, Option<VariableMap>), Error>
|
||||||
where
|
where
|
||||||
F: Fn(&mut process::ChildStdin, &mut Vec<String>) -> Result<Option<VariableMap>, Error>;
|
F: Fn(&mut process::ChildStdin, &mut Vec<String>) -> Result<Option<VariableMap>, Error>;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +63,11 @@ pub fn get_column(text: String, column: Option<u8>, delimiter: Option<&str>) ->
|
||||||
|
|
||||||
fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> Result<String, Error> {
|
fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> Result<String, Error> {
|
||||||
Ok(match suggestion_type {
|
Ok(match suggestion_type {
|
||||||
SuggestionType::SingleSelection => text.lines().next().context("Not sufficient data for single selection")?.to_string(),
|
SuggestionType::SingleSelection => text
|
||||||
|
.lines()
|
||||||
|
.next()
|
||||||
|
.context("Not sufficient data for single selection")?
|
||||||
|
.to_string(),
|
||||||
SuggestionType::MultipleSelections | SuggestionType::Disabled | SuggestionType::SnippetSelection => {
|
SuggestionType::MultipleSelections | SuggestionType::Disabled | SuggestionType::SnippetSelection => {
|
||||||
let len = text.len();
|
let len = text.len();
|
||||||
if len > 1 {
|
if len > 1 {
|
||||||
|
@ -70,14 +79,18 @@ fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> Res
|
||||||
let lines: Vec<&str> = text.lines().collect();
|
let lines: Vec<&str> = text.lines().collect();
|
||||||
|
|
||||||
match (lines.get(0), lines.get(1), lines.get(2)) {
|
match (lines.get(0), lines.get(1), lines.get(2)) {
|
||||||
(Some(one), Some(termination), Some(two)) if *termination == "enter" || termination.is_empty() => {
|
(Some(one), Some(termination), Some(two))
|
||||||
|
if *termination == "enter" || termination.is_empty() =>
|
||||||
|
{
|
||||||
if two.is_empty() {
|
if two.is_empty() {
|
||||||
(*one).to_string()
|
(*one).to_string()
|
||||||
} else {
|
} else {
|
||||||
(*two).to_string()
|
(*two).to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Some(one), Some(termination), None) if *termination == "enter" || termination.is_empty() => (*one).to_string(),
|
(Some(one), Some(termination), None) if *termination == "enter" || termination.is_empty() => {
|
||||||
|
(*one).to_string()
|
||||||
|
}
|
||||||
(Some(one), Some(termination), _) if *termination == "tab" => (*one).to_string(),
|
(Some(one), Some(termination), _) if *termination == "tab" => (*one).to_string(),
|
||||||
_ => "".to_string(),
|
_ => "".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -87,10 +100,13 @@ fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> Res
|
||||||
|
|
||||||
fn parse(out: Output, opts: Opts) -> Result<String, Error> {
|
fn parse(out: Output, opts: Opts) -> Result<String, Error> {
|
||||||
let text = match out.status.code() {
|
let text = match out.status.code() {
|
||||||
Some(0) | Some(1) | Some(2) => String::from_utf8(out.stdout).context("Invalid utf8 received from finder")?,
|
Some(0) | Some(1) | Some(2) => {
|
||||||
|
String::from_utf8(out.stdout).context("Invalid utf8 received from finder")?
|
||||||
|
}
|
||||||
Some(130) => process::exit(130),
|
Some(130) => process::exit(130),
|
||||||
_ => {
|
_ => {
|
||||||
let err = String::from_utf8(out.stderr).unwrap_or_else(|_| "<stderr contains invalid UTF-8>".to_owned());
|
let err = String::from_utf8(out.stderr)
|
||||||
|
.unwrap_or_else(|_| "<stderr contains invalid UTF-8>".to_owned());
|
||||||
panic!("External command failed:\n {}", err)
|
panic!("External command failed:\n {}", err)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -102,7 +118,12 @@ fn parse(out: Output, opts: Opts) -> Result<String, Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Finder for FinderChoice {
|
impl Finder for FinderChoice {
|
||||||
fn call<F>(&self, finder_opts: Opts, files: &mut Vec<String>, stdin_fn: F) -> Result<(String, Option<VariableMap>), Error>
|
fn call<F>(
|
||||||
|
&self,
|
||||||
|
finder_opts: Opts,
|
||||||
|
files: &mut Vec<String>,
|
||||||
|
stdin_fn: F,
|
||||||
|
) -> Result<(String, Option<VariableMap>), Error>
|
||||||
where
|
where
|
||||||
F: Fn(&mut process::ChildStdin, &mut Vec<String>) -> Result<Option<VariableMap>, Error>,
|
F: Fn(&mut process::ChildStdin, &mut Vec<String>) -> Result<Option<VariableMap>, Error>,
|
||||||
{
|
{
|
||||||
|
@ -189,7 +210,11 @@ impl Finder for FinderChoice {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(o) = opts.overrides {
|
if let Some(o) = opts.overrides {
|
||||||
o.as_str().split(' ').map(|s| s.to_string()).filter(|s| !s.is_empty()).for_each(|s| {
|
o.as_str()
|
||||||
|
.split(' ')
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.for_each(|s| {
|
||||||
command.arg(s);
|
command.arg(s);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -214,7 +239,10 @@ impl Finder for FinderChoice {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let stdin = child.stdin.as_mut().ok_or_else(|| anyhow!("Unable to acquire stdin of finder"))?;
|
let stdin = child
|
||||||
|
.stdin
|
||||||
|
.as_mut()
|
||||||
|
.ok_or_else(|| anyhow!("Unable to acquire stdin of finder"))?;
|
||||||
let result_map = stdin_fn(stdin, files).context("Failed to pass data to finder")?;
|
let result_map = stdin_fn(stdin, files).context("Failed to pass data to finder")?;
|
||||||
|
|
||||||
let out = child.wait_with_output().context("Failed to wait for finder")?;
|
let out = child.wait_with_output().context("Failed to wait for finder")?;
|
||||||
|
|
|
@ -11,17 +11,25 @@ pub fn handle_config(config: Config) -> Result<(), Error> {
|
||||||
Some(c) => match c {
|
Some(c) => match c {
|
||||||
Preview { line } => cmds::preview::main(&line),
|
Preview { line } => cmds::preview::main(&line),
|
||||||
|
|
||||||
PreviewVar { selection, query, variable } => cmds::preview::main_var(&selection, &query, &variable),
|
PreviewVar {
|
||||||
|
selection,
|
||||||
|
query,
|
||||||
|
variable,
|
||||||
|
} => cmds::preview::main_var(&selection, &query, &variable),
|
||||||
|
|
||||||
Widget { shell } => cmds::shell::main(shell).context("Failed to print shell widget code"),
|
Widget { shell } => cmds::shell::main(shell).context("Failed to print shell widget code"),
|
||||||
|
|
||||||
Fn { func, args } => cmds::func::main(func, args.to_vec()).with_context(|| format!("Failed to execute function `{:#?}`", func)),
|
Fn { func, args } => cmds::func::main(func, args.to_vec())
|
||||||
|
.with_context(|| format!("Failed to execute function `{:#?}`", func)),
|
||||||
|
|
||||||
Info { info } => cmds::info::main(info).with_context(|| format!("Failed to fetch info `{:#?}`", info)),
|
Info { info } => {
|
||||||
|
cmds::info::main(info).with_context(|| format!("Failed to fetch info `{:#?}`", info))
|
||||||
|
}
|
||||||
|
|
||||||
Repo { cmd } => match cmd {
|
Repo { cmd } => match cmd {
|
||||||
RepoCommand::Add { uri } => {
|
RepoCommand::Add { uri } => {
|
||||||
cmds::repo::add(uri.clone(), &config.finder).with_context(|| format!("Failed to import cheatsheets from `{}`", uri))?;
|
cmds::repo::add(uri.clone(), &config.finder)
|
||||||
|
.with_context(|| format!("Failed to import cheatsheets from `{}`", uri))?;
|
||||||
cmds::core::main(config)
|
cmds::core::main(config)
|
||||||
}
|
}
|
||||||
RepoCommand::Browse => {
|
RepoCommand::Browse => {
|
||||||
|
@ -31,10 +39,17 @@ pub fn handle_config(config: Config) -> Result<(), Error> {
|
||||||
},
|
},
|
||||||
|
|
||||||
Alfred { cmd } => match cmd {
|
Alfred { cmd } => match cmd {
|
||||||
AlfredCommand::Start => cmds::alfred::main(config).context("Failed to call Alfred starting function"),
|
AlfredCommand::Start => {
|
||||||
AlfredCommand::Suggestions => cmds::alfred::suggestions(config, false).context("Failed to call Alfred suggestion function"),
|
cmds::alfred::main(config).context("Failed to call Alfred starting function")
|
||||||
AlfredCommand::Check => cmds::alfred::suggestions(config, true).context("Failed to call Alfred check function"),
|
}
|
||||||
AlfredCommand::Transform => cmds::alfred::transform().context("Failed to call Alfred transform function"),
|
AlfredCommand::Suggestions => cmds::alfred::suggestions(config, false)
|
||||||
|
.context("Failed to call Alfred suggestion function"),
|
||||||
|
AlfredCommand::Check => {
|
||||||
|
cmds::alfred::suggestions(config, true).context("Failed to call Alfred check function")
|
||||||
|
}
|
||||||
|
AlfredCommand::Transform => {
|
||||||
|
cmds::alfred::transform().context("Failed to call Alfred transform function")
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => cmds::core::main(config),
|
_ => cmds::core::main(config),
|
||||||
|
|
|
@ -45,8 +45,18 @@ fn parse_opts(text: &str) -> Result<FinderOpts, Error> {
|
||||||
.map(|flag_and_value| {
|
.map(|flag_and_value| {
|
||||||
if let [flag, value] = flag_and_value {
|
if let [flag, value] = flag_and_value {
|
||||||
match flag.as_str() {
|
match flag.as_str() {
|
||||||
"--headers" | "--header-lines" => opts.header_lines = value.parse::<u8>().context("Value for `--headers` is invalid u8")?,
|
"--headers" | "--header-lines" => {
|
||||||
"--column" => opts.column = Some(value.parse::<u8>().context("Value for `--column` is invalid u8")?),
|
opts.header_lines = value
|
||||||
|
.parse::<u8>()
|
||||||
|
.context("Value for `--headers` is invalid u8")?
|
||||||
|
}
|
||||||
|
"--column" => {
|
||||||
|
opts.column = Some(
|
||||||
|
value
|
||||||
|
.parse::<u8>()
|
||||||
|
.context("Value for `--column` is invalid u8")?,
|
||||||
|
)
|
||||||
|
}
|
||||||
"--map" => opts.map = Some(value.to_string()),
|
"--map" => opts.map = Some(value.to_string()),
|
||||||
"--delimiter" => opts.delimiter = Some(value.to_string()),
|
"--delimiter" => opts.delimiter = Some(value.to_string()),
|
||||||
"--query" => opts.query = Some(value.to_string()),
|
"--query" => opts.query = Some(value.to_string()),
|
||||||
|
@ -145,7 +155,8 @@ pub fn read_lines(
|
||||||
let mut should_break = false;
|
let mut should_break = false;
|
||||||
|
|
||||||
for (line_nr, line_result) in lines.enumerate() {
|
for (line_nr, line_result) in lines.enumerate() {
|
||||||
let line = line_result.with_context(|| format!("Failed to read line number {} in cheatsheet `{}`", line_nr, id))?;
|
let line = line_result
|
||||||
|
.with_context(|| format!("Failed to read line number {} in cheatsheet `{}`", line_nr, id))?;
|
||||||
|
|
||||||
if should_break {
|
if should_break {
|
||||||
break;
|
break;
|
||||||
|
@ -180,8 +191,13 @@ pub fn read_lines(
|
||||||
else if line.starts_with('$') {
|
else if line.starts_with('$') {
|
||||||
should_break = write_cmd(&tags, &comment, &snippet, &file_index, writer, stdin).is_err();
|
should_break = write_cmd(&tags, &comment, &snippet, &file_index, writer, stdin).is_err();
|
||||||
snippet = String::from("");
|
snippet = String::from("");
|
||||||
let (variable, command, opts) = parse_variable_line(&line)
|
let (variable, command, opts) = parse_variable_line(&line).with_context(|| {
|
||||||
.with_context(|| format!("Failed to parse variable line. See line number {} in cheatsheet `{}`", line_nr + 1, id))?;
|
format!(
|
||||||
|
"Failed to parse variable line. See line number {} in cheatsheet `{}`",
|
||||||
|
line_nr + 1,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
})?;
|
||||||
variables.insert_suggestion(&tags, &variable, (String::from(command), opts));
|
variables.insert_suggestion(&tags, &variable, (String::from(command), opts));
|
||||||
}
|
}
|
||||||
// snippet
|
// snippet
|
||||||
|
@ -212,7 +228,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_variable_line() {
|
fn test_parse_variable_line() {
|
||||||
let (variable, command, command_options) = parse_variable_line("$ user : echo -e \"$(whoami)\\nroot\" --- --prevent-extra").unwrap();
|
let (variable, command, command_options) =
|
||||||
|
parse_variable_line("$ user : echo -e \"$(whoami)\\nroot\" --- --prevent-extra").unwrap();
|
||||||
assert_eq!(command, " echo -e \"$(whoami)\\nroot\" ");
|
assert_eq!(command, " echo -e \"$(whoami)\\nroot\" ");
|
||||||
assert_eq!(variable, "user");
|
assert_eq!(variable, "user");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
14
src/tldr.rs
14
src/tldr.rs
|
@ -60,7 +60,12 @@ fn markdown_lines(query: &str, markdown: &str) -> impl Iterator<Item = Result<St
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_all(query: &str, markdown: &str, stdin: &mut std::process::ChildStdin, writer: &mut dyn Writer) -> Result<Option<VariableMap>, Error> {
|
fn read_all(
|
||||||
|
query: &str,
|
||||||
|
markdown: &str,
|
||||||
|
stdin: &mut std::process::ChildStdin,
|
||||||
|
writer: &mut dyn Writer,
|
||||||
|
) -> Result<Option<VariableMap>, Error> {
|
||||||
let mut variables = VariableMap::new();
|
let mut variables = VariableMap::new();
|
||||||
let mut visited_lines = HashSet::new();
|
let mut visited_lines = HashSet::new();
|
||||||
parser::read_lines(
|
parser::read_lines(
|
||||||
|
@ -145,7 +150,12 @@ impl Fetcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fetcher::Fetcher for Fetcher {
|
impl fetcher::Fetcher for Fetcher {
|
||||||
fn fetch(&self, stdin: &mut std::process::ChildStdin, writer: &mut dyn Writer, _files: &mut Vec<String>) -> Result<Option<VariableMap>, Error> {
|
fn fetch(
|
||||||
|
&self,
|
||||||
|
stdin: &mut std::process::ChildStdin,
|
||||||
|
writer: &mut dyn Writer,
|
||||||
|
_files: &mut Vec<String>,
|
||||||
|
) -> Result<Option<VariableMap>, Error> {
|
||||||
let markdown = fetch(&self.query)?;
|
let markdown = fetch(&self.query)?;
|
||||||
read_all(&self.query, &markdown, stdin, writer)
|
read_all(&self.query, &markdown, stdin, writer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,22 @@ use crate::display::Writer;
|
||||||
use crate::structures::item::Item;
|
use crate::structures::item::Item;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
fn add_msg(tags: &str, comment: &str, snippet: &str, writer: &mut dyn Writer, stdin: &mut std::process::ChildStdin) {
|
fn add_msg(
|
||||||
|
tags: &str,
|
||||||
|
comment: &str,
|
||||||
|
snippet: &str,
|
||||||
|
writer: &mut dyn Writer,
|
||||||
|
stdin: &mut std::process::ChildStdin,
|
||||||
|
) {
|
||||||
let item = Item {
|
let item = Item {
|
||||||
tags: &tags,
|
tags: &tags,
|
||||||
comment: &comment,
|
comment: &comment,
|
||||||
snippet: &snippet,
|
snippet: &snippet,
|
||||||
file_index: &0,
|
file_index: &0,
|
||||||
};
|
};
|
||||||
stdin.write_all(writer.write(item).as_bytes()).expect("Could not write to fzf's stdin");
|
stdin
|
||||||
|
.write_all(writer.write(item).as_bytes())
|
||||||
|
.expect("Could not write to fzf's stdin");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn populate_cheatsheet(writer: &mut dyn Writer, stdin: &mut std::process::ChildStdin) {
|
pub fn populate_cheatsheet(writer: &mut dyn Writer, stdin: &mut std::process::ChildStdin) {
|
||||||
|
@ -20,6 +28,12 @@ pub fn populate_cheatsheet(writer: &mut dyn Writer, stdin: &mut std::process::Ch
|
||||||
writer,
|
writer,
|
||||||
stdin,
|
stdin,
|
||||||
);
|
);
|
||||||
add_msg("cheatsheets", "Browse for cheatsheet repos", "navi repo browse", writer, stdin);
|
add_msg(
|
||||||
|
"cheatsheets",
|
||||||
|
"Browse for cheatsheet repos",
|
||||||
|
"navi repo browse",
|
||||||
|
writer,
|
||||||
|
stdin,
|
||||||
|
);
|
||||||
add_msg("more info", "Read --help message", "navi --help", writer, stdin);
|
add_msg("more info", "Read --help message", "navi --help", writer, stdin);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue