Apply lightness to preset color profile
This commit is contained in:
parent
86e442b8a4
commit
b0737a33ba
11 changed files with 343 additions and 90 deletions
78
Cargo.lock
generated
78
Cargo.lock
generated
|
@ -32,6 +32,15 @@ version = "1.0.86"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "approx"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
@ -60,6 +69,12 @@ version = "3.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "by_address"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.99"
|
version = "1.0.99"
|
||||||
|
@ -99,6 +114,26 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more"
|
||||||
|
version = "1.0.0-beta.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more-impl"
|
||||||
|
version = "1.0.0-beta.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "directories"
|
name = "directories"
|
||||||
version = "5.0.1"
|
version = "5.0.1"
|
||||||
|
@ -126,6 +161,12 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fast-srgb8"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -157,15 +198,17 @@ dependencies = [
|
||||||
"bpaf",
|
"bpaf",
|
||||||
"chrono",
|
"chrono",
|
||||||
"deranged",
|
"deranged",
|
||||||
|
"derive_more",
|
||||||
"directories",
|
"directories",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"palette",
|
||||||
"regex",
|
"regex",
|
||||||
"rgb",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_path_to_error",
|
"serde_path_to_error",
|
||||||
"shell-words",
|
"shell-words",
|
||||||
"strum",
|
"strum",
|
||||||
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
|
@ -303,6 +346,29 @@ version = "4.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
|
checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "palette"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"fast-srgb8",
|
||||||
|
"palette_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "palette_derive"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30"
|
||||||
|
dependencies = [
|
||||||
|
"by_address",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -367,12 +433,6 @@ version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rgb"
|
|
||||||
version = "0.8.37"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.17"
|
version = "1.0.17"
|
||||||
|
@ -407,9 +467,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.118"
|
version = "1.0.120"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
|
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
|
|
@ -15,11 +15,13 @@ license = "MIT"
|
||||||
ansi_colours = { version = "1.2.2", default-features = false }
|
ansi_colours = { version = "1.2.2", default-features = false }
|
||||||
anyhow = { version = "1.0.86", default-features = false }
|
anyhow = { version = "1.0.86", default-features = false }
|
||||||
bpaf = { version = "0.9.12", default-features = false }
|
bpaf = { version = "0.9.12", default-features = false }
|
||||||
|
bytemuck = { version = "1.16.1", default-features = false }
|
||||||
chrono = { version = "0.4.38", default-features = false }
|
chrono = { version = "0.4.38", default-features = false }
|
||||||
deranged = { version = "0.3.11", default-features = false }
|
deranged = { version = "0.3.11", default-features = false }
|
||||||
derive_more = { version = "1.0.0-beta.6", default-features = false }
|
derive_more = { version = "1.0.0-beta.6", default-features = false }
|
||||||
directories = { version = "5.0.1", default-features = false }
|
directories = { version = "5.0.1", default-features = false }
|
||||||
indexmap = { version = "2.2.6", default-features = false }
|
indexmap = { version = "2.2.6", default-features = false }
|
||||||
|
palette = { version = "0.7.6", default-features = false }
|
||||||
regex = { version = "1.10.5", default-features = false }
|
regex = { version = "1.10.5", default-features = false }
|
||||||
rgb = { version = "0.8.37", default-features = false }
|
rgb = { version = "0.8.37", default-features = false }
|
||||||
serde = { version = "1.0.203", default-features = false }
|
serde = { version = "1.0.203", default-features = false }
|
||||||
|
@ -27,6 +29,7 @@ serde_json = { version = "1.0.118", default-features = false }
|
||||||
serde_path_to_error = { version = "0.1.16", default-features = false }
|
serde_path_to_error = { version = "0.1.16", default-features = false }
|
||||||
shell-words = { version = "1.1.0", default-features = false }
|
shell-words = { version = "1.1.0", default-features = false }
|
||||||
strum = { version = "0.26.3", default-features = false }
|
strum = { version = "0.26.3", default-features = false }
|
||||||
|
thiserror = { version = "1.0.61", default-features = false }
|
||||||
tracing = { version = "0.1.40", default-features = false }
|
tracing = { version = "0.1.40", default-features = false }
|
||||||
tracing-subscriber = { version = "0.3.18", default-features = false }
|
tracing-subscriber = { version = "0.3.18", default-features = false }
|
||||||
unicode-normalization = { version = "0.1.23", default-features = false }
|
unicode-normalization = { version = "0.1.23", default-features = false }
|
||||||
|
|
|
@ -13,18 +13,21 @@ default-run = "hyfetch"
|
||||||
# ansi_colours = { workspace = true, features = ["rgb"] }
|
# ansi_colours = { workspace = true, features = ["rgb"] }
|
||||||
anyhow = { workspace = true, features = ["std"] }
|
anyhow = { workspace = true, features = ["std"] }
|
||||||
bpaf = { workspace = true, features = [] }
|
bpaf = { workspace = true, features = [] }
|
||||||
|
# bytemuck = { workspace = true, features = [] }
|
||||||
chrono = { workspace = true, features = ["clock", "std"] }
|
chrono = { workspace = true, features = ["clock", "std"] }
|
||||||
deranged = { workspace = true, features = ["serde", "std"] }
|
deranged = { workspace = true, features = ["serde", "std"] }
|
||||||
# derive_more = { workspace = true, features = ["std"] }
|
derive_more = { workspace = true, features = ["from", "from_str", "into", "std"] }
|
||||||
directories = { workspace = true, features = [] }
|
directories = { workspace = true, features = [] }
|
||||||
indexmap = { workspace = true, features = ["serde", "std"] }
|
indexmap = { workspace = true, features = ["serde", "std"] }
|
||||||
|
palette = { workspace = true, features = ["std"] }
|
||||||
regex = { workspace = true, features = ["perf", "std", "unicode"] }
|
regex = { workspace = true, features = ["perf", "std", "unicode"] }
|
||||||
rgb = { workspace = true, features = [] }
|
# rgb = { workspace = true, features = [] }
|
||||||
serde = { workspace = true, features = ["derive", "std"] }
|
serde = { workspace = true, features = ["derive", "std"] }
|
||||||
serde_json = { workspace = true, features = ["std"] }
|
serde_json = { workspace = true, features = ["std"] }
|
||||||
serde_path_to_error = { workspace = true, features = [] }
|
serde_path_to_error = { workspace = true, features = [] }
|
||||||
shell-words = { workspace = true, features = ["std"] }
|
shell-words = { workspace = true, features = ["std"] }
|
||||||
strum = { workspace = true, features = ["derive", "std"] }
|
strum = { workspace = true, features = ["derive", "std"] }
|
||||||
|
thiserror = { workspace = true, features = [] }
|
||||||
tracing = { workspace = true, features = ["attributes", "std"] }
|
tracing = { workspace = true, features = ["attributes", "std"] }
|
||||||
tracing-subscriber = { workspace = true, features = ["ansi", "fmt", "smallvec", "std", "tracing-log"] }
|
tracing-subscriber = { workspace = true, features = ["ansi", "fmt", "smallvec", "std", "tracing-log"] }
|
||||||
|
|
||||||
|
|
|
@ -5,16 +5,17 @@ use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
use directories::ProjectDirs;
|
|
||||||
use hyfetch::cli_options::options;
|
use hyfetch::cli_options::options;
|
||||||
use hyfetch::models::Config;
|
use hyfetch::models::Config;
|
||||||
use hyfetch::neofetch_util::get_distro_ascii;
|
use hyfetch::neofetch_util::get_distro_ascii;
|
||||||
|
use hyfetch::presets::AssignLightness;
|
||||||
|
use hyfetch::utils::get_cache_path;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let options = options().run();
|
let options = options().run();
|
||||||
|
|
||||||
init_tracing_subsriber(options.debug).context("Failed to init tracing subscriber")?;
|
init_tracing_subsriber(options.debug).context("failed to init tracing subscriber")?;
|
||||||
|
|
||||||
debug!(?options, "CLI options");
|
debug!(?options, "CLI options");
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ fn main() -> Result<()> {
|
||||||
if options.test_print {
|
if options.test_print {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
get_distro_ascii(options.distro.as_ref()).context("Failed to get distro ascii")?
|
get_distro_ascii(options.distro.as_ref()).context("failed to get distro ascii")?
|
||||||
);
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -31,20 +32,18 @@ fn main() -> Result<()> {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
let config = if options.config {
|
let config = if options.config {
|
||||||
create_config(options.config_file).context("Failed to create config")?
|
create_config(options.config_file).context("failed to create config")?
|
||||||
} else if let Some(config) =
|
} else if let Some(config) =
|
||||||
read_config(&options.config_file).context("Failed to read config")?
|
read_config(&options.config_file).context("failed to read config")?
|
||||||
{
|
{
|
||||||
config
|
config
|
||||||
} else {
|
} else {
|
||||||
create_config(options.config_file).context("Failed to create config")?
|
create_config(options.config_file).context("failed to create config")?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if it's June (pride month)
|
||||||
let now = chrono::Local::now();
|
let now = chrono::Local::now();
|
||||||
let cache_path = ProjectDirs::from("", "", "hyfetch")
|
let cache_path = get_cache_path().context("failed to get cache path")?;
|
||||||
.context("Failed to get base dirs")?
|
|
||||||
.cache_dir()
|
|
||||||
.to_owned();
|
|
||||||
let june_path = cache_path.join(format!("animation-displayed-{}", now.year()));
|
let june_path = cache_path.join(format!("animation-displayed-{}", now.year()));
|
||||||
let show_pride_month =
|
let show_pride_month =
|
||||||
options.june || now.month() == 6 && !june_path.is_file() && io::stdout().is_terminal();
|
options.june || now.month() == 6 && !june_path.is_file() && io::stdout().is_terminal();
|
||||||
|
@ -58,14 +57,31 @@ fn main() -> Result<()> {
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
if !june_path.is_file() {
|
if !june_path.is_file() {
|
||||||
fs::create_dir_all(cache_path).context("Failed to create cache dir")?;
|
fs::create_dir_all(cache_path).context("failed to create cache dir")?;
|
||||||
File::create(&june_path)
|
File::create(&june_path)
|
||||||
.with_context(|| format!("Failed to create file {june_path:?}"))?;
|
.with_context(|| format!("failed to create file {june_path:?}"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
|
// Get preset
|
||||||
|
let preset = options.preset.unwrap_or(config.preset);
|
||||||
|
let color_profile = preset.color_profile();
|
||||||
|
debug!(?color_profile, "color profile");
|
||||||
|
|
||||||
|
// Lighten
|
||||||
|
let color_profile = if let Some(scale) = options.scale {
|
||||||
|
color_profile.lighten(scale)
|
||||||
|
} else if let Some(lightness) = options.lightness {
|
||||||
|
color_profile.with_lightness(AssignLightness::Replace(lightness))
|
||||||
|
} else {
|
||||||
|
color_profile.with_lightness_dl(config.lightness(), config.light_dark)
|
||||||
|
};
|
||||||
|
debug!(?color_profile, "lightened color profile");
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,18 +101,18 @@ where
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(err).with_context(|| format!("Failed to open {path:?}"));
|
return Err(err).with_context(|| format!("failed to open {path:?}"));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
|
||||||
file.read_to_string(&mut buf)
|
file.read_to_string(&mut buf)
|
||||||
.with_context(|| format!("Failed to read {path:?}"))?;
|
.with_context(|| format!("failed to read {path:?}"))?;
|
||||||
|
|
||||||
let deserializer = &mut serde_json::Deserializer::from_str(&buf);
|
let deserializer = &mut serde_json::Deserializer::from_str(&buf);
|
||||||
let config: Config = serde_path_to_error::deserialize(deserializer)
|
let config: Config = serde_path_to_error::deserialize(deserializer)
|
||||||
.with_context(|| format!("Failed to parse {path:?}"))?;
|
.with_context(|| format!("failed to parse {path:?}"))?;
|
||||||
|
|
||||||
debug!(?config, "read config");
|
debug!(?config, "read config");
|
||||||
|
|
||||||
|
@ -158,5 +174,5 @@ fn init_tracing_subsriber(debug: bool) -> Result<()> {
|
||||||
|
|
||||||
subscriber
|
subscriber
|
||||||
.try_init()
|
.try_init()
|
||||||
.context("Failed to set the global default subscriber")
|
.context("failed to set the global default subscriber")
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use bpaf::{construct, long, OptionParser, Parser};
|
||||||
use directories::BaseDirs;
|
use directories::BaseDirs;
|
||||||
use strum::VariantNames;
|
use strum::VariantNames;
|
||||||
|
|
||||||
|
use crate::color_util::Lightness;
|
||||||
use crate::presets::Preset;
|
use crate::presets::Preset;
|
||||||
use crate::types::{AnsiMode, Backend};
|
use crate::types::{AnsiMode, Backend};
|
||||||
|
|
||||||
|
@ -19,9 +20,9 @@ pub struct Options {
|
||||||
pub mode: Option<AnsiMode>,
|
pub mode: Option<AnsiMode>,
|
||||||
pub backend: Option<Backend>,
|
pub backend: Option<Backend>,
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
pub colors_scale: Option<f32>,
|
pub scale: Option<f32>,
|
||||||
pub colors_set_lightness: Option<f32>,
|
pub lightness: Option<Lightness>,
|
||||||
pub colors_use_overlay: bool,
|
pub overlay: bool,
|
||||||
pub june: bool,
|
pub june: bool,
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub distro: Option<String>,
|
pub distro: Option<String>,
|
||||||
|
@ -42,7 +43,7 @@ pub fn options() -> OptionParser<Options> {
|
||||||
.fallback_with(|| {
|
.fallback_with(|| {
|
||||||
Ok::<_, anyhow::Error>(
|
Ok::<_, anyhow::Error>(
|
||||||
BaseDirs::new()
|
BaseDirs::new()
|
||||||
.context("Failed to get base dirs")?
|
.context("failed to get base dirs")?
|
||||||
.config_dir()
|
.config_dir()
|
||||||
.join("hyfetch.json"),
|
.join("hyfetch.json"),
|
||||||
)
|
)
|
||||||
|
@ -107,15 +108,15 @@ BACKEND={{{}}}",
|
||||||
.argument::<String>("ARGS")
|
.argument::<String>("ARGS")
|
||||||
.parse(|s| shell_words::split(&s).context("ARGS should be valid command-line arguments"))
|
.parse(|s| shell_words::split(&s).context("ARGS should be valid command-line arguments"))
|
||||||
.fallback(vec![]);
|
.fallback(vec![]);
|
||||||
let colors_scale = long("c-scale")
|
let scale = long("c-scale")
|
||||||
.help("Lighten colors by a multiplier")
|
.help("Lighten colors by a multiplier")
|
||||||
.argument("SCALE")
|
.argument("SCALE")
|
||||||
.optional();
|
.optional();
|
||||||
let colors_set_lightness = long("c-set-l")
|
let lightness = long("c-set-l")
|
||||||
.help("Set lightness value of the colors")
|
.help("Set lightness value of the colors")
|
||||||
.argument("LIGHT")
|
.argument("LIGHTNESS")
|
||||||
.optional();
|
.optional();
|
||||||
let colors_use_overlay = long("c-overlay")
|
let overlay = long("c-overlay")
|
||||||
.help("Use experimental overlay color adjusting instead of HSL lightness")
|
.help("Use experimental overlay color adjusting instead of HSL lightness")
|
||||||
.switch();
|
.switch();
|
||||||
let june = long("june").help("Show pride month easter egg").switch();
|
let june = long("june").help("Show pride month easter egg").switch();
|
||||||
|
@ -151,9 +152,9 @@ BACKEND={{{}}}",
|
||||||
mode,
|
mode,
|
||||||
backend,
|
backend,
|
||||||
args,
|
args,
|
||||||
colors_scale,
|
scale,
|
||||||
colors_set_lightness,
|
lightness,
|
||||||
colors_use_overlay,
|
overlay,
|
||||||
june,
|
june,
|
||||||
debug,
|
debug,
|
||||||
distro,
|
distro,
|
||||||
|
|
|
@ -1,49 +1,113 @@
|
||||||
use anyhow::{anyhow, Context, Result};
|
use std::num::ParseFloatError;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
use deranged::RangedU8;
|
use deranged::RangedU8;
|
||||||
use rgb::RGB8;
|
use derive_more::{From, FromStr, Into};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Represents the lightness component in HSL.
|
||||||
|
///
|
||||||
|
/// The range of valid values is
|
||||||
|
/// `(`[`Lightness::MIN`]`..=`[`Lightness::MAX`]`)`.
|
||||||
|
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Deserialize, Into, Serialize)]
|
||||||
|
pub struct Lightness(f32);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum LightnessError {
|
||||||
|
#[error(
|
||||||
|
"invalid lightness {0}, expected value between {} and {}",
|
||||||
|
Lightness::MIN,
|
||||||
|
Lightness::MAX
|
||||||
|
)]
|
||||||
|
OutOfRange(f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ParseLightnessError {
|
||||||
|
#[error("invalid float")]
|
||||||
|
InvalidFloat(#[from] ParseFloatError),
|
||||||
|
#[error("invalid lightness")]
|
||||||
|
InvalidLightness(#[from] LightnessError),
|
||||||
|
}
|
||||||
|
|
||||||
/// An indexed color where the color palette is the set of colors used in
|
/// An indexed color where the color palette is the set of colors used in
|
||||||
/// neofetch ascii art.
|
/// neofetch ascii art.
|
||||||
///
|
///
|
||||||
/// The range of valid values as supported in neofetch is `1`-`6`.
|
/// The range of valid values as supported in neofetch is
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
|
/// `(`[`NeofetchAsciiIndexedColor::MIN`]`..
|
||||||
pub struct NeofetchAsciiIndexedColor(RangedU8<1, 6>);
|
/// =`[`NeofetchAsciiIndexedColor::MAX`]`)`.
|
||||||
|
#[derive(
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Ord,
|
||||||
|
PartialOrd,
|
||||||
|
Hash,
|
||||||
|
Debug,
|
||||||
|
Deserialize,
|
||||||
|
From,
|
||||||
|
FromStr,
|
||||||
|
Into,
|
||||||
|
Serialize,
|
||||||
|
)]
|
||||||
|
pub struct NeofetchAsciiIndexedColor(
|
||||||
|
RangedU8<{ NeofetchAsciiIndexedColor::MIN }, { NeofetchAsciiIndexedColor::MAX }>,
|
||||||
|
);
|
||||||
|
|
||||||
/// An indexed color where the color palette is the set of unique colors in a
|
/// An indexed color where the color palette is the set of unique colors in a
|
||||||
/// preset.
|
/// preset.
|
||||||
///
|
///
|
||||||
/// The range of valid values depends on the number of unique colors in a
|
/// The range of valid values depends on the number of unique colors in a
|
||||||
/// certain preset.
|
/// certain preset.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
|
#[derive(
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Ord,
|
||||||
|
PartialOrd,
|
||||||
|
Hash,
|
||||||
|
Debug,
|
||||||
|
Deserialize,
|
||||||
|
From,
|
||||||
|
FromStr,
|
||||||
|
Into,
|
||||||
|
Serialize,
|
||||||
|
)]
|
||||||
pub struct PresetIndexedColor(usize);
|
pub struct PresetIndexedColor(usize);
|
||||||
|
|
||||||
pub trait FromHex {
|
impl Lightness {
|
||||||
/// Creates color from hex code.
|
const MAX: f32 = 1.0f32;
|
||||||
fn from_hex<S>(hex: S) -> Result<RGB8>
|
const MIN: f32 = 0.0f32;
|
||||||
where
|
|
||||||
S: AsRef<str>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromHex for RGB8 {
|
pub fn new(value: f32) -> Result<Self, LightnessError> {
|
||||||
fn from_hex<S>(hex: S) -> Result<RGB8>
|
if !(Self::MIN..=Self::MAX).contains(&value) {
|
||||||
where
|
return Err(LightnessError::OutOfRange(value));
|
||||||
S: AsRef<str>,
|
|
||||||
{
|
|
||||||
let hex = hex.as_ref();
|
|
||||||
|
|
||||||
let hex = hex.strip_prefix('#').unwrap_or(hex);
|
|
||||||
if hex.len() != 6 {
|
|
||||||
Err(anyhow!("invalid length for hex color"))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let r =
|
Ok(Self(value))
|
||||||
u8::from_str_radix(&hex[0..2], 16).context("Failed to parse hex color component")?;
|
|
||||||
let g =
|
|
||||||
u8::from_str_radix(&hex[2..4], 16).context("Failed to parse hex color component")?;
|
|
||||||
let b =
|
|
||||||
u8::from_str_radix(&hex[4..6], 16).context("Failed to parse hex color component")?;
|
|
||||||
|
|
||||||
Ok(RGB8::new(r, g, b))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<f32> for Lightness {
|
||||||
|
type Error = LightnessError;
|
||||||
|
|
||||||
|
fn try_from(value: f32) -> Result<Self, Self::Error> {
|
||||||
|
Lightness::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Lightness {
|
||||||
|
type Err = ParseLightnessError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Lightness::new(s.parse()?)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NeofetchAsciiIndexedColor {
|
||||||
|
const MAX: u8 = 6;
|
||||||
|
const MIN: u8 = 1;
|
||||||
|
}
|
||||||
|
|
|
@ -5,3 +5,4 @@ pub mod models;
|
||||||
pub mod neofetch_util;
|
pub mod neofetch_util;
|
||||||
pub mod presets;
|
pub mod presets;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
pub mod utils;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::color_util::Lightness;
|
||||||
use crate::neofetch_util::ColorAlignment;
|
use crate::neofetch_util::ColorAlignment;
|
||||||
use crate::presets::Preset;
|
use crate::presets::Preset;
|
||||||
use crate::types::{AnsiMode, Backend, LightDark};
|
use crate::types::{AnsiMode, Backend, LightDark};
|
||||||
|
@ -9,7 +10,7 @@ pub struct Config {
|
||||||
pub preset: Preset,
|
pub preset: Preset,
|
||||||
pub mode: AnsiMode,
|
pub mode: AnsiMode,
|
||||||
pub light_dark: LightDark,
|
pub light_dark: LightDark,
|
||||||
pub lightness: Option<f32>,
|
lightness: Option<Lightness>,
|
||||||
pub color_align: ColorAlignment,
|
pub color_align: ColorAlignment,
|
||||||
pub backend: Backend,
|
pub backend: Backend,
|
||||||
#[serde(with = "self::args_serde_with")]
|
#[serde(with = "self::args_serde_with")]
|
||||||
|
@ -18,6 +19,20 @@ pub struct Config {
|
||||||
pub pride_month_disable: bool,
|
pub pride_month_disable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn default_lightness(term: &LightDark) -> Lightness {
|
||||||
|
match term {
|
||||||
|
LightDark::Dark => Lightness::new(0.65).unwrap(),
|
||||||
|
LightDark::Light => Lightness::new(0.4).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lightness(&self) -> Lightness {
|
||||||
|
self.lightness
|
||||||
|
.unwrap_or_else(|| Self::default_lightness(&self.light_dark))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod args_serde_with {
|
mod args_serde_with {
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ use tracing::debug;
|
||||||
use crate::color_util::{NeofetchAsciiIndexedColor, PresetIndexedColor};
|
use crate::color_util::{NeofetchAsciiIndexedColor, PresetIndexedColor};
|
||||||
use crate::distros::Distro;
|
use crate::distros::Distro;
|
||||||
|
|
||||||
|
const NEOFETCH_COLOR_PATTERN: &str = r"\$\{c[0-9]\}";
|
||||||
static NEOFETCH_COLOR_RE: OnceLock<Regex> = OnceLock::new();
|
static NEOFETCH_COLOR_RE: OnceLock<Regex> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
|
||||||
|
@ -41,14 +42,14 @@ pub fn get_command_path() -> Result<PathBuf> {
|
||||||
let path = path.join("neofetch");
|
let path = path.join("neofetch");
|
||||||
match path.try_exists() {
|
match path.try_exists() {
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
return path.canonicalize().context("Failed to canonicalize path");
|
return path.canonicalize().context("failed to canonicalize path");
|
||||||
},
|
},
|
||||||
Ok(false) => {
|
Ok(false) => {
|
||||||
Err(anyhow!("{path:?} does not exist or is not readable"))?;
|
Err(anyhow!("{path:?} does not exist or is not readable"))?;
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
Err(err)
|
Err(err)
|
||||||
.with_context(|| format!("Failed to check for existence of {path:?}"))?;
|
.with_context(|| format!("failed to check for existence of {path:?}"))?;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +64,7 @@ pub fn get_command_path() -> Result<PathBuf> {
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return path.canonicalize().context("Failed to canonicalize path");
|
return path.canonicalize().context("failed to canonicalize path");
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(anyhow!("neofetch command not found"))
|
Err(anyhow!("neofetch command not found"))
|
||||||
|
@ -80,7 +81,7 @@ where
|
||||||
distro.as_ref().into()
|
distro.as_ref().into()
|
||||||
} else {
|
} else {
|
||||||
get_distro_name()
|
get_distro_name()
|
||||||
.context("Failed to get distro name")?
|
.context("failed to get distro name")?
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
debug!(%distro, "distro name");
|
debug!(%distro, "distro name");
|
||||||
|
@ -101,7 +102,7 @@ where
|
||||||
|
|
||||||
let Some(width) = NEOFETCH_COLOR_RE
|
let Some(width) = NEOFETCH_COLOR_RE
|
||||||
.get_or_init(|| {
|
.get_or_init(|| {
|
||||||
Regex::new(r"\$\{c[0-9]\}").expect("neofetch color regex should not be invalid")
|
Regex::new(NEOFETCH_COLOR_PATTERN).expect("neofetch color regex should not be invalid")
|
||||||
})
|
})
|
||||||
.replace_all(asc, "")
|
.replace_all(asc, "")
|
||||||
.split('\n')
|
.split('\n')
|
||||||
|
@ -140,11 +141,11 @@ fn run_neofetch_command_piped<S>(args: &[S]) -> Result<String>
|
||||||
where
|
where
|
||||||
S: AsRef<OsStr> + fmt::Debug,
|
S: AsRef<OsStr> + fmt::Debug,
|
||||||
{
|
{
|
||||||
let mut command = make_neofetch_command(args).context("Failed to make neofetch command")?;
|
let mut command = make_neofetch_command(args).context("failed to make neofetch command")?;
|
||||||
|
|
||||||
let output = command
|
let output = command
|
||||||
.output()
|
.output()
|
||||||
.context("Failed to execute neofetch as child process")?;
|
.context("failed to execute neofetch as child process")?;
|
||||||
debug!(?output, "neofetch output");
|
debug!(?output, "neofetch output");
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
|
@ -168,7 +169,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let out = String::from_utf8(output.stdout)
|
let out = String::from_utf8(output.stdout)
|
||||||
.context("Failed to process neofetch output as it contains invalid UTF-8")?
|
.context("failed to process neofetch output as it contains invalid UTF-8")?
|
||||||
.trim()
|
.trim()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
Ok(out)
|
Ok(out)
|
||||||
|
@ -181,7 +182,7 @@ where
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
let mut command = Command::new("bash");
|
let mut command = Command::new("bash");
|
||||||
command.arg(get_command_path().context("Failed to get neofetch command path")?);
|
command.arg(get_command_path().context("failed to get neofetch command path")?);
|
||||||
command.args(args);
|
command.args(args);
|
||||||
Ok(command)
|
Ok(command)
|
||||||
}
|
}
|
||||||
|
@ -194,5 +195,5 @@ where
|
||||||
#[tracing::instrument(level = "debug")]
|
#[tracing::instrument(level = "debug")]
|
||||||
fn get_distro_name() -> Result<String> {
|
fn get_distro_name() -> Result<String> {
|
||||||
run_neofetch_command_piped(&["ascii_distro_name"])
|
run_neofetch_command_piped(&["ascii_distro_name"])
|
||||||
.context("Failed to get distro name from neofetch")
|
.context("failed to get distro name from neofetch")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,16 @@ use std::iter;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use rgb::RGB8;
|
use palette::encoding::{self, Linear};
|
||||||
|
use palette::num::ClampAssign;
|
||||||
|
use palette::{Hsl, IntoColorMut, LinSrgb, Srgb};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{EnumString, VariantNames};
|
use strum::{EnumString, VariantNames};
|
||||||
|
|
||||||
use crate::color_util::FromHex;
|
use crate::color_util::Lightness;
|
||||||
|
use crate::types::LightDark;
|
||||||
|
|
||||||
#[derive(Clone, Hash, Debug, Deserialize, EnumString, Serialize, VariantNames)]
|
#[derive(Copy, Clone, Hash, Debug, Deserialize, EnumString, Serialize, VariantNames)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
#[strum(serialize_all = "kebab-case")]
|
#[strum(serialize_all = "kebab-case")]
|
||||||
pub enum Preset {
|
pub enum Preset {
|
||||||
|
@ -82,9 +85,16 @@ pub enum Preset {
|
||||||
Xenogender,
|
Xenogender,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
|
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||||
pub struct ColorProfile {
|
pub struct ColorProfile {
|
||||||
pub colors: Vec<RGB8>,
|
pub colors: Vec<Srgb<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum AssignLightness {
|
||||||
|
Replace(Lightness),
|
||||||
|
ClampMax(Lightness),
|
||||||
|
ClampMin(Lightness),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Preset {
|
impl Preset {
|
||||||
|
@ -376,7 +386,7 @@ impl Preset {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColorProfile {
|
impl ColorProfile {
|
||||||
pub fn new(colors: Vec<RGB8>) -> Self {
|
pub fn new(colors: Vec<Srgb<u8>>) -> Self {
|
||||||
Self { colors }
|
Self { colors }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,9 +396,9 @@ impl ColorProfile {
|
||||||
{
|
{
|
||||||
let colors = hex_colors
|
let colors = hex_colors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(RGB8::from_hex)
|
.map(|s| s.as_ref().parse())
|
||||||
.collect::<Result<_>>()
|
.collect::<Result<_, _>>()
|
||||||
.context("Failed to parse hex colors")?;
|
.context("failed to parse hex colors")?;
|
||||||
Ok(Self::new(colors))
|
Ok(Self::new(colors))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,12 +424,79 @@ impl ColorProfile {
|
||||||
Ok(Self::new(weighted_colors))
|
Ok(Self::new(weighted_colors))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new color profile, with the colors lightened by a multiplier.
|
||||||
|
pub fn lighten(&self, multiplier: f32) -> Self {
|
||||||
|
let mut rgb_f32_colors: Vec<LinSrgb> =
|
||||||
|
self.colors.iter().map(|c| c.into_linear()).collect();
|
||||||
|
|
||||||
|
{
|
||||||
|
let hsl_f32_colors: &mut [Hsl<Linear<encoding::Srgb>>] =
|
||||||
|
&mut rgb_f32_colors.into_color_mut();
|
||||||
|
|
||||||
|
for hsl_f32_color in hsl_f32_colors {
|
||||||
|
hsl_f32_color.lightness *= multiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rgb_u8_colors: Vec<_> = rgb_f32_colors
|
||||||
|
.into_iter()
|
||||||
|
.map(Srgb::<u8>::from_linear)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
colors: rgb_u8_colors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new color profile, with the colors set to the specified HSL
|
||||||
|
/// lightness value.
|
||||||
|
pub fn with_lightness(&self, assign_lightness: AssignLightness) -> Self {
|
||||||
|
let mut rgb_f32_colors: Vec<_> =
|
||||||
|
self.colors.iter().map(|c| c.into_format::<f32>()).collect();
|
||||||
|
|
||||||
|
{
|
||||||
|
let hsl_f32_colors: &mut [Hsl] = &mut rgb_f32_colors.into_color_mut();
|
||||||
|
|
||||||
|
for hsl_f32_color in hsl_f32_colors {
|
||||||
|
match assign_lightness {
|
||||||
|
AssignLightness::Replace(lightness) => {
|
||||||
|
hsl_f32_color.lightness = lightness.into();
|
||||||
|
},
|
||||||
|
AssignLightness::ClampMax(lightness) => {
|
||||||
|
hsl_f32_color.lightness.clamp_max_assign(lightness.into());
|
||||||
|
},
|
||||||
|
AssignLightness::ClampMin(lightness) => {
|
||||||
|
hsl_f32_color.lightness.clamp_min_assign(lightness.into());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rgb_u8_colors: Vec<_> = rgb_f32_colors
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| c.into_format::<u8>())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
colors: rgb_u8_colors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new color profile, with the colors set to the specified HSL
|
||||||
|
/// lightness value, with respect to dark/light terminals.
|
||||||
|
pub fn with_lightness_dl(&self, lightness: Lightness, term: LightDark) -> Self {
|
||||||
|
match term {
|
||||||
|
LightDark::Dark => self.with_lightness(AssignLightness::ClampMin(lightness)),
|
||||||
|
LightDark::Light => self.with_lightness(AssignLightness::ClampMax(lightness)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates another color profile with only the unique colors.
|
/// Creates another color profile with only the unique colors.
|
||||||
pub fn unique_colors(&self) -> Self {
|
pub fn unique_colors(&self) -> Self {
|
||||||
let unique_colors = self.colors.iter().collect::<IndexSet<_>>();
|
let unique_colors: IndexSet<[u8; 3]> = self.colors.iter().map(|c| (*c).into()).collect();
|
||||||
let unique_colors = {
|
let unique_colors = {
|
||||||
let mut v = Vec::with_capacity(unique_colors.len());
|
let mut v = Vec::with_capacity(unique_colors.len());
|
||||||
v.extend(unique_colors);
|
v.extend(unique_colors.into_iter().map(Srgb::<u8>::from));
|
||||||
v
|
v
|
||||||
};
|
};
|
||||||
Self::new(unique_colors)
|
Self::new(unique_colors)
|
||||||
|
|
12
crates/hyfetch/src/utils.rs
Normal file
12
crates/hyfetch/src/utils.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use directories::ProjectDirs;
|
||||||
|
|
||||||
|
pub fn get_cache_path() -> Result<PathBuf> {
|
||||||
|
let path = ProjectDirs::from("", "", "hyfetch")
|
||||||
|
.context("failed to get base dirs")?
|
||||||
|
.cache_dir()
|
||||||
|
.to_owned();
|
||||||
|
Ok(path)
|
||||||
|
}
|
Loading…
Reference in a new issue