diff --git a/Cargo.lock b/Cargo.lock index 9ec53ac9..75b9e262 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "bpaf" version = "0.9.12" @@ -75,7 +81,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets", + "windows-targets 0.52.5", ] [[package]] @@ -84,12 +90,53 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "serde", +] + +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -109,9 +156,14 @@ dependencies = [ "anyhow", "bpaf", "chrono", + "deranged", + "directories", "indexmap", "regex", "rgb", + "serde", + "serde_json", + "serde_path_to_error", "shell-words", "strum", "tracing", @@ -150,6 +202,7 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] @@ -158,6 +211,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "js-sys" version = "0.3.69" @@ -179,6 +238,16 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "log" version = "0.4.21" @@ -216,6 +285,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "overload" version = "0.1.1" @@ -252,6 +327,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.5" @@ -293,6 +379,53 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -356,6 +489,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -459,6 +612,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -541,7 +700,31 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -550,28 +733,46 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.5" @@ -584,24 +785,48 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.5" diff --git a/Cargo.toml b/Cargo.toml index daa07c16..7e6afec7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,15 @@ ansi_colours = { version = "1.2.2", default-features = false } anyhow = { version = "1.0.86", default-features = false } bpaf = { version = "0.9.12", default-features = false } chrono = { version = "0.4.38", default-features = false } +deranged = { version = "0.3.11", default-features = false } derive_more = { version = "1.0.0-beta.6", default-features = false } +directories = { version = "5.0.1", default-features = false } indexmap = { version = "2.2.6", default-features = false } regex = { version = "1.10.5", default-features = false } rgb = { version = "0.8.37", default-features = false } +serde = { version = "1.0.203", default-features = false } +serde_json = { version = "1.0.118", default-features = false } +serde_path_to_error = { version = "0.1.16", default-features = false } shell-words = { version = "1.1.0", default-features = false } strum = { version = "0.26.3", default-features = false } tracing = { version = "0.1.40", default-features = false } diff --git a/crates/hyfetch/Cargo.toml b/crates/hyfetch/Cargo.toml index 61f854fd..da729b80 100644 --- a/crates/hyfetch/Cargo.toml +++ b/crates/hyfetch/Cargo.toml @@ -14,10 +14,15 @@ default-run = "hyfetch" anyhow = { workspace = true, features = ["std"] } bpaf = { workspace = true, features = [] } chrono = { workspace = true, features = ["clock", "std"] } +deranged = { workspace = true, features = ["serde", "std"] } # derive_more = { workspace = true, features = ["std"] } -indexmap = { workspace = true, features = ["std"] } +directories = { workspace = true, features = [] } +indexmap = { workspace = true, features = ["serde", "std"] } regex = { workspace = true, features = ["perf", "std", "unicode"] } rgb = { workspace = true, features = [] } +serde = { workspace = true, features = ["derive", "std"] } +serde_json = { workspace = true, features = ["std"] } +serde_path_to_error = { workspace = true, features = [] } shell-words = { workspace = true, features = ["std"] } strum = { workspace = true, features = ["derive", "std"] } tracing = { workspace = true, features = ["attributes", "std"] } diff --git a/crates/hyfetch/src/bin/hyfetch.rs b/crates/hyfetch/src/bin/hyfetch.rs index 951c4890..e9f81820 100644 --- a/crates/hyfetch/src/bin/hyfetch.rs +++ b/crates/hyfetch/src/bin/hyfetch.rs @@ -1,13 +1,18 @@ -use std::io::{self, IsTerminal}; +use std::fmt; +use std::fs::{self, File}; +use std::io::{self, ErrorKind, IsTerminal, Read}; +use std::path::Path; use anyhow::{Context, Result}; use chrono::Datelike; +use directories::ProjectDirs; use hyfetch::cli_options::options; +use hyfetch::models::Config; use hyfetch::neofetch_util::get_distro_ascii; use tracing::debug; fn main() -> Result<()> { - let options = options().fallback_to_usage().run(); + let options = options().run(); init_tracing_subsriber(options.debug).context("Failed to init tracing subscriber")?; @@ -25,25 +30,26 @@ fn main() -> Result<()> { // TODO - // TODO - // let config = if options.config { - // create_config() - // } else { - // check_config(options.config_file) - // }; + let config = if options.config { + create_config(options.config_file).context("Failed to create config")? + } else if let Some(config) = + read_config(&options.config_file).context("Failed to read config")? + { + config + } else { + create_config(options.config_file).context("Failed to create config")? + }; let now = chrono::Local::now(); - let show_pride_month = options.june - || now.month() == 6 - // TODO - // && !config.pride_month_shown.contains(now.year()) - // && !june_path.is_file() - && io::stdout().is_terminal(); + let cache_path = ProjectDirs::from("", "", "hyfetch") + .context("Failed to get base dirs")? + .cache_dir() + .to_owned(); + let june_path = cache_path.join(format!("animation-displayed-{}", now.year())); + let show_pride_month = + options.june || now.month() == 6 && !june_path.is_file() && io::stdout().is_terminal(); - if show_pride_month - // TODO - // && !config.pride_month_disable - { + if show_pride_month && !config.pride_month_disable { // TODO // pride_month.start_animation(); println!(); @@ -51,7 +57,11 @@ fn main() -> Result<()> { println!("(You can always view the animation again with `hyfetch --june`)"); println!(); - // TODO + if !june_path.is_file() { + fs::create_dir_all(cache_path).context("Failed to create cache dir")?; + File::create(&june_path) + .with_context(|| format!("Failed to create file {june_path:?}"))?; + } } // TODO @@ -59,6 +69,51 @@ fn main() -> Result<()> { Ok(()) } +/// Reads config from file. +/// +/// Returns `None` if the config file does not exist. +#[tracing::instrument(level = "debug")] +fn read_config

(path: P) -> Result> +where + P: AsRef + fmt::Debug, +{ + let path = path.as_ref(); + + let mut file = match File::open(path) { + Ok(file) => file, + Err(err) if err.kind() == ErrorKind::NotFound => { + return Ok(None); + }, + Err(err) => { + return Err(err).with_context(|| format!("Failed to open {path:?}")); + }, + }; + + let mut buf = String::new(); + + file.read_to_string(&mut buf) + .with_context(|| format!("Failed to read {path:?}"))?; + + let deserializer = &mut serde_json::Deserializer::from_str(&buf); + let config: Config = serde_path_to_error::deserialize(deserializer) + .with_context(|| format!("Failed to parse {path:?}"))?; + + debug!(?config, "read config"); + + Ok(Some(config)) +} + +/// Creates config interactively. +/// +/// The config is automatically stored to file. +#[tracing::instrument(level = "debug")] +fn create_config

(path: P) -> Result +where + P: AsRef + fmt::Debug, +{ + todo!() +} + fn init_tracing_subsriber(debug: bool) -> Result<()> { use std::env; use std::str::FromStr; diff --git a/crates/hyfetch/src/cli_options.rs b/crates/hyfetch/src/cli_options.rs index 71c8e9a7..9a170c86 100644 --- a/crates/hyfetch/src/cli_options.rs +++ b/crates/hyfetch/src/cli_options.rs @@ -5,6 +5,7 @@ use anyhow::Context; #[cfg(feature = "autocomplete")] use bpaf::ShellComp; use bpaf::{construct, long, OptionParser, Parser}; +use directories::BaseDirs; use strum::VariantNames; use crate::presets::Preset; @@ -13,11 +14,11 @@ use crate::types::{AnsiMode, Backend}; #[derive(Clone, Debug)] pub struct Options { pub config: bool, - pub config_file: Option, + pub config_file: PathBuf, pub preset: Option, pub mode: Option, pub backend: Option, - pub backend_args: Vec, + pub args: Vec, pub colors_scale: Option, pub colors_set_lightness: Option, pub colors_use_overlay: bool, @@ -37,7 +38,16 @@ pub fn options() -> OptionParser { .argument("CONFIG_FILE"); #[cfg(feature = "autocomplete")] let config_file = config_file.complete_shell(ShellComp::Nothing); - let config_file = config_file.optional(); + let config_file = config_file + .fallback_with(|| { + Ok::<_, anyhow::Error>( + BaseDirs::new() + .context("Failed to get base dirs")? + .config_dir() + .join("hyfetch.json"), + ) + }) + .debug_fallback(); let preset = long("preset") .short('p') .help(&*format!( @@ -49,7 +59,11 @@ PRESET={{{}}}", #[cfg(feature = "autocomplete")] let preset = preset.complete(complete_preset); let preset = preset - .parse(|s| Preset::from_str(&s).with_context(|| format!("Failed to parse preset `{s}`"))) + .parse(|s| { + Preset::from_str(&s).with_context(|| { + format!("PRESET should be one of {{{}}}", Preset::VARIANTS.join(",")) + }) + }) .optional(); let mode = long("mode") .short('m') @@ -62,7 +76,11 @@ MODE={{{}}}", #[cfg(feature = "autocomplete")] let mode = mode.complete(complete_mode); let mode = mode - .parse(|s| AnsiMode::from_str(&s).with_context(|| format!("Failed to parse mode `{s}`"))) + .parse(|s| { + AnsiMode::from_str(&s).with_context(|| { + format!("MODE should be one of {{{}}}", AnsiMode::VARIANTS.join(",")) + }) + }) .optional(); let backend = long("backend") .short('b') @@ -75,12 +93,19 @@ BACKEND={{{}}}", #[cfg(feature = "autocomplete")] let backend = backend.complete(complete_backend); let backend = backend - .parse(|s| Backend::from_str(&s).with_context(|| format!("Failed to parse backend `{s}`"))) + .parse(|s| { + Backend::from_str(&s).with_context(|| { + format!( + "BACKEND should be one of {{{}}}", + Backend::VARIANTS.join(",") + ) + }) + }) .optional(); - let backend_args = long("args") + let args = long("args") .help("Additional arguments pass-through to backend") .argument::("ARGS") - .parse(|s| shell_words::split(&s).context("Failed to split args for shell")) + .parse(|s| shell_words::split(&s).context("ARGS should be valid command-line arguments")) .fallback(vec![]); let colors_scale = long("c-scale") .help("Lighten colors by a multiplier") @@ -125,7 +150,7 @@ BACKEND={{{}}}", preset, mode, backend, - backend_args, + args, colors_scale, colors_set_lightness, colors_use_overlay, diff --git a/crates/hyfetch/src/color_util.rs b/crates/hyfetch/src/color_util.rs index 76ca649b..9ef2cd0e 100644 --- a/crates/hyfetch/src/color_util.rs +++ b/crates/hyfetch/src/color_util.rs @@ -1,5 +1,22 @@ use anyhow::{anyhow, Context, Result}; +use deranged::RangedU8; use rgb::RGB8; +use serde::{Deserialize, Serialize}; + +/// An indexed color where the color palette is the set of colors used in +/// neofetch ascii art. +/// +/// The range of valid values as supported in neofetch is `1`-`6`. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)] +pub struct NeofetchAsciiIndexedColor(RangedU8<1, 6>); + +/// An indexed color where the color palette is the set of unique colors in a +/// preset. +/// +/// The range of valid values depends on the number of unique colors in a +/// certain preset. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)] +pub struct PresetIndexedColor(usize); pub trait FromHex { /// Creates color from hex code. diff --git a/crates/hyfetch/src/lib.rs b/crates/hyfetch/src/lib.rs index 6a1fcfb8..7aa0943f 100644 --- a/crates/hyfetch/src/lib.rs +++ b/crates/hyfetch/src/lib.rs @@ -1,6 +1,7 @@ pub mod cli_options; pub mod color_util; pub mod distros; +pub mod models; pub mod neofetch_util; pub mod presets; pub mod types; diff --git a/crates/hyfetch/src/models.rs b/crates/hyfetch/src/models.rs new file mode 100644 index 00000000..907a9c3a --- /dev/null +++ b/crates/hyfetch/src/models.rs @@ -0,0 +1,71 @@ +use serde::{Deserialize, Serialize}; + +use crate::neofetch_util::ColorAlignment; +use crate::presets::Preset; +use crate::types::{AnsiMode, Backend, LightDark}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Config { + pub preset: Preset, + pub mode: AnsiMode, + pub light_dark: LightDark, + pub lightness: Option, + pub color_align: ColorAlignment, + pub backend: Backend, + #[serde(with = "self::args_serde_with")] + pub args: Vec, + pub distro: Option, + pub pride_month_disable: bool, +} + +mod args_serde_with { + use std::fmt; + + use serde::de::{self, value, Deserialize, Deserializer, SeqAccess, Visitor}; + use serde::ser::Serializer; + + pub(super) fn serialize(value: &Vec, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&shell_words::join(value)) + } + + pub(super) fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct StringOrVec; + + impl<'de> Visitor<'de> for StringOrVec { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string or list of strings") + } + + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(vec![]) + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + shell_words::split(s).map_err(de::Error::custom) + } + + fn visit_seq(self, seq: S) -> Result + where + S: SeqAccess<'de>, + { + Deserialize::deserialize(value::SeqAccessDeserializer::new(seq)) + } + } + + deserializer.deserialize_any(StringOrVec) + } +} diff --git a/crates/hyfetch/src/neofetch_util.rs b/crates/hyfetch/src/neofetch_util.rs index d0f341d2..10f17759 100644 --- a/crates/hyfetch/src/neofetch_util.rs +++ b/crates/hyfetch/src/neofetch_util.rs @@ -4,14 +4,35 @@ use std::ffi::OsStr; use std::os::unix::process::ExitStatusExt as _; use std::path::{Path, PathBuf}; use std::process::Command; +use std::sync::OnceLock; use std::{env, fmt}; use anyhow::{anyhow, Context, Result}; +use indexmap::IndexMap; use regex::Regex; +use serde::{Deserialize, Serialize}; use tracing::debug; +use crate::color_util::{NeofetchAsciiIndexedColor, PresetIndexedColor}; use crate::distros::Distro; +static NEOFETCH_COLOR_RE: OnceLock = OnceLock::new(); + +#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase", tag = "mode")] +pub enum ColorAlignment { + Horizontal { + fore_back: Option<(NeofetchAsciiIndexedColor, NeofetchAsciiIndexedColor)>, + }, + Vertical { + fore_back: Option<(NeofetchAsciiIndexedColor, NeofetchAsciiIndexedColor)>, + }, + Custom { + #[serde(rename = "custom_colors")] + colors: IndexMap, + }, +} + /// Gets the absolute path of the neofetch command. pub fn get_command_path() -> Result { if let Ok(workspace_dir) = env::var("CARGO_WORKSPACE_DIR") { @@ -72,13 +93,16 @@ where } /// Gets distro ascii width and height, ignoring color code. -pub fn ascii_size(asc: S, neofetch_color_re: &Regex) -> (u8, u8) +pub fn ascii_size(asc: S) -> (u8, u8) where S: AsRef, { let asc = asc.as_ref(); - let Some(width) = neofetch_color_re + let Some(width) = NEOFETCH_COLOR_RE + .get_or_init(|| { + Regex::new(r"\$\{c[0-9]\}").expect("neofetch color regex should not be invalid") + }) .replace_all(asc, "") .split('\n') .map(|line| line.len()) @@ -98,14 +122,11 @@ where { let asc = asc.as_ref(); - let neofetch_color_re = - Regex::new(r"\$\{c[0-9]\}").expect("neofetch color regex should not be invalid"); + let (w, _) = ascii_size(asc); - let (w, _) = ascii_size(asc, &neofetch_color_re); - - let mut buf = "".to_owned(); + let mut buf = String::new(); for line in asc.split('\n') { - let (line_w, _) = ascii_size(line, &neofetch_color_re); + let (line_w, _) = ascii_size(line); let pad = " ".repeat((w - line_w) as usize); buf.push_str(&format!("{line}{pad}\n")) } diff --git a/crates/hyfetch/src/presets.rs b/crates/hyfetch/src/presets.rs index 20c61edc..4e242f7b 100644 --- a/crates/hyfetch/src/presets.rs +++ b/crates/hyfetch/src/presets.rs @@ -3,11 +3,13 @@ use std::iter; use anyhow::{anyhow, Context, Result}; use indexmap::IndexSet; use rgb::RGB8; +use serde::{Deserialize, Serialize}; use strum::{EnumString, VariantNames}; use crate::color_util::FromHex; -#[derive(Clone, Hash, Debug, EnumString, VariantNames)] +#[derive(Clone, Hash, Debug, Deserialize, EnumString, Serialize, VariantNames)] +#[serde(rename_all = "kebab-case")] #[strum(serialize_all = "kebab-case")] pub enum Preset { Abrosexual, @@ -44,13 +46,16 @@ pub enum Preset { Genderfaun, Genderfluid, Genderflux, + #[serde(rename = "gendernonconforming1")] #[strum(serialize = "gendernonconforming1")] GenderNonconforming1, + #[serde(rename = "gendernonconforming2")] #[strum(serialize = "gendernonconforming2")] GenderNonconforming2, Gendervoid, Girlflux, Greygender, + #[serde(alias = "biromantic2")] Greysexual, Gynesexual, Intergender, @@ -79,7 +84,7 @@ pub enum Preset { #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct ColorProfile { - colors: Vec, + pub colors: Vec, } impl Preset { @@ -409,10 +414,6 @@ impl ColorProfile { Ok(Self::new(weighted_colors)) } - pub fn colors(&self) -> &[RGB8] { - &self.colors - } - /// Creates another color profile with only the unique colors. pub fn unique_colors(&self) -> Self { let unique_colors = self.colors.iter().collect::>(); diff --git a/crates/hyfetch/src/types.rs b/crates/hyfetch/src/types.rs index 263b0d0d..49a40b73 100644 --- a/crates/hyfetch/src/types.rs +++ b/crates/hyfetch/src/types.rs @@ -1,14 +1,25 @@ +use serde::{Deserialize, Serialize}; use strum::{EnumString, VariantNames}; -#[derive(Clone, Eq, PartialEq, Hash, Debug, EnumString, VariantNames)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize, EnumString, Serialize, VariantNames)] +#[serde(rename_all = "lowercase")] #[strum(serialize_all = "lowercase")] pub enum AnsiMode { + #[serde(rename = "8bit")] #[strum(serialize = "8bit")] Ansi256, Rgb, } -#[derive(Clone, Eq, PartialEq, Hash, Debug, EnumString, VariantNames)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum LightDark { + Light, + Dark, +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize, EnumString, Serialize, VariantNames)] +#[serde(rename_all = "kebab-case")] #[strum(serialize_all = "kebab-case")] pub enum Backend { Qwqfetch,