From 3cbc4aec8a855e5fd07554d501b7e3289b15b61a Mon Sep 17 00:00:00 2001 From: Gabriel Moreno Date: Sat, 3 Jan 2026 14:06:25 -0400 Subject: [PATCH 1/2] feat: implement `config.toml` segment loading --- Cargo.lock | 62 ++++++++++++++ Cargo.toml | 2 + src/macfetch/utils/config.rs | 151 +++++++++++++++++++++++++++++++++++ src/macfetch/utils/mod.rs | 1 + src/main.rs | 32 +------- 5 files changed, 220 insertions(+), 28 deletions(-) create mode 100644 src/macfetch/utils/config.rs diff --git a/Cargo.lock b/Cargo.lock index e007a63..c6fd9cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -556,9 +556,11 @@ dependencies = [ "iron-oxide", "libc", "os-version", + "serde", "sys-info", "sysctl", "system-info", + "toml", "users", ] @@ -739,6 +741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -761,6 +764,15 @@ dependencies = [ "syn 2.0.112", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "shlex" version = "1.3.0" @@ -906,6 +918,47 @@ dependencies = [ "syn 2.0.112", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "typenum" version = "1.19.0" @@ -1124,3 +1177,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index d623ea7..c22c760 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,5 @@ system-info = "0.1.2" core-graphics = "0.22.3" iron-oxide = "0.1.1" cached = "0.42.0" +serde = { version = "1.0", features = ["derive"] } +toml = "0.8" diff --git a/src/macfetch/utils/config.rs b/src/macfetch/utils/config.rs new file mode 100644 index 0000000..1220d16 --- /dev/null +++ b/src/macfetch/utils/config.rs @@ -0,0 +1,151 @@ +use colored::ColoredString; +use serde::Deserialize; +use std::collections::HashMap; +use std::env; +use std::fs; +use std::path::PathBuf; + +use crate::macfetch::segments; + +#[derive(Deserialize, Default)] +pub struct GeneralConfig { + pub segments: Option>, +} + +#[derive(Deserialize, Default)] +pub struct Config { + pub general: Option, +} + +fn config_path() -> Option { + env::var("HOME") + .ok() + .map(|home| PathBuf::from(home).join(".config/macfetch/config.toml")) +} + +fn load_config() -> Option { + let path = config_path()?; + let content = fs::read_to_string(&path).ok()?; + + match toml::from_str(&content) { + Ok(config) => Some(config), + Err(e) => { + eprintln!("Warning: Failed to parse config file: {}", e); + None + } + } +} + +fn segment_registry() -> HashMap<&'static str, fn() -> ColoredString> { + let mut map: HashMap<&'static str, fn() -> ColoredString> = HashMap::new(); + + map.insert("machine", segments::machine); + map.insert("separator", segments::separator); + map.insert("os", segments::os); + map.insert("host", segments::host); + map.insert("kernel", segments::kernel); + map.insert("uptime", segments::uptime); + map.insert("packages", segments::packages); + map.insert("shell", segments::shell); + map.insert("resolution", segments::resolution); + map.insert("de", segments::de); + map.insert("wm", segments::wm); + map.insert("terminal", segments::terminal); + map.insert("cpu", segments::cpu); + map.insert("gpu", segments::gpu); + map.insert("battery", segments::battery); + map.insert("memory", segments::memory); + map.insert("empty", segments::empty); + map.insert("dark_colors", segments::dark_colors); + map.insert("light_colors", segments::light_colors); + + map +} + +pub fn default_segments() -> Vec ColoredString> { + vec![ + segments::machine, + segments::separator, + segments::os, + segments::host, + segments::kernel, + segments::uptime, + segments::packages, + segments::shell, + segments::resolution, + segments::de, + segments::wm, + segments::terminal, + segments::cpu, + segments::gpu, + segments::battery, + segments::memory, + segments::empty, + segments::dark_colors, + segments::light_colors, + ] +} + +fn resolve_segments(names: &[String]) -> Vec ColoredString> { + let registry = segment_registry(); + let mut segments = Vec::new(); + + for name in names { + match registry.get(name.as_str()) { + Some(&func) => segments.push(func), + None => eprintln!("Warning: Unknown segment '{}', skipping", name), + } + } + + segments +} + +pub fn get_segments() -> Vec ColoredString> { + match load_config() { + Some(config) => match config.general { + Some(general) => match general.segments { + Some(names) if !names.is_empty() => resolve_segments(&names), + _ => default_segments(), + }, + None => default_segments(), + }, + None => default_segments(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_resolve_segments_valid() { + let names = vec!["os".to_string(), "cpu".to_string()]; + let segments = resolve_segments(&names); + assert_eq!(segments.len(), 2); + } + + #[test] + fn test_resolve_segments_invalid_skipped() { + let names = vec![ + "os".to_string(), + "invalid_segment".to_string(), + "cpu".to_string(), + ]; + let segments = resolve_segments(&names); + assert_eq!(segments.len(), 2); + } + + #[test] + fn test_default_segments_count() { + let segments = default_segments(); + assert_eq!(segments.len(), 19); + } + + #[test] + fn test_segment_registry_has_all_segments() { + let registry = segment_registry(); + assert_eq!(registry.len(), 19); + assert!(registry.contains_key("machine")); + assert!(registry.contains_key("light_colors")); + } +} diff --git a/src/macfetch/utils/mod.rs b/src/macfetch/utils/mod.rs index b3dc944..ca37df0 100644 --- a/src/macfetch/utils/mod.rs +++ b/src/macfetch/utils/mod.rs @@ -1,4 +1,5 @@ pub mod cache; pub mod cli; +pub mod config; pub mod ctl; pub mod host; diff --git a/src/main.rs b/src/main.rs index da50f09..890f8b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,36 +1,12 @@ mod macfetch; use crate::macfetch::ascii; -use crate::macfetch::segments::{ - battery, cpu, dark_colors, de, empty, gpu, host, kernel, light_colors, machine, memory, os, - packages, resolution, separator, shell, terminal, uptime, wm, -}; +use crate::macfetch::utils::config; fn main() { macfetch::utils::cli::handle_cli_args(); - macfetch::render( - ascii::constants::DARWIN, - vec![ - machine, - separator, - os, - host, - kernel, - uptime, - packages, - shell, - resolution, - de, - wm, - terminal, - cpu, - gpu, - battery, - memory, - empty, - dark_colors, - light_colors, - ], - ); + let segments = config::get_segments(); + + macfetch::render(ascii::constants::DARWIN, segments); } From e74542a5c726ad99df8b72e9462e66de852e4a5e Mon Sep 17 00:00:00 2001 From: Gabriel Moreno Date: Sat, 3 Jan 2026 14:08:10 -0400 Subject: [PATCH 2/2] docs: add config file instructions --- CLAUDE.md | 8 +++++--- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b4749a5..3004866 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,8 @@ Macfetch is a macOS-only Neofetch alternative written in Rust. It displays syste - **`src/macfetch/mod.rs`**: Contains the `render()` function that pairs ASCII logo lines with segment output lines. - **`src/macfetch/segments/mod.rs`**: Individual segment functions (os, cpu, memory, etc.) that return `ColoredString`. Each segment fetches system data and formats it with `titled_segment()`. - **`src/macfetch/ascii/mod.rs`**: ASCII art generation with `generate_logo()` that returns colored logo lines. -- **`src/macfetch/utils/`**: Helper modules for CLI handling, sysctl queries, host info, and caching. +- **`src/macfetch/utils/`**: Helper modules for CLI handling, sysctl queries, host info, caching, and configuration. +- **`src/macfetch/utils/config.rs`**: TOML configuration loading. Maps segment names to functions and builds the segment list from `~/.config/macfetch/config.toml`. ### Key Patterns @@ -45,5 +46,6 @@ GitHub Actions runs on every push/PR to `main`: 1. Create a new function in `src/macfetch/segments/mod.rs` returning `ColoredString` 2. Use `titled_segment(name, value)` for consistent formatting -3. Add the function to the segments vector in `main.rs` -4. Export the function in the `use` statement in `main.rs` +3. Register the segment in `src/macfetch/utils/config.rs`: + - Add it to `segment_registry()` HashMap + - Add it to `default_segments()` vector (if it should appear by default) diff --git a/README.md b/README.md index 906de9d..12933ce 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,51 @@ cargo build --release # for the optimized release target Your binary should be available under `target/x86_64-apple-darwin/`and withing the folder of the build target you chose (either `debug` or `release`). +## Configuration + +Macfetch can be customized via a TOML configuration file located at `~/.config/macfetch/config.toml`. + +### Customizing Segments + +You can control which segments are displayed and in what order by specifying a `segments` list under `[general]`: + +```toml +[general] +segments = [ + "machine", + "separator", + "os", + "cpu", + "memory", +] +``` + +Only the segments listed will be rendered, in the order specified. If no config file exists, Macfetch uses the default segment order. + +### Available Segments + +| Segment | Description | +|---------|-------------| +| `machine` | Username and hostname | +| `separator` | Dashed separator line | +| `os` | macOS version | +| `host` | Hardware model | +| `kernel` | Kernel version | +| `uptime` | System uptime | +| `packages` | Homebrew package count | +| `shell` | Current shell | +| `resolution` | Display resolution | +| `de` | Desktop environment (Aqua) | +| `wm` | Window manager (Quartz Compositor) | +| `terminal` | Terminal emulator | +| `cpu` | Processor info | +| `gpu` | Graphics card | +| `battery` | Battery percentage | +| `memory` | RAM usage | +| `empty` | Blank line | +| `dark_colors` | Dark color palette | +| `light_colors` | Light color palette | + ## License Licensed under the [MIT](https://opensource.org/licenses/MIT) license.