2021-04-05 12:37:35 +00:00
|
|
|
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
2021-08-07 13:52:30 +00:00
|
|
|
use crate::fs;
|
2021-04-04 20:46:33 +00:00
|
|
|
use crate::hash::fnv;
|
2020-03-20 01:01:50 +00:00
|
|
|
use crate::structures::cheat::VariableMap;
|
2020-08-27 18:15:09 +00:00
|
|
|
use crate::structures::item::Item;
|
2021-04-16 12:23:53 +00:00
|
|
|
use crate::writer;
|
2021-04-16 11:29:04 +00:00
|
|
|
use anyhow::{Context, Result};
|
2020-03-04 21:01:23 +00:00
|
|
|
use regex::Regex;
|
2020-03-18 15:29:29 +00:00
|
|
|
use std::collections::HashSet;
|
2020-03-04 21:01:23 +00:00
|
|
|
use std::io::Write;
|
|
|
|
|
2020-03-21 02:22:11 +00:00
|
|
|
lazy_static! {
|
2020-08-28 12:21:24 +00:00
|
|
|
pub static ref VAR_LINE_REGEX: Regex = Regex::new(r"^\$\s*([^:]+):(.*)").expect("Invalid regex");
|
2020-03-21 02:22:11 +00:00
|
|
|
}
|
|
|
|
|
2021-04-16 11:29:04 +00:00
|
|
|
fn parse_opts(text: &str) -> Result<FinderOpts> {
|
2020-03-04 21:01:23 +00:00
|
|
|
let mut multi = false;
|
2020-03-17 15:39:38 +00:00
|
|
|
let mut prevent_extra = false;
|
2021-08-07 13:31:53 +00:00
|
|
|
|
|
|
|
let mut opts = FinderOpts::var_default();
|
2020-04-11 02:01:35 +00:00
|
|
|
|
2020-08-28 12:21:24 +00:00
|
|
|
let parts = shellwords::split(text).map_err(|_| anyhow!("Given options are missing a closing quote"))?;
|
2020-03-22 00:51:38 +00:00
|
|
|
|
|
|
|
parts
|
|
|
|
.into_iter()
|
|
|
|
.filter(|part| {
|
|
|
|
// We'll take parts in pairs of 2: (argument, value). Flags don't have a value tho, so we filter and handle them beforehand.
|
|
|
|
match part.as_str() {
|
|
|
|
"--multi" => {
|
|
|
|
multi = true;
|
|
|
|
false
|
|
|
|
}
|
|
|
|
"--prevent-extra" => {
|
|
|
|
prevent_extra = true;
|
|
|
|
false
|
|
|
|
}
|
2021-08-07 13:52:30 +00:00
|
|
|
"--expand" => {
|
|
|
|
opts.map = Some(format!("{} fn map::expand", fs::exe_string()));
|
|
|
|
false
|
|
|
|
}
|
2020-03-22 00:51:38 +00:00
|
|
|
_ => true,
|
2020-03-04 21:01:23 +00:00
|
|
|
}
|
2020-03-22 00:51:38 +00:00
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.chunks(2)
|
2021-01-07 12:31:34 +00:00
|
|
|
.try_for_each(|flag_and_value| {
|
2020-03-22 00:51:38 +00:00
|
|
|
if let [flag, value] = flag_and_value {
|
|
|
|
match flag.as_str() {
|
2020-11-27 12:35:15 +00:00
|
|
|
"--headers" | "--header-lines" => {
|
|
|
|
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")?,
|
|
|
|
)
|
|
|
|
}
|
2020-04-11 02:01:35 +00:00
|
|
|
"--map" => opts.map = Some(value.to_string()),
|
2020-03-22 00:51:38 +00:00
|
|
|
"--delimiter" => opts.delimiter = Some(value.to_string()),
|
|
|
|
"--query" => opts.query = Some(value.to_string()),
|
|
|
|
"--filter" => opts.filter = Some(value.to_string()),
|
|
|
|
"--preview" => opts.preview = Some(value.to_string()),
|
|
|
|
"--preview-window" => opts.preview_window = Some(value.to_string()),
|
|
|
|
"--header" => opts.header = Some(value.to_string()),
|
2021-04-15 12:23:35 +00:00
|
|
|
"--fzf-overrides" => opts.overrides = Some(value.to_string()),
|
2020-03-22 00:51:38 +00:00
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
} else if let [flag] = flag_and_value {
|
2020-03-22 15:31:49 +00:00
|
|
|
Err(anyhow!("No value provided for the flag `{}`", flag))
|
2020-03-22 00:51:38 +00:00
|
|
|
} else {
|
|
|
|
unreachable!() // Chunking by 2 allows only for tuples of 1 or 2 items...
|
|
|
|
}
|
|
|
|
})
|
2020-03-27 14:36:37 +00:00
|
|
|
.context("Failed to parse finder options")?;
|
2020-03-04 21:01:23 +00:00
|
|
|
|
2020-03-20 01:01:50 +00:00
|
|
|
let suggestion_type = match (multi, prevent_extra) {
|
2020-03-24 12:42:54 +00:00
|
|
|
(true, _) => SuggestionType::MultipleSelections, // multi wins over prevent-extra
|
2020-03-20 01:01:50 +00:00
|
|
|
(false, false) => SuggestionType::SingleRecommendation,
|
|
|
|
(false, true) => SuggestionType::SingleSelection,
|
|
|
|
};
|
|
|
|
opts.suggestion_type = suggestion_type;
|
|
|
|
|
2020-03-22 00:51:38 +00:00
|
|
|
Ok(opts)
|
2020-03-04 21:01:23 +00:00
|
|
|
}
|
|
|
|
|
2021-04-16 11:29:04 +00:00
|
|
|
fn parse_variable_line(line: &str) -> Result<(&str, &str, Option<FinderOpts>)> {
|
2020-08-28 12:21:24 +00:00
|
|
|
let caps = VAR_LINE_REGEX
|
|
|
|
.captures(line)
|
|
|
|
.ok_or_else(|| anyhow!("No variables, command, and options found in the line `{}`", line))?;
|
2020-03-22 00:51:38 +00:00
|
|
|
let variable = caps
|
|
|
|
.get(1)
|
2020-03-22 15:31:49 +00:00
|
|
|
.ok_or_else(|| anyhow!("No variable captured in the line `{}`", line))?
|
2020-03-22 00:51:38 +00:00
|
|
|
.as_str()
|
|
|
|
.trim();
|
|
|
|
let mut command_plus_opts = caps
|
|
|
|
.get(2)
|
2020-03-22 15:31:49 +00:00
|
|
|
.ok_or_else(|| anyhow!("No command and options captured in the line `{}`", line))?
|
2020-03-22 00:51:38 +00:00
|
|
|
.as_str()
|
|
|
|
.split("---");
|
|
|
|
let command = command_plus_opts
|
|
|
|
.next()
|
2020-03-22 15:31:49 +00:00
|
|
|
.ok_or_else(|| anyhow!("No command captured in the line `{}`", line))?;
|
2020-03-22 00:51:38 +00:00
|
|
|
let command_options = command_plus_opts.next().map(parse_opts).transpose()?;
|
|
|
|
Ok((variable, command, command_options))
|
2020-03-04 21:01:23 +00:00
|
|
|
}
|
|
|
|
|
2020-11-21 18:42:35 +00:00
|
|
|
fn write_cmd(
|
2021-04-15 13:49:23 +00:00
|
|
|
item: &Item,
|
2020-11-21 18:42:35 +00:00
|
|
|
stdin: &mut std::process::ChildStdin,
|
2021-04-15 13:49:23 +00:00
|
|
|
allowlist: Option<&Vec<String>>,
|
|
|
|
denylist: Option<&Vec<String>>,
|
2021-08-07 15:07:17 +00:00
|
|
|
visited_lines: &mut HashSet<u64>,
|
2021-04-16 11:29:04 +00:00
|
|
|
) -> Result<()> {
|
2021-08-08 11:12:19 +00:00
|
|
|
if item.comment.is_empty() || item.snippet.trim().is_empty() {
|
2021-04-15 13:49:23 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
2021-04-17 15:06:29 +00:00
|
|
|
|
2021-08-07 15:07:17 +00:00
|
|
|
let hash = fnv(&format!("{}{}", &item.comment, &item.snippet));
|
|
|
|
if visited_lines.contains(&hash) {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
visited_lines.insert(hash);
|
|
|
|
|
2021-04-17 15:06:29 +00:00
|
|
|
if let Some(list) = denylist {
|
2021-04-15 13:49:23 +00:00
|
|
|
for v in list {
|
2021-04-17 15:06:29 +00:00
|
|
|
if item.tags.contains(v) {
|
2021-04-15 13:49:23 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
2020-03-14 21:08:57 +00:00
|
|
|
}
|
2021-04-17 15:06:29 +00:00
|
|
|
|
|
|
|
if let Some(list) = allowlist {
|
|
|
|
let mut should_allow = false;
|
2021-04-15 13:49:23 +00:00
|
|
|
for v in list {
|
|
|
|
if item.tags.contains(v) {
|
2021-04-17 15:06:29 +00:00
|
|
|
should_allow = true;
|
|
|
|
break;
|
2021-04-15 13:49:23 +00:00
|
|
|
}
|
|
|
|
}
|
2021-04-17 15:06:29 +00:00
|
|
|
if !should_allow {
|
|
|
|
return Ok(());
|
|
|
|
}
|
2021-04-15 13:49:23 +00:00
|
|
|
}
|
2021-04-17 15:06:29 +00:00
|
|
|
|
2021-04-15 13:49:23 +00:00
|
|
|
return stdin
|
2021-04-16 12:23:53 +00:00
|
|
|
.write_all(writer::write(item).as_bytes())
|
2021-04-15 13:49:23 +00:00
|
|
|
.context("Failed to write command to finder's stdin");
|
2020-03-14 21:08:57 +00:00
|
|
|
}
|
|
|
|
|
2020-10-08 20:45:01 +00:00
|
|
|
fn without_prefix(line: &str) -> String {
|
|
|
|
if line.len() > 2 {
|
|
|
|
String::from(line[2..].trim())
|
|
|
|
} else {
|
|
|
|
String::from("")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 13:49:23 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2020-08-27 18:15:09 +00:00
|
|
|
pub fn read_lines(
|
2021-04-16 11:29:04 +00:00
|
|
|
lines: impl Iterator<Item = Result<String>>,
|
2020-08-27 18:15:09 +00:00
|
|
|
id: &str,
|
2020-11-26 20:36:20 +00:00
|
|
|
file_index: usize,
|
2020-03-18 11:38:13 +00:00
|
|
|
variables: &mut VariableMap,
|
2020-03-18 15:29:29 +00:00
|
|
|
visited_lines: &mut HashSet<u64>,
|
2020-03-04 21:01:23 +00:00
|
|
|
stdin: &mut std::process::ChildStdin,
|
2021-04-15 13:49:23 +00:00
|
|
|
allowlist: Option<&Vec<String>>,
|
|
|
|
denylist: Option<&Vec<String>>,
|
2021-04-16 11:29:04 +00:00
|
|
|
) -> Result<()> {
|
2021-04-15 13:49:23 +00:00
|
|
|
let mut item = Item::new();
|
|
|
|
item.file_index = file_index;
|
|
|
|
|
2020-03-18 11:38:13 +00:00
|
|
|
let mut should_break = false;
|
2020-03-04 21:01:23 +00:00
|
|
|
|
2021-10-15 16:16:10 +00:00
|
|
|
let mut variable_cmd = String::from("");
|
|
|
|
|
2020-08-27 18:15:09 +00:00
|
|
|
for (line_nr, line_result) in lines.enumerate() {
|
2020-11-27 12:35:15 +00:00
|
|
|
let line = line_result
|
|
|
|
.with_context(|| format!("Failed to read line number {} in cheatsheet `{}`", line_nr, id))?;
|
2020-03-23 16:16:13 +00:00
|
|
|
|
2020-03-22 00:51:38 +00:00
|
|
|
if should_break {
|
|
|
|
break;
|
|
|
|
}
|
2020-03-19 12:19:50 +00:00
|
|
|
|
2020-03-22 00:51:38 +00:00
|
|
|
// duplicate
|
2021-04-15 13:49:23 +00:00
|
|
|
if !item.tags.is_empty() && !item.comment.is_empty() {}
|
2020-03-22 00:51:38 +00:00
|
|
|
// blank
|
|
|
|
if line.is_empty() {
|
2021-08-07 15:24:39 +00:00
|
|
|
if !(&item.snippet).is_empty() {
|
|
|
|
item.snippet.push_str(writer::LINE_SEPARATOR);
|
|
|
|
}
|
2020-03-22 00:51:38 +00:00
|
|
|
}
|
|
|
|
// tag
|
|
|
|
else if line.starts_with('%') {
|
2021-08-07 15:07:17 +00:00
|
|
|
should_break = write_cmd(&item, stdin, allowlist, denylist, visited_lines).is_err();
|
2021-04-15 13:49:23 +00:00
|
|
|
item.snippet = String::from("");
|
|
|
|
item.tags = without_prefix(&line);
|
2020-03-22 00:51:38 +00:00
|
|
|
}
|
2020-08-28 12:12:44 +00:00
|
|
|
// dependency
|
2020-08-23 23:09:27 +00:00
|
|
|
else if line.starts_with('@') {
|
2020-10-08 20:45:01 +00:00
|
|
|
let tags_dependency = without_prefix(&line);
|
2021-04-15 13:49:23 +00:00
|
|
|
variables.insert_dependency(&item.tags, &tags_dependency);
|
2020-08-23 23:09:27 +00:00
|
|
|
}
|
2020-03-22 00:51:38 +00:00
|
|
|
// metacomment
|
|
|
|
else if line.starts_with(';') {
|
|
|
|
}
|
|
|
|
// comment
|
|
|
|
else if line.starts_with('#') {
|
2021-08-07 15:07:17 +00:00
|
|
|
should_break = write_cmd(&item, stdin, allowlist, denylist, visited_lines).is_err();
|
2021-04-15 13:49:23 +00:00
|
|
|
item.snippet = String::from("");
|
|
|
|
item.comment = without_prefix(&line);
|
2020-03-22 00:51:38 +00:00
|
|
|
}
|
|
|
|
// variable
|
2021-10-15 16:16:10 +00:00
|
|
|
else if !variable_cmd.is_empty() || (line.starts_with('$') && line.contains(':')) {
|
2021-08-07 15:07:17 +00:00
|
|
|
should_break = write_cmd(&item, stdin, allowlist, denylist, visited_lines).is_err();
|
2021-10-15 16:16:10 +00:00
|
|
|
|
2021-04-15 13:49:23 +00:00
|
|
|
item.snippet = String::from("");
|
2021-10-15 16:16:10 +00:00
|
|
|
|
|
|
|
variable_cmd.push_str(line.trim_end_matches('\\'));
|
|
|
|
|
|
|
|
if !line.ends_with('\\') {
|
|
|
|
let full_variable_cmd = variable_cmd.clone();
|
|
|
|
let (variable, command, opts) =
|
|
|
|
parse_variable_line(&full_variable_cmd).with_context(|| {
|
|
|
|
format!(
|
|
|
|
"Failed to parse variable line. See line number {} in cheatsheet `{}`",
|
|
|
|
line_nr + 1,
|
|
|
|
id
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
variable_cmd = String::from("");
|
|
|
|
variables.insert_suggestion(&item.tags, variable, (String::from(command), opts));
|
|
|
|
}
|
2020-03-22 00:51:38 +00:00
|
|
|
}
|
|
|
|
// snippet
|
|
|
|
else {
|
2021-04-15 13:49:23 +00:00
|
|
|
if !(&item.snippet).is_empty() {
|
|
|
|
item.snippet.push_str(writer::LINE_SEPARATOR);
|
2020-03-04 21:01:23 +00:00
|
|
|
}
|
2021-04-15 13:49:23 +00:00
|
|
|
item.snippet.push_str(&line);
|
2020-03-04 21:01:23 +00:00
|
|
|
}
|
2020-03-22 00:51:38 +00:00
|
|
|
}
|
2020-03-15 16:46:58 +00:00
|
|
|
|
2020-03-22 00:51:38 +00:00
|
|
|
if !should_break {
|
2021-08-07 15:07:17 +00:00
|
|
|
let _ = write_cmd(&item, stdin, allowlist, denylist, visited_lines);
|
2020-03-04 21:01:23 +00:00
|
|
|
}
|
2020-03-14 21:08:57 +00:00
|
|
|
|
2020-03-22 00:51:38 +00:00
|
|
|
Ok(())
|
2020-03-04 21:01:23 +00:00
|
|
|
}
|
|
|
|
|
2020-03-14 08:09:09 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_variable_line() {
|
2020-11-27 12:35:15 +00:00
|
|
|
let (variable, command, command_options) =
|
|
|
|
parse_variable_line("$ user : echo -e \"$(whoami)\\nroot\" --- --prevent-extra").unwrap();
|
2020-03-14 08:09:09 +00:00
|
|
|
assert_eq!(command, " echo -e \"$(whoami)\\nroot\" ");
|
|
|
|
assert_eq!(variable, "user");
|
2021-08-07 13:31:53 +00:00
|
|
|
let opts = command_options.unwrap();
|
|
|
|
assert_eq!(opts.header_lines, 0);
|
|
|
|
assert_eq!(opts.column, None);
|
|
|
|
assert_eq!(opts.delimiter, None);
|
|
|
|
assert_eq!(opts.suggestion_type, SuggestionType::SingleSelection);
|
2020-03-14 08:09:09 +00:00
|
|
|
}
|
|
|
|
}
|