From 404721e04d8604aa2a979600fa67ce288243816e Mon Sep 17 00:00:00 2001 From: Timon Vonk Date: Sun, 15 Mar 2026 16:12:25 +0100 Subject: [PATCH 1/2] feat: Enable library usage --- src/lib.rs | 9 +++++++++ src/rewrite.rs | 16 ++++++++++++++++ src/toml_filter.rs | 26 +++++++++++++++++++++++++- src/tracking.rs | 4 ++-- tests/library_api.rs | 39 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/lib.rs create mode 100644 src/rewrite.rs create mode 100644 tests/library_api.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..c8aab97b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +pub mod config; +mod discover; +pub mod filter; +pub mod parser; +pub mod rewrite; +pub mod tee; +pub mod toml_filter; +pub mod tracking; +pub mod utils; diff --git a/src/rewrite.rs b/src/rewrite.rs new file mode 100644 index 00000000..e4d8043d --- /dev/null +++ b/src/rewrite.rs @@ -0,0 +1,16 @@ +use crate::config::Config; + +pub use crate::discover::registry::{ + classify_command, has_rtk_disabled_prefix, rewrite_command, split_command_chain, + strip_disabled_prefix, Classification, +}; + +/// Rewrite a shell command using the exclusions configured for RTK. +pub fn rewrite_with_config(command: &str, config: &Config) -> Option { + rewrite_command(command, &config.hooks.exclude_commands) +} + +/// Rewrite a shell command using the default RTK config from disk. +pub fn rewrite_with_default_config(command: &str) -> anyhow::Result> { + Ok(rewrite_with_config(command, &Config::load()?)) +} diff --git a/src/toml_filter.rs b/src/toml_filter.rs index 36eb52ab..fc50a949 100644 --- a/src/toml_filter.rs +++ b/src/toml_filter.rs @@ -176,9 +176,17 @@ pub struct TomlFilterRegistry { } impl TomlFilterRegistry { + /// Load only the embedded built-in registry. This is deterministic and does + /// not consult the filesystem or host configuration. + pub fn builtin() -> Result { + Ok(Self { + filters: Self::parse_and_compile(BUILTIN_TOML, "builtin")?, + }) + } + /// Load registry from disk + built-in. Emits warnings to stderr on parse /// errors but never panics — bad files are silently ignored. - fn load() -> Self { + pub fn load() -> Self { let mut filters = Vec::new(); // Priority 1: project-local .rtk/filters.toml @@ -210,6 +218,13 @@ impl TomlFilterRegistry { TomlFilterRegistry { filters } } + /// Build a registry from TOML content without touching the filesystem. + pub fn from_toml_str(content: &str, source: &str) -> Result { + Ok(Self { + filters: Self::parse_and_compile(content, source)?, + }) + } + fn parse_and_compile(content: &str, source: &str) -> Result, String> { let file: TomlFilterFile = toml::from_str(content) .map_err(|e| format!("TOML parse error in {}: {}", source, e))?; @@ -1586,6 +1601,15 @@ match_command = "^make\\b" ); } + #[test] + fn test_builtin_registry_uses_embedded_filters_only() { + let registry = TomlFilterRegistry::builtin().expect("builtin registry should compile"); + let filter = find_filter_in("mix format", ®istry.filters).expect("mix format filter"); + let output = apply_filter(filter, ""); + + assert_eq!(output, "mix format: ok"); + } + /// Verify that every built-in filter has at least one inline test. /// Prevents shipping filters with zero test coverage. #[test] diff --git a/src/tracking.rs b/src/tracking.rs index 66363a6d..08e23e21 100644 --- a/src/tracking.rs +++ b/src/tracking.rs @@ -1028,8 +1028,8 @@ pub fn estimate_tokens(text: &str) -> usize { /// use rtk::tracking::TimedExecution; /// /// let timer = TimedExecution::start(); -/// let input = execute_standard_command()?; -/// let output = execute_rtk_command()?; +/// let input = "raw output"; +/// let output = "filtered output"; /// timer.track("ls -la", "rtk ls", &input, &output); /// # Ok::<(), anyhow::Error>(()) /// ``` diff --git a/tests/library_api.rs b/tests/library_api.rs new file mode 100644 index 00000000..039f9f1b --- /dev/null +++ b/tests/library_api.rs @@ -0,0 +1,39 @@ +use rtk::config::Config; +use rtk::rewrite::{rewrite_command, rewrite_with_config}; +use rtk::toml_filter::{apply_filter, find_filter_in, TomlFilterRegistry}; + +#[test] +fn rewrite_public_api_rewrites_supported_commands() { + assert_eq!( + rewrite_command("git status", &[]), + Some("rtk git status".to_string()) + ); +} + +#[test] +fn rewrite_with_config_respects_excluded_commands() { + let mut config = Config::default(); + config.hooks.exclude_commands = vec!["git".to_string()]; + + assert_eq!(rewrite_with_config("git status", &config), None); +} + +#[test] +fn toml_filter_registry_from_toml_str_builds_reusable_filters() { + let registry = TomlFilterRegistry::from_toml_str( + r#" +schema_version = 1 + +[filters.tests] +match_command = "^pytest" +keep_lines_matching = ["^FAIL", "^PASS"] +"#, + "test", + ) + .expect("valid filter registry"); + + let filter = find_filter_in("pytest tests/", ®istry.filters).expect("matching filter"); + let output = apply_filter(filter, "noise\nPASS first\nFAIL second"); + + assert_eq!(output, "PASS first\nFAIL second"); +} From 1b8347509645a5cffb3d1bea3318a82874ecd841 Mon Sep 17 00:00:00 2001 From: Timon Vonk Date: Sun, 15 Mar 2026 16:33:20 +0100 Subject: [PATCH 2/2] Only expose what we use --- src/lib.rs | 9 +-------- src/rewrite.rs | 16 ---------------- src/toml_filter.rs | 4 ++-- src/tracking.rs | 4 ++-- src/utils.rs | 22 +++++++++++----------- tests/library_api.rs | 39 --------------------------------------- 6 files changed, 16 insertions(+), 78 deletions(-) delete mode 100644 src/rewrite.rs delete mode 100644 tests/library_api.rs diff --git a/src/lib.rs b/src/lib.rs index c8aab97b..2fc3145e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,2 @@ -pub mod config; -mod discover; -pub mod filter; -pub mod parser; -pub mod rewrite; -pub mod tee; +mod utils; pub mod toml_filter; -pub mod tracking; -pub mod utils; diff --git a/src/rewrite.rs b/src/rewrite.rs deleted file mode 100644 index e4d8043d..00000000 --- a/src/rewrite.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::config::Config; - -pub use crate::discover::registry::{ - classify_command, has_rtk_disabled_prefix, rewrite_command, split_command_chain, - strip_disabled_prefix, Classification, -}; - -/// Rewrite a shell command using the exclusions configured for RTK. -pub fn rewrite_with_config(command: &str, config: &Config) -> Option { - rewrite_command(command, &config.hooks.exclude_commands) -} - -/// Rewrite a shell command using the default RTK config from disk. -pub fn rewrite_with_default_config(command: &str) -> anyhow::Result> { - Ok(rewrite_with_config(command, &Config::load()?)) -} diff --git a/src/toml_filter.rs b/src/toml_filter.rs index fc50a949..c2bb91b9 100644 --- a/src/toml_filter.rs +++ b/src/toml_filter.rs @@ -186,7 +186,7 @@ impl TomlFilterRegistry { /// Load registry from disk + built-in. Emits warnings to stderr on parse /// errors but never panics — bad files are silently ignored. - pub fn load() -> Self { + fn load() -> Self { let mut filters = Vec::new(); // Priority 1: project-local .rtk/filters.toml @@ -219,7 +219,7 @@ impl TomlFilterRegistry { } /// Build a registry from TOML content without touching the filesystem. - pub fn from_toml_str(content: &str, source: &str) -> Result { + fn from_toml_str(content: &str, source: &str) -> Result { Ok(Self { filters: Self::parse_and_compile(content, source)?, }) diff --git a/src/tracking.rs b/src/tracking.rs index 08e23e21..66363a6d 100644 --- a/src/tracking.rs +++ b/src/tracking.rs @@ -1028,8 +1028,8 @@ pub fn estimate_tokens(text: &str) -> usize { /// use rtk::tracking::TimedExecution; /// /// let timer = TimedExecution::start(); -/// let input = "raw output"; -/// let output = "filtered output"; +/// let input = execute_standard_command()?; +/// let output = execute_rtk_command()?; /// timer.track("ls -la", "rtk ls", &input, &output); /// # Ok::<(), anyhow::Error>(()) /// ``` diff --git a/src/utils.rs b/src/utils.rs index b1b00f88..134ce97f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -20,7 +20,7 @@ use std::process::Command; /// use rtk::utils::truncate; /// assert_eq!(truncate("hello world", 8), "hello..."); /// assert_eq!(truncate("hi", 10), "hi"); -/// ``` +/// ```ignore pub fn truncate(s: &str, max_len: usize) -> String { let char_count = s.chars().count(); if char_count <= max_len { @@ -39,7 +39,7 @@ pub fn truncate(s: &str, max_len: usize) -> String { /// * `text` - Text potentially containing ANSI escape codes /// /// # Examples -/// ``` +/// ```ignore /// use rtk::utils::strip_ansi; /// let colored = "\x1b[31mError\x1b[0m"; /// assert_eq!(strip_ansi(colored), "Error"); @@ -61,11 +61,11 @@ pub fn strip_ansi(text: &str) -> String { /// `(stdout: String, stderr: String, exit_code: i32)` /// /// # Examples -/// ```no_run +/// ``` /// use rtk::utils::execute_command; /// let (stdout, stderr, code) = execute_command("echo", &["test"]).unwrap(); /// assert_eq!(code, 0); -/// ``` +/// ```ignore #[allow(dead_code)] pub fn execute_command(cmd: &str, args: &[&str]) -> Result<(String, String, i32)> { let output = Command::new(cmd) @@ -89,12 +89,12 @@ pub fn execute_command(cmd: &str, args: &[&str]) -> Result<(String, String, i32) /// Formatted string (e.g. "1.2M", "59.2K", "694") /// /// # Examples -/// ``` +/// ```ignore /// use rtk::utils::format_tokens; /// assert_eq!(format_tokens(1_234_567), "1.2M"); /// assert_eq!(format_tokens(59_234), "59.2K"); /// assert_eq!(format_tokens(694), "694"); -/// ``` +/// ```ignore pub fn format_tokens(n: usize) -> String { if n >= 1_000_000 { format!("{:.1}M", n as f64 / 1_000_000.0) @@ -114,13 +114,13 @@ pub fn format_tokens(n: usize) -> String { /// Formatted string with $ prefix /// /// # Examples -/// ``` +/// ```ignore /// use rtk::utils::format_usd; /// assert_eq!(format_usd(1234.567), "$1234.57"); /// assert_eq!(format_usd(12.345), "$12.35"); /// assert_eq!(format_usd(0.123), "$0.12"); /// assert_eq!(format_usd(0.0096), "$0.0096"); -/// ``` +/// ```ignore pub fn format_usd(amount: f64) -> String { if !amount.is_finite() { return "$0.00".to_string(); @@ -141,12 +141,12 @@ pub fn format_usd(amount: f64) -> String { /// Formatted string like "$3.86/MTok" /// /// # Examples -/// ``` +/// ```ignore /// use rtk::utils::format_cpt; /// assert_eq!(format_cpt(0.000003), "$3.00/MTok"); /// assert_eq!(format_cpt(0.0000038), "$3.80/MTok"); /// assert_eq!(format_cpt(0.00000386), "$3.86/MTok"); -/// ``` +/// ```ignore pub fn format_cpt(cpt: f64) -> String { if !cpt.is_finite() || cpt <= 0.0 { return "$0.00/MTok".to_string(); @@ -210,7 +210,7 @@ pub fn ok_confirmation(action: &str, detail: &str) -> String { /// Returns "pnpm", "yarn", or "npm" based on lockfile presence. /// /// # Examples -/// ```no_run +/// ```ignore /// use rtk::utils::detect_package_manager; /// let pm = detect_package_manager(); /// // Returns "pnpm" if pnpm-lock.yaml exists, "yarn" if yarn.lock, else "npm" diff --git a/tests/library_api.rs b/tests/library_api.rs deleted file mode 100644 index 039f9f1b..00000000 --- a/tests/library_api.rs +++ /dev/null @@ -1,39 +0,0 @@ -use rtk::config::Config; -use rtk::rewrite::{rewrite_command, rewrite_with_config}; -use rtk::toml_filter::{apply_filter, find_filter_in, TomlFilterRegistry}; - -#[test] -fn rewrite_public_api_rewrites_supported_commands() { - assert_eq!( - rewrite_command("git status", &[]), - Some("rtk git status".to_string()) - ); -} - -#[test] -fn rewrite_with_config_respects_excluded_commands() { - let mut config = Config::default(); - config.hooks.exclude_commands = vec!["git".to_string()]; - - assert_eq!(rewrite_with_config("git status", &config), None); -} - -#[test] -fn toml_filter_registry_from_toml_str_builds_reusable_filters() { - let registry = TomlFilterRegistry::from_toml_str( - r#" -schema_version = 1 - -[filters.tests] -match_command = "^pytest" -keep_lines_matching = ["^FAIL", "^PASS"] -"#, - "test", - ) - .expect("valid filter registry"); - - let filter = find_filter_in("pytest tests/", ®istry.filters).expect("matching filter"); - let output = apply_filter(filter, "noise\nPASS first\nFAIL second"); - - assert_eq!(output, "PASS first\nFAIL second"); -}