From 791aaadd49dc3222b8939073bbb21bd9e646e961 Mon Sep 17 00:00:00 2001 From: graelo Date: Wed, 27 May 2020 10:04:42 +0200 Subject: [PATCH] refactor: refactor --- Cargo.toml | 2 +- src/{swapper.rs => bridge.rs} | 136 +++++-- src/error.rs | 2 + src/lib.rs | 112 ++++-- src/main.rs | 17 +- src/process.rs | 21 + src/regexes.rs | 25 ++ src/state.rs | 738 +++++++++++++++++----------------- src/tmux.rs | 42 +- src/view.rs | 28 +- 10 files changed, 655 insertions(+), 468 deletions(-) rename src/{swapper.rs => bridge.rs} (79%) create mode 100644 src/process.rs create mode 100644 src/regexes.rs diff --git a/Cargo.toml b/Cargo.toml index b3ed495..23e7a2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,4 @@ path = "src/main.rs" [[bin]] name = "tmux-copyrat" -path = "src/swapper.rs" +path = "src/bridge.rs" diff --git a/src/swapper.rs b/src/bridge.rs similarity index 79% rename from src/swapper.rs rename to src/bridge.rs index a01966a..b33f95e 100644 --- a/src/swapper.rs +++ b/src/bridge.rs @@ -1,10 +1,11 @@ use clap::Clap; use regex::Regex; -use std::path; +use std::collections::HashMap; use std::process::Command; +use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; -use copyrat::error; +use copyrat::{error, process, CliOpt}; mod tmux; @@ -46,7 +47,7 @@ const TMP_FILE: &str = "/tmp/copyrat-last"; pub struct Swapper<'a> { executor: Box<&'a mut dyn Executor>, - directory: &'a path::Path, + // directory: &'a path::Path, command: &'a str, alt_command: &'a str, active_pane_id: Option, @@ -61,7 +62,7 @@ pub struct Swapper<'a> { impl<'a> Swapper<'a> { fn new( executor: Box<&'a mut dyn Executor>, - directory: &'a path::Path, + // directory: &'a path::Path, command: &'a str, alt_command: &'a str, ) -> Swapper<'a> { @@ -72,7 +73,7 @@ impl<'a> Swapper<'a> { Swapper { executor, - directory, + // directory, command, alt_command, active_pane_id: None, @@ -198,10 +199,10 @@ impl<'a> Swapper<'a> { // NOTE: For debugging add echo $PWD && sleep 5 after tee let pane_command = format!( - "tmux capture-pane -t {active_id} -p{scroll_params} | {dir}/target/release/thumbs -f '%U:%H' -t {tmpfile} {args}; tmux swap-pane -t {active_id}; tmux wait-for -S {signal}", + "tmux capture-pane -t {active_id} -p{scroll_params} | target/release/thumbs -f '%U:%H' -t {tmpfile} {args}; tmux swap-pane -t {active_id}; tmux wait-for -S {signal}", active_id = active_pane_id, scroll_params = scroll_params, - dir = self.directory.to_str().unwrap(), + // dir = self.directory.to_str().unwrap(), tmpfile = TMP_FILE, args = args.join(" "), signal = self.signal @@ -317,7 +318,7 @@ mod tests { let last_command_outputs = vec!["%97:100:24:1:active\n%106:100:24:1:nope\n%107:100:24:1:nope\n".to_string()]; let mut executor = TestShell::new(last_command_outputs); - let mut swapper = Swapper::new(Box::new(&mut executor), &path::Path::new(""), "", ""); + let mut swapper = Swapper::new(Box::new(&mut executor), "", ""); swapper.capture_active_pane(); @@ -333,7 +334,7 @@ mod tests { "%106:100:24:1:nope\n%98:100:24:1:active\n%107:100:24:1:nope\n".to_string(), ]; let mut executor = TestShell::new(last_command_outputs); - let mut swapper = Swapper::new(Box::new(&mut executor), &path::Path::new(""), "", ""); + let mut swapper = Swapper::new(Box::new(&mut executor), "", ""); swapper.capture_active_pane(); swapper.execute_thumbs(); @@ -348,21 +349,13 @@ mod tests { /// Main configuration, parsed from command line. #[derive(Clap, Debug)] #[clap(author, about, version)] -struct Opt { - /// Directory where to execute copyrat. - #[clap(long, required = true)] - directory: path::PathBuf, - +struct BridgeOpt { /// Command to execute on selection. - #[clap(short, long, default_value = "'tmux set-buffer {}'")] + #[clap(short, long, default_value = "tmux set-buffer {}")] command: String, - /// Command to execute on alt selection. - #[clap( - short, - long, - default_value = "'tmux set-buffer {} && tmux-paste-buffer'" - )] + /// Command to execute on uppercased selection. + #[clap(short, long, default_value = "tmux set-buffer {} && tmux-paste-buffer")] alt_command: String, /// Retrieve options from tmux. @@ -372,22 +365,54 @@ struct Opt { /// option) as this saves both a command call (about 10ms) and a Regex /// compilation. #[clap(long)] - options_from_tmux: bool, + get_options_from_tmux: bool, /// Optionally capture entire pane history. #[clap(long, arg_enum, default_value = "entire-history")] capture: tmux::CaptureRegion, + + // Include CLI Options + #[clap(flatten)] + cli_options: CliOpt, +} + +impl BridgeOpt { + /// Try parsing provided options, and update self with the valid values. + pub fn merge_map( + &mut self, + options: &HashMap, + ) -> Result<(), error::ParseError> { + for (name, value) in options { + match name.as_ref() { + "@copyrat-command" => { + self.command = String::from(value); + } + "@copyrat-alt-command" => { + self.alt_command = String::from(value); + } + "@copyrat-capture" => { + self.capture = tmux::CaptureRegion::from_str(&value)?; + } + + // Ignore unknown options. + _ => (), + } + } + + // Pass the call to cli_options. + self.cli_options.merge_map(options)?; + + Ok(()) + } } fn main() -> Result<(), error::ParseError> { - let opt = Opt::parse(); - // let dir = args.value_of("dir").unwrap(); - // let command = args.value_of("command").unwrap(); - // let upcase_command = args.value_of("upcase_command").unwrap(); + let mut opt = BridgeOpt::parse(); - // if dir.is_empty() { - // panic!("Invalid tmux-thumbs execution. Are you trying to execute tmux-thumbs directly?") - // } + if opt.get_options_from_tmux { + let tmux_options = tmux::get_options("@copyrat-")?; + opt.merge_map(&tmux_options)?; + } let panes: Vec = tmux::list_panes()?; let active_pane = panes @@ -395,23 +420,46 @@ fn main() -> Result<(), error::ParseError> { .find(|p| p.is_active) .expect("One tmux pane should be active"); - let content = tmux::capture_pane(&active_pane, &opt.capture)?; + let buffer = tmux::capture_pane(&active_pane, &opt.capture)?; - let mut executor = RealShell::new(); + let selections: Vec<(String, bool)> = if active_pane.in_mode { + // TODO: fancy stuff + vec![(String::new(), false)] + } else { + copyrat::run(buffer, &opt.cli_options) + }; - let mut swapper = Swapper::new( - Box::new(&mut executor), - opt.directory.as_path(), - &opt.command, - &opt.alt_command, - ); + selections.iter().for_each(|(text, b)| { + let command = if *b { + opt.alt_command.replace("{}", text) + } else { + opt.command.replace("{}", text) + }; + let args: Vec<&str> = vec![]; + + // Simply execute the command as is and don't mind about potential + // errors. + process::execute(&command, &args).unwrap(); + }); + + if false { + let mut executor = RealShell::new(); + + let mut swapper = Swapper::new( + Box::new(&mut executor), + // opt.directory.as_path(), + &opt.command, + &opt.alt_command, + ); + + swapper.capture_active_pane(); + swapper.execute_thumbs(); + swapper.swap_panes(); + swapper.wait_thumbs(); + swapper.retrieve_content(); + swapper.destroy_content(); + swapper.execute_command(); + } - swapper.capture_active_pane(); - swapper.execute_thumbs(); - swapper.swap_panes(); - swapper.wait_thumbs(); - swapper.retrieve_content(); - swapper.destroy_content(); - swapper.execute_command(); Ok(()) } diff --git a/src/error.rs b/src/error.rs index d38f483..aa233cf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,7 @@ pub enum ParseError { ExpectedPaneIdMarker, ExpectedInt(std::num::ParseIntError), ExpectedBool(std::str::ParseBoolError), + ExpectedString(String), ProcessFailure(String), } @@ -22,6 +23,7 @@ impl fmt::Display for ParseError { ParseError::ExpectedPaneIdMarker => write!(f, "Expected pane id marker"), ParseError::ExpectedInt(msg) => write!(f, "Expected an int: {}", msg), ParseError::ExpectedBool(msg) => write!(f, "Expected a bool: {}", msg), + ParseError::ExpectedString(msg) => write!(f, "Expected {}", msg), ParseError::ProcessFailure(msg) => write!(f, "{}", msg), } } diff --git a/src/lib.rs b/src/lib.rs index 2fc1f80..d951492 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,13 @@ use clap::Clap; +use std::collections::HashMap; use std::path; +use std::str::FromStr; pub mod alphabets; pub mod colors; pub mod error; +pub mod process; +pub mod regexes; pub mod state; pub mod view; @@ -11,8 +15,8 @@ pub mod view; /// /// # Note /// -/// Maybe the decision to move ownership is a bit bold. -pub fn run(buffer: String, opt: &Opt) -> String { +/// Maybe the decision to take ownership of the buffer is a bit bold. +pub fn run(buffer: String, opt: &CliOpt) -> Vec<(String, bool)> { let lines: Vec<&str> = buffer.split('\n').collect(); let mut state = state::State::new(&lines, &opt.alphabet, &opt.custom_regex); @@ -27,7 +31,6 @@ pub fn run(buffer: String, opt: &Opt) -> String { } }, }; - let uppercased_marker = opt.uppercased_marker; let selections: Vec<(String, bool)> = { let mut viewbox = view::View::new( @@ -43,32 +46,13 @@ pub fn run(buffer: String, opt: &Opt) -> String { viewbox.present() }; - // Early exit, signaling tmux we had no selections. - if selections.is_empty() { - std::process::exit(1); - } - - let output: String = if uppercased_marker { - selections - .iter() - .map(|(text, uppercased)| format!("{}:{}", *uppercased, text)) - .collect::>() - .join("\n") - } else { - selections - .iter() - .map(|(text, _)| text.as_str()) - .collect::>() - .join("\n") - }; - - output + selections } /// Main configuration, parsed from command line. #[derive(Clap, Debug)] #[clap(author, about, version)] -pub struct Opt { +pub struct CliOpt { /// Alphabet to draw hints from. /// /// Possible values are "{A}", "{A}-homerow", "{A}-left-hand", @@ -78,13 +62,20 @@ pub struct Opt { parse(try_from_str = alphabets::parse_alphabet))] alphabet: alphabets::Alphabet, + // /// Which existing regexes to use. + // #[clap(short = "x", long, arg_enum)] + // regex_id: Vec, + + // TODO: choose if pre-baked regexes is a good idea + // TODO: check if compiled regexes are possible + /// Additional regex patterns. + #[clap(short = "X", long)] + custom_regex: Vec, + /// Enable multi-selection. #[clap(short, long)] multi_selection: bool, - #[clap(flatten)] - colors: view::ViewColors, - /// Reverse the order for assigned hints. #[clap(short, long)] reverse: bool, @@ -93,14 +84,13 @@ pub struct Opt { #[clap(short, long)] unique_hint: bool, + #[clap(flatten)] + colors: view::ViewColors, + /// Align hint with its match. #[clap(short = "a", long, arg_enum, default_value = "leading")] hint_alignment: view::HintAlignment, - /// Additional regex patterns. - #[clap(short = "c", long)] - custom_regex: Vec, - /// Optional hint styling. /// /// Underline or surround the hint for increased visibility. @@ -121,7 +111,7 @@ pub struct Opt { /// indicating if hint key was uppercased. This is only used by /// tmux-copyrat, so it is hidden (skipped) from the CLI. #[clap(skip)] - uppercased_marker: bool, + pub uppercased_marker: bool, } /// Type introduced due to parsing limitation, @@ -132,6 +122,20 @@ enum HintStyleCli { Surround, } +impl FromStr for HintStyleCli { + type Err = error::ParseError; + + fn from_str(s: &str) -> Result { + match s { + "leading" => Ok(HintStyleCli::Underline), + "trailing" => Ok(HintStyleCli::Surround), + _ => Err(error::ParseError::ExpectedString(String::from( + "underline or surround", + ))), + } + } +} + fn parse_chars(src: &str) -> Result<(char, char), error::ParseError> { if src.len() != 2 { return Err(error::ParseError::ExpectedSurroundingPair); @@ -140,3 +144,47 @@ fn parse_chars(src: &str) -> Result<(char, char), error::ParseError> { let chars: Vec = src.chars().collect(); Ok((chars[0], chars[1])) } + +impl CliOpt { + /// Try parsing provided options, and update self with the valid values. + pub fn merge_map( + &mut self, + options: &HashMap, + ) -> Result<(), error::ParseError> { + for (name, value) in options { + match name.as_ref() { + "@copyrat-alphabet" => { + self.alphabet = alphabets::parse_alphabet(value)?; + } + "@copyrat-regex-id" => (), // TODO + "@copyrat-custom-regex" => self.custom_regex = vec![String::from(value)], + "@copyrat-multi-selection" => { + self.multi_selection = value.parse::()?; + } + "@copyrat-reverse" => { + self.reverse = value.parse::()?; + } + "@copyrat-unique-hint" => { + self.unique_hint = value.parse::()?; + } + + "@copyrat-match-fg" => self.colors.match_fg = colors::parse_color(value)?, + "@copyrat-match-bg" => self.colors.match_bg = colors::parse_color(value)?, + "@copyrat-focused-fg" => self.colors.focused_fg = colors::parse_color(value)?, + "@copyrat-focused-bg" => self.colors.focused_bg = colors::parse_color(value)?, + "@copyrat-hint-fg" => self.colors.hint_fg = colors::parse_color(value)?, + "@copyrat-hint-bg" => self.colors.hint_bg = colors::parse_color(value)?, + + "@copyrat-hint-alignment" => { + self.hint_alignment = view::HintAlignment::from_str(&value)? + } + "@copyrat-hint-style" => self.hint_style = Some(HintStyleCli::from_str(&value)?), + + // Ignore unknown options. + _ => (), + } + } + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index cec8f83..5c7f128 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,10 @@ use std::fs::OpenOptions; use std::io::prelude::*; use std::io::{self, Read}; -use copyrat::{run, Opt}; +use copyrat::{run, CliOpt}; fn main() { - let opt = Opt::parse(); + let opt = CliOpt::parse(); // Copy the pane contents (piped in via stdin) into a buffer, and split lines. let stdin = io::stdin(); @@ -17,7 +17,18 @@ fn main() { // Execute copyrat over the buffer (will take control over stdout). // This returns the selected matches. - let output: String = run(buffer, &opt); + let selections: Vec<(String, bool)> = run(buffer, &opt); + + // Early exit, signaling no selections were found. + if selections.is_empty() { + std::process::exit(1); + } + + let output = selections + .iter() + .map(|(text, _)| text.as_str()) + .collect::>() + .join("\n"); // Write output to a target_path if provided, else print to original stdout. match opt.target_path { diff --git a/src/process.rs b/src/process.rs new file mode 100644 index 0000000..af6a5f1 --- /dev/null +++ b/src/process.rs @@ -0,0 +1,21 @@ +use std::process::Command; + +use crate::error::ParseError; + +/// Execute an arbitrary Unix command and return the stdout as a `String` if +/// successful. +pub fn execute(command: &str, args: &Vec<&str>) -> Result { + let output = Command::new(command).args(args).output()?; + + if !output.status.success() { + let msg = String::from_utf8_lossy(&output.stderr); + return Err(ParseError::ProcessFailure(format!( + "Process failure: {} {}, error {}", + command, + args.join(" "), + msg + ))); + } + + Ok(String::from_utf8_lossy(&output.stdout).to_string()) +} diff --git a/src/regexes.rs b/src/regexes.rs new file mode 100644 index 0000000..a9bbd79 --- /dev/null +++ b/src/regexes.rs @@ -0,0 +1,25 @@ +pub const EXCLUDE_PATTERNS: [(&'static str, &'static str); 1] = + [("bash", r"[[:cntrl:]]\[([0-9]{1,2};)?([0-9]{1,2})?m")]; + +pub const PATTERNS: [(&'static str, &'static str); 14] = [ + ("markdown_url", r"\[[^]]*\]\(([^)]+)\)"), + ( + "url", + r"((https?://|git@|git://|ssh://|ftp://|file:///)[^ ]+)", + ), + ("diff_a", r"--- a/([^ ]+)"), + ("diff_b", r"\+\+\+ b/([^ ]+)"), + ("docker", r"sha256:([0-9a-f]{64})"), + ("path", r"(([.\w\-@~]+)?(/[.\w\-@]+)+)"), + ("color", r"#[0-9a-fA-F]{6}"), + ( + "uid", + r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", + ), + ("ipfs", r"Qm[0-9a-zA-Z]{44}"), + ("sha", r"[0-9a-f]{7,40}"), + ("ip", r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"), + ("ipv6", r"[A-f0-9:]+:+[A-f0-9:]+[%\w\d]+"), + ("address", r"0x[0-9a-fA-F]+"), + ("number", r"[0-9]{4,}"), +]; diff --git a/src/state.rs b/src/state.rs index 69db1bc..f896186 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,445 +3,463 @@ use regex::Regex; use std::collections::HashMap; use std::fmt; -const EXCLUDE_PATTERNS: [(&'static str, &'static str); 1] = [("bash", r"[[:cntrl:]]\[([0-9]{1,2};)?([0-9]{1,2})?m")]; - -const PATTERNS: [(&'static str, &'static str); 14] = [ - ("markdown_url", r"\[[^]]*\]\(([^)]+)\)"), - ("url", r"((https?://|git@|git://|ssh://|ftp://|file:///)[^ ]+)"), - ("diff_a", r"--- a/([^ ]+)"), - ("diff_b", r"\+\+\+ b/([^ ]+)"), - ("docker", r"sha256:([0-9a-f]{64})"), - ("path", r"(([.\w\-@~]+)?(/[.\w\-@]+)+)"), - ("color", r"#[0-9a-fA-F]{6}"), - ("uid", r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"), - ("ipfs", r"Qm[0-9a-zA-Z]{44}"), - ("sha", r"[0-9a-f]{7,40}"), - ("ip", r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"), - ("ipv6", r"[A-f0-9:]+:+[A-f0-9:]+[%\w\d]+"), - ("address", r"0x[0-9a-fA-F]+"), - ("number", r"[0-9]{4,}"), -]; +use crate::regexes::{EXCLUDE_PATTERNS, PATTERNS}; #[derive(Clone)] pub struct Match<'a> { - pub x: i32, - pub y: i32, - pub pattern: &'a str, - pub text: &'a str, - pub hint: Option, + pub x: i32, + pub y: i32, + pub pattern: &'a str, + pub text: &'a str, + pub hint: Option, } impl<'a> fmt::Debug for Match<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Match {{ x: {}, y: {}, pattern: {}, text: {}, hint: <{}> }}", - self.x, - self.y, - self.pattern, - self.text, - self.hint.clone().unwrap_or("".to_string()) - ) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Match {{ x: {}, y: {}, pattern: {}, text: {}, hint: <{}> }}", + self.x, + self.y, + self.pattern, + self.text, + self.hint.clone().unwrap_or("".to_string()) + ) + } } impl<'a> PartialEq for Match<'a> { - fn eq(&self, other: &Match) -> bool { - self.x == other.x && self.y == other.y - } + fn eq(&self, other: &Match) -> bool { + self.x == other.x && self.y == other.y + } } pub struct State<'a> { - pub lines: &'a Vec<&'a str>, - alphabet: &'a Alphabet, - regexp: &'a Vec, + pub lines: &'a Vec<&'a str>, + alphabet: &'a Alphabet, + regexp: &'a Vec, } impl<'a> State<'a> { - pub fn new(lines: &'a Vec<&'a str>, alphabet: &'a Alphabet, regexp: &'a Vec) -> State<'a> { - State { - lines, - alphabet, - regexp, + pub fn new( + lines: &'a Vec<&'a str>, + alphabet: &'a Alphabet, + regexp: &'a Vec, + ) -> State<'a> { + State { + lines, + alphabet, + regexp, + } } - } - pub fn matches(&self, reverse: bool, unique: bool) -> Vec> { - let mut matches = Vec::new(); + pub fn matches(&self, reverse: bool, unique: bool) -> Vec> { + let mut matches = Vec::new(); - let exclude_patterns = EXCLUDE_PATTERNS - .iter() - .map(|tuple| (tuple.0, Regex::new(tuple.1).unwrap())) - .collect::>(); + let exclude_patterns = EXCLUDE_PATTERNS + .iter() + .map(|tuple| (tuple.0, Regex::new(tuple.1).unwrap())) + .collect::>(); - let custom_patterns = self - .regexp - .iter() - .map(|regexp| ("custom", Regex::new(regexp).expect("Invalid custom regexp"))) - .collect::>(); + let custom_patterns = self + .regexp + .iter() + .map(|regexp| ("custom", Regex::new(regexp).expect("Invalid custom regexp"))) + .collect::>(); - let patterns = PATTERNS - .iter() - .map(|tuple| (tuple.0, Regex::new(tuple.1).unwrap())) - .collect::>(); + let patterns = PATTERNS + .iter() + .map(|tuple| (tuple.0, Regex::new(tuple.1).unwrap())) + .collect::>(); - let all_patterns = [exclude_patterns, custom_patterns, patterns].concat(); + let all_patterns = [exclude_patterns, custom_patterns, patterns].concat(); - for (index, line) in self.lines.iter().enumerate() { - let mut chunk: &str = line; - let mut offset: i32 = 0; + for (index, line) in self.lines.iter().enumerate() { + let mut chunk: &str = line; + let mut offset: i32 = 0; - loop { - let submatches = all_patterns - .iter() - .filter_map(|tuple| match tuple.1.find_iter(chunk).nth(0) { - Some(m) => Some((tuple.0, tuple.1.clone(), m)), - None => None, - }) - .collect::>(); - let first_match_option = submatches.iter().min_by(|x, y| x.2.start().cmp(&y.2.start())); + loop { + let submatches = all_patterns + .iter() + .filter_map(|tuple| match tuple.1.find_iter(chunk).nth(0) { + Some(m) => Some((tuple.0, tuple.1.clone(), m)), + None => None, + }) + .collect::>(); + let first_match_option = submatches + .iter() + .min_by(|x, y| x.2.start().cmp(&y.2.start())); - if let Some(first_match) = first_match_option { - let (name, pattern, matching) = first_match; - let text = matching.as_str(); + if let Some(first_match) = first_match_option { + let (name, pattern, matching) = first_match; + let text = matching.as_str(); - if let Some(captures) = pattern.captures(text) { - let (subtext, substart) = if let Some(capture) = captures.get(1) { - (capture.as_str(), capture.start()) - } else { - (matching.as_str(), 0) - }; + if let Some(captures) = pattern.captures(text) { + let (subtext, substart) = if let Some(capture) = captures.get(1) { + (capture.as_str(), capture.start()) + } else { + (matching.as_str(), 0) + }; - // Never hint or broke bash color sequences - if *name != "bash" { - matches.push(Match { - x: offset + matching.start() as i32 + substart as i32, - y: index as i32, - pattern: name, - text: subtext, - hint: None, - }); + // Never hint or broke bash color sequences + if *name != "bash" { + matches.push(Match { + x: offset + matching.start() as i32 + substart as i32, + y: index as i32, + pattern: name, + text: subtext, + hint: None, + }); + } + + chunk = chunk.get(matching.end()..).expect("Unknown chunk"); + offset += matching.end() as i32; + } else { + panic!("No matching?"); + } + } else { + break; + } } + } - chunk = chunk.get(matching.end()..).expect("Unknown chunk"); - offset += matching.end() as i32; - } else { - panic!("No matching?"); - } + // let alphabet = super::alphabets::get_alphabet(self.alphabet); + let mut hints = self.alphabet.hints(matches.len()); + + // This looks wrong but we do a pop after + if !reverse { + hints.reverse(); } else { - break; + matches.reverse(); + hints.reverse(); } - } - } - // let alphabet = super::alphabets::get_alphabet(self.alphabet); - let mut hints = self.alphabet.hints(matches.len()); + if unique { + let mut previous: HashMap<&str, String> = HashMap::new(); - // This looks wrong but we do a pop after - if !reverse { - hints.reverse(); - } else { - matches.reverse(); - hints.reverse(); - } - - if unique { - let mut previous: HashMap<&str, String> = HashMap::new(); - - for mat in &mut matches { - if let Some(previous_hint) = previous.get(mat.text) { - mat.hint = Some(previous_hint.clone()); - } else if let Some(hint) = hints.pop() { - mat.hint = Some(hint.to_string().clone()); - previous.insert(mat.text, hint.to_string().clone()); + for mat in &mut matches { + if let Some(previous_hint) = previous.get(mat.text) { + mat.hint = Some(previous_hint.clone()); + } else if let Some(hint) = hints.pop() { + mat.hint = Some(hint.to_string().clone()); + previous.insert(mat.text, hint.to_string().clone()); + } + } + } else { + for mat in &mut matches { + if let Some(hint) = hints.pop() { + mat.hint = Some(hint.to_string().clone()); + } + } } - } - } else { - for mat in &mut matches { - if let Some(hint) = hints.pop() { - mat.hint = Some(hint.to_string().clone()); + + if reverse { + matches.reverse(); } - } - } - if reverse { - matches.reverse(); + matches } - - matches - } } #[cfg(test)] mod tests { - use super::*; - use crate::alphabets::Alphabet; + use super::*; + use crate::alphabets::Alphabet; - fn split(output: &str) -> Vec<&str> { - output.split("\n").collect::>() - } + fn split(output: &str) -> Vec<&str> { + output.split("\n").collect::>() + } - #[test] - fn match_reverse() { - let lines = split("lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_reverse() { + let lines = split("lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 3); - assert_eq!(results.first().unwrap().hint.clone().unwrap(), "a"); - assert_eq!(results.last().unwrap().hint.clone().unwrap(), "c"); - } + assert_eq!(results.len(), 3); + assert_eq!(results.first().unwrap().hint.clone().unwrap(), "a"); + assert_eq!(results.last().unwrap().hint.clone().unwrap(), "c"); + } - #[test] - fn match_unique() { - let lines = split("lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, true); + #[test] + fn match_unique() { + let lines = split("lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, true); - assert_eq!(results.len(), 3); - assert_eq!(results.first().unwrap().hint.clone().unwrap(), "a"); - assert_eq!(results.last().unwrap().hint.clone().unwrap(), "a"); - } + assert_eq!(results.len(), 3); + assert_eq!(results.first().unwrap().hint.clone().unwrap(), "a"); + assert_eq!(results.last().unwrap().hint.clone().unwrap(), "a"); + } - #[test] - fn match_docker() { - let lines = split("latest sha256:30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_docker() { + let lines = split("latest sha256:30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 1); - assert_eq!( - results.get(0).unwrap().text, - "30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4" - ); - } + assert_eq!(results.len(), 1); + assert_eq!( + results.get(0).unwrap().text, + "30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4" + ); + } - #[test] - fn match_bash() { - let lines = split("path: /var/log/nginx.log\npath: test/log/nginx-2.log:32folder/.nginx@4df2.log"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_bash() { + let lines = split("path: /var/log/nginx.log\npath: test/log/nginx-2.log:32folder/.nginx@4df2.log"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 3); - assert_eq!(results.get(0).unwrap().text, "/var/log/nginx.log"); - assert_eq!(results.get(1).unwrap().text, "test/log/nginx-2.log"); - assert_eq!(results.get(2).unwrap().text, "folder/.nginx@4df2.log"); - } + assert_eq!(results.len(), 3); + assert_eq!(results.get(0).unwrap().text, "/var/log/nginx.log"); + assert_eq!(results.get(1).unwrap().text, "test/log/nginx-2.log"); + assert_eq!(results.get(2).unwrap().text, "folder/.nginx@4df2.log"); + } - #[test] - fn match_paths() { - let lines = split("Lorem /tmp/foo/bar_lol, lorem\n Lorem /var/log/boot-strap.log lorem ../log/kern.log lorem"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_paths() { + let lines = split("Lorem /tmp/foo/bar_lol, lorem\n Lorem /var/log/boot-strap.log lorem ../log/kern.log lorem"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 3); - assert_eq!(results.get(0).unwrap().text.clone(), "/tmp/foo/bar_lol"); - assert_eq!(results.get(1).unwrap().text.clone(), "/var/log/boot-strap.log"); - assert_eq!(results.get(2).unwrap().text.clone(), "../log/kern.log"); - } + assert_eq!(results.len(), 3); + assert_eq!(results.get(0).unwrap().text.clone(), "/tmp/foo/bar_lol"); + assert_eq!( + results.get(1).unwrap().text.clone(), + "/var/log/boot-strap.log" + ); + assert_eq!(results.get(2).unwrap().text.clone(), "../log/kern.log"); + } - #[test] - fn match_home() { - let lines = split("Lorem ~/.gnu/.config.txt, lorem"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_home() { + let lines = split("Lorem ~/.gnu/.config.txt, lorem"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 1); - assert_eq!(results.get(0).unwrap().text.clone(), "~/.gnu/.config.txt"); - } + assert_eq!(results.len(), 1); + assert_eq!(results.get(0).unwrap().text.clone(), "~/.gnu/.config.txt"); + } - #[test] - fn match_uids() { - let lines = split("Lorem ipsum 123e4567-e89b-12d3-a456-426655440000 lorem\n Lorem lorem lorem"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_uids() { + let lines = + split("Lorem ipsum 123e4567-e89b-12d3-a456-426655440000 lorem\n Lorem lorem lorem"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 1); - } + assert_eq!(results.len(), 1); + } - #[test] - fn match_shas() { - let lines = split("Lorem fd70b5695 5246ddf f924213 lorem\n Lorem 973113963b491874ab2e372ee60d4b4cb75f717c lorem"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_shas() { + let lines = split("Lorem fd70b5695 5246ddf f924213 lorem\n Lorem 973113963b491874ab2e372ee60d4b4cb75f717c lorem"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 4); - assert_eq!(results.get(0).unwrap().text.clone(), "fd70b5695"); - assert_eq!(results.get(1).unwrap().text.clone(), "5246ddf"); - assert_eq!(results.get(2).unwrap().text.clone(), "f924213"); - assert_eq!( - results.get(3).unwrap().text.clone(), - "973113963b491874ab2e372ee60d4b4cb75f717c" - ); - } + assert_eq!(results.len(), 4); + assert_eq!(results.get(0).unwrap().text.clone(), "fd70b5695"); + assert_eq!(results.get(1).unwrap().text.clone(), "5246ddf"); + assert_eq!(results.get(2).unwrap().text.clone(), "f924213"); + assert_eq!( + results.get(3).unwrap().text.clone(), + "973113963b491874ab2e372ee60d4b4cb75f717c" + ); + } - #[test] - fn match_ips() { - let lines = split("Lorem ipsum 127.0.0.1 lorem\n Lorem 255.255.10.255 lorem 127.0.0.1 lorem"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_ips() { + let lines = + split("Lorem ipsum 127.0.0.1 lorem\n Lorem 255.255.10.255 lorem 127.0.0.1 lorem"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 3); - assert_eq!(results.get(0).unwrap().text.clone(), "127.0.0.1"); - assert_eq!(results.get(1).unwrap().text.clone(), "255.255.10.255"); - assert_eq!(results.get(2).unwrap().text.clone(), "127.0.0.1"); - } + assert_eq!(results.len(), 3); + assert_eq!(results.get(0).unwrap().text.clone(), "127.0.0.1"); + assert_eq!(results.get(1).unwrap().text.clone(), "255.255.10.255"); + assert_eq!(results.get(2).unwrap().text.clone(), "127.0.0.1"); + } - #[test] - fn match_ipv6s() { - let lines = split("Lorem ipsum fe80::2:202:fe4 lorem\n Lorem 2001:67c:670:202:7ba8:5e41:1591:d723 lorem fe80::2:1 lorem ipsum fe80:22:312:fe::1%eth0"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_ipv6s() { + let lines = split("Lorem ipsum fe80::2:202:fe4 lorem\n Lorem 2001:67c:670:202:7ba8:5e41:1591:d723 lorem fe80::2:1 lorem ipsum fe80:22:312:fe::1%eth0"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 4); - assert_eq!(results.get(0).unwrap().text.clone(), "fe80::2:202:fe4"); - assert_eq!( - results.get(1).unwrap().text.clone(), - "2001:67c:670:202:7ba8:5e41:1591:d723" - ); - assert_eq!(results.get(2).unwrap().text.clone(), "fe80::2:1"); - assert_eq!(results.get(3).unwrap().text.clone(), "fe80:22:312:fe::1%eth0"); - } + assert_eq!(results.len(), 4); + assert_eq!(results.get(0).unwrap().text.clone(), "fe80::2:202:fe4"); + assert_eq!( + results.get(1).unwrap().text.clone(), + "2001:67c:670:202:7ba8:5e41:1591:d723" + ); + assert_eq!(results.get(2).unwrap().text.clone(), "fe80::2:1"); + assert_eq!( + results.get(3).unwrap().text.clone(), + "fe80:22:312:fe::1%eth0" + ); + } - #[test] - fn match_markdown_urls() { - let lines = split("Lorem ipsum [link](https://github.io?foo=bar) ![](http://cdn.com/img.jpg) lorem"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_markdown_urls() { + let lines = split( + "Lorem ipsum [link](https://github.io?foo=bar) ![](http://cdn.com/img.jpg) lorem", + ); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 2); - assert_eq!(results.get(0).unwrap().pattern.clone(), "markdown_url"); - assert_eq!(results.get(0).unwrap().text.clone(), "https://github.io?foo=bar"); - assert_eq!(results.get(1).unwrap().pattern.clone(), "markdown_url"); - assert_eq!(results.get(1).unwrap().text.clone(), "http://cdn.com/img.jpg"); - } + assert_eq!(results.len(), 2); + assert_eq!(results.get(0).unwrap().pattern.clone(), "markdown_url"); + assert_eq!( + results.get(0).unwrap().text.clone(), + "https://github.io?foo=bar" + ); + assert_eq!(results.get(1).unwrap().pattern.clone(), "markdown_url"); + assert_eq!( + results.get(1).unwrap().text.clone(), + "http://cdn.com/img.jpg" + ); + } - #[test] - fn match_urls() { - let lines = split("Lorem ipsum https://www.rust-lang.org/tools lorem\n Lorem ipsumhttps://crates.io lorem https://github.io?foo=bar lorem ssh://github.io"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_urls() { + let lines = split("Lorem ipsum https://www.rust-lang.org/tools lorem\n Lorem ipsumhttps://crates.io lorem https://github.io?foo=bar lorem ssh://github.io"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 4); - assert_eq!(results.get(0).unwrap().text.clone(), "https://www.rust-lang.org/tools"); - assert_eq!(results.get(0).unwrap().pattern.clone(), "url"); - assert_eq!(results.get(1).unwrap().text.clone(), "https://crates.io"); - assert_eq!(results.get(1).unwrap().pattern.clone(), "url"); - assert_eq!(results.get(2).unwrap().text.clone(), "https://github.io?foo=bar"); - assert_eq!(results.get(2).unwrap().pattern.clone(), "url"); - assert_eq!(results.get(3).unwrap().text.clone(), "ssh://github.io"); - assert_eq!(results.get(3).unwrap().pattern.clone(), "url"); - } + assert_eq!(results.len(), 4); + assert_eq!( + results.get(0).unwrap().text.clone(), + "https://www.rust-lang.org/tools" + ); + assert_eq!(results.get(0).unwrap().pattern.clone(), "url"); + assert_eq!(results.get(1).unwrap().text.clone(), "https://crates.io"); + assert_eq!(results.get(1).unwrap().pattern.clone(), "url"); + assert_eq!( + results.get(2).unwrap().text.clone(), + "https://github.io?foo=bar" + ); + assert_eq!(results.get(2).unwrap().pattern.clone(), "url"); + assert_eq!(results.get(3).unwrap().text.clone(), "ssh://github.io"); + assert_eq!(results.get(3).unwrap().pattern.clone(), "url"); + } - #[test] - fn match_addresses() { - let lines = split("Lorem 0xfd70b5695 0x5246ddf lorem\n Lorem 0x973113tlorem"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_addresses() { + let lines = split("Lorem 0xfd70b5695 0x5246ddf lorem\n Lorem 0x973113tlorem"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 3); - assert_eq!(results.get(0).unwrap().text.clone(), "0xfd70b5695"); - assert_eq!(results.get(1).unwrap().text.clone(), "0x5246ddf"); - assert_eq!(results.get(2).unwrap().text.clone(), "0x973113"); - } + assert_eq!(results.len(), 3); + assert_eq!(results.get(0).unwrap().text.clone(), "0xfd70b5695"); + assert_eq!(results.get(1).unwrap().text.clone(), "0x5246ddf"); + assert_eq!(results.get(2).unwrap().text.clone(), "0x973113"); + } - #[test] - fn match_hex_colors() { - let lines = split("Lorem #fd7b56 lorem #FF00FF\n Lorem #00fF05 lorem #abcd00 lorem #afRR00"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_hex_colors() { + let lines = + split("Lorem #fd7b56 lorem #FF00FF\n Lorem #00fF05 lorem #abcd00 lorem #afRR00"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 4); - assert_eq!(results.get(0).unwrap().text.clone(), "#fd7b56"); - assert_eq!(results.get(1).unwrap().text.clone(), "#FF00FF"); - assert_eq!(results.get(2).unwrap().text.clone(), "#00fF05"); - assert_eq!(results.get(3).unwrap().text.clone(), "#abcd00"); - } + assert_eq!(results.len(), 4); + assert_eq!(results.get(0).unwrap().text.clone(), "#fd7b56"); + assert_eq!(results.get(1).unwrap().text.clone(), "#FF00FF"); + assert_eq!(results.get(2).unwrap().text.clone(), "#00fF05"); + assert_eq!(results.get(3).unwrap().text.clone(), "#abcd00"); + } - #[test] - fn match_ipfs() { - let lines = split("Lorem QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ lorem Qmfoobar"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_ipfs() { + let lines = split("Lorem QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ lorem Qmfoobar"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 1); - assert_eq!( - results.get(0).unwrap().text.clone(), - "QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ" - ); - } + assert_eq!(results.len(), 1); + assert_eq!( + results.get(0).unwrap().text.clone(), + "QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ" + ); + } - #[test] - fn match_process_port() { - let lines = + #[test] + fn match_process_port() { + let lines = split("Lorem 5695 52463 lorem\n Lorem 973113 lorem 99999 lorem 8888 lorem\n 23456 lorem 5432 lorem 23444"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 8); - } + assert_eq!(results.len(), 8); + } - #[test] - fn match_diff_a() { - let lines = split("Lorem lorem\n--- a/src/main.rs"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_diff_a() { + let lines = split("Lorem lorem\n--- a/src/main.rs"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 1); - assert_eq!(results.get(0).unwrap().text.clone(), "src/main.rs"); - } + assert_eq!(results.len(), 1); + assert_eq!(results.get(0).unwrap().text.clone(), "src/main.rs"); + } - #[test] - fn match_diff_b() { - let lines = split("Lorem lorem\n+++ b/src/main.rs"); - let custom = [].to_vec(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + #[test] + fn match_diff_b() { + let lines = split("Lorem lorem\n+++ b/src/main.rs"); + let custom = [].to_vec(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 1); - assert_eq!(results.get(0).unwrap().text.clone(), "src/main.rs"); - } + assert_eq!(results.len(), 1); + assert_eq!(results.get(0).unwrap().text.clone(), "src/main.rs"); + } - #[test] - fn priority() { - let lines = split("Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"); + #[test] + fn priority() { + let lines = split("Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"); - let custom: Vec = ["CUSTOM-[0-9]{4,}", "ISSUE-[0-9]{3}"] - .iter() - .map(|&s| s.to_string()) - .collect(); - let alphabet = Alphabet("abcd".to_string()); - let results = State::new(&lines, &alphabet, &custom).matches(false, false); + let custom: Vec = ["CUSTOM-[0-9]{4,}", "ISSUE-[0-9]{3}"] + .iter() + .map(|&s| s.to_string()) + .collect(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); - assert_eq!(results.len(), 9); - assert_eq!(results.get(0).unwrap().text.clone(), "http://foo.bar"); - assert_eq!(results.get(1).unwrap().text.clone(), "CUSTOM-52463"); - assert_eq!(results.get(2).unwrap().text.clone(), "ISSUE-123"); - assert_eq!(results.get(3).unwrap().text.clone(), "/var/fd70b569/9999.log"); - assert_eq!(results.get(4).unwrap().text.clone(), "52463"); - assert_eq!(results.get(5).unwrap().text.clone(), "973113"); - assert_eq!( - results.get(6).unwrap().text.clone(), - "123e4567-e89b-12d3-a456-426655440000" - ); - assert_eq!(results.get(7).unwrap().text.clone(), "8888"); - assert_eq!(results.get(8).unwrap().text.clone(), "https://crates.io/23456/fd70b569"); - } + assert_eq!(results.len(), 9); + assert_eq!(results.get(0).unwrap().text.clone(), "http://foo.bar"); + assert_eq!(results.get(1).unwrap().text.clone(), "CUSTOM-52463"); + assert_eq!(results.get(2).unwrap().text.clone(), "ISSUE-123"); + assert_eq!( + results.get(3).unwrap().text.clone(), + "/var/fd70b569/9999.log" + ); + assert_eq!(results.get(4).unwrap().text.clone(), "52463"); + assert_eq!(results.get(5).unwrap().text.clone(), "973113"); + assert_eq!( + results.get(6).unwrap().text.clone(), + "123e4567-e89b-12d3-a456-426655440000" + ); + assert_eq!(results.get(7).unwrap().text.clone(), "8888"); + assert_eq!( + results.get(8).unwrap().text.clone(), + "https://crates.io/23456/fd70b569" + ); + } } diff --git a/src/tmux.rs b/src/tmux.rs index 48f4447..dece861 100644 --- a/src/tmux.rs +++ b/src/tmux.rs @@ -1,26 +1,10 @@ use clap::Clap; -use copyrat::error::ParseError; use regex::Regex; use std::collections::HashMap; -use std::process::Command; +use std::str::FromStr; -/// Execute an arbitrary Unix command and return the stdout as a `String` if -/// successful. -fn execute(command: &str, args: &Vec<&str>) -> Result { - let output = Command::new(command).args(args).output()?; - - if !output.status.success() { - let msg = String::from_utf8_lossy(&output.stderr); - return Err(ParseError::ProcessFailure(format!( - "Process failure: {} {}, error {}", - command, - args.join(" "), - msg - ))); - } - - Ok(String::from_utf8_lossy(&output.stdout).to_string()) -} +use copyrat::error::ParseError; +use copyrat::process; #[derive(Debug, PartialEq)] pub struct Pane { @@ -97,7 +81,7 @@ pub fn list_panes() -> Result, ParseError> { "#{pane_id}:#{?pane_in_mode,1,0}:#{pane_height}:#{scroll_position}:#{?pane_active,active,nope}", ]; - let output = execute("tmux", &args)?; + let output = process::execute("tmux", &args)?; // Each call to `Pane::parse` returns a `Result`. All results // are collected into a Result, _>, thanks to `collect()`. @@ -116,7 +100,7 @@ pub fn list_panes() -> Result, ParseError> { pub fn get_options(prefix: &str) -> Result, ParseError> { let args = vec!["show", "-g"]; - let output = execute("tmux", &args)?; + let output = process::execute("tmux", &args)?; let lines: Vec<&str> = output.split('\n').collect(); let pattern = format!(r#"{prefix}([\w\-0-9]+) "?(\w+)"?"#, prefix = prefix); @@ -192,6 +176,20 @@ pub enum CaptureRegion { //Region(i32, i32), } +impl FromStr for CaptureRegion { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + match s { + "leading" => Ok(CaptureRegion::EntireHistory), + "trailing" => Ok(CaptureRegion::VisibleArea), + _ => Err(ParseError::ExpectedString(String::from( + "entire-history or visible-area", + ))), + } + } +} + /// Returns the entire Pane content as a `String`. /// /// `CaptureRegion` specifies if the visible area is captured, or the entire @@ -224,7 +222,7 @@ pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result = args.split(' ').collect(); - let output = execute("tmux", &args)?; + let output = process::execute("tmux", &args)?; Ok(output) // format!( diff --git a/src/view.rs b/src/view.rs index f0ac6b9..c3eb5e6 100644 --- a/src/view.rs +++ b/src/view.rs @@ -1,8 +1,10 @@ use super::{colors, state}; +use crate::error::ParseError; use clap::Clap; use std::char; use std::io::{stdout, Read, Write}; +use std::str::FromStr; use termion::async_stdin; use termion::event::Key; use termion::input::TermRead; @@ -30,32 +32,32 @@ pub struct ViewColors { /// Foreground color for matches. #[clap(long, default_value = "green", parse(try_from_str = colors::parse_color))] - match_fg: Box, + pub match_fg: Box, /// Background color for matches. #[clap(long, default_value = "black", parse(try_from_str = colors::parse_color))] - match_bg: Box, + pub match_bg: Box, /// Foreground color for the focused match. #[clap(long, default_value = "blue", parse(try_from_str = colors::parse_color))] - focused_fg: Box, + pub focused_fg: Box, /// Background color for the focused match. #[clap(long, default_value = "black", parse(try_from_str = colors::parse_color))] - focused_bg: Box, + pub focused_bg: Box, /// Foreground color for hints. #[clap(long, default_value = "white", parse(try_from_str = colors::parse_color))] - hint_fg: Box, + pub hint_fg: Box, /// Background color for hints. #[clap(long, default_value = "black", parse(try_from_str = colors::parse_color))] - hint_bg: Box, + pub hint_bg: Box, } /// Describes if, during rendering, a hint should aligned to the leading edge of @@ -66,6 +68,20 @@ pub enum HintAlignment { Trailing, } +impl FromStr for HintAlignment { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + match s { + "leading" => Ok(HintAlignment::Leading), + "trailing" => Ok(HintAlignment::Trailing), + _ => Err(ParseError::ExpectedString(String::from( + "leading or trailing", + ))), + } + } +} + /// Describes the style of contrast to be used during rendering of the hint's /// text. ///