mirror of
https://github.com/denisidoro/navi
synced 2024-11-10 14:04:17 +00:00
Minor fixes (#285)
This commit is contained in:
parent
a08f3cd5d5
commit
a0bba94db3
8 changed files with 139 additions and 101 deletions
98
src/cheat.rs
98
src/cheat.rs
|
@ -18,13 +18,13 @@ pub struct SuggestionOpts {
|
|||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum SuggestionType {
|
||||
/// fzf will not print any suggestions.
|
||||
/// fzf will not print any suggestions
|
||||
Disabled,
|
||||
/// fzf will only select one of the suggestions
|
||||
SingleSelection,
|
||||
/// fzf will select multiple ones of the suggestions
|
||||
/// fzf will select multiple suggestions
|
||||
MultipleSelections,
|
||||
/// fzf will select one of the suggestions or use the Query
|
||||
/// fzf will select one of the suggestions or use the query
|
||||
SingleRecommendation,
|
||||
/// initial snippet selection
|
||||
SnippetSelection,
|
||||
|
@ -32,6 +32,22 @@ pub enum SuggestionType {
|
|||
|
||||
pub type Suggestion = (String, Option<SuggestionOpts>);
|
||||
|
||||
pub struct VariableMap(HashMap<u64, Suggestion>);
|
||||
|
||||
impl VariableMap {
|
||||
pub fn new() -> Self {
|
||||
Self(HashMap::new())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, tags: &str, variable: &str, value: Suggestion) -> Option<Suggestion> {
|
||||
self.0.insert(gen_key(tags, variable), value)
|
||||
}
|
||||
|
||||
pub fn get(&self, tags: &str, variable: &str) -> Option<&Suggestion> {
|
||||
self.0.get(&gen_key(tags, variable))
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_quotes(txt: &str) -> String {
|
||||
txt.replace('"', "").replace('\'', "")
|
||||
}
|
||||
|
@ -95,33 +111,36 @@ fn write_cmd(
|
|||
} else {
|
||||
stdin
|
||||
.write_all(
|
||||
display::format_line(
|
||||
&tags[..],
|
||||
&comment[..],
|
||||
&snippet[3..],
|
||||
tag_width,
|
||||
comment_width,
|
||||
)
|
||||
.as_bytes(),
|
||||
display::format_line(&tags, &comment, &snippet, tag_width, comment_width)
|
||||
.as_bytes(),
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_key(tags: &str, variable: &str) -> u64 {
|
||||
format!("{};{}", tags, variable).hash_line()
|
||||
}
|
||||
|
||||
fn read_file(
|
||||
path: &str,
|
||||
variables: &mut HashMap<String, Suggestion>,
|
||||
variables: &mut VariableMap,
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
set: &mut HashSet<u64>,
|
||||
) -> bool {
|
||||
let mut tags = String::from("");
|
||||
let mut comment = String::from("");
|
||||
let mut snippet = String::from("");
|
||||
let mut should_break = false;
|
||||
|
||||
let (tag_width, comment_width) = *display::WIDTHS;
|
||||
|
||||
if let Ok(lines) = filesystem::read_lines(path) {
|
||||
for l in lines {
|
||||
if should_break {
|
||||
break;
|
||||
}
|
||||
|
||||
let line = l.unwrap();
|
||||
let hash = line.hash_line();
|
||||
if set.contains(&hash) {
|
||||
|
@ -135,7 +154,7 @@ fn read_file(
|
|||
// tag
|
||||
else if line.starts_with('%') {
|
||||
if !write_cmd(&tags, &comment, &snippet, tag_width, comment_width, stdin) {
|
||||
break;
|
||||
should_break = true
|
||||
}
|
||||
snippet = String::from("");
|
||||
tags = String::from(&line[2..]);
|
||||
|
@ -146,7 +165,7 @@ fn read_file(
|
|||
// comment
|
||||
else if line.starts_with('#') {
|
||||
if !write_cmd(&tags, &comment, &snippet, tag_width, comment_width, stdin) {
|
||||
break;
|
||||
should_break = true
|
||||
}
|
||||
snippet = String::from("");
|
||||
comment = String::from(&line[2..]);
|
||||
|
@ -154,33 +173,35 @@ fn read_file(
|
|||
// variable
|
||||
else if line.starts_with('$') {
|
||||
if !write_cmd(&tags, &comment, &snippet, tag_width, comment_width, stdin) {
|
||||
break;
|
||||
should_break = true
|
||||
}
|
||||
snippet = String::from("");
|
||||
let (variable, command, opts) = parse_variable_line(&line);
|
||||
variables.insert(
|
||||
format!("{};{}", tags, variable),
|
||||
(String::from(command), opts),
|
||||
);
|
||||
variables.insert(&tags, &variable, (String::from(command), opts));
|
||||
}
|
||||
// snippet
|
||||
// first snippet line
|
||||
else if (&snippet).is_empty() {
|
||||
snippet.push_str(&line);
|
||||
}
|
||||
// other snippet lines
|
||||
else {
|
||||
snippet.push_str(display::LINE_SEPARATOR);
|
||||
snippet.push_str(&line);
|
||||
}
|
||||
}
|
||||
|
||||
if !should_break {
|
||||
write_cmd(&tags, &comment, &snippet, tag_width, comment_width, stdin);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn read_all(
|
||||
config: &Config,
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
) -> HashMap<String, Suggestion> {
|
||||
let mut variables: HashMap<String, Suggestion> = HashMap::new();
|
||||
pub fn read_all(config: &Config, stdin: &mut std::process::ChildStdin) -> VariableMap {
|
||||
let mut variables = VariableMap::new();
|
||||
let mut found_something = false;
|
||||
let paths = filesystem::cheat_paths(config);
|
||||
let folders = paths.split(':');
|
||||
|
@ -233,14 +254,25 @@ mod tests {
|
|||
#[test]
|
||||
fn test_read_file() {
|
||||
let path = "tests/cheats/ssh.cheat";
|
||||
let mut variables: HashMap<String, Suggestion> = HashMap::new();
|
||||
let mut variables = VariableMap::new();
|
||||
let mut child = Command::new("cat").stdin(Stdio::piped()).spawn().unwrap();
|
||||
let child_stdin = child.stdin.as_mut().unwrap();
|
||||
read_file(path, &mut variables, child_stdin);
|
||||
let mut result: HashMap<String, (String, std::option::Option<_>)> = HashMap::new();
|
||||
result.insert(
|
||||
"ssh;user".to_string(),
|
||||
(
|
||||
let mut set: HashSet<u64> = HashSet::new();
|
||||
read_file(path, &mut variables, child_stdin, &mut set);
|
||||
let mut result = VariableMap::new();
|
||||
let suggestion = (
|
||||
r#" echo -e "$(whoami)\nroot" "#.to_string(),
|
||||
Some(SuggestionOpts {
|
||||
header_lines: 0,
|
||||
column: None,
|
||||
delimiter: None,
|
||||
suggestion_type: SuggestionType::SingleRecommendation,
|
||||
}),
|
||||
);
|
||||
result.insert("ssh", "user", suggestion);
|
||||
let actual_suggestion = result.get("ssh", "user");
|
||||
assert_eq!(
|
||||
Some(&(
|
||||
r#" echo -e "$(whoami)\nroot" "#.to_string(),
|
||||
Some(SuggestionOpts {
|
||||
header_lines: 0,
|
||||
|
@ -248,8 +280,8 @@ mod tests {
|
|||
delimiter: None,
|
||||
suggestion_type: SuggestionType::SingleRecommendation,
|
||||
}),
|
||||
),
|
||||
)),
|
||||
actual_suggestion
|
||||
);
|
||||
assert_eq!(variables, result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,18 +119,10 @@ fn prompt_without_suggestions(variable_name: &str) -> String {
|
|||
output
|
||||
}
|
||||
|
||||
fn gen_replacement(value: &str) -> String {
|
||||
if value.contains(' ') {
|
||||
format!("\"{}\"", value)
|
||||
} else {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_variables_from_snippet(
|
||||
snippet: &str,
|
||||
tags: &str,
|
||||
variables: HashMap<String, cheat::Suggestion>,
|
||||
variables: cheat::VariableMap,
|
||||
config: &Config,
|
||||
) -> String {
|
||||
let mut interpolated_snippet = String::from(snippet);
|
||||
|
@ -141,23 +133,22 @@ fn replace_variables_from_snippet(
|
|||
let bracketed_variable_name = &captures[0];
|
||||
let variable_name = &bracketed_variable_name[1..bracketed_variable_name.len() - 1];
|
||||
|
||||
if values.get(variable_name).is_none() {
|
||||
let key = format!("{};{}", tags, variable_name);
|
||||
let value = values
|
||||
.get(variable_name)
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
variables
|
||||
.get(&tags, &variable_name)
|
||||
.map(|suggestion| {
|
||||
prompt_with_suggestions(variable_name, &config, suggestion, &values)
|
||||
})
|
||||
.unwrap_or_else(|| prompt_without_suggestions(variable_name))
|
||||
});
|
||||
|
||||
let value = match variables.get(&key[..]) {
|
||||
Some(suggestion) => {
|
||||
prompt_with_suggestions(variable_name, &config, suggestion, &values)
|
||||
}
|
||||
None => prompt_without_suggestions(variable_name),
|
||||
};
|
||||
values.insert(variable_name.to_string(), value.clone());
|
||||
|
||||
values.insert(variable_name.to_string(), value.clone());
|
||||
|
||||
interpolated_snippet = interpolated_snippet.replace(
|
||||
bracketed_variable_name,
|
||||
gen_replacement(&value[..]).as_str(),
|
||||
);
|
||||
}
|
||||
interpolated_snippet =
|
||||
interpolated_snippet.replacen(bracketed_variable_name, value.as_str(), 1);
|
||||
}
|
||||
|
||||
interpolated_snippet
|
||||
|
|
|
@ -10,8 +10,8 @@ fn extract_elements(argstr: &str) -> (&str, &str, &str) {
|
|||
(tags, comment, snippet)
|
||||
}
|
||||
|
||||
pub fn main(line: String) -> Result<(), Box<dyn Error>> {
|
||||
let (tags, comment, snippet) = extract_elements(&line[..]);
|
||||
pub fn main(line: &str) -> Result<(), Box<dyn Error>> {
|
||||
let (tags, comment, snippet) = extract_elements(line);
|
||||
display::preview(comment, tags, snippet);
|
||||
process::exit(0)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ use crate::cheat;
|
|||
use crate::cheat::SuggestionType;
|
||||
use crate::cheat::SuggestionType::SingleSelection;
|
||||
use crate::display;
|
||||
use std::collections::HashMap;
|
||||
use std::process;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
|
@ -51,9 +50,9 @@ fn get_column(text: String, column: Option<u8>, delimiter: Option<&str>) -> Stri
|
|||
}
|
||||
}
|
||||
|
||||
pub fn call<F>(opts: Opts, stdin_fn: F) -> (String, Option<HashMap<String, cheat::Suggestion>>)
|
||||
pub fn call<F>(opts: Opts, stdin_fn: F) -> (String, Option<cheat::VariableMap>)
|
||||
where
|
||||
F: Fn(&mut process::ChildStdin) -> Option<HashMap<String, cheat::Suggestion>>,
|
||||
F: Fn(&mut process::ChildStdin) -> Option<cheat::VariableMap>,
|
||||
{
|
||||
let mut fzf_command = Command::new("fzf");
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use crate::cmds;
|
||||
use crate::cmds::core::Variant;
|
||||
use crate::option::Command::{Best, Fn, Query, Repo, Search, Widget};
|
||||
use crate::option::{Config, InternalCommand, RepoCommand};
|
||||
use crate::option::Command::{Best, Fn, Preview, Query, Repo, Search, Widget};
|
||||
use crate::option::{Config, RepoCommand};
|
||||
use std::error::Error;
|
||||
|
||||
pub fn handle_config(mut config: Config) -> Result<(), Box<dyn Error>> {
|
||||
match config.cmd.as_mut() {
|
||||
None => cmds::core::main(Variant::Core, config, true),
|
||||
Some(c) => match c {
|
||||
Preview { line } => cmds::preview::main(&line[..]),
|
||||
Query { query } => cmds::query::main(query.clone(), config),
|
||||
Best { query, args } => cmds::best::main(query.clone(), args.to_vec(), config),
|
||||
Search { query } => cmds::search::main(query.clone(), config),
|
||||
|
@ -20,9 +21,3 @@ pub fn handle_config(mut config: Config) -> Result<(), Box<dyn Error>> {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_internal_command(cmd: InternalCommand) -> Result<(), Box<dyn Error>> {
|
||||
match cmd {
|
||||
InternalCommand::Preview { line } => cmds::preview::main(line),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,5 @@ mod welcome;
|
|||
use std::error::Error;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let internal_cmd = option::internal_command_from_env();
|
||||
if let Some(cmd) = internal_cmd {
|
||||
handler::handle_internal_command(cmd)
|
||||
} else {
|
||||
handler::handle_config(option::config_from_env())
|
||||
}
|
||||
handler::handle_config(option::config_from_env())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::env;
|
||||
use structopt::StructOpt;
|
||||
use structopt::{clap::AppSettings, StructOpt};
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(after_help = r#"EXAMPLES:
|
||||
|
@ -14,6 +13,7 @@ use structopt::StructOpt;
|
|||
navi --fzf-overrides ' --with-nth 1,2' # shows only the comment and tag columns
|
||||
navi --fzf-overrides ' --nth 1,2' # search will consider only the first two columns
|
||||
navi --fzf-overrides ' --no-exact' # looser search algorithm"#)]
|
||||
#[structopt(setting = AppSettings::AllowLeadingHyphen)]
|
||||
pub struct Config {
|
||||
/// List of :-separated paths containing .cheat files
|
||||
#[structopt(short, long, env = "NAVI_PATH")]
|
||||
|
@ -78,6 +78,12 @@ pub enum Command {
|
|||
#[structopt(subcommand)]
|
||||
cmd: RepoCommand,
|
||||
},
|
||||
/// Used for fzf's preview window
|
||||
#[structopt(setting = AppSettings::Hidden)]
|
||||
Preview {
|
||||
/// Selection line
|
||||
line: String,
|
||||
},
|
||||
/// Shows the path for shell widget files
|
||||
Widget {
|
||||
/// bash, zsh or fish
|
||||
|
@ -96,10 +102,6 @@ pub enum RepoCommand {
|
|||
Browse,
|
||||
}
|
||||
|
||||
pub enum InternalCommand {
|
||||
Preview { line: String },
|
||||
}
|
||||
|
||||
pub fn config_from_env() -> Config {
|
||||
Config::from_args()
|
||||
}
|
||||
|
@ -107,15 +109,3 @@ pub fn config_from_env() -> Config {
|
|||
pub fn config_from_iter(args: Vec<&str>) -> Config {
|
||||
Config::from_iter(args)
|
||||
}
|
||||
|
||||
pub fn internal_command_from_env() -> Option<InternalCommand> {
|
||||
let mut args = env::args();
|
||||
args.next();
|
||||
if args.next() == Some(String::from("preview")) {
|
||||
Some(InternalCommand::Preview {
|
||||
line: args.next().unwrap(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,56 @@
|
|||
% test
|
||||
; author: CI/CD
|
||||
|
||||
# reset terminal title
|
||||
% test, ci/cd
|
||||
|
||||
# escape code + subshell
|
||||
echo -ne "\033]0;$(hostname)\007"
|
||||
|
||||
# Find primary, local IP address
|
||||
# sed with replacement
|
||||
echo "8.8.8.8 via 172.17.0.1 dev eth0 src 172.17.0.2" | sed -E 's/.*src ([0-9.]+).*/\1/p' | head -n1
|
||||
|
||||
# simple echo
|
||||
# trivial case
|
||||
echo "foo"
|
||||
|
||||
# multiline command without backslash
|
||||
# multiline command: without backslash
|
||||
echo "foo"
|
||||
echo "bar"
|
||||
|
||||
# multiline command with backslash
|
||||
# env var
|
||||
echo "$HOME"
|
||||
|
||||
# multiline command: with backslash
|
||||
echo 'lorem ipsum'
|
||||
echo "foo" \
|
||||
| grep -q "bar" \
|
||||
&& echo "match" \
|
||||
|| echo "no match"
|
||||
|| echo "no match"
|
||||
|
||||
# second column: default delimiter
|
||||
echo "<table_elem> is cool"
|
||||
|
||||
# second column: custom delimiter
|
||||
echo "<table_elem2> is cool"
|
||||
|
||||
# return multiple results: single words
|
||||
echo "I like these languages: "$(printf '%s' "<langs>" | tr '\n' ',' | sed 's/,/, /g')""
|
||||
|
||||
# return multiple results: multiple words
|
||||
echo "I like these examples: "$(printf '%s' "<examples>" | sed 's/^..*$/"&"/' | awk 1 ORS=', ' | sed 's/, $//')""
|
||||
|
||||
# multiple replacements
|
||||
echo "<x> <y> <x> <z>"
|
||||
|
||||
# multi-word
|
||||
echo "<multiword>"
|
||||
|
||||
$ x: echo '1 2 3' | tr ' ' '\n'
|
||||
$ y: echo 'a b c' | tr ' ' '\n'
|
||||
$ z: echo 'foo bar' | tr ' ' '\n'
|
||||
$ table_elem: echo -e '0 rust rust-lang.org\n1 clojure clojure.org' --- --column 2
|
||||
$ table_elem2: echo -e '0;rust;rust-lang.org\n1;clojure;clojure.org' --- --column 2 --delimiter ';'
|
||||
$ langs: echo 'clojure rust javascript' | tr ' ' '\n' --- --multi
|
||||
$ examples: echo -e 'foo bar\nlorem ipsum\ndolor sit' --- --multi
|
||||
$ multiword: echo -e 'foo bar\nlorem ipsum\ndolor sit\nbaz'
|
||||
|
||||
# this should be displayed
|
||||
echo hi
|
Loading…
Reference in a new issue