diff --git a/Cargo.lock b/Cargo.lock index d7c0b83..62da427 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,7 +98,7 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colight" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "clap", @@ -167,9 +167,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -178,9 +178,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] diff --git a/Cargo.toml b/Cargo.toml index 941691b..62f4ce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "colight" -version = "0.2.0" +version = "0.3.0" edition = "2021" [dependencies] diff --git a/src/main.rs b/src/main.rs index 05b3f82..ddfb14e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,31 +20,41 @@ struct Args { /// How long back in history to look for matches #[arg(long, default_value_t = 1024)] window_size: usize, + + /// How much to penalize age of matches, higher values will make older matches less likely to be highlighted + #[arg(long, default_value_t = 0.01)] + age_penalty: f32, } fn main() -> anyhow::Result<()> { let args = Args::parse(); + assert!(args.window_size > 0); + assert!(args.age_penalty >= 0.0); let stdin = std::io::stdin(); let si = stdin.lock(); let stdout = StandardStream::stdout(ColorChoice::Auto); let so = stdout.lock(); - print_comp(si, so, args.window_size)?; + print_comp(si, so, args)?; Ok(()) } -fn print_comp(si: I, so: O, window_size: usize) -> anyhow::Result<()> +fn print_comp(si: I, so: O, args: Args) -> anyhow::Result<()> where O: WriteColor, I: Read, { + let Args { + window_size, + age_penalty, + } = args; // refcell so we can reset on scope exit let soc = RefCell::new(so); defer! { soc.borrow_mut().reset().unwrap(); } - let pr = |buffered: VecDeque| -> anyhow::Result<()> { + let pr = |buffered: VecDeque, age: Option| -> anyhow::Result<()> { if !buffered.is_empty() { - let compression = 1f32 / buffered.len() as f32; + let score = 1f32 / (buffered.len() as f32 + age.unwrap_or(0) as f32 * age_penalty); let (a, b) = buffered.as_slices(); for line in a .split_inclusive(|&c| c == b'\n') @@ -52,7 +62,7 @@ where { // set the color at the start of each line as some terminals seem to reset soc.borrow_mut() - .set_color(ColorSpec::new().set_fg(Some(color_map(compression))))?; + .set_color(ColorSpec::new().set_fg(Some(color_map(score))))?; soc.borrow_mut().write_all(line)?; } } @@ -63,7 +73,7 @@ where loop { let mut byte_buf = [0; 1]; if color_stripper.read_exact(&mut byte_buf).is_err() { - pr(searcher.flush())?; + pr(searcher.flush(), None)?; break; }; // keep going until we find no more matches @@ -71,8 +81,9 @@ where SearchState::Buffering => {} SearchState::Flushed { buffer: last_found_needle, + age, } => { - pr(last_found_needle)?; + pr(last_found_needle, age)?; } } } @@ -89,50 +100,6 @@ mod tests { use super::*; use std::io; - #[test] - fn test_window_searcher() { - let mut s = WindowSearcher::new(4); - assert_eq!( - s.search(b'a'), - SearchState::Flushed { - buffer: VecDeque::from(*b"") - } - ); - assert_eq!( - s.search(b'b'), - SearchState::Flushed { - buffer: VecDeque::from(*b"a") - } - ); - assert_eq!( - s.search(b'a'), - SearchState::Flushed { - buffer: VecDeque::from(*b"b") - } - ); - assert_eq!(s.search(b'b'), SearchState::Buffering); - assert_eq!( - s.search(b'c'), - SearchState::Flushed { - buffer: VecDeque::from(*b"ab") - } - ); - assert_eq!( - s.search(b'a'), - SearchState::Flushed { - buffer: VecDeque::from(*b"c") - } - ); - assert_eq!( - s.search(b'a'), - SearchState::Flushed { - buffer: VecDeque::from(*b"a") - } - ); - assert_eq!(s.search(b'b'), SearchState::Buffering); - assert_eq!(s.flush(), VecDeque::from(*b"ab")); - } - struct NullStdout; impl WriteColor for NullStdout { fn supports_color(&self) -> bool { @@ -166,7 +133,10 @@ mod tests { [Mon Mar 1 09:20:01 CET 2021] start new app: /Applications/app.app", ), &mut s, - 1024, + Args { + window_size: 1024, + age_penalty: 0.0, + }, ) .unwrap(); } @@ -183,7 +153,10 @@ ab ", ), &mut s, - 1024, + Args { + window_size: 1024, + age_penalty: 0.0, + }, ) .unwrap(); } diff --git a/src/window_searcher.rs b/src/window_searcher.rs index af65138..e4d6f07 100644 --- a/src/window_searcher.rs +++ b/src/window_searcher.rs @@ -10,7 +10,11 @@ pub(crate) struct WindowSearcher { #[derive(Debug, PartialEq, Eq)] pub(crate) enum SearchState { Buffering, - Flushed { buffer: VecDeque }, + Flushed { + buffer: VecDeque, + /// How long ago in bytes the buffer was found in the haystack. Or None if no match was found. + age: Option, + }, } impl WindowSearcher { @@ -24,6 +28,10 @@ impl WindowSearcher { } pub(crate) fn search(&mut self, next_byte: u8) -> SearchState { + let age = self + .matches + .last() + .map(|&i| self.haystack.len() - i - self.needle.len()); self.matches .retain_mut(|i| self.haystack.get(*i + self.needle.len()) == Some(&next_byte)); let r = if self.matches.is_empty() { @@ -35,7 +43,7 @@ impl WindowSearcher { self.matches = (0..self.haystack.len()) .filter(|&i| self.haystack[i] == next_byte) .collect(); - SearchState::Flushed { buffer } + SearchState::Flushed { buffer, age } } else { SearchState::Buffering }; @@ -47,3 +55,66 @@ impl WindowSearcher { std::mem::take(&mut self.needle) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_window_searcher() { + let mut s = WindowSearcher::new(4); + let r = s.search(b'a'); + assert_eq!( + r, + SearchState::Flushed { + buffer: VecDeque::from(*b""), + age: None, + } + ); + let r = s.search(b'b'); + assert_eq!( + r, + SearchState::Flushed { + buffer: VecDeque::from(*b"a"), + age: None, + } + ); + let r = s.search(b'a'); + assert_eq!( + r, + SearchState::Flushed { + buffer: VecDeque::from(*b"b"), + age: None, + } + ); + assert_eq!(s.search(b'b'), SearchState::Buffering); + assert_eq!( + s.search(b'c'), + SearchState::Flushed { + buffer: VecDeque::from(*b"ab"), + age: Some(0), + } + ); + let r = s.search(b'a'); + assert_eq!( + r, + SearchState::Flushed { + buffer: VecDeque::from(*b"c"), + age: None, + } + ); + let r = s.search(b'a'); + assert_eq!( + r, + SearchState::Flushed { + buffer: VecDeque::from(*b"a"), + // 2 since the needle isn't added to the haystack until it is flushed + age: Some(2), + } + ); + assert_eq!(s.search(b'b'), SearchState::Buffering); + assert_eq!(s.flush(), VecDeque::from(*b"ab")); + } + + // TODO: test that the window size is respected +}