diff options
37 files changed, 3639 insertions, 2176 deletions
diff --git a/src/config.rs b/src/config.rs index be3b774..a92d868 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,5 @@ +//! Handles configuration loading and saving + use std::fs; use std::io::{Read, Write}; use std::path::PathBuf; @@ -18,25 +20,25 @@ macro_rules! vec_of_strings { #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] pub struct Config { - pub no_color : Option<bool>, + pub no_color: Option<bool>, pub gitignore: Option<bool>, pub hgignore: Option<bool>, pub dockerignore: Option<bool>, - pub is_zip_archive : Option<Vec<String>>, - pub is_archive : Option<Vec<String>>, - pub is_audio : Option<Vec<String>>, - pub is_book : Option<Vec<String>>, - pub is_doc : Option<Vec<String>>, - pub is_font : Option<Vec<String>>, - pub is_image : Option<Vec<String>>, - pub is_source : Option<Vec<String>>, - pub is_video : Option<Vec<String>>, - pub default_file_size_format : Option<String>, + pub is_zip_archive: Option<Vec<String>>, + pub is_archive: Option<Vec<String>>, + pub is_audio: Option<Vec<String>>, + pub is_book: Option<Vec<String>>, + pub is_doc: Option<Vec<String>>, + pub is_font: Option<Vec<String>>, + pub is_image: Option<Vec<String>>, + pub is_source: Option<Vec<String>>, + pub is_video: Option<Vec<String>>, + pub default_file_size_format: Option<String>, pub check_for_updates: Option<bool>, #[serde(skip_serializing, default = "get_false")] - pub debug : bool, + pub debug: bool, #[serde(skip)] - save : bool, + save: bool, } fn get_false() -> bool { @@ -68,13 +70,10 @@ impl Config { } pub fn from(config_file: PathBuf) -> Result<Config, String> { - if let Ok(mut file) = fs::File::open(&config_file) { + if let Ok(mut file) = fs::File::open(config_file) { let mut contents = String::new(); - if let Ok(_) = file.read_to_string(&mut contents) { - match toml::from_str(&contents) { - Ok(config) => Ok(config), - Err(err) => Err(err.to_string()) - } + if file.read_to_string(&mut contents).is_ok() { + toml::from_str(&contents).map_err(|err| err.to_string()) } else { Err("Could not read config file. Using default settings.".to_string()) } @@ -97,18 +96,13 @@ impl Config { #[cfg(not(windows))] fn get_project_dir() -> Option<PathBuf> { - match ProjectDirs::from("", ORGANIZATION, APPLICATION) { - Some(pd) => Some(pd.config_dir().to_path_buf()), - _ => None - } + ProjectDirs::from("", ORGANIZATION, APPLICATION).map(|pd| pd.config_dir().to_path_buf()) } #[cfg(windows)] fn get_project_dir() -> Option<PathBuf> { - match ProjectDirs::from("", ORGANIZATION, APPLICATION) { - Some(pd) => Some(pd.config_dir().parent().unwrap().to_path_buf()), - _ => None - } + ProjectDirs::from("", ORGANIZATION, APPLICATION) + .map(|pd| pd.config_dir().parent().unwrap().to_path_buf()) } pub fn save(&self) { @@ -133,29 +127,54 @@ impl Config { let toml = toml::to_string_pretty(&self).unwrap(); if let Ok(mut file) = fs::File::create(&config_file) { - let _ = file.write_all(&toml.as_bytes()); + let _ = file.write_all(toml.as_bytes()); } } pub fn default() -> Config { Config { - no_color : Some(false), - gitignore : Some(false), - hgignore : Some(false), - dockerignore : Some(false), - is_zip_archive : vec_of_strings![".zip", ".jar", ".war", ".ear"], - is_archive : vec_of_strings![".7z", ".bz2", ".bzip2", ".gz", ".gzip", ".lz", ".rar", ".tar", ".xz", ".zip"], - is_audio : vec_of_strings![".aac", ".aiff", ".amr", ".flac", ".gsm", ".m4a", ".m4b", ".m4p", ".mp3", ".ogg", ".wav", ".wma"], - is_book : vec_of_strings![".azw3", ".chm", ".djv", ".djvu", ".epub", ".fb2", ".mobi", ".pdf"], - is_doc : vec_of_strings![".accdb", ".doc", ".docm", ".docx", ".dot", ".dotm", ".dotx", ".mdb", ".odp", ".ods", ".odt", ".pdf", ".potm", ".potx", ".ppt", ".pptm", ".pptx", ".rtf", ".xlm", ".xls", ".xlsm", ".xlsx", ".xlt", ".xltm", ".xltx", ".xps"], - is_font : vec_of_strings![".eot", ".fon", ".otc", ".otf", ".ttc", ".ttf", ".woff", ".woff2"], - is_image : vec_of_strings![".bmp", ".exr", ".gif", ".heic", ".jpeg", ".jpg", ".jxl", ".png", ".psb", ".psd", ".svg", ".tga", ".tiff", ".webp"], - is_source : vec_of_strings![".asm", ".bas", ".c", ".cc", ".ceylon", ".clj", ".coffee", ".cpp", ".cs", ".d", ".dart", ".elm", ".erl", ".go", ".gradle", ".groovy", ".h", ".hh", ".hpp", ".java", ".jl", ".js", ".jsp", ".jsx", ".kt", ".kts", ".lua", ".nim", ".pas", ".php", ".pl", ".pm", ".py", ".rb", ".rs", ".scala", ".sol", ".swift", ".tcl", ".ts", ".tsx", ".vala", ".vb", ".zig"], - is_video : vec_of_strings![".3gp", ".avi", ".flv", ".m4p", ".m4v", ".mkv", ".mov", ".mp4", ".mpeg", ".mpg", ".webm", ".wmv"], - default_file_size_format : Some(String::new()), - check_for_updates : Some(false), - debug : false, - save : true, + no_color: Some(false), + gitignore: Some(false), + hgignore: Some(false), + dockerignore: Some(false), + is_zip_archive: vec_of_strings![".zip", ".jar", ".war", ".ear"], + is_archive: vec_of_strings![ + ".7z", ".bz2", ".bzip2", ".gz", ".gzip", ".lz", ".rar", ".tar", ".xz", ".zip" + ], + is_audio: vec_of_strings![ + ".aac", ".aiff", ".amr", ".flac", ".gsm", ".m4a", ".m4b", ".m4p", ".mp3", ".ogg", + ".wav", ".wma" + ], + is_book: vec_of_strings![ + ".azw3", ".chm", ".djv", ".djvu", ".epub", ".fb2", ".mobi", ".pdf" + ], + is_doc: vec_of_strings![ + ".accdb", ".doc", ".docm", ".docx", ".dot", ".dotm", ".dotx", ".mdb", ".odp", + ".ods", ".odt", ".pdf", ".potm", ".potx", ".ppt", ".pptm", ".pptx", ".rtf", ".xlm", + ".xls", ".xlsm", ".xlsx", ".xlt", ".xltm", ".xltx", ".xps" + ], + is_font: vec_of_strings![ + ".eot", ".fon", ".otc", ".otf", ".ttc", ".ttf", ".woff", ".woff2" + ], + is_image: vec_of_strings![ + ".bmp", ".exr", ".gif", ".heic", ".jpeg", ".jpg", ".jxl", ".png", ".psb", ".psd", + ".svg", ".tga", ".tiff", ".webp" + ], + is_source: vec_of_strings![ + ".asm", ".bas", ".c", ".cc", ".ceylon", ".clj", ".coffee", ".cpp", ".cs", ".d", + ".dart", ".elm", ".erl", ".go", ".gradle", ".groovy", ".h", ".hh", ".hpp", ".java", + ".jl", ".js", ".jsp", ".jsx", ".kt", ".kts", ".lua", ".nim", ".pas", ".php", ".pl", + ".pm", ".py", ".rb", ".rs", ".scala", ".sol", ".swift", ".tcl", ".ts", ".tsx", + ".vala", ".vb", ".zig" + ], + is_video: vec_of_strings![ + ".3gp", ".avi", ".flv", ".m4p", ".m4v", ".mkv", ".mov", ".mp4", ".mpeg", ".mpg", + ".webm", ".wmv" + ], + default_file_size_format: Some(String::new()), + check_for_updates: Some(false), + debug: false, + save: true, } } } @@ -170,4 +189,4 @@ mod tests { assert!(config.is_source.unwrap().contains(&String::from(".rs"))); } -}
\ No newline at end of file +} diff --git a/src/expr.rs b/src/expr.rs index e66fcf3..2d5f5db 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -170,7 +170,7 @@ impl Expr { result.extend(right.get_required_fields()); } - if let Some( field) = self.field { + if let Some(field) = self.field { result.insert(field); } @@ -190,7 +190,7 @@ impl Expr { fn contains_numeric_field(expr: &Expr) -> bool { let field = match expr.field { Some(ref field) => field.is_numeric_field(), - None => false + None => false, }; if field { @@ -199,19 +199,17 @@ impl Expr { let function = match expr.function { Some(ref function) => function.is_numeric_function(), - None => false + None => false, }; if function { return true; } - let left = match expr.left { - Some(ref left) => Self::contains_numeric_field(&left), - None => false - }; - - left + match expr.left { + Some(ref left) => Self::contains_numeric_field(left), + None => false, + } } pub fn contains_datetime(&self) -> bool { @@ -221,19 +219,17 @@ impl Expr { fn contains_datetime_field(expr: &Expr) -> bool { let field = match expr.field { Some(ref field) => field.is_datetime_field(), - None => false + None => false, }; if field { return true; } - let left = match expr.left { - Some(ref left) => Self::contains_datetime_field(&left), - None => false - }; - - left + match expr.left { + Some(ref left) => Self::contains_datetime_field(left), + None => false, + } } pub fn contains_colorized(&self) -> bool { @@ -247,19 +243,17 @@ impl Expr { let field = match expr.field { Some(ref field) => field.is_colorized_field(), - None => false + None => false, }; if field { return true; } - let left = match expr.left { - Some(ref left) => Self::contains_colorized_field(&left), - None => false - }; - - left + match expr.left { + Some(ref left) => Self::contains_colorized_field(left), + None => false, + } } } @@ -287,7 +281,7 @@ impl Display for Expr { } if let Some(ref val) = self.val { - fmt.write_str(&val)?; + fmt.write_str(val)?; } if let Some(ref right) = self.right { diff --git a/src/field.rs b/src/field.rs index 4edd6ec..ee71784 100644 --- a/src/field.rs +++ b/src/field.rs @@ -1,3 +1,5 @@ +//! Defines the various fields available in the query language + use std::fmt::Display; use std::fmt::Error; use std::fmt::Formatter; @@ -189,134 +191,128 @@ impl FromStr for Field { } impl Display for Field { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error>{ + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { write!(f, "{:?}", self) } } impl Serialize for Field { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where S: Serializer + where + S: Serializer, { serializer.serialize_str(&self.to_string()) } } impl Field { + #[rustfmt::skip] pub fn is_numeric_field(&self) -> bool { - match self { - Field::Size | Field::FormattedSize + matches!(self, Field::Size | Field::FormattedSize | Field::Uid | Field::Gid | Field::Width | Field::Height | Field::LineCount | Field::Duration | Field::Bitrate | Field::Freq | Field::Year - | Field::ExifGpsLatitude | Field::ExifGpsLongitude | Field::ExifGpsAltitude => true, - _ => false - } + | Field::ExifGpsLatitude | Field::ExifGpsLongitude | Field::ExifGpsAltitude) } pub fn is_datetime_field(&self) -> bool { - match self { - Field::Created | Field::Accessed | Field::Modified - | Field::ExifDateTime => true, - _ => false - } + matches!( + self, + Field::Created | Field::Accessed | Field::Modified | Field::ExifDateTime + ) } pub fn is_boolean_field(&self) -> bool { - match self { + matches!( + self, Field::IsDir - | Field::IsFile - | Field::UserRead - | Field::UserWrite - | Field::UserExec - | Field::UserAll - | Field::GroupRead - | Field::GroupWrite - | Field::GroupExec - | Field::GroupAll - | Field::OtherRead - | Field::OtherWrite - | Field::OtherExec - | Field::OtherAll - | Field::Suid - | Field::Sgid - | Field::IsSymlink - | Field::IsPipe - | Field::IsCharacterDevice - | Field::IsBlockDevice - | Field::IsSocket - | Field::IsHidden - | Field::HasXattrs - | Field::IsEmpty - | Field::IsShebang - | Field::IsBinary - | Field::IsText - | Field::IsArchive - | Field::IsAudio - | Field::IsBook - | Field::IsDoc - | Field::IsFont - | Field::IsImage - | Field::IsSource - | Field::IsVideo => true, - _ => false - } + | Field::IsFile + | Field::UserRead + | Field::UserWrite + | Field::UserExec + | Field::UserAll + | Field::GroupRead + | Field::GroupWrite + | Field::GroupExec + | Field::GroupAll + | Field::OtherRead + | Field::OtherWrite + | Field::OtherExec + | Field::OtherAll + | Field::Suid + | Field::Sgid + | Field::IsSymlink + | Field::IsPipe + | Field::IsCharacterDevice + | Field::IsBlockDevice + | Field::IsSocket + | Field::IsHidden + | Field::HasXattrs + | Field::IsEmpty + | Field::IsShebang + | Field::IsBinary + | Field::IsText + | Field::IsArchive + | Field::IsAudio + | Field::IsBook + | Field::IsDoc + | Field::IsFont + | Field::IsImage + | Field::IsSource + | Field::IsVideo + ) } pub fn is_available_for_archived_files(&self) -> bool { - match self { + matches!( + self, Field::Name - | Field::Extension - | Field::Path - | Field::AbsPath - | Field::Directory - | Field::AbsDir - | Field::Size - | Field::FormattedSize - | Field::IsDir - | Field::IsFile - | Field::IsSymlink - | Field::IsPipe - | Field::IsCharacterDevice - | Field::IsBlockDevice - | Field::IsSocket - | Field::Mode - | Field::UserRead - | Field::UserWrite - | Field::UserExec - | Field::UserAll - | Field::GroupRead - | Field::GroupWrite - | Field::GroupExec - | Field::GroupAll - | Field::OtherRead - | Field::OtherWrite - | Field::OtherExec - | Field::OtherAll - | Field::Suid - | Field::Sgid - | Field::IsHidden - | Field::IsEmpty - | Field::Modified - | Field::IsArchive - | Field::IsAudio - | Field::IsBook - | Field::IsDoc - | Field::IsFont - | Field::IsImage - | Field::IsSource - | Field::IsVideo - => true, - _ => false - } + | Field::Extension + | Field::Path + | Field::AbsPath + | Field::Directory + | Field::AbsDir + | Field::Size + | Field::FormattedSize + | Field::IsDir + | Field::IsFile + | Field::IsSymlink + | Field::IsPipe + | Field::IsCharacterDevice + | Field::IsBlockDevice + | Field::IsSocket + | Field::Mode + | Field::UserRead + | Field::UserWrite + | Field::UserExec + | Field::UserAll + | Field::GroupRead + | Field::GroupWrite + | Field::GroupExec + | Field::GroupAll + | Field::OtherRead + | Field::OtherWrite + | Field::OtherExec + | Field::OtherAll + | Field::Suid + | Field::Sgid + | Field::IsHidden + | Field::IsEmpty + | Field::Modified + | Field::IsArchive + | Field::IsAudio + | Field::IsBook + | Field::IsDoc + | Field::IsFont + | Field::IsImage + | Field::IsSource + | Field::IsVideo + ) } pub fn is_colorized_field(&self) -> bool { - match self { - Field::Name => true, - _ => false - } + matches!(self, Field::Name) } } diff --git a/src/fileinfo.rs b/src/fileinfo.rs index c7d8d1b..96f3b63 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -12,6 +12,6 @@ pub fn to_file_info(zipped_file: &zip::read::ZipFile) -> FileInfo { name: zipped_file.name().to_string(), size: zipped_file.size(), mode: zipped_file.unix_mode(), - modified: zipped_file.last_modified() + modified: zipped_file.last_modified(), } } diff --git a/src/function.rs b/src/function.rs index 0a10650..074416c 100644 --- a/src/function.rs +++ b/src/function.rs @@ -1,3 +1,6 @@ +//! Functions for processing values in the query language. +//! This module contains both the regular and aggregate functions used in the query language. + use std::collections::HashMap; use std::fmt::Display; use std::fmt::Error; @@ -19,11 +22,11 @@ use xattr::FileExt; use human_time::ToHumanTimeString; use crate::fileinfo::FileInfo; -use crate::util::{capitalize, error_exit, format_date}; use crate::util::format_datetime; use crate::util::parse_datetime; use crate::util::parse_filesize; use crate::util::str_to_bool; +use crate::util::{capitalize, error_exit, format_date}; #[derive(Clone, Debug)] pub enum VariantType { @@ -105,8 +108,8 @@ impl Variant { result += &value.to_owned(); result - }, - false => value.to_owned() + } + false => value.to_owned(), }; Variant { @@ -123,8 +126,14 @@ impl Variant { pub fn from_bool(value: bool) -> Variant { Variant { value_type: VariantType::Bool, - string_value: match value { true => String::from("true"), _ => String::from("false") }, - int_value: match value { true => Some(1), _ => Some(0) }, + string_value: match value { + true => String::from("true"), + _ => String::from("false"), + }, + int_value: match value { + true => Some(1), + _ => Some(0), + }, float_value: None, bool_value: Some(value), dt_from: None, @@ -161,8 +170,8 @@ impl Variant { Ok(i) => i as i64, _ => match parse_filesize(&self.string_value) { Some(size) => size as i64, - _ => 0 - } + _ => 0, + }, } } } @@ -181,8 +190,8 @@ impl Variant { Ok(f) => f, _ => match parse_filesize(&self.string_value) { Some(size) => size as f64, - _ => 0.0 - } + _ => 0.0, + }, } } } @@ -207,8 +216,8 @@ impl Variant { match parse_datetime(&self.string_value) { Ok((dt_from, dt_to)) => { return (dt_from, dt_to); - }, - _ => error_exit("Can't parse datetime", &self.string_value) + } + _ => error_exit("Can't parse datetime", &self.string_value), } } @@ -217,80 +226,142 @@ impl Variant { } impl Display for Variant { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error>{ + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { write!(f, "{}", self.to_string()) } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] pub enum Function { + // ===== Regular functions ===== + // String conversion functions + /// Convert the value to lowercase Lower, + /// Convert the value to UPPERCASE Upper, + /// Capitalize the first letter of each word (Title Case) InitCap, + /// Get the length of the string Length, + /// Convert the value to base64 ToBase64, + /// Read the value as base64 FromBase64, + + // String manipulation functions + /// Concatenate the value with the arguments + Concat, + /// Concatenate the arguments, separated by the value + ConcatWs, + /// Get a substring of the value, from a position and length + Substring, + /// Replace a substring in the value with another string + Replace, + /// Trim whitespace from the value + Trim, + /// Trim whitespace from the start of the value + LTrim, + /// Trim whitespace from the end of the value + RTrim, + + // Numeric functions + /// Get the binary representation of the value Bin, + /// Get the hexadecimal representation of the value Hex, + /// Get the octal representation of the value Oct, + /// Raise the value to the power of another value Power, + /// Get the square root of the value Sqrt, + // Japanese string functions + /// Check if the string contains Japanese characters ContainsJapanese, + /// Check if the string contains Hiragana characters ContainsHiragana, + /// Check if the string contains Katakana characters ContainsKatakana, + /// Check if the string contains Kana characters ContainsKana, + /// Check if the string contains Kanji characters ContainsKanji, - Concat, - ConcatWs, - Substring, - Replace, - Trim, - LTrim, - RTrim, - Coalesce, + // Formatting functions + /// Format a file size in human-readable format FormatSize, + /// Format a time duration in human-readable format FormatTime, - Min, - Max, - Avg, - Sum, - Count, - - StdDevPop, - StdDevSamp, - VarPop, - VarSamp, - + // Date and time functions + /// Get the current date CurrentDate, + /// Get the day from a date Day, + /// Get the month from a date Month, + /// Get the year from a date Year, + /// Get the day of the week from a date DayOfWeek, + // File functions #[cfg(all(unix, feature = "users"))] + /// Get the current user ID CurrentUid, #[cfg(all(unix, feature = "users"))] + /// Get the current username CurrentUser, #[cfg(all(unix, feature = "users"))] + /// Get the current group ID CurrentGid, #[cfg(all(unix, feature = "users"))] + /// Get the current group name CurrentGroup, + /// Checks if a file contains a substring Contains, #[cfg(unix)] + /// Check if the file has a specific extended attribute HasXattr, #[cfg(unix)] + /// Get the value of an extended attribute Xattr, #[cfg(target_os = "linux")] + /// Check if the file has capabilities (security.capability xattr) HasCapabilities, #[cfg(target_os = "linux")] + /// Check if the file has a specific capability (security.capability xattr) HasCapability, + // Miscellaneous functions + /// Return the first non-empty value + Coalesce, + /// Gets a random number from 0 to the value, or between two values Random, + + // ===== Aggregate functions ===== + /// Get the minimum value + Min, + /// Get the maximum value + Max, + /// Get the average value + Avg, + /// Get the sum of all values + Sum, + /// Get the number of values + Count, + + /// Get the population standard deviation + StdDevPop, + /// Get the sample standard deviation + StdDevSamp, + /// Get the population variance + VarPop, + /// Get the sample variance + VarSamp, } impl FromStr for Function { @@ -377,52 +448,56 @@ impl FromStr for Function { } impl Display for Function { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error>{ + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { write!(f, "{:?}", self) } } impl Serialize for Function { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where S: Serializer + where + S: Serializer, { serializer.serialize_str(&self.to_string()) } } impl Function { + /// Check if the function is an aggregate function pub fn is_aggregate_function(&self) -> bool { - match self { + matches!( + self, Function::Min - | Function::Max - | Function::Avg - | Function::Sum - | Function::Count - | Function::StdDevPop - | Function::StdDevSamp - | Function::VarPop - | Function::VarSamp => true, - _ => false - } + | Function::Max + | Function::Avg + | Function::Sum + | Function::Count + | Function::StdDevPop + | Function::StdDevSamp + | Function::VarPop + | Function::VarSamp + ) } + /// Check if the function is a numeric function, i.e. it returns a numeric value. pub fn is_numeric_function(&self) -> bool { if self.is_aggregate_function() { return true; } - match self { + matches!( + self, Function::Length - | Function::Random - | Function::Day - | Function::Month - | Function::Year - | Function::Power - | Function::Sqrt => true, - _ => false - } + | Function::Random + | Function::Day + | Function::Month + | Function::Year + | Function::Power + | Function::Sqrt + ) } + /// Check if the function is a boolean function, i.e. it returns a boolean value. pub fn is_boolean_function(&self) -> bool { #[cfg(unix)] if self == &Function::HasXattr { @@ -434,119 +509,77 @@ impl Function { return true; } - match self { + matches!( + self, Function::Contains - | Function::ContainsHiragana - | Function::ContainsKatakana - | Function::ContainsKana - | Function::ContainsKanji - | Function::ContainsJapanese => true, - _ => false - } + | Function::ContainsHiragana + | Function::ContainsKatakana + | Function::ContainsKana + | Function::ContainsKanji + | Function::ContainsJapanese + |