Minor fixes (#285)

This commit is contained in:
Denis Isidoro 2020-03-18 08:38:13 -03:00 committed by GitHub
parent a08f3cd5d5
commit a0bba94db3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 139 additions and 101 deletions

View file

@ -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);
}
}

View file

@ -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

View file

@ -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)
}

View file

@ -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");

View file

@ -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),
}
}

View file

@ -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())
}

View file

@ -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
}
}

View file

@ -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