Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
62 changes: 62 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
151 changes: 151 additions & 0 deletions src/macfetch/utils/config.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<String>>,
}

#[derive(Deserialize, Default)]
pub struct Config {
pub general: Option<GeneralConfig>,
}

fn config_path() -> Option<PathBuf> {
env::var("HOME")
.ok()
.map(|home| PathBuf::from(home).join(".config/macfetch/config.toml"))
}

fn load_config() -> Option<Config> {
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<fn() -> 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<fn() -> 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<fn() -> 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"));
}
}
1 change: 1 addition & 0 deletions src/macfetch/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod cache;
pub mod cli;
pub mod config;
pub mod ctl;
pub mod host;
32 changes: 4 additions & 28 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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);
}