From 90244ce4d468bfa9f7f2442bddb7d65e839019fe Mon Sep 17 00:00:00 2001 From: Me0wo <152751263+Sn0wo2@users.noreply.github.com> Date: Thu, 21 May 2026 08:50:02 +0800 Subject: [PATCH 01/13] chore(deps): remove unused dependencies --- Cargo.lock | 87 ------------------------------------------------------ Cargo.toml | 5 ---- 2 files changed, 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1943eff..ec9fba08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,21 +8,16 @@ version = "0.1.1" dependencies = [ "acta-build", "ansi_colours", - "anstyle", "anstyle-lossy", "arc-swap", - "arrayvec", "chrono", "compact_str", - "derive_more", "flate2", "nerd-font-symbols", "owo-colors", "serde", "smallvec", - "smart-default", "supports-color", - "tap", "thiserror", "tokio", "tracing", @@ -105,12 +100,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - [[package]] name = "autocfg" version = "1.5.0" @@ -186,15 +175,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -234,29 +214,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", - "unicode-xid", -] - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -486,15 +443,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustversion" version = "1.0.22" @@ -516,12 +464,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - [[package]] name = "serde" version = "1.0.228" @@ -598,17 +540,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "smart-default" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -641,12 +572,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "thiserror" version = "2.0.18" @@ -810,18 +735,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-segmentation" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "valuable" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 6d5f9ec4..aa9c0e3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,6 @@ serde = { version = "1", features = ["derive"], optional = true } tracing = "0.1" tracing-log = "0.2" smallvec = "1" -arrayvec = "0.7" tracing-appender = { version = "0.2", optional = true } nerd-font-symbols = { version = "0.3.0", optional = true } tokio = { version = "1", default-features = false, features = [ @@ -57,14 +56,10 @@ tracing-subscriber = { version = "0.3", features = [ "ansi", "std", ] } -smart-default = "0.7.1" supports-color = "3" ansi_colours = "1.2" anstyle-lossy = "1.1" -anstyle = "1" arc-swap = "1.7.1" -derive_more = { version = "2.0.0", features = ["full"] } -tap = "1.0.1" compact_str = { version = "0.9", features = ["serde"] } [build-dependencies] From fc20a163d137bc741ae17e42f6d56ea34df3b755 Mon Sep 17 00:00:00 2001 From: Me0wo <152751263+Sn0wo2@users.noreply.github.com> Date: Thu, 21 May 2026 08:50:43 +0800 Subject: [PATCH 02/13] refactor(acta-build): simplify API and make infallible --- build.rs | 20 ++++--------- crates/acta-build/Cargo.toml | 2 +- crates/acta-build/src/lib.rs | 57 ++++++++++++++++++++++++++++++------ 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/build.rs b/build.rs index be2555f7..e0a5889f 100644 --- a/build.rs +++ b/build.rs @@ -1,19 +1,11 @@ #![allow(clippy::expect_used)] fn main() { - let max = match acta_build::walk_src_max_width("src", "src/") { - Ok(m) => m, - Err(e) => { - println!("cargo::warning=walk_src_max_width failed: {e}"); - 20 - } - }; - std::fs::write( - std::path::Path::new(&std::env::var("OUT_DIR").expect("Cargo should set OUT_DIR")) - .join("path_width"), - max.to_string(), - ) - .expect("failed to write path_width to OUT_DIR"); - + let width = acta_build::walk_src_max_width("src", "src/"); + let out_dir = std::env::var_os("OUT_DIR").expect("Cargo should set OUT_DIR"); + let path = std::path::Path::new(&out_dir).join("path_width"); + if let Err(e) = std::fs::write(&path, width.to_string()) { + println!("cargo::warning=failed to write {}: {e}", path.display()); + } println!("cargo::rerun-if-changed=src"); } diff --git a/crates/acta-build/Cargo.toml b/crates/acta-build/Cargo.toml index ea089b9b..6aaffc57 100644 --- a/crates/acta-build/Cargo.toml +++ b/crates/acta-build/Cargo.toml @@ -2,7 +2,7 @@ name = "acta-build" version.workspace = true edition = "2024" -description = "Build-time helper for acta path width calculation" +description = "Build-time helper for acta: compute path-column width from a project's source tree" repository = "https://github.com/Sn0wo2/acta" publish = true license = "Apache-2.0" diff --git a/crates/acta-build/src/lib.rs b/crates/acta-build/src/lib.rs index f7bb521e..254506da 100644 --- a/crates/acta-build/src/lib.rs +++ b/crates/acta-build/src/lib.rs @@ -1,14 +1,55 @@ +//! Build-time helper for [acta](https://crates.io/crates/acta). +//! +//! Call from your `build.rs` to compute a sensible default `path_width` for +//! [`acta::Formatter`] based on the longest source-file path in your project. +//! +//! # Example +//! +//! Add to your `Cargo.toml`: +//! +//! ```toml +//! [build-dependencies] +//! acta-build = "0.1" +//! ``` +//! +//! In `build.rs`: +//! +//! ```no_run +//! # fn example() { +//! let width = acta_build::walk_src_max_width("src", "src/"); +//! println!("cargo:rustc-env=ACTA_PATH_WIDTH={width}"); +//! println!("cargo:rerun-if-changed=src"); +//! # } +//! ``` +//! +//! Then in your code: +//! +//! ```ignore +//! let width: usize = env!("ACTA_PATH_WIDTH").parse().unwrap_or(40); +//! let formatter = acta::Formatter::new().with_path_width(width); +//! ``` + +use std::path::Path; use walkdir::WalkDir; -pub fn walk_src_max_width(dir: &str, strip_prefix: &str) -> Result { - let entries: Vec<_> = WalkDir::new(dir) +const FALLBACK_WIDTH: usize = 40; +const PADDING: usize = 4; + +/// Walk `dir` recursively, find every `.rs` file, and return +/// `max(path_len_after_stripping(strip_prefix)) + 4`. +/// +/// Returns [`FALLBACK_WIDTH`] (40) if `dir` does not exist or contains no +/// `.rs` files — safe to call unconditionally from a `build.rs`. +#[must_use] +pub fn walk_src_max_width(dir: impl AsRef, strip_prefix: &str) -> usize { + let entries: Vec<_> = WalkDir::new(dir.as_ref()) .into_iter() - .filter_map(|e| e.ok()) + .filter_map(Result::ok) .filter(|e| e.path().extension().is_some_and(|ext| ext == "rs")) .collect(); if entries.is_empty() { - return Err(format!("no .rs files found in {dir}")); + return FALLBACK_WIDTH; } let max = entries @@ -17,12 +58,10 @@ pub fn walk_src_max_width(dir: &str, strip_prefix: &str) -> Result Date: Thu, 21 May 2026 08:51:09 +0800 Subject: [PATCH 03/13] refactor(config): replace Level::Custom with Filter::from_directive --- src/config/depth.rs | 34 ------ src/config/mod.rs | 254 +++++++++++++++++++------------------------- src/config/test.rs | 35 +++++- 3 files changed, 140 insertions(+), 183 deletions(-) delete mode 100644 src/config/depth.rs diff --git a/src/config/depth.rs b/src/config/depth.rs deleted file mode 100644 index 68078319..00000000 --- a/src/config/depth.rs +++ /dev/null @@ -1,34 +0,0 @@ -use supports_color::Stream; - -use super::{ColorDepth, WriterTarget}; - -pub fn detect(target: &WriterTarget) -> ColorDepth { - let stream = match target { - WriterTarget::Stdout => Stream::Stdout, - WriterTarget::Stderr => Stream::Stderr, - #[cfg(feature = "file")] - WriterTarget::File { .. } => return ColorDepth::NoColor, - #[cfg(any(feature = "custom-async", feature = "native-async"))] - WriterTarget::AsyncStdout(_) => Stream::Stdout, - #[cfg(any(feature = "custom-async", feature = "native-async"))] - WriterTarget::AsyncStderr(_) => Stream::Stderr, - }; - - if let Some(level) = supports_color::on_cached(stream) { - if level.has_16m { - return ColorDepth::TrueColor; - } - if level.has_256 { - return ColorDepth::Ansi256; - } - if level.has_basic { - return ColorDepth::Ansi16; - } - } - - ColorDepth::NoColor -} - -pub fn detect_nerd() -> bool { - std::env::var("NERD_FONT").is_ok_and(|v| !v.is_empty() && v != "0" && v != "false") -} diff --git a/src/config/mod.rs b/src/config/mod.rs index 1b9aaeeb..5b4df8ab 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,10 +4,6 @@ use std::collections::HashMap; #[cfg(feature = "file")] use std::path::PathBuf; -pub mod depth; - -pub use depth::detect; - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] @@ -144,7 +140,7 @@ impl Default for Icons { type Rgb = (u8, u8, u8); #[derive(Clone, Copy, Debug)] -#[non_exhaustive] +#[allow(clippy::exhaustive_structs)] pub struct Theme { pub accent: Rgb, pub secondary: Rgb, @@ -158,7 +154,7 @@ pub struct Theme { impl Theme { #[allow(clippy::too_many_arguments)] - pub const fn new( + const fn from_palette( accent: Rgb, secondary: Rgb, text: Rgb, @@ -179,10 +175,7 @@ impl Theme { trace, } } - /// - /// light blue, pink, white, bright red, gold, off white - /// with some dimmed for terminal readability. - #[rustfmt::skip] // 让下面的颜色对齐 + #[rustfmt::skip] pub const fn acta() -> Self { const LIGHT_BLUE: Rgb = (91, 206, 250); // #5BCEFA const PINK: Rgb = (245, 169, 184); // #F5A9B8 @@ -190,115 +183,84 @@ impl Theme { const BRIGHT_RED: Rgb = (255, 85, 85); // #FF5555 const GOLD: Rgb = (255, 200, 60); // #FFC83C const OFF_WHITE: Rgb = (240, 240, 240); // #F0F0F0 - - Self::new( - LIGHT_BLUE, PINK, WHITE, BRIGHT_RED, GOLD, LIGHT_BLUE, PINK, OFF_WHITE, - ) + Self::from_palette(LIGHT_BLUE, PINK, WHITE, BRIGHT_RED, GOLD, LIGHT_BLUE, PINK, OFF_WHITE) } - /// - /// cyan, pink, white, bright red, gold, gray - /// with some dimmed for terminal readability. - #[rustfmt::skip] // ↑ + #[rustfmt::skip] pub const fn monokai() -> Self { - const CYAN: Rgb = (102, 217, 239); // #66D9EF - const PINK: Rgb = (249, 38, 114); // #F92672 - const WHITE: Rgb = (248, 248, 242); // #F8F8F2 - const BRIGHT_RED: Rgb = (255, 85, 85); // #FF5555 - const GOLD: Rgb = (255, 200, 60); // #FFC83C - const GRAY: Rgb = (180, 180, 180); // #B4B4B4 - - Self::new(CYAN, PINK, WHITE, BRIGHT_RED, GOLD, CYAN, PINK, GRAY) + const CYAN: Rgb = (102, 217, 239); // #66D9EF + const PINK: Rgb = (249, 38, 114); // #F92672 + const WHITE: Rgb = (248, 248, 242); // #F8F8F2 + const BRIGHT_RED: Rgb = (255, 85, 85); // #FF5555 + const GOLD: Rgb = (255, 200, 60); // #FFC83C + const GRAY: Rgb = (180, 180, 180); // #B4B4B4 + Self::from_palette(CYAN, PINK, WHITE, BRIGHT_RED, GOLD, CYAN, PINK, GRAY) } - /// - /// cyan, pink, white, bright red, gold, gray - /// with some dimmed for terminal readability. - #[rustfmt::skip] // ↑ + #[rustfmt::skip] pub const fn dracula() -> Self { - const CYAN: Rgb = (139, 233, 253); // #8BE9FD - const PINK: Rgb = (255, 121, 198); // #FF79C6 - const WHITE: Rgb = (248, 248, 242); // #F8F8F2 - const BRIGHT_RED: Rgb = (255, 85, 85); // #FF5555 - const GOLD: Rgb = (255, 200, 60); // #FFC83C - const GRAY: Rgb = (180, 180, 180); // #B4B4B4 - - Self::new(CYAN, PINK, WHITE, BRIGHT_RED, GOLD, CYAN, PINK, GRAY) + const CYAN: Rgb = (139, 233, 253); // #8BE9FD + const PINK: Rgb = (255, 121, 198); // #FF79C6 + const WHITE: Rgb = (248, 248, 242); // #F8F8F2 + const BRIGHT_RED: Rgb = (255, 85, 85); // #FF5555 + const GOLD: Rgb = (255, 200, 60); // #FFC83C + const GRAY: Rgb = (180, 180, 180); // #B4B4B4 + Self::from_palette(CYAN, PINK, WHITE, BRIGHT_RED, GOLD, CYAN, PINK, GRAY) } - /// - /// blue, green, white, red, yellow, gray - /// with some dimmed for terminal readability. - #[rustfmt::skip] // ↑ + #[rustfmt::skip] pub const fn nord() -> Self { - const BLUE: Rgb = (136, 192, 208); // #88C0D0 - const GREEN: Rgb = (163, 190, 140); // #A3BE8C - const WHITE: Rgb = (216, 222, 233); // #D8DEE9 - const RED: Rgb = (191, 97, 106); // #BF616A - const YELLOW: Rgb = (235, 203, 139); // #EBCB8B - const GRAY: Rgb = (180, 180, 180); // #B4B4B4 - - Self::new(BLUE, GREEN, WHITE, RED, YELLOW, BLUE, GREEN, GRAY) + const BLUE: Rgb = (136, 192, 208); // #88C0D0 + const GREEN: Rgb = (163, 190, 140); // #A3BE8C + const WHITE: Rgb = (216, 222, 233); // #D8DEE9 + const RED: Rgb = (191, 97, 106); // #BF616A + const YELLOW: Rgb = (235, 203, 139); // #EBCB8B + const GRAY: Rgb = (180, 180, 180); // #B4B4B4 + Self::from_palette(BLUE, GREEN, WHITE, RED, YELLOW, BLUE, GREEN, GRAY) } - /// - /// blue, mauve, text, red, yellow, gray - /// with some dimmed for terminal readability. - #[rustfmt::skip] // ↑ + #[rustfmt::skip] pub const fn catppuccin_mocha() -> Self { - const BLUE: Rgb = (137, 180, 250); // #89B4FA - const MAUVE: Rgb = (203, 166, 247); // #CBA6F7 - const TEXT: Rgb = (205, 214, 244); // #CDD6F4 - const RED: Rgb = (243, 139, 168); // #F38BA8 - const YELLOW: Rgb = (249, 226, 175); // #F9E2AF - const GRAY: Rgb = (180, 180, 180); // #B4B4B4 - - Self::new(BLUE, MAUVE, TEXT, RED, YELLOW, BLUE, MAUVE, GRAY) + const BLUE: Rgb = (137, 180, 250); // #89B4FA + const MAUVE: Rgb = (203, 166, 247); // #CBA6F7 + const TEXT: Rgb = (205, 214, 244); // #CDD6F4 + const RED: Rgb = (243, 139, 168); // #F38BA8 + const YELLOW: Rgb = (249, 226, 175); // #F9E2AF + const GRAY: Rgb = (180, 180, 180); // #B4B4B4 + Self::from_palette(BLUE, MAUVE, TEXT, RED, YELLOW, BLUE, MAUVE, GRAY) } - /// - /// aqua, orange, light, red, yellow, gray - /// with some dimmed for terminal readability. - #[rustfmt::skip] // ↑ + #[rustfmt::skip] pub const fn gruvbox() -> Self { - const AQUA: Rgb = (131, 165, 152); // #83A598 - const ORANGE: Rgb = (254, 128, 25); // #FE8019 - const LIGHT: Rgb = (235, 219, 178); // #EBDBB2 - const RED: Rgb = (251, 73, 52); // #FB4934 - const YELLOW: Rgb = (250, 189, 47); // #FABD2F - const GRAY: Rgb = (180, 180, 180); // #B4B4B4 - - Self::new(AQUA, ORANGE, LIGHT, RED, YELLOW, AQUA, ORANGE, GRAY) + const AQUA: Rgb = (131, 165, 152); // #83A598 + const ORANGE: Rgb = (254, 128, 25); // #FE8019 + const LIGHT: Rgb = (235, 219, 178); // #EBDBB2 + const RED: Rgb = (251, 73, 52); // #FB4934 + const YELLOW: Rgb = (250, 189, 47); // #FABD2F + const GRAY: Rgb = (180, 180, 180); // #B4B4B4 + Self::from_palette(AQUA, ORANGE, LIGHT, RED, YELLOW, AQUA, ORANGE, GRAY) } - /// - /// blue, purple, white, red, yellow, gray - /// with some dimmed for terminal readability. - #[rustfmt::skip] // ↑ + #[rustfmt::skip] pub const fn one_dark() -> Self { - const BLUE: Rgb = (97, 175, 239); // #61AFEF - const PURPLE: Rgb = (198, 120, 221); // #C678DD - const WHITE: Rgb = (171, 178, 191); // #ABB2BF - const RED: Rgb = (224, 108, 117); // #E06C75 - const YELLOW: Rgb = (229, 192, 123); // #E5C07B - const GRAY: Rgb = (180, 180, 180); // #B4B4B4 - - Self::new(BLUE, PURPLE, WHITE, RED, YELLOW, BLUE, PURPLE, GRAY) + const BLUE: Rgb = (97, 175, 239); // #61AFEF + const PURPLE: Rgb = (198, 120, 221); // #C678DD + const WHITE: Rgb = (171, 178, 191); // #ABB2BF + const RED: Rgb = (224, 108, 117); // #E06C75 + const YELLOW: Rgb = (229, 192, 123); // #E5C07B + const GRAY: Rgb = (180, 180, 180); // #B4B4B4 + Self::from_palette(BLUE, PURPLE, WHITE, RED, YELLOW, BLUE, PURPLE, GRAY) } - /// - /// blue, purple, white, red, yellow, gray - /// with some dimmed for terminal readability. - #[rustfmt::skip] // ↑ + #[rustfmt::skip] pub const fn tokyo_night() -> Self { - const BLUE: Rgb = (122, 162, 247); // #7AA2F7 - const PURPLE: Rgb = (187, 154, 247); // #BB9AF7 - const WHITE: Rgb = (192, 202, 245); // #C0CAF5 - const RED: Rgb = (247, 118, 142); // #F7768E - const YELLOW: Rgb = (224, 175, 104); // #E0AF68 - const GRAY: Rgb = (180, 180, 180); // #B4B4B4 - - Self::new(BLUE, PURPLE, WHITE, RED, YELLOW, BLUE, PURPLE, GRAY) + const BLUE: Rgb = (122, 162, 247); // #7AA2F7 + const PURPLE: Rgb = (187, 154, 247); // #BB9AF7 + const WHITE: Rgb = (192, 202, 245); // #C0CAF5 + const RED: Rgb = (247, 118, 142); // #F7768E + const YELLOW: Rgb = (224, 175, 104); // #E0AF68 + const GRAY: Rgb = (180, 180, 180); // #B4B4B4 + Self::from_palette(BLUE, PURPLE, WHITE, RED, YELLOW, BLUE, PURPLE, GRAY) } } @@ -418,7 +380,12 @@ pub enum Rotation { Compress, } -#[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "lowercase") +)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum Level { Error, @@ -427,11 +394,10 @@ pub enum Level { Debug, Trace, Off, - Custom(String), } impl Level { - pub const fn as_directive(&self) -> &str { + pub const fn as_directive(self) -> &'static str { match self { Self::Error => "error", Self::Warn => "warn", @@ -439,50 +405,51 @@ impl Level { Self::Debug => "debug", Self::Trace => "trace", Self::Off => "off", - Self::Custom(s) => s.as_str(), - } - } - - pub fn parse_str(s: &str) -> Self { - match s { - "error" => Self::Error, - "warn" => Self::Warn, - "info" => Self::Info, - "debug" => Self::Debug, - "trace" => Self::Trace, - "off" => Self::Off, - other => Self::Custom(other.to_owned()), } } } +/// Tracing filter directive. +/// +/// Built either from a `Level` (structured) or a raw `EnvFilter`-compatible +/// directive string. Per-target overrides added via [`with_target`] are +/// appended after the base; [`remove_target`] only removes entries that were +/// added structurally — entries embedded in a raw base string cannot be +/// removed without rebuilding the filter. +/// +/// [`with_target`]: Filter::with_target +/// [`remove_target`]: Filter::remove_target #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug)] #[non_exhaustive] pub struct Filter { - level: Level, - + base: compact_str::CompactString, targets: HashMap, } impl Filter { - pub fn new(level: impl Into) -> Self { + pub fn new(level: Level) -> Self { Self { - level: level.into(), + base: level.as_directive().into(), targets: HashMap::new(), } } - pub const fn level(&self) -> &Level { - &self.level + /// Build a `Filter` from a raw `EnvFilter`-style directive string, + /// e.g. `"info,my_crate=debug,my_crate::db=trace"`. + pub fn from_directive(directive: impl Into) -> Self { + Self { + base: directive.into(), + targets: HashMap::new(), + } } pub fn with_target( &mut self, target: impl Into, - level: impl Into, + level: Level, ) -> &mut Self { - self.targets.insert(target.into(), level.into()); + self.targets.insert(target.into(), level); self } @@ -491,7 +458,7 @@ impl Filter { } pub fn as_directive(&self) -> String { - let mut directive = String::from(self.level.as_directive()); + let mut directive = String::from(self.base.as_str()); for (target, level) in &self.targets { directive.push(','); directive.push_str(target); @@ -502,24 +469,15 @@ impl Filter { } } -impl From for Filter { - fn from(level: Level) -> Self { - Self::new(level) - } -} - -#[cfg(feature = "serde")] -impl serde::Serialize for Level { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_str(self.as_directive()) +impl Default for Filter { + fn default() -> Self { + Self::new(Level::Info) } } -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for Level { - fn deserialize>(deserializer: D) -> Result { - let s = String::deserialize(deserializer)?; - Ok(Self::parse_str(&s)) +impl From for Filter { + fn from(level: Level) -> Self { + Self::new(level) } } @@ -610,7 +568,8 @@ impl Default for Writer { #[derive(Clone, Debug)] #[non_exhaustive] pub struct Config { - pub level: Level, + #[cfg_attr(feature = "serde", serde(default))] + pub filter: Filter, #[cfg_attr(feature = "serde", serde(default))] pub writers: Vec, } @@ -624,7 +583,7 @@ impl Config { impl Default for Config { fn default() -> Self { Self { - level: Level::Info, + filter: Filter::default(), writers: vec![Writer::default()], } } @@ -634,13 +593,20 @@ impl Default for Config { #[must_use] #[allow(clippy::module_name_repetitions)] pub struct ConfigBuilder { - level: Option, + filter: Option, writers: Vec, } impl ConfigBuilder { - pub fn level(mut self, level: impl Into) -> Self { - self.level = Some(level.into()); + /// Convenience: set the filter to a single level. + pub fn level(mut self, level: Level) -> Self { + self.filter = Some(Filter::new(level)); + self + } + + /// Set the full filter (for raw directives or pre-built filters). + pub fn filter(mut self, filter: Filter) -> Self { + self.filter = Some(filter); self } @@ -652,7 +618,7 @@ impl ConfigBuilder { pub fn build(self) -> Config { let defaults = Config::default(); Config { - level: self.level.unwrap_or(defaults.level), + filter: self.filter.unwrap_or(defaults.filter), writers: if self.writers.is_empty() { defaults.writers } else { diff --git a/src/config/test.rs b/src/config/test.rs index 169475d0..870f1b2f 100644 --- a/src/config/test.rs +++ b/src/config/test.rs @@ -1,9 +1,11 @@ +#![allow(clippy::indexing_slicing)] + use super::*; #[test] fn config_default() { let config = Config::default(); - assert!(matches!(config.level, Level::Info)); + assert_eq!(config.filter.as_directive(), "info"); assert_eq!(config.writers.len(), 1); let w = &config.writers[0]; assert!(matches!(w.format, Format::Compact(_))); @@ -29,10 +31,18 @@ fn config_builder() { .level(Level::Debug) .with_writer(Writer::default()) .build(); - assert_eq!(cfg.level.as_directive(), "debug"); + assert_eq!(cfg.filter.as_directive(), "debug"); assert_eq!(cfg.writers.len(), 1); } +#[test] +fn config_builder_filter() { + let cfg = Config::builder() + .filter(Filter::from_directive("info,my_crate=debug")) + .build(); + assert_eq!(cfg.filter.as_directive(), "info,my_crate=debug"); +} + #[test] fn config_builder_multiple_writers() { let cfg = Config::builder() @@ -60,9 +70,18 @@ fn level_directives() { } #[test] -fn level_custom_directive() { - let level = Level::Custom("info,my_crate=debug".into()); - assert_eq!(level.as_directive(), "info,my_crate=debug"); +fn filter_from_directive() { + let f = Filter::from_directive("info,my_crate=debug"); + assert_eq!(f.as_directive(), "info,my_crate=debug"); +} + +#[test] +fn filter_from_directive_with_extra_target() { + let mut f = Filter::from_directive("info,bar=warn"); + f.with_target("foo", Level::Trace); + let directive = f.as_directive(); + assert!(directive.starts_with("info,bar=warn")); + assert!(directive.contains("foo=trace")); } #[test] @@ -122,6 +141,12 @@ fn filter_from_level_debug() { assert_eq!(filter.as_directive(), "debug"); } +#[test] +fn filter_default_is_info() { + let filter = Filter::default(); + assert_eq!(filter.as_directive(), "info"); +} + #[test] #[cfg(feature = "file")] fn writer_file_target() { From 824858a119875724592185be6c641cc9a92027fc Mon Sep 17 00:00:00 2001 From: Me0wo <152751263+Sn0wo2@users.noreply.github.com> Date: Thu, 21 May 2026 08:51:35 +0800 Subject: [PATCH 04/13] refactor(color): extract Styled struct and remove mapping module --- src/color/mapping.rs | 30 ----------- src/color/mod.rs | 4 +- src/color/style.rs | 118 ++++++++++++++++++++++++++++--------------- src/color/tests.rs | 65 +++++++++--------------- 4 files changed, 102 insertions(+), 115 deletions(-) delete mode 100644 src/color/mapping.rs diff --git a/src/color/mapping.rs b/src/color/mapping.rs deleted file mode 100644 index 3aa969b7..00000000 --- a/src/color/mapping.rs +++ /dev/null @@ -1,30 +0,0 @@ -use owo_colors::AnsiColors; - -use anstyle::RgbColor; -use anstyle_lossy::{palette::Palette, rgb_to_ansi}; - -/// Delegates to the `anstyle_lossy` crate which uses a weighted -/// Euclidean distance metric against a platform-aware default palette -/// (VGA on Unix, Windows 10 Console on Windows). -pub fn rgb_to_ansi16(r: u8, g: u8, b: u8) -> AnsiColors { - let ansi = rgb_to_ansi(RgbColor(r, g, b), Palette::default()); - const PALETTE: [AnsiColors; 16] = [ - AnsiColors::Black, - AnsiColors::Red, - AnsiColors::Green, - AnsiColors::Yellow, - AnsiColors::Blue, - AnsiColors::Magenta, - AnsiColors::Cyan, - AnsiColors::White, - AnsiColors::BrightBlack, - AnsiColors::BrightRed, - AnsiColors::BrightGreen, - AnsiColors::BrightYellow, - AnsiColors::BrightBlue, - AnsiColors::BrightMagenta, - AnsiColors::BrightCyan, - AnsiColors::BrightWhite, - ]; - *PALETTE.get(ansi as usize).unwrap_or(&AnsiColors::White) -} diff --git a/src/color/mod.rs b/src/color/mod.rs index a5e6d87a..69f46614 100644 --- a/src/color/mod.rs +++ b/src/color/mod.rs @@ -1,8 +1,6 @@ -pub mod mapping; pub mod style; #[cfg(test)] mod tests; -pub use mapping::rgb_to_ansi16; -pub use style::{rgb_to_owo, rgb_to_owo_on, theme_fg_dimmed}; +pub(crate) use style::Styled; diff --git a/src/color/style.rs b/src/color/style.rs index 39be94ff..d2b54b00 100644 --- a/src/color/style.rs +++ b/src/color/style.rs @@ -1,51 +1,87 @@ -use ansi_colours::ansi256_from_rgb; -use owo_colors::{DynColors, Style as OwoStyle, XtermColors}; +use anstyle_lossy::palette::Palette; +use anstyle_lossy::rgb_to_ansi; +use owo_colors::AnsiColors; +use owo_colors::Rgb; +use owo_colors::Style as OwoStyle; +use owo_colors::XtermColors; -use crate::color::mapping::rgb_to_ansi16; -use crate::config::ColorDepth; +use crate::ColorDepth; -pub fn rgb_to_owo(rgb: (u8, u8, u8), depth: ColorDepth) -> OwoStyle { - let r = rgb.0; - let g = rgb.1; - let b = rgb.2; +#[derive(Clone, Copy, Debug)] +pub struct Styled { + rgb: Rgb, + depth: ColorDepth, + on: bool, +} - match depth { - ColorDepth::TrueColor => OwoStyle::new().color(DynColors::Rgb(r, g, b)), - ColorDepth::Ansi256 => { - let idx = ansi256_from_rgb((r, g, b)); - OwoStyle::new().color(DynColors::Xterm(XtermColors::from(idx))) - } - ColorDepth::Ansi16 => { - let c = rgb_to_ansi16(r, g, b); - OwoStyle::new().color(DynColors::Ansi(c)) +impl Styled { + pub(crate) const fn new(rgb: Rgb, depth: ColorDepth) -> Self { + Self { + rgb, + depth, + on: false, } - ColorDepth::NoColor => OwoStyle::new(), } -} -/// Dims the foreground by dividing brightness by 4 to ensure high text legibility against the background. -pub fn rgb_to_owo_on(r: u8, g: u8, b: u8, depth: ColorDepth) -> OwoStyle { - let dr = r >> 2; - let dg = g >> 2; - let db = b >> 2; - match depth { - ColorDepth::TrueColor => OwoStyle::new() - .color(DynColors::Rgb(dr, dg, db)) - .on_color(DynColors::Rgb(r, g, b)), - ColorDepth::Ansi256 => OwoStyle::new() - .color(DynColors::Xterm(XtermColors::from(ansi256_from_rgb(( - dr, dg, db, - ))))) - .on_color(DynColors::Xterm(XtermColors::from(ansi256_from_rgb(( - r, g, b, - ))))), - ColorDepth::Ansi16 => OwoStyle::new() - .color(DynColors::Ansi(rgb_to_ansi16(dr, dg, db))) - .on_color(DynColors::Ansi(rgb_to_ansi16(r, g, b))), - ColorDepth::NoColor => OwoStyle::new(), + pub(crate) const fn dimmed(mut self) -> Self { + self.rgb = Rgb(self.rgb.0 >> 2, self.rgb.1 >> 2, self.rgb.2 >> 2); + self + } + + pub(crate) const fn on(mut self) -> Self { + self.on = true; + self + } + + pub(crate) const fn as_tuple(&self) -> (u8, u8, u8) { + (self.rgb.0, self.rgb.1, self.rgb.2) } } -pub fn theme_fg_dimmed(rgb: (u8, u8, u8), depth: ColorDepth) -> OwoStyle { - rgb_to_owo((rgb.0 >> 2, rgb.1 >> 2, rgb.2 >> 2), depth) +impl From for OwoStyle { + fn from(s: Styled) -> Self { + let style = Self::new(); + match s.depth { + ColorDepth::TrueColor => { + if s.on { + return style.on_truecolor(s.rgb.0, s.rgb.1, s.rgb.2); + } + style.truecolor(s.rgb.0, s.rgb.1, s.rgb.2) + } + ColorDepth::Ansi256 => { + let ansi = XtermColors::from(ansi_colours::ansi256_from_rgb(s.as_tuple())); + if s.on { + return style.on_color(ansi); + } + style.color(ansi) + } + ColorDepth::Ansi16 => { + let ansi = *[ + AnsiColors::Black, + AnsiColors::Red, + AnsiColors::Green, + AnsiColors::Yellow, + AnsiColors::Blue, + AnsiColors::Magenta, + AnsiColors::Cyan, + AnsiColors::White, + AnsiColors::BrightBlack, + AnsiColors::BrightRed, + AnsiColors::BrightGreen, + AnsiColors::BrightYellow, + AnsiColors::BrightBlue, + AnsiColors::BrightMagenta, + AnsiColors::BrightCyan, + AnsiColors::BrightWhite, + ] + .get(rgb_to_ansi(s.as_tuple().into(), Palette::default()) as usize) + .unwrap_or(&AnsiColors::White); + if s.on { + return style.on_color(ansi); + } + style.color(ansi) + } + ColorDepth::NoColor => style, + } + } } diff --git a/src/color/tests.rs b/src/color/tests.rs index b63429ac..cc6db641 100644 --- a/src/color/tests.rs +++ b/src/color/tests.rs @@ -1,43 +1,26 @@ -#[cfg(test)] -mod tests { - use ansi_colours::ansi256_from_rgb; - use owo_colors::AnsiColors; +use ansi_colours::ansi256_from_rgb; - use crate::color::mapping::rgb_to_ansi16; - - #[test] - fn rgb256_primary_red() { - assert_eq!(ansi256_from_rgb((255, 0, 0)), 196); - } - #[test] - fn rgb256_primary_green() { - assert_eq!(ansi256_from_rgb((0, 255, 0)), 46); - } - #[test] - fn rgb256_primary_blue() { - assert_eq!(ansi256_from_rgb((0, 0, 255)), 21); - } - #[test] - fn rgb256_gray() { - assert_eq!(ansi256_from_rgb((128, 128, 128)), 244); - } - #[test] - fn rgb256_black() { - assert_eq!(ansi256_from_rgb((0, 0, 0)), 16); - } - #[test] - fn rgb256_white() { - assert_eq!(ansi256_from_rgb((255, 255, 255)), 231); - } - - #[test] - fn rgb16_primary_red() { - // anstyle-lossy maps (255,0,0) → Red (not BrightRed) - // on both VGA and WIN10_CONSOLE palettes - assert_eq!(rgb_to_ansi16(255, 0, 0), AnsiColors::Red); - } - #[test] - fn rgb16_green() { - assert_eq!(rgb_to_ansi16(0, 128, 0), AnsiColors::Green); - } +#[test] +fn rgb256_primary_red() { + assert_eq!(ansi256_from_rgb((255, 0, 0)), 196); +} +#[test] +fn rgb256_primary_green() { + assert_eq!(ansi256_from_rgb((0, 255, 0)), 46); +} +#[test] +fn rgb256_primary_blue() { + assert_eq!(ansi256_from_rgb((0, 0, 255)), 21); +} +#[test] +fn rgb256_gray() { + assert_eq!(ansi256_from_rgb((128, 128, 128)), 244); +} +#[test] +fn rgb256_black() { + assert_eq!(ansi256_from_rgb((0, 0, 0)), 16); +} +#[test] +fn rgb256_white() { + assert_eq!(ansi256_from_rgb((255, 255, 255)), 231); } From 5ff59018b731d95dcd58bc8caa964cf43f750c12 Mon Sep 17 00:00:00 2001 From: Me0wo <152751263+Sn0wo2@users.noreply.github.com> Date: Thu, 21 May 2026 08:52:02 +0800 Subject: [PATCH 05/13] refactor(core): simplify builder, update formatter and public API --- src/builder.rs | 304 +++++++++++++----------------------------------- src/fmt/mod.rs | 232 +++++++++++++++++++++--------------- src/fmt/test.rs | 2 +- src/lib.rs | 16 +-- src/prelude.rs | 12 +- 5 files changed, 230 insertions(+), 336 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index f148abbf..cef0255d 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -6,53 +6,47 @@ use std::io; #[cfg(feature = "file")] use std::path::PathBuf; use std::sync::Arc; +use tracing_subscriber::Registry; use tracing_subscriber::fmt::format::FmtSpan; +use tracing_subscriber::layer::Layered; use tracing_subscriber::prelude::*; -pub type FmtLayer = - Box + Send + Sync>; - -// tracing_subscriber::reload::Handle needs the concrete S. -// Six optional layers stacked → this pyramid. Users never name it. -type Layer1 = tracing_subscriber::layer::Layered< - Option>, - tracing_subscriber::Registry, ->; -type Layer2 = tracing_subscriber::layer::Layered>, Layer1>; -type Layer3 = tracing_subscriber::layer::Layered>, Layer2>; -type Layer4 = tracing_subscriber::layer::Layered>, Layer3>; -pub(crate) type InnerSubscriber = - tracing_subscriber::layer::Layered>, Layer4>; - -pub(crate) struct ReloadHandle( - pub(crate) tracing_subscriber::reload::Handle, -); - -impl ReloadHandle { - fn new( - filter: tracing_subscriber::EnvFilter, - ) -> ( - tracing_subscriber::reload::Layer, - Self, - ) { - let (layer, handle) = tracing_subscriber::reload::Layer::new(filter); - (layer, Self(handle)) - } +#[cfg(any(feature = "file", feature = "custom-async", feature = "native-async"))] +use crate::writer; - fn modify(&self, f: impl FnOnce(&mut tracing_subscriber::EnvFilter)) -> crate::Result<()> { - self.0.modify(f)?; - Ok(()) - } -} +pub(crate) type BoxedLayer = Box + Send + Sync>; +pub(crate) type InnerSubscriber = Layered, Registry>; +pub(crate) type ReloadHandle = + tracing_subscriber::reload::Handle; + +#[allow(clippy::single_call_fn)] +fn detect_color_depth(target: &WriterTarget) -> ColorDepth { + use supports_color::Stream; + let stream = match *target { + WriterTarget::Stdout => Stream::Stdout, + WriterTarget::Stderr => Stream::Stderr, + #[cfg(feature = "file")] + WriterTarget::File { .. } => return ColorDepth::NoColor, + #[cfg(any(feature = "custom-async", feature = "native-async"))] + WriterTarget::AsyncStdout(_) => Stream::Stdout, + #[cfg(any(feature = "custom-async", feature = "native-async"))] + WriterTarget::AsyncStderr(_) => Stream::Stderr, + }; -impl From