From 8379ae2136cd1ad3d40c06ed8ba6ca54d6a7e305 Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Sat, 21 Mar 2026 17:36:29 -0300 Subject: [PATCH] refactor: rename plugin features with type prefix for consistency - Plugin features now use type_ prefix (meta_magic, filter_grep, etc.) - Added meta_all_musl and filter_all_musl for MUSL-compatible builds - grep filter plugin made optional via filter_grep feature flag - Removed regex crate from grep-related code, uses strip_prefix instead - Updated CHANGELOG.md with breaking change documentation --- CHANGELOG.md | 14 ++++++++++++++ Cargo.toml | 32 +++++++++++++++++--------------- src/filter_plugin/mod.rs | 32 ++++++++++++++++++-------------- src/lib.rs | 17 ++++++++++------- src/meta_plugin/magic_file.rs | 22 +++++++++++----------- src/meta_plugin/mod.rs | 14 +++++++------- src/modes/common.rs | 16 +++++----------- src/modes/server/common.rs | 1 + src/modes/server/mod.rs | 12 ++++++------ src/tests/meta_plugin/mod.rs | 4 ++-- 10 files changed, 91 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28361b9..7e2797c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- New `filter_grep` feature to optionally include the grep filter plugin (regex-based line filtering). Disabling this feature removes the `regex` crate and its ~800 KiB dependency stack from the binary. +- New `meta_all_musl` feature for all MUSL-compatible meta plugins (excludes `meta_magic` which requires libmagic) +- New `filter_all_musl` feature for all MUSL-compatible filter plugins - Database index on `items(ts)` column for faster ORDER BY sorting - Server API `ItemInfo` now includes `file_size` — actual filesystem-reported size of the item data file @@ -23,6 +26,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed `once_cell` crate (replaced with `std::sync::LazyLock` from Rust 1.80) - Removed `lazy_static` crate (replaced with `std::sync::LazyLock`) +### Breaking + +- Plugin feature flags renamed with type prefix for consistency: + - `magic` → `meta_magic` + - `infer` → `meta_infer` + - `tree_magic_mini` → `meta_tree_magic_mini` + - `tokens` → `meta_tokens` + - `grep` → `filter_grep` + - `all-meta-plugins` → `meta_all` + - `all-filter-plugins` → `filter_all` + ### Fixed - CLI help text typo: "metatdata" → "metadata" in `--get` and `--info` descriptions diff --git a/Cargo.toml b/Cargo.toml index 8381419..220deeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ tree_magic_mini = { version = "3.2", optional = true } nix = { version = "0.30", features = ["fs", "process"] } comfy-table = "7.2" pwhash = "1.0" -regex = "1.10" +regex = { version = "1.10", optional = true } ringbuf = "0.4" rusqlite = { version = "0.37", features = ["bundled", "array", "chrono"] } rusqlite_migration = "2.3" @@ -85,14 +85,15 @@ tiktoken-rs = { version = "0.9", optional = true } tempfile = "3.3" [features] -# Default features include core compression engines and swagger UI +# Default features include core compression engines plugins that support MUSL default = [ "client", "gzip", - "infer", + "filter_grep", + "meta_infer", "lz4", - "tokens", - "tree_magic_mini", + "meta_tokens", + "meta_tree_magic_mini", "zstd" ] @@ -106,14 +107,18 @@ bzip2 = [] xz = [] zstd = ["dep:zstd"] -# Plugin features (meta and filter) -all-meta-plugins = ["dep:magic", "dep:infer", "dep:tree_magic_mini"] -all-filter-plugins = [] +# Meta plugin features +meta_magic = ["dep:magic"] +meta_infer = ["dep:infer"] +meta_tree_magic_mini = ["dep:tree_magic_mini"] +meta_tokens = ["dep:tiktoken-rs"] +meta_all = ["meta_magic", "meta_infer", "meta_tree_magic_mini", "meta_tokens"] +meta_all_musl = ["meta_infer", "meta_tree_magic_mini", "meta_tokens"] -# Individual plugin features -magic = ["dep:magic"] -infer = ["dep:infer"] -tree_magic_mini = ["dep:tree_magic_mini"] +# Filter plugin features +filter_grep = ["dep:regex"] +filter_all = ["filter_grep"] +filter_all_musl = ["filter_grep"] # Swagger UI feature swagger = ["dep:utoipa-swagger-ui"] @@ -121,8 +126,5 @@ swagger = ["dep:utoipa-swagger-ui"] # Client feature (HTTP client for remote server) client = ["dep:ureq", "dep:os_pipe"] -# Token counting feature (LLM token support via tiktoken) -tokens = ["dep:tiktoken-rs"] - [dev-dependencies] rand = "0.9" diff --git a/src/filter_plugin/mod.rs b/src/filter_plugin/mod.rs index f02e819..fb033ed 100644 --- a/src/filter_plugin/mod.rs +++ b/src/filter_plugin/mod.rs @@ -2,6 +2,7 @@ use std::io::{Read, Result, Write}; use std::str::FromStr; use strum::EnumString; +#[cfg(feature = "filter_grep")] pub mod grep; /// Filter plugin module for processing input streams. /// @@ -16,7 +17,7 @@ pub mod grep; /// ``` /// # use std::io::{Read, Write}; /// # use keep::filter_plugin::parse_filter_string; -/// let mut chain = parse_filter_string("head_lines(10)|grep(pattern=error)")?; +/// let mut chain = parse_filter_string("head_lines(10)|tail_lines(5)")?; /// # let mut reader: &mut dyn Read = &mut std::io::empty(); /// # let mut writer: Vec = Vec::new(); /// # chain.filter(&mut reader, &mut writer)?; @@ -26,12 +27,13 @@ pub mod head; pub mod skip; pub mod strip_ansi; pub mod tail; -#[cfg(feature = "tokens")] +#[cfg(feature = "meta_tokens")] pub mod tokens; pub mod utils; use std::collections::HashMap; +#[cfg(feature = "filter_grep")] pub use grep::GrepFilter; pub use head::{HeadBytesFilter, HeadLinesFilter}; pub use skip::{SkipBytesFilter, SkipLinesFilter}; @@ -199,13 +201,14 @@ pub enum FilterType { TailLines, SkipBytes, SkipLines, + #[cfg(feature = "filter_grep")] Grep, StripAnsi, - #[cfg(feature = "tokens")] + #[cfg(feature = "meta_tokens")] HeadTokens, - #[cfg(feature = "tokens")] + #[cfg(feature = "meta_tokens")] SkipTokens, - #[cfg(feature = "tokens")] + #[cfg(feature = "meta_tokens")] TailTokens, } @@ -356,9 +359,8 @@ impl FilterChain { /// # Examples /// /// ``` - /// # use keep::filter_plugin::{FilterChain, GrepFilter}; + /// # use keep::filter_plugin::FilterChain; /// let mut chain = FilterChain::new(); - /// chain.add_plugin(Box::new(GrepFilter::new("error".to_string()).unwrap())); /// ``` pub fn add_plugin(&mut self, plugin: Box) { self.plugins.push(plugin); @@ -535,6 +537,7 @@ fn create_filter_with_options( // Get the default options for this filter type by creating a temporary instance // To do this, we need to create a default instance of the appropriate filter let option_defs = match filter_type { + #[cfg(feature = "filter_grep")] FilterType::Grep => grep::GrepFilter::new("".to_string())?.options(), FilterType::HeadBytes => head::HeadBytesFilter::new(0).options(), FilterType::HeadLines => head::HeadLinesFilter::new(0).options(), @@ -543,11 +546,11 @@ fn create_filter_with_options( FilterType::SkipBytes => skip::SkipBytesFilter::new(0).options(), FilterType::SkipLines => skip::SkipLinesFilter::new(0).options(), FilterType::StripAnsi => strip_ansi::StripAnsiFilter::new().options(), - #[cfg(feature = "tokens")] + #[cfg(feature = "meta_tokens")] FilterType::HeadTokens => tokens::HeadTokensFilter::new(0).options(), - #[cfg(feature = "tokens")] + #[cfg(feature = "meta_tokens")] FilterType::SkipTokens => tokens::SkipTokensFilter::new(0).options(), - #[cfg(feature = "tokens")] + #[cfg(feature = "meta_tokens")] FilterType::TailTokens => tokens::TailTokensFilter::new(0).options(), }; @@ -617,6 +620,7 @@ fn create_specific_filter( options: &HashMap, ) -> Result> { match filter_type { + #[cfg(feature = "filter_grep")] FilterType::Grep => { let pattern = options .get("pattern") @@ -717,7 +721,7 @@ fn create_specific_filter( } Ok(Box::new(strip_ansi::StripAnsiFilter::new())) } - #[cfg(feature = "tokens")] + #[cfg(feature = "meta_tokens")] FilterType::HeadTokens => { let count = options .get("count") @@ -735,7 +739,7 @@ fn create_specific_filter( f.encoding = encoding; Ok(Box::new(f)) } - #[cfg(feature = "tokens")] + #[cfg(feature = "meta_tokens")] FilterType::SkipTokens => { let count = options .get("count") @@ -753,7 +757,7 @@ fn create_specific_filter( f.encoding = encoding; Ok(Box::new(f)) } - #[cfg(feature = "tokens")] + #[cfg(feature = "meta_tokens")] FilterType::TailTokens => { let count = options .get("count") @@ -774,7 +778,7 @@ fn create_specific_filter( } } -#[cfg(feature = "tokens")] +#[cfg(feature = "meta_tokens")] fn parse_encoding_option( options: &std::collections::HashMap, ) -> (crate::tokenizer::TokenEncoding, crate::tokenizer::Tokenizer) { diff --git a/src/lib.rs b/src/lib.rs index af2842e..75767d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ pub mod services; #[cfg(feature = "client")] pub mod client; -#[cfg(feature = "tokens")] +#[cfg(feature = "meta_tokens")] pub mod tokenizer; // Re-export Args struct for library usage @@ -56,9 +56,12 @@ pub use services::CoreError; // Import all filter plugins to ensure they register themselves #[allow(unused_imports)] -use filter_plugin::{grep, head, skip, strip_ansi, tail}; +#[cfg(feature = "filter_grep")] +use filter_plugin::grep; +#[allow(unused_imports)] +use filter_plugin::{head, skip, strip_ansi, tail}; -#[cfg(feature = "tokens")] +#[cfg(feature = "meta_tokens")] #[allow(unused_imports)] use filter_plugin::tokens as token_filters; @@ -66,19 +69,19 @@ use crate::meta_plugin::{ cwd, digest, env, exec, hostname, keep_pid, read_rate, read_time, shell, shell_pid, user, }; -#[cfg(feature = "magic")] +#[cfg(feature = "meta_magic")] #[allow(unused_imports)] use crate::meta_plugin::magic_file; -#[cfg(feature = "tokens")] +#[cfg(feature = "meta_tokens")] #[allow(unused_imports)] use crate::meta_plugin::tokens; -#[cfg(feature = "infer")] +#[cfg(feature = "meta_infer")] #[allow(unused_imports)] use crate::meta_plugin::infer_plugin; -#[cfg(feature = "tree_magic_mini")] +#[cfg(feature = "meta_tree_magic_mini")] #[allow(unused_imports)] use crate::meta_plugin::tree_magic_mini; diff --git a/src/meta_plugin/magic_file.rs b/src/meta_plugin/magic_file.rs index 10897c3..28048a2 100644 --- a/src/meta_plugin/magic_file.rs +++ b/src/meta_plugin/magic_file.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "magic")] +#[cfg(feature = "meta_magic")] use magic::{Cookie, CookieFlags}; -#[cfg(not(feature = "magic"))] +#[cfg(not(feature = "meta_magic"))] use std::process::{Command, Stdio}; use std::io::{self, Write}; @@ -16,12 +16,12 @@ use crate::meta_plugin::{ // separate cookies can be used from different threads concurrently without // synchronization. Using thread_local! avoids unsafe impl Send since the // storage is inherently !Send. -#[cfg(feature = "magic")] +#[cfg(feature = "meta_magic")] thread_local! { static MAGIC_COOKIE: std::cell::RefCell> = const { std::cell::RefCell::new(None) }; } -#[cfg(feature = "magic")] +#[cfg(feature = "meta_magic")] #[derive(Debug)] pub struct MagicFileMetaPluginImpl { buffer: Vec, @@ -30,7 +30,7 @@ pub struct MagicFileMetaPluginImpl { base: BaseMetaPlugin, } -#[cfg(feature = "magic")] +#[cfg(feature = "meta_magic")] impl MagicFileMetaPluginImpl { pub fn new( options: Option>, @@ -113,7 +113,7 @@ impl MagicFileMetaPluginImpl { } } -#[cfg(feature = "magic")] +#[cfg(feature = "meta_magic")] impl MetaPlugin for MagicFileMetaPluginImpl { fn is_finalized(&self) -> bool { self.is_finalized @@ -222,10 +222,10 @@ impl MetaPlugin for MagicFileMetaPluginImpl { } } -#[cfg(feature = "magic")] +#[cfg(feature = "meta_magic")] pub use MagicFileMetaPluginImpl as MagicFileMetaPlugin; -#[cfg(not(feature = "magic"))] +#[cfg(not(feature = "meta_magic"))] #[derive(Debug)] pub struct FallbackMagicFileMetaPlugin { buffer: Vec, @@ -234,7 +234,7 @@ pub struct FallbackMagicFileMetaPlugin { base: BaseMetaPlugin, } -#[cfg(not(feature = "magic"))] +#[cfg(not(feature = "meta_magic"))] impl FallbackMagicFileMetaPlugin { pub fn new( options: Option>, @@ -336,7 +336,7 @@ impl FallbackMagicFileMetaPlugin { } } -#[cfg(not(feature = "magic"))] +#[cfg(not(feature = "meta_magic"))] impl MetaPlugin for FallbackMagicFileMetaPlugin { fn is_finalized(&self) -> bool { self.is_finalized @@ -441,7 +441,7 @@ impl MetaPlugin for FallbackMagicFileMetaPlugin { } } -#[cfg(not(feature = "magic"))] +#[cfg(not(feature = "meta_magic"))] pub use FallbackMagicFileMetaPlugin as MagicFileMetaPlugin; use crate::meta_plugin::register_meta_plugin; diff --git a/src/meta_plugin/mod.rs b/src/meta_plugin/mod.rs index 63e7af1..2c4ca2e 100644 --- a/src/meta_plugin/mod.rs +++ b/src/meta_plugin/mod.rs @@ -8,7 +8,7 @@ pub mod digest; pub mod env; pub mod exec; pub mod hostname; -#[cfg(feature = "infer")] +#[cfg(feature = "meta_infer")] pub mod infer_plugin; pub mod keep_pid; pub mod magic_file; @@ -17,32 +17,32 @@ pub mod read_time; pub mod shell; pub mod shell_pid; pub mod text; -#[cfg(feature = "tokens")] +#[cfg(feature = "meta_tokens")] pub mod tokens; -#[cfg(feature = "tree_magic_mini")] +#[cfg(feature = "meta_tree_magic_mini")] pub mod tree_magic_mini; pub mod user; pub use digest::DigestMetaPlugin; pub use exec::MetaPluginExec; -#[cfg(feature = "magic")] +#[cfg(feature = "meta_magic")] pub use magic_file::MagicFileMetaPlugin; // pub use text::TextMetaPlugin; // Removed duplicate pub use cwd::CwdMetaPlugin; pub use env::EnvMetaPlugin; pub use hostname::HostnameMetaPlugin; -#[cfg(feature = "infer")] +#[cfg(feature = "meta_infer")] pub use infer_plugin::InferMetaPlugin; pub use keep_pid::KeepPidMetaPlugin; pub use read_rate::ReadRateMetaPlugin; pub use read_time::ReadTimeMetaPlugin; pub use shell::ShellMetaPlugin; pub use shell_pid::ShellPidMetaPlugin; -#[cfg(feature = "tree_magic_mini")] +#[cfg(feature = "meta_tree_magic_mini")] pub use tree_magic_mini::TreeMagicMiniMetaPlugin; pub use user::UserMetaPlugin; -#[cfg(not(feature = "magic"))] +#[cfg(not(feature = "meta_magic"))] pub use magic_file::FallbackMagicFileMetaPlugin as MagicFileMetaPlugin; type PluginConstructor = fn( diff --git a/src/modes/common.rs b/src/modes/common.rs index 9501398..4fd82fe 100644 --- a/src/modes/common.rs +++ b/src/modes/common.rs @@ -22,7 +22,6 @@ use clap::Command; use clap::error::ErrorKind; use comfy_table::{Attribute, Cell, ContentArrangement, Table}; use log::debug; -use regex::Regex; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::env; @@ -56,23 +55,18 @@ pub enum OutputFormat { Yaml, } -static KEEP_META_RE: std::sync::LazyLock = - std::sync::LazyLock::new(|| Regex::new(r"^KEEP_META_(.+)$").unwrap()); - pub const IMPORT_FORMAT_ERROR: &str = "Unsupported import format: {} (expected .keep.tar or .meta.yml)"; pub fn get_meta_from_env() -> HashMap { debug!("COMMON: Getting meta from KEEP_META_*"); let mut meta_env: HashMap = HashMap::new(); + const PREFIX: &str = "KEEP_META_"; for (key, value) in env::vars() { - if let Some(meta_name_caps) = KEEP_META_RE.captures(key.as_str()) { - let name = meta_name_caps.get(1).map(|m| m.as_str().to_string()); - if let Some(name) = name { - if name != "PLUGINS" { - debug!("COMMON: Found meta: {}={}", name, value); - meta_env.insert(name, value); - } + if let Some(name) = key.strip_prefix(PREFIX) { + if !name.is_empty() && name != "PLUGINS" { + debug!("COMMON: Found meta: {}={}", name, value); + meta_env.insert(name.to_string(), value); } } } diff --git a/src/modes/server/common.rs b/src/modes/server/common.rs index a072624..1bbb009 100644 --- a/src/modes/server/common.rs +++ b/src/modes/server/common.rs @@ -532,6 +532,7 @@ pub struct TagsQuery { /// ```rust /// use keep::modes::server::common::ListItemsQuery; /// let query = ListItemsQuery { +/// ids: None, /// tags: Some("important".to_string()), /// order: Some("newest".to_string()), /// start: Some(0), diff --git a/src/modes/server/mod.rs b/src/modes/server/mod.rs index e130f23..f464206 100644 --- a/src/modes/server/mod.rs +++ b/src/modes/server/mod.rs @@ -179,12 +179,12 @@ async fn run_server( let addr: SocketAddr = bind_address.parse()?; // Warn if authentication is enabled without TLS - if config.password.is_some() || config.password_hash.is_some() || config.jwt_secret.is_some() { - if config.cert_file.is_none() || config.key_file.is_none() { - log::warn!( - "SECURITY: Authentication enabled but TLS is not configured. Credentials will be transmitted in plain text!" - ); - } + if (config.password.is_some() || config.password_hash.is_some() || config.jwt_secret.is_some()) + && (config.cert_file.is_none() || config.key_file.is_none()) + { + log::warn!( + "SECURITY: Authentication enabled but TLS is not configured. Credentials will be transmitted in plain text!" + ); } // Build the app into a service diff --git a/src/tests/meta_plugin/mod.rs b/src/tests/meta_plugin/mod.rs index 0b4323b..f4affbf 100644 --- a/src/tests/meta_plugin/mod.rs +++ b/src/tests/meta_plugin/mod.rs @@ -3,10 +3,10 @@ #[cfg(test)] pub mod digest_tests; -#[cfg(feature = "infer")] +#[cfg(feature = "meta_infer")] #[cfg(test)] pub mod infer_tests; -#[cfg(feature = "tree_magic_mini")] +#[cfg(feature = "meta_tree_magic_mini")] #[cfg(test)] pub mod tree_magic_mini_tests;