mirror of
https://github.com/denisidoro/navi
synced 2024-11-10 14:04:17 +00:00
Refactor core package (#476)
This commit is contained in:
parent
16a8b27734
commit
3870cd7c5f
4 changed files with 264 additions and 231 deletions
224
src/actor.rs
Normal file
224
src/actor.rs
Normal file
|
@ -0,0 +1,224 @@
|
|||
use crate::clipboard;
|
||||
use crate::display;
|
||||
use crate::env_vars;
|
||||
use crate::extractor;
|
||||
|
||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||
use crate::finder::Finder;
|
||||
use crate::shell::{BashSpawnError, IS_FISH};
|
||||
use crate::structures::cheat::{Suggestion, VariableMap};
|
||||
use crate::structures::config::Action;
|
||||
use crate::structures::config::Config;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Error;
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
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_DELIMITER);
|
||||
env::remove_var(env_vars::PREVIEW_MAP);
|
||||
|
||||
let mut extra_preview = None;
|
||||
|
||||
let (suggestions, opts) = if let Some(s) = suggestion {
|
||||
let (suggestion_command, suggestion_opts) = s;
|
||||
|
||||
if let Some(sopts) = suggestion_opts {
|
||||
if let Some(c) = &sopts.column {
|
||||
env::set_var(env_vars::PREVIEW_COLUMN, c.to_string());
|
||||
}
|
||||
if let Some(d) = &sopts.delimiter {
|
||||
env::set_var(env_vars::PREVIEW_DELIMITER, d);
|
||||
}
|
||||
if let Some(m) = &sopts.map {
|
||||
env::set_var(env_vars::PREVIEW_MAP, m);
|
||||
}
|
||||
if let Some(p) = &sopts.preview {
|
||||
extra_preview = Some(format!(";echo;{}", p));
|
||||
}
|
||||
}
|
||||
|
||||
let child = Command::new("bash")
|
||||
.stdout(Stdio::piped())
|
||||
.arg("-c")
|
||||
.arg(&suggestion_command)
|
||||
.spawn()
|
||||
.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,
|
||||
)
|
||||
.context("Suggestions are invalid utf8")?;
|
||||
|
||||
(text, suggestion_opts)
|
||||
} else {
|
||||
('\n'.to_string(), &None)
|
||||
};
|
||||
|
||||
let mut opts = FinderOpts {
|
||||
overrides: config.fzf_overrides_var.clone(),
|
||||
preview: Some(format!(
|
||||
r#"{prefix}navi preview-var "$(cat <<NAVIEOF
|
||||
{{+}}
|
||||
NAVIEOF
|
||||
)" "$(cat <<NAVIEOF
|
||||
{{q}}
|
||||
NAVIEOF
|
||||
)" "{name}"; {extra}{suffix}"#,
|
||||
prefix = if *IS_FISH { "bash -c '" } else { "" },
|
||||
suffix = if *IS_FISH { "'" } else { "" },
|
||||
name = variable_name,
|
||||
extra = extra_preview.clone().unwrap_or_default()
|
||||
)),
|
||||
..opts.clone().unwrap_or_default()
|
||||
};
|
||||
|
||||
opts.query = env::var(format!("{}__query", variable_name)).ok();
|
||||
|
||||
if let Ok(f) = env::var(format!("{}__best", variable_name)) {
|
||||
opts.filter = Some(f);
|
||||
opts.suggestion_type = SuggestionType::SingleSelection;
|
||||
}
|
||||
|
||||
if opts.preview_window.is_none() {
|
||||
opts.preview_window = Some(if extra_preview.is_none() {
|
||||
format!("up:{}", variable_count + 3)
|
||||
} else {
|
||||
"right:50%".to_string()
|
||||
});
|
||||
}
|
||||
|
||||
if suggestion.is_none() {
|
||||
opts.suggestion_type = SuggestionType::Disabled;
|
||||
};
|
||||
|
||||
let (output, _) = config
|
||||
.finder
|
||||
.call(opts, &mut Vec::new(), |stdin, _| {
|
||||
stdin
|
||||
.write_all(suggestions.as_bytes())
|
||||
.context("Could not write to finder's stdin")?;
|
||||
Ok(None)
|
||||
})
|
||||
.context("finder was unable to prompt with suggestions")?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn unique_result_count(results: &[&str]) -> usize {
|
||||
let mut vars = results.to_owned();
|
||||
vars.sort_unstable();
|
||||
vars.dedup();
|
||||
vars.len()
|
||||
}
|
||||
|
||||
fn replace_variables_from_snippet(
|
||||
snippet: &str,
|
||||
tags: &str,
|
||||
variables: VariableMap,
|
||||
config: &Config,
|
||||
) -> Result<String, Error> {
|
||||
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 variable_count = unique_result_count(&variables_found);
|
||||
|
||||
for bracketed_variable_name in variables_found {
|
||||
let variable_name = &bracketed_variable_name[1..bracketed_variable_name.len() - 1];
|
||||
|
||||
let env_variable_name = variable_name.replace('-', "_");
|
||||
let env_value = env::var(&env_variable_name);
|
||||
|
||||
let value = if let Ok(e) = env_value {
|
||||
e
|
||||
} else if let Some(suggestion) = variables.get_suggestion(&tags, &variable_name) {
|
||||
let mut new_suggestion = suggestion.clone();
|
||||
new_suggestion.0 =
|
||||
replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone(), config)?;
|
||||
prompt_finder(variable_name, &config, Some(&new_suggestion), variable_count)?
|
||||
} else {
|
||||
prompt_finder(variable_name, &config, None, variable_count)?
|
||||
};
|
||||
|
||||
env::set_var(env_variable_name, &value);
|
||||
|
||||
interpolated_snippet = if value.as_str() == "\n" {
|
||||
interpolated_snippet.replacen(bracketed_variable_name, "", 1)
|
||||
} else {
|
||||
interpolated_snippet.replacen(bracketed_variable_name, value.as_str(), 1)
|
||||
};
|
||||
}
|
||||
|
||||
Ok(interpolated_snippet)
|
||||
}
|
||||
|
||||
// TODO: make it depend on less inputs
|
||||
pub fn act(
|
||||
extractions: Result<extractor::Output, Error>,
|
||||
config: Config,
|
||||
files: &mut Vec<String>,
|
||||
variables: Option<VariableMap>,
|
||||
) -> Result<(), Error> {
|
||||
let (key, tags, comment, snippet, file_index) = extractions.unwrap();
|
||||
|
||||
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_TAGS, &tags);
|
||||
env::set_var(env_vars::PREVIEW_COMMENT, &comment);
|
||||
|
||||
let interpolated_snippet = display::with_new_lines(
|
||||
replace_variables_from_snippet(
|
||||
snippet,
|
||||
tags,
|
||||
variables.expect("No variables received from finder"),
|
||||
&config,
|
||||
)
|
||||
.context("Failed to replace variables from snippet")?,
|
||||
);
|
||||
|
||||
match config.action() {
|
||||
Action::Print => {
|
||||
println!("{}", interpolated_snippet);
|
||||
}
|
||||
Action::Save(filepath) => {
|
||||
fs::write(filepath, interpolated_snippet).context("Unable to save output")?;
|
||||
}
|
||||
Action::Execute => match key {
|
||||
"ctrl-y" => {
|
||||
clipboard::copy(interpolated_snippet)?;
|
||||
}
|
||||
_ => {
|
||||
Command::new("bash")
|
||||
.arg("-c")
|
||||
.arg(&interpolated_snippet[..])
|
||||
.spawn()
|
||||
.map_err(|e| BashSpawnError::new(&interpolated_snippet[..], e))?
|
||||
.wait()
|
||||
.context("bash was not running")?;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
240
src/cmds/core.rs
240
src/cmds/core.rs
|
@ -1,14 +1,16 @@
|
|||
use crate::actor;
|
||||
use crate::cheatsh;
|
||||
use crate::clipboard;
|
||||
|
||||
use crate::display;
|
||||
use crate::env_vars;
|
||||
|
||||
use crate::extractor;
|
||||
use crate::fetcher::Fetcher;
|
||||
use crate::filesystem;
|
||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||
use crate::finder::Finder;
|
||||
use crate::shell::{BashSpawnError, IS_FISH};
|
||||
use crate::structures::cheat::{Suggestion, VariableMap};
|
||||
use crate::structures::config::Action;
|
||||
|
||||
use crate::structures::cheat::VariableMap;
|
||||
|
||||
use crate::structures::config::Config;
|
||||
use crate::structures::config::Source;
|
||||
use crate::tldr;
|
||||
|
@ -16,12 +18,6 @@ use crate::welcome;
|
|||
use anyhow::Context;
|
||||
use anyhow::Error;
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn gen_core_finder_opts(config: &Config) -> Result<FinderOpts, Error> {
|
||||
let opts = FinderOpts {
|
||||
preview: if config.no_preview {
|
||||
|
@ -47,181 +43,6 @@ fn gen_core_finder_opts(config: &Config) -> Result<FinderOpts, Error> {
|
|||
Ok(opts)
|
||||
}
|
||||
|
||||
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 key = if is_single {
|
||||
"enter"
|
||||
} else {
|
||||
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 tags = parts.next().unwrap_or("");
|
||||
let comment = parts.next().unwrap_or("");
|
||||
let snippet = parts.next().unwrap_or("");
|
||||
let file_index = parts.next().unwrap_or("").parse().ok();
|
||||
Ok((key, tags, comment, snippet, file_index))
|
||||
}
|
||||
|
||||
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_DELIMITER);
|
||||
env::remove_var(env_vars::PREVIEW_MAP);
|
||||
|
||||
let mut extra_preview = None;
|
||||
|
||||
let (suggestions, opts) = if let Some(s) = suggestion {
|
||||
let (suggestion_command, suggestion_opts) = s;
|
||||
|
||||
if let Some(sopts) = suggestion_opts {
|
||||
if let Some(c) = &sopts.column {
|
||||
env::set_var(env_vars::PREVIEW_COLUMN, c.to_string());
|
||||
}
|
||||
if let Some(d) = &sopts.delimiter {
|
||||
env::set_var(env_vars::PREVIEW_DELIMITER, d);
|
||||
}
|
||||
if let Some(m) = &sopts.map {
|
||||
env::set_var(env_vars::PREVIEW_MAP, m);
|
||||
}
|
||||
if let Some(p) = &sopts.preview {
|
||||
extra_preview = Some(format!(";echo;{}", p));
|
||||
}
|
||||
}
|
||||
|
||||
let child = Command::new("bash")
|
||||
.stdout(Stdio::piped())
|
||||
.arg("-c")
|
||||
.arg(&suggestion_command)
|
||||
.spawn()
|
||||
.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,
|
||||
)
|
||||
.context("Suggestions are invalid utf8")?;
|
||||
|
||||
(text, suggestion_opts)
|
||||
} else {
|
||||
('\n'.to_string(), &None)
|
||||
};
|
||||
|
||||
let mut opts = FinderOpts {
|
||||
overrides: config.fzf_overrides_var.clone(),
|
||||
preview: Some(format!(
|
||||
r#"{prefix}navi preview-var "$(cat <<NAVIEOF
|
||||
{{+}}
|
||||
NAVIEOF
|
||||
)" "$(cat <<NAVIEOF
|
||||
{{q}}
|
||||
NAVIEOF
|
||||
)" "{name}"; {extra}{suffix}"#,
|
||||
prefix = if *IS_FISH { "bash -c '" } else { "" },
|
||||
suffix = if *IS_FISH { "'" } else { "" },
|
||||
name = variable_name,
|
||||
extra = extra_preview.clone().unwrap_or_default()
|
||||
)),
|
||||
..opts.clone().unwrap_or_default()
|
||||
};
|
||||
|
||||
opts.query = env::var(format!("{}__query", variable_name)).ok();
|
||||
|
||||
if let Ok(f) = env::var(format!("{}__best", variable_name)) {
|
||||
opts.filter = Some(f);
|
||||
opts.suggestion_type = SuggestionType::SingleSelection;
|
||||
}
|
||||
|
||||
if opts.preview_window.is_none() {
|
||||
opts.preview_window = Some(if extra_preview.is_none() {
|
||||
format!("up:{}", variable_count + 3)
|
||||
} else {
|
||||
"right:50%".to_string()
|
||||
});
|
||||
}
|
||||
|
||||
if suggestion.is_none() {
|
||||
opts.suggestion_type = SuggestionType::Disabled;
|
||||
};
|
||||
|
||||
let (output, _) = config
|
||||
.finder
|
||||
.call(opts, &mut Vec::new(), |stdin, _| {
|
||||
stdin
|
||||
.write_all(suggestions.as_bytes())
|
||||
.context("Could not write to finder's stdin")?;
|
||||
Ok(None)
|
||||
})
|
||||
.context("finder was unable to prompt with suggestions")?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn unique_result_count(results: &[&str]) -> usize {
|
||||
let mut vars = results.to_owned();
|
||||
vars.sort_unstable();
|
||||
vars.dedup();
|
||||
vars.len()
|
||||
}
|
||||
|
||||
fn replace_variables_from_snippet(
|
||||
snippet: &str,
|
||||
tags: &str,
|
||||
variables: VariableMap,
|
||||
config: &Config,
|
||||
) -> Result<String, Error> {
|
||||
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 variable_count = unique_result_count(&variables_found);
|
||||
|
||||
for bracketed_variable_name in variables_found {
|
||||
let variable_name = &bracketed_variable_name[1..bracketed_variable_name.len() - 1];
|
||||
|
||||
let env_variable_name = variable_name.replace('-', "_");
|
||||
let env_value = env::var(&env_variable_name);
|
||||
|
||||
let value = if let Ok(e) = env_value {
|
||||
e
|
||||
} else if let Some(suggestion) = variables.get_suggestion(&tags, &variable_name) {
|
||||
let mut new_suggestion = suggestion.clone();
|
||||
new_suggestion.0 =
|
||||
replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone(), config)?;
|
||||
prompt_finder(variable_name, &config, Some(&new_suggestion), variable_count)?
|
||||
} else {
|
||||
prompt_finder(variable_name, &config, None, variable_count)?
|
||||
};
|
||||
|
||||
env::set_var(env_variable_name, &value);
|
||||
|
||||
interpolated_snippet = if value.as_str() == "\n" {
|
||||
interpolated_snippet.replacen(bracketed_variable_name, "", 1)
|
||||
} else {
|
||||
interpolated_snippet.replacen(bracketed_variable_name, value.as_str(), 1)
|
||||
};
|
||||
}
|
||||
|
||||
Ok(interpolated_snippet)
|
||||
}
|
||||
|
||||
pub fn main(config: Config) -> Result<(), Error> {
|
||||
let opts = gen_core_finder_opts(&config).context("Failed to generate finder options")?;
|
||||
|
||||
|
@ -251,56 +72,13 @@ pub fn main(config: Config) -> Result<(), Error> {
|
|||
})
|
||||
.context("Failed getting selection and variables from finder")?;
|
||||
|
||||
let extractions = extract_from_selections(&raw_selection, config.best_match);
|
||||
let extractions = extractor::extract_from_selections(&raw_selection, config.best_match);
|
||||
|
||||
if extractions.is_err() {
|
||||
return main(config);
|
||||
}
|
||||
|
||||
let (key, tags, comment, snippet, file_index) = extractions.unwrap();
|
||||
|
||||
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_TAGS, &tags);
|
||||
env::set_var(env_vars::PREVIEW_COMMENT, &comment);
|
||||
|
||||
let interpolated_snippet = display::with_new_lines(
|
||||
replace_variables_from_snippet(
|
||||
snippet,
|
||||
tags,
|
||||
variables.expect("No variables received from finder"),
|
||||
&config,
|
||||
)
|
||||
.context("Failed to replace variables from snippet")?,
|
||||
);
|
||||
|
||||
match config.action() {
|
||||
Action::Print => {
|
||||
println!("{}", interpolated_snippet);
|
||||
}
|
||||
Action::Save(filepath) => {
|
||||
fs::write(filepath, interpolated_snippet).context("Unable to save output")?;
|
||||
}
|
||||
Action::Execute => match key {
|
||||
"ctrl-y" => {
|
||||
clipboard::copy(interpolated_snippet)?;
|
||||
}
|
||||
_ => {
|
||||
Command::new("bash")
|
||||
.arg("-c")
|
||||
.arg(&interpolated_snippet[..])
|
||||
.spawn()
|
||||
.map_err(|e| BashSpawnError::new(&interpolated_snippet[..], e))?
|
||||
.wait()
|
||||
.context("bash was not running")?;
|
||||
}
|
||||
},
|
||||
};
|
||||
actor::act(extractions, config, &mut files, variables)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
29
src/extractor.rs
Normal file
29
src/extractor.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use crate::display;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Error;
|
||||
|
||||
pub type Output<'a> = (&'a str, &'a str, &'a str, &'a str, Option<usize>);
|
||||
|
||||
pub fn extract_from_selections(raw_snippet: &str, is_single: bool) -> Result<Output, Error> {
|
||||
let mut lines = raw_snippet.split('\n');
|
||||
let key = if is_single {
|
||||
"enter"
|
||||
} else {
|
||||
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 tags = parts.next().unwrap_or("");
|
||||
let comment = parts.next().unwrap_or("");
|
||||
let snippet = parts.next().unwrap_or("");
|
||||
let file_index = parts.next().unwrap_or("").parse().ok();
|
||||
Ok((key, tags, comment, snippet, file_index))
|
||||
}
|
|
@ -3,11 +3,13 @@ extern crate lazy_static;
|
|||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
|
||||
mod actor;
|
||||
mod cheatsh;
|
||||
mod clipboard;
|
||||
mod cmds;
|
||||
mod display;
|
||||
mod env_vars;
|
||||
mod extractor;
|
||||
mod fetcher;
|
||||
mod filesystem;
|
||||
mod finder;
|
||||
|
|
Loading…
Reference in a new issue