Skip to content

Commit 515e4b1

Browse files
authored
feat: add persistent color scheme support with 5 built-in palettes (#4)
* feat: add persistent color scheme support with 5 built-in palettes * chore: run cargo build
1 parent c733eec commit 515e4b1

File tree

8 files changed

+496
-36
lines changed

8 files changed

+496
-36
lines changed

CHANGELOG.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.1.0] - 2026-03-09
11+
12+
### Added
13+
14+
- Five built-in color schemes for table output, selectable and persisted per user:
15+
- `default` - GitHub-style SemVer severity (`#D73A49` / `#0366D6` / `#28A745`)
16+
- `okabe-ito` - Color-blind safe Okabe–Ito palette (`#E69F00` / `#0072B2` / `#009E73`)
17+
- `traffic-light` - Classic red/yellow/green (`#E74C3C` / `#F1C40F` / `#2ECC71`)
18+
- `severity` - Monitoring/observability style (`#8E44AD` / `#3498DB` / `#95A5A6`)
19+
- `high-contrast` - Maximum distinction, color-blind safe (`#CC79A7` / `#0072B2` / `#F0E442`)
20+
- All colors rendered with true-color (24-bit) escape codes for exact hex fidelity
21+
- `--set-color-scheme` flag: run without a value to preview all schemes visually, or pass a scheme name to save it permanently
22+
- Color scheme preference persisted to `~/.config/pycu/config.toml` (Linux/macOS) or `%APPDATA%\pycu\config.toml` (Windows)
23+
- First-run interactive prompt to choose a color scheme on initial install
24+
- `--uninstall` now also removes the `pycu/` config directory
25+
1026
## [1.0.0] - 2026-03-08
1127

1228
### Added
@@ -26,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2642
- Progress bar during PyPI lookups
2743
- Install scripts for Linux/macOS (`install.sh`) and Windows (`install.ps1`)
2844

29-
[Unreleased]: https://github.com/Logic-py/python-check-updates/compare/1.0.0...HEAD
45+
[Unreleased]: https://github.com/Logic-py/python-check-updates/compare/1.1.0...HEAD
3046

47+
[1.1.0]: https://github.com/Logic-py/python-check-updates/compare/1.0.0...1.1.0
3148
[1.0.0]: https://github.com/Logic-py/python-check-updates/releases/tag/1.0.0

Cargo.lock

Lines changed: 60 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pycu"
3-
version = "1.0.1"
3+
version = "1.1.0"
44
edition = "2024"
55
rust-version = "1.85"
66
description = "Check your Python dependencies for newer versions on PyPI"
@@ -18,6 +18,7 @@ path = "src/main.rs"
1818

1919
[dependencies]
2020
anyhow = "1"
21+
dirs = "6"
2122
clap = { version = "4", features = ["derive"] }
2223
flate2 = "1"
2324
futures = "0.3"
@@ -34,4 +35,5 @@ toml = "1"
3435
zip = { version = "8.2.0", default-features = false, features = ["deflate"] }
3536

3637
[dev-dependencies]
38+
tempfile = "3"
3739
wiremock = "0.6"

src/cli.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clap::{Parser, ValueEnum};
2+
use serde::{Deserialize, Serialize};
23
use std::path::PathBuf;
34

45
#[derive(Parser)]
@@ -28,6 +29,12 @@ pub struct Cli {
2829
#[arg(short = 't', long, value_name = "LEVEL", default_value = "latest")]
2930
pub target: TargetLevel,
3031

32+
/// Preview all color schemes, or set one persistently.
33+
/// Without a value: show all schemes visually.
34+
/// With a value: save that scheme and exit.
35+
#[arg(long, value_name = "SCHEME", num_args = 0..=1, default_missing_value = "")]
36+
pub set_color_scheme: Option<String>,
37+
3138
/// Update pycu itself to the latest release
3239
#[arg(long)]
3340
pub self_update: bool,
@@ -49,9 +56,49 @@ pub enum TargetLevel {
4956
Patch,
5057
}
5158

59+
#[derive(ValueEnum, Serialize, Deserialize, Clone, PartialEq, Debug)]
60+
#[serde(rename_all = "kebab-case")]
61+
pub enum ColorScheme {
62+
/// #D73A49 / #0366D6 / #28A745 - GitHub-style SemVer severity (default)
63+
Default,
64+
/// #E69F00 / #0072B2 / #009E73 - Okabe–Ito, color-blind safe
65+
OkabeIto,
66+
/// #E74C3C / #F1C40F / #2ECC71 - traffic-light (red/yellow/green)
67+
TrafficLight,
68+
/// #8E44AD / #3498DB / #95A5A6 - monitoring style (purple/blue/gray)
69+
Severity,
70+
/// #CC79A7 / #0072B2 / #F0E442 - maximum distinction, color-blind safe
71+
HighContrast,
72+
}
73+
5274
pub async fn run() -> anyhow::Result<()> {
5375
let cli = Cli::parse();
5476

77+
if let Some(raw) = cli.set_color_scheme {
78+
if raw.is_empty() {
79+
// --set-color-scheme used without a value: show the preview
80+
crate::output::table::print_color_scheme_preview();
81+
} else {
82+
// --set-color-scheme <SCHEME>: parse, save, confirm
83+
use clap::ValueEnum;
84+
let scheme = ColorScheme::from_str(&raw, true).map_err(|e| anyhow::anyhow!(
85+
"Unknown color scheme '{}'. {}\nRun `pycu --set-color-scheme` to see all options.",
86+
raw, e
87+
))?;
88+
let config = crate::config::Config {
89+
color_scheme: scheme.clone(),
90+
};
91+
crate::config::save(&config)?;
92+
let path = crate::config::config_path().unwrap_or_else(|| PathBuf::from("config.toml"));
93+
println!(
94+
"Color scheme set to '{}' and saved to {}.",
95+
raw,
96+
path.display()
97+
);
98+
}
99+
return Ok(());
100+
}
101+
55102
// Self-update is independent of any project file
56103
if cli.self_update {
57104
let client = crate::pypi::client::PypiClient::new()?.into_inner();
@@ -62,6 +109,12 @@ pub async fn run() -> anyhow::Result<()> {
62109
return crate::uninstall::run();
63110
}
64111

112+
// Load persisted config, running first-run setup if no config exists or is unreadable
113+
let config = match crate::config::load() {
114+
Ok(Some(cfg)) => cfg,
115+
Ok(None) | Err(_) => crate::config::first_run_setup()?,
116+
};
117+
65118
let file_path = match cli.file {
66119
Some(p) => p,
67120
None => resolve_default_file()?,
@@ -97,7 +150,7 @@ pub async fn run() -> anyhow::Result<()> {
97150
.collect();
98151

99152
if cli.upgrade {
100-
crate::output::table::print_table(&updates, false);
153+
crate::output::table::print_table(&updates, false, &config.color_scheme);
101154
let count = crate::upgrade::apply_upgrades(&file_path, &updates)?;
102155
if count > 0 {
103156
println!(
@@ -113,7 +166,7 @@ pub async fn run() -> anyhow::Result<()> {
113166
if cli.json {
114167
crate::output::json::print_json(&updates)?;
115168
} else {
116-
crate::output::table::print_table(&updates, true);
169+
crate::output::table::print_table(&updates, true, &config.color_scheme);
117170
}
118171

119172
Ok(())

0 commit comments

Comments
 (0)