diff --git a/src/main.rs b/src/main.rs index e543e63..35d540b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,27 @@ -#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)] +// Enable clippy 'hard mode' +#![warn(clippy::all, clippy::pedantic, clippy::nursery)] +// Intended behavior (10_f64 as i32) +#![allow(clippy::cast_possible_truncation)] +// Cannot be fixed +#![allow(clippy::multiple_crate_versions)] +#![allow(clippy::struct_excessive_bools)] + +// #![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)] // #![allow(dead_code, unused_imports)] mod errors; +mod math_eval; mod shuf; mod sort; mod uniq; use clap::{Parser, Subcommand}; use errors::KakMessage; -use shuf::ShufOptions; -use sort::SortOptions; use std::{ env, fs, fs::{File, OpenOptions}, io::Write, str::FromStr, }; -use uniq::UniqOptions; #[derive(Parser, Debug)] #[clap(about, version, author)] @@ -31,9 +37,11 @@ struct Cli { #[derive(Subcommand, Debug)] enum Commands { - Sort(SortOptions), - Shuf(ShufOptions), - Uniq(UniqOptions), + Sort(sort::Options), + Shuf(shuf::Options), + Uniq(uniq::Options), + #[clap(visible_aliases = &["bc", "eval"])] + MathEval(math_eval::Options), } #[derive(PartialEq, PartialOrd, Ord, Eq, Debug)] @@ -102,7 +110,7 @@ impl SelectionDesc { let sorted_a = self.sort(); let sorted_b = b.sort(); - sorted_b.left >= sorted_a.left && sorted_b.right <= sorted_b.right + sorted_b.left >= sorted_a.left && sorted_b.right <= sorted_a.right } } @@ -114,7 +122,7 @@ fn main() { eprintln!( "{} (Debug info: {})", msg.0, - msg.1.as_ref().unwrap_or(&String::default()) + msg.1.as_ref().map_or("", String::as_str) ); msg } @@ -150,9 +158,10 @@ fn run() -> Result { })?; match &options.command { - Commands::Sort(sort_options) => sort::sort(sort_options), - Commands::Shuf(shuf_options) => shuf::shuf(shuf_options), - Commands::Uniq(uniq_options) => uniq::uniq(uniq_options), + Commands::Sort(o) => sort::sort(o), + Commands::Shuf(o) => shuf::shuf(o), + Commands::Uniq(o) => uniq::uniq(o), + Commands::MathEval(o) => math_eval::math_eval(o), } } @@ -195,21 +204,38 @@ pub fn get_var(var_name: &str) -> Result { #[cfg(test)] mod test { use super::*; + const sd: SelectionDesc = SelectionDesc { + left: AnchorPosition { row: 18, col: 9 }, + right: AnchorPosition { row: 10, col: 1 }, + }; #[test] fn test_anchor_position() { - let sd = SelectionDesc { - left: AnchorPosition { row: 18, col: 9 }, - right: AnchorPosition { row: 10, col: 0 }, - }; + // let sd = SelectionDesc { + // left: AnchorPosition { row: 18, col: 9 }, + // right: AnchorPosition { row: 10, col: 0 }, + // }; // Check parsing - assert_eq!(SelectionDesc::from_str("18.9,10.0").unwrap(), sd); + assert_eq!(SelectionDesc::from_str("18.9,10.1").unwrap(), sd); // Check if multiple parsed ones match assert_eq!( - SelectionDesc::from_str("18.9,10.0").unwrap(), - SelectionDesc::from_str("18.9,10.0").unwrap() + SelectionDesc::from_str("18.9,10.1").unwrap(), + SelectionDesc::from_str("18.9,10.1").unwrap() ); // Check if sorting works - assert_eq!(sd.sort(), SelectionDesc::from_str("10.0,18.9").unwrap()); + assert_eq!(sd.sort(), SelectionDesc::from_str("10.1,18.9").unwrap()); assert_eq!(sd.sort(), sd.sort().sort()); } + + #[test] + fn test_contains() { + assert_true!(sd.contains(sd)); + assert_false!(sd.contains(SelectionDesc::from_str("17.9,10.1").unwrap())) + assert_false!(sd.contains(SelectionDesc::from_str("18.8,10.1").unwrap())) + assert_false!(sd.contains(SelectionDesc::from_str("18.9,11.1").unwrap())) + assert_false!(sd.contains(SelectionDesc::from_str("18.9,10.2").unwrap())) + assert_true!(sd.contains(SelectionDesc::from_str("19.9,10.1").unwrap())) + assert_true!(sd.contains(SelectionDesc::from_str("18.10,10.1").unwrap())) + assert_true!(sd.contains(SelectionDesc::from_str("18.9,9.1").unwrap())) + assert_true!(sd.contains(SelectionDesc::from_str("18.9,10.0").unwrap())) + } } diff --git a/src/math_eval.rs b/src/math_eval.rs new file mode 100644 index 0000000..7d27602 --- /dev/null +++ b/src/math_eval.rs @@ -0,0 +1,44 @@ +use crate::{kak_response, open_command_fifo, KakMessage}; +use evalexpr::{eval, Value}; +use std::io::Write; + +#[derive(clap::StructOpt, Debug)] +pub struct Options; +pub fn math_eval(_options: &Options) -> Result { + let selections = kak_response("%val{selections}")?; + + let mut f = open_command_fifo()?; + write!(f, "reg '\"'")?; + + let mut err = None; + let mut err_count: usize = 0; + + for i in selections.iter().map(|s| { + // TODO: Do all of these need to be strings? + match eval(s) { + Ok(Value::Float(f)) => Some(f.to_string()), + Ok(Value::Int(f)) => Some(f.to_string()), + // TODO: Should this be none? + Ok(_) => None, + Err(e) => { + eprintln!("Error: {:?}", e); + if err.is_none() { + err = Some(e); + err_count = err_count.saturating_add(1); + } + None + } + } + }) { + // TODO: String allocation? + let new_selection = i.map(|s| s.replace('\'', "''")); + // .unwrap_or_else(|| "".to_string()); + write!(f, " '{}'", new_selection.as_deref().unwrap_or(""))?; + } + write!(f, " ; exec R;")?; + + Ok(KakMessage( + format!("MathEval {} selections", selections.len()), + None, + )) +} diff --git a/src/shuf.rs b/src/shuf.rs index a121057..181b2f6 100644 --- a/src/shuf.rs +++ b/src/shuf.rs @@ -2,8 +2,8 @@ use crate::{kak_response, open_command_fifo, KakMessage}; use rand::{seq::SliceRandom, thread_rng}; use std::io::Write; #[derive(clap::StructOpt, Debug)] -pub struct ShufOptions; -pub fn shuf(_shuf_options: &ShufOptions) -> Result { +pub struct Options; +pub fn shuf(_options: &Options) -> Result { let mut selections = kak_response("%val{selections}")?; let mut rng = thread_rng(); @@ -12,7 +12,7 @@ pub fn shuf(_shuf_options: &ShufOptions) -> Result { let mut f = open_command_fifo()?; write!(f, "reg '\"'")?; - for i in selections.iter() { + for i in &selections { let new_selection = i.replace('\'', "''"); write!(f, " '{}'", new_selection)?; } diff --git a/src/sort.rs b/src/sort.rs index 82b901a..ebecbb6 100644 --- a/src/sort.rs +++ b/src/sort.rs @@ -4,7 +4,7 @@ use regex::Regex; use std::{cmp::Ordering, io::Write, str::FromStr}; #[derive(clap::StructOpt, Debug)] -pub struct SortOptions { +pub struct Options { #[clap(index = 1)] regex: Option, #[clap(short = 's', long)] @@ -40,7 +40,7 @@ struct SortableSelection<'a> { /// Gets a Vec of sortable selections with a given list of subselections and descriptions fn get_sortable_selections_subselections<'a, 'b, 'tmp, S: AsRef + std::fmt::Debug + 'a>( - sort_options: &'b SortOptions, + options: &'b Options, selections: &'a [S], selections_desc: &'tmp [S], subselections: &'a [S], @@ -57,7 +57,7 @@ fn get_sortable_selections_subselections<'a, 'b, 'tmp, S: AsRef + std::fmt: .zip(selections_desc.iter()) .map(|(s, sd)| { Ok(( - to_sortable_selection(s.as_ref(), sort_options), + to_sortable_selection(s.as_ref(), options), SelectionDesc::from_str(sd.as_ref())?, )) }) @@ -80,7 +80,7 @@ fn get_sortable_selections_subselections<'a, 'b, 'tmp, S: AsRef + std::fmt: for (s, s_desc) in &mut sortable_selections { for i in &subselections { if s_desc.contains(&i.1) { - s.subselections.push(i.0.clone()); + s.subselections.push(i.0); } } } @@ -106,9 +106,9 @@ fn get_sortable_selections_subselections<'a, 'b, 'tmp, S: AsRef + std::fmt: fn to_sortable_selection<'a, 'b>( selection: &'a str, - sort_options: &'b SortOptions, + options: &'b Options, ) -> SortableSelection<'a> { - if sort_options.no_skip_whitespace { + if options.no_skip_whitespace { SortableSelection { content: selection, content_comparison: selection, @@ -123,10 +123,10 @@ fn to_sortable_selection<'a, 'b>( } } -pub fn sort(sort_options: &SortOptions) -> Result { - eprintln!("Got sort options: {:?}", sort_options); +pub fn sort(options: &Options) -> Result { + eprintln!("Got sort options: {:?}", options); - let subselections: Option<(Vec, Vec)> = sort_options + let subselections: Option<(Vec, Vec)> = options .subselections_register .map::, Vec), KakMessage>, _>(|_c| { let subselections = kak_response("%val{selections}")?; @@ -142,7 +142,7 @@ pub fn sort(sort_options: &SortOptions) -> Result { let selections_desc = kak_response("%val{selections_desc}")?; get_sortable_selections_subselections( - sort_options, + options, &selections, &selections_desc, subselections, @@ -151,13 +151,13 @@ pub fn sort(sort_options: &SortOptions) -> Result { } None => selections .iter() - .map(|s| to_sortable_selection(s, sort_options)) + .map(|s| to_sortable_selection(s, options)) .collect(), }; zipped.sort_by(|a, b| { for (a_subselection, b_subselection) in a.subselections.iter().zip(b.subselections.iter()) { - let comparison = if sort_options.lexicographic_sort { + let comparison = if options.lexicographic_sort { a_subselection.cmp(b_subselection) } else { compare_str(a_subselection, b_subselection) @@ -168,7 +168,7 @@ pub fn sort(sort_options: &SortOptions) -> Result { } } - if sort_options.lexicographic_sort { + if options.lexicographic_sort { a.content_comparison.cmp(b.content_comparison) } else { compare_str(a.content_comparison, b.content_comparison) @@ -179,7 +179,7 @@ pub fn sort(sort_options: &SortOptions) -> Result { write!(f, "reg '\"'")?; - let iter: Box> = if sort_options.reverse { + let iter: Box> = if options.reverse { Box::new(zipped.iter().rev()) } else { Box::new(zipped.iter()) diff --git a/src/uniq.rs b/src/uniq.rs index b48ff86..32a914a 100644 --- a/src/uniq.rs +++ b/src/uniq.rs @@ -4,27 +4,27 @@ use std::{ io::Write, }; #[derive(clap::StructOpt, Debug)] -pub struct UniqOptions { +pub struct Options { #[clap(short, long)] ignore_case: bool, // TODO: Can we invert a boolean? This name is terrible #[clap(short = 'S', long)] no_skip_whitespace: bool, } -pub fn uniq(uniq_options: &UniqOptions) -> Result { +pub fn uniq(options: &Options) -> Result { let selections = kak_response("%val{selections}")?; let mut f = open_command_fifo()?; write!(f, "reg '\"'")?; for i in selections.iter().scan(HashMap::new(), |state, s| { - let key = if uniq_options.no_skip_whitespace { + let key = if options.no_skip_whitespace { s } else { s.trim() }; - let key = if uniq_options.ignore_case { + let key = if options.ignore_case { key.to_lowercase() } else { // TODO: Do I really need to clone this?