Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15ca855a04 | |||
| 666d2dc90a | |||
| 34583294c6 | |||
| 03615ab4ee | |||
| c722c73e79 | |||
| 8f5199974b | |||
| 8168877fb1 | |||
| 4e20b18d45 | |||
| 5dc1709f58 | |||
| fb1e35172e | |||
| fc9292be3f | |||
| 3f41cb40e2 | |||
| 729024a45f | |||
| ef1407d00e | |||
| 075fc467d2 |
Generated
+323
-242
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -17,14 +17,14 @@ ansi_colours = { version = "1.2.2", default-features = false }
|
||||
anstream = { version = "0.6.14", default-features = false }
|
||||
anyhow = { version = "1.0.86", default-features = false }
|
||||
bpaf = { version = "0.9.12", default-features = false }
|
||||
crossterm = { version = "0.27.0", default-features = false }
|
||||
crossterm = { version = "0.29.0", default-features = false }
|
||||
deranged = { version = "0.3.11", default-features = false }
|
||||
directories = { version = "5.0.1", default-features = false }
|
||||
directories = { version = "6.0.0", default-features = false }
|
||||
enable-ansi-support = { version = "0.2.1", default-features = false }
|
||||
enterpolation = { version = "0.2.1", default-features = false }
|
||||
fastrand = { version = "2.1.0", default-features = false }
|
||||
indexmap = { version = "2.2.6", default-features = false }
|
||||
itertools = { version = "0.13.0", default-features = false }
|
||||
itertools = { version = "0.14.0", default-features = false }
|
||||
normpath = { version = "1.2.0", default-features = false }
|
||||
palette = { version = "0.7.6", default-features = false }
|
||||
regex = { version = "1.10.5", default-features = false }
|
||||
@@ -33,14 +33,14 @@ 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 }
|
||||
strum = { version = "0.27.2", default-features = false }
|
||||
supports-color = { version = "3.0.0", default-features = false }
|
||||
tempfile = { version = "3.10.1", default-features = false }
|
||||
terminal-colorsaurus = { version = "0.4.3", default-features = false }
|
||||
terminal_size = { version = "0.3.0", default-features = false }
|
||||
terminal-colorsaurus = { version = "1.0.0", default-features = false }
|
||||
terminal_size = { version = "0.4.3", default-features = false }
|
||||
thiserror = { version = "1.0.61", default-features = false }
|
||||
time = { version = "0.3.36", default-features = false }
|
||||
toml_edit = { version = "0.22.16", default-features = false }
|
||||
toml_edit = { version = "0.23.6", default-features = false }
|
||||
tracing = { version = "0.1.40", default-features = false }
|
||||
tracing-subscriber = { version = "0.3.18", default-features = false }
|
||||
unicode-normalization = { version = "0.1.23", default-features = false }
|
||||
|
||||
@@ -34,7 +34,7 @@ fn main() {
|
||||
let dir = PathBuf::from(env::var_os("CARGO_WORKSPACE_DIR").unwrap_or_else(|| env::var_os("CARGO_MANIFEST_DIR").unwrap()));
|
||||
let o = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
|
||||
for file in &["neofetch", "hyfetch"] {
|
||||
for file in &["neofetch", "hyfetch/data"] {
|
||||
let src = anything_that_exist(&[
|
||||
&dir.join(file),
|
||||
&dir.join("../../").join(file),
|
||||
@@ -45,6 +45,7 @@ fn main() {
|
||||
// Copy either file or directory
|
||||
if src.is_dir() {
|
||||
let opt = CopyOptions { overwrite: true, copy_inside: true, ..CopyOptions::default() };
|
||||
println!("copying {} to {}", src.display(), dst.display());
|
||||
fs_extra::dir::copy(&src, &dst, &opt).expect("Failed to copy directory to OUT_DIR");
|
||||
}
|
||||
else { fs::copy(&src, &dst).expect("Failed to copy file to OUT_DIR"); }
|
||||
|
||||
@@ -61,8 +61,8 @@ impl RawAsciiArt {
|
||||
|
||||
Ok(NormalizedAsciiArt {
|
||||
lines,
|
||||
w,
|
||||
h,
|
||||
w: w.try_into().context("width does not fit in u8")?,
|
||||
h: h.try_into().context("height does not fit in u8")?,
|
||||
fg: self.fg.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
+150
-180
@@ -2,7 +2,8 @@ use std::borrow::Cow;
|
||||
use std::cmp;
|
||||
use std::fmt::Write as _;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, IsTerminal as _, Read as _, Write as _};
|
||||
use std::io::{self, IsTerminal as _, Read as _};
|
||||
use std::iter;
|
||||
use std::iter::zip;
|
||||
use std::num::NonZeroU8;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -24,8 +25,8 @@ use hyfetch::models::Config;
|
||||
#[cfg(feature = "macchina")]
|
||||
use hyfetch::neofetch_util::macchina_path;
|
||||
use hyfetch::neofetch_util::{self, add_pkg_path, fastfetch_path, get_distro_ascii, get_distro_name, literal_input, ColorAlignment, NEOFETCH_COLORS_AC, NEOFETCH_COLOR_PATTERNS, TEST_ASCII};
|
||||
use hyfetch::presets::{AssignLightness, Preset};
|
||||
use hyfetch::pride_month;
|
||||
use hyfetch::presets::{AssignLightness, ColorProfile, Preset};
|
||||
use hyfetch::{pride_month, printc};
|
||||
use hyfetch::types::{AnsiMode, Backend, TerminalTheme};
|
||||
use hyfetch::utils::{get_cache_path, input};
|
||||
use hyfetch::font_logo::get_font_logo;
|
||||
@@ -64,15 +65,12 @@ fn main() -> Result<()> {
|
||||
});
|
||||
|
||||
if options.test_print {
|
||||
let asc = get_distro_ascii(distro, backend).context("failed to get distro ascii")?;
|
||||
writeln!(io::stdout(), "{asc}", asc = asc.asc)
|
||||
.context("failed to write ascii to stdout")?;
|
||||
println!("{asc}", asc = get_distro_ascii(distro, backend)?.asc);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if options.print_font_logo {
|
||||
let logo = get_font_logo(backend).context("failed to get font logo")?;
|
||||
writeln!(io::stdout(), "{}", logo).context("failed to write logo to stdout")?;
|
||||
println!("{}", get_font_logo(backend)?);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -110,14 +108,9 @@ fn main() -> Result<()> {
|
||||
|
||||
if show_pride_month && !config.pride_month_disable {
|
||||
pride_month::start_animation(color_mode).context("failed to draw pride month animation")?;
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"\nHappy pride month!\n(You can always view the animation again with `hyfetch \
|
||||
--june`)\n"
|
||||
)
|
||||
.context("failed to write message to stdout")?;
|
||||
println!("\nHappy pride month!\n(You can always view the animation again with `hyfetch --june`)\n");
|
||||
|
||||
if !june_path.is_file() {
|
||||
if !june_path.is_file() && !options.june {
|
||||
File::create(&june_path)
|
||||
.with_context(|| format!("failed to create file {june_path:?}"))?;
|
||||
}
|
||||
@@ -129,9 +122,43 @@ fn main() -> Result<()> {
|
||||
let backend = options.backend.unwrap_or(config.backend);
|
||||
let args = options.args.as_ref().or(config.args.as_ref());
|
||||
|
||||
fn parse_preset_string(preset_string: &str) -> Result<ColorProfile> {
|
||||
if preset_string.contains('#') {
|
||||
let colors: Vec<&str> = preset_string.split(',').map(|s| s.trim()).collect();
|
||||
for color in &colors {
|
||||
if !color.starts_with('#') ||
|
||||
(color.len() != 4 && color.len() != 7) ||
|
||||
!color[1..].chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Err(anyhow::anyhow!("invalid hex color: {}", color));
|
||||
}
|
||||
}
|
||||
ColorProfile::from_hex_colors(colors)
|
||||
.context("failed to create color profile from hex")
|
||||
} else if preset_string == "random" {
|
||||
let mut rng = fastrand::Rng::new();
|
||||
let preset = *rng
|
||||
.choice(<Preset as VariantArray>::VARIANTS)
|
||||
.expect("preset iterator should not be empty");
|
||||
Ok(preset.color_profile())
|
||||
} else {
|
||||
use std::str::FromStr;
|
||||
let preset = Preset::from_str(preset_string)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"PRESET should be comma-separated hex colors or one of {{{presets}}}",
|
||||
presets = <Preset as VariantNames>::VARIANTS
|
||||
.iter()
|
||||
.chain(iter::once(&"random"))
|
||||
.join(",")
|
||||
)
|
||||
})?;
|
||||
Ok(preset.color_profile())
|
||||
}
|
||||
}
|
||||
|
||||
// Get preset
|
||||
let preset = options.preset.unwrap_or(config.preset);
|
||||
let color_profile = preset.color_profile();
|
||||
let preset_string = options.preset.as_deref().unwrap_or(&config.preset);
|
||||
let color_profile = parse_preset_string(preset_string)?;
|
||||
debug!(?color_profile, "color profile");
|
||||
|
||||
// Lighten
|
||||
@@ -149,7 +176,14 @@ fn main() -> Result<()> {
|
||||
};
|
||||
debug!(?color_profile, "lightened color profile");
|
||||
|
||||
let asc = if let Some(path) = options.ascii_file {
|
||||
let asc = if let Some(path_str) = config.custom_ascii_path {
|
||||
let path = PathBuf::from(path_str);
|
||||
RawAsciiArt {
|
||||
asc: fs::read_to_string(&path)
|
||||
.with_context(|| format!("failed to read ascii from {path:?}"))?,
|
||||
fg: Vec::new(),
|
||||
}
|
||||
} else if let Some(path) = options.ascii_file {
|
||||
RawAsciiArt {
|
||||
asc: fs::read_to_string(&path)
|
||||
.with_context(|| format!("failed to read ascii from {path:?}"))?,
|
||||
@@ -207,9 +241,9 @@ fn det_bg() -> Result<Option<Srgb<u8>>, terminal_colorsaurus::Error> {
|
||||
}
|
||||
|
||||
background_color(QueryOptions::default())
|
||||
.map(|terminal_colorsaurus::Color { r, g, b }| Some(Srgb::new(r, g, b).into_format()))
|
||||
.map(|terminal_colorsaurus::Color { r, g, b , .. }| Some(Srgb::new(r, g, b).into_format()))
|
||||
.or_else(|err| {
|
||||
if matches!(err, terminal_colorsaurus::Error::UnsupportedTerminal) {
|
||||
if matches!(err, terminal_colorsaurus::Error::UnsupportedTerminal(_)) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(err)
|
||||
@@ -236,10 +270,6 @@ fn create_config(
|
||||
} else if color_level.has_256 {
|
||||
AnsiMode::Ansi256
|
||||
} else if color_level.has_basic {
|
||||
// unimplemented!(
|
||||
// "{mode} color mode not supported",
|
||||
// mode = AnsiMode::Ansi16.as_ref()
|
||||
// );
|
||||
AnsiMode::Ansi256
|
||||
} else {
|
||||
unreachable!();
|
||||
@@ -279,13 +309,8 @@ fn create_config(
|
||||
.expect("`option_counter` should not overflow `u8`");
|
||||
}
|
||||
|
||||
fn print_title_prompt(
|
||||
option_counter: NonZeroU8,
|
||||
prompt: &str,
|
||||
color_mode: AnsiMode,
|
||||
) -> Result<()> {
|
||||
printc(format!("&a{option_counter}. {prompt}"), color_mode)
|
||||
.context("failed to print prompt")
|
||||
fn print_title_prompt(option_counter: NonZeroU8, prompt: &str) {
|
||||
printc!("&a{option_counter}. {prompt}");
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
@@ -295,14 +320,8 @@ fn create_config(
|
||||
let (Width(term_w), Height(term_h)) = terminal_size().context("failed to get terminal size")?;
|
||||
let (term_w_min, term_h_min) = ((asc.w as u32 * 2 + 4).clamp(0, u16::MAX.into()) as u16, 30);
|
||||
if term_w < term_w_min || term_h < term_h_min {
|
||||
printc(
|
||||
format!(
|
||||
"&cWarning: Your terminal is too small ({term_w} * {term_h}).\nPlease resize \
|
||||
it to at least ({term_w_min} * {term_h_min}) for better experience."
|
||||
),
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to print message")?;
|
||||
printc!("&cWarning: Your terminal is too small ({term_w} * {term_h}).\n\
|
||||
Please resize it to at least ({term_w_min} * {term_h_min}) for better experience.");
|
||||
input(Some("Press enter to continue...")).context("failed to read input")?;
|
||||
}
|
||||
}
|
||||
@@ -347,18 +366,14 @@ fn create_config(
|
||||
(t - a) * ((d - c) / (b - a)) + c
|
||||
}
|
||||
|
||||
{
|
||||
let label = format!(
|
||||
"{label:^term_w$}",
|
||||
label = "8bit Color Testing",
|
||||
term_w = usize::from(term_w)
|
||||
);
|
||||
let print_color_testing = |label: &str, mode: AnsiMode| {
|
||||
let label = format!("{label:^term_w$}", term_w = usize::from(term_w));
|
||||
let line = zip(gradient.iter(), label.chars()).fold(
|
||||
String::new(),
|
||||
|mut s, (&rgb_f32_color, t)| {
|
||||
let rgb_u8_color = Srgb::<u8>::from_linear(rgb_f32_color);
|
||||
let back = rgb_u8_color
|
||||
.to_ansi_string(AnsiMode::Ansi256, ForegroundBackground::Background);
|
||||
.to_ansi_string(mode, ForegroundBackground::Background);
|
||||
let fore = rgb_u8_color
|
||||
.contrast_grayscale()
|
||||
.to_ansi_string(AnsiMode::Ansi256, ForegroundBackground::Foreground);
|
||||
@@ -366,42 +381,15 @@ fn create_config(
|
||||
s
|
||||
},
|
||||
);
|
||||
printc(line, AnsiMode::Ansi256).context("failed to print 8-bit color test line")?;
|
||||
}
|
||||
{
|
||||
let label = format!(
|
||||
"{label:^term_w$}",
|
||||
label = "RGB Color Testing",
|
||||
term_w = usize::from(term_w)
|
||||
);
|
||||
let line = zip(gradient.iter(), label.chars()).fold(
|
||||
String::new(),
|
||||
|mut s, (&rgb_f32_color, t)| {
|
||||
let rgb_u8_color = Srgb::<u8>::from_linear(rgb_f32_color);
|
||||
let back = rgb_u8_color
|
||||
.to_ansi_string(AnsiMode::Rgb, ForegroundBackground::Background);
|
||||
let fore = rgb_u8_color
|
||||
.contrast_grayscale()
|
||||
.to_ansi_string(AnsiMode::Ansi256, ForegroundBackground::Foreground);
|
||||
write!(s, "{back}{fore}{t}").unwrap();
|
||||
s
|
||||
},
|
||||
);
|
||||
printc(line, AnsiMode::Rgb).context("failed to print RGB color test line")?;
|
||||
}
|
||||
printc!("{line}");
|
||||
};
|
||||
|
||||
writeln!(io::stdout()).context("failed to write to stdout")?;
|
||||
print_title_prompt(
|
||||
option_counter,
|
||||
"Which &bcolor system &ado you want to use?",
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to print title prompt")?;
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"(If you can't see colors under \"RGB Color Testing\", please choose 8bit)\n"
|
||||
)
|
||||
.context("failed to write message to stdout")?;
|
||||
print_color_testing("8bit Color Testing", AnsiMode::Ansi256);
|
||||
print_color_testing("RGB Color Testing", AnsiMode::Rgb);
|
||||
|
||||
println!();
|
||||
print_title_prompt(option_counter, "Which &bcolor system &ado you want to use?");
|
||||
println!("(If you can't see colors under \"RGB Color Testing\", please choose 8bit)\n");
|
||||
|
||||
let choice = literal_input(
|
||||
"Your choice?",
|
||||
@@ -434,20 +422,14 @@ fn create_config(
|
||||
|
||||
clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
|
||||
|
||||
print_title_prompt(
|
||||
option_counter,
|
||||
"Is your terminal in &blight mode&~ or &4dark mode&~?",
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to print title prompt")?;
|
||||
print_title_prompt(option_counter, "Is your terminal in &blight mode&~ or &4dark mode&~?");
|
||||
let choice = literal_input(
|
||||
"",
|
||||
TerminalTheme::VARIANTS,
|
||||
TerminalTheme::Dark.as_ref(),
|
||||
true,
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to ask for choice input")?;
|
||||
)?;
|
||||
Ok((
|
||||
choice.parse().expect("selected theme should be valid"),
|
||||
"Selected background color",
|
||||
@@ -518,18 +500,12 @@ fn create_config(
|
||||
|
||||
let print_flag_page = |page, page_num: u8| -> Result<()> {
|
||||
clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
|
||||
print_title_prompt(option_counter, "Let's choose a flag!", color_mode)
|
||||
.context("failed to print title prompt")?;
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"Available flag presets:\nPage: {page_num} of {num_pages}\n",
|
||||
page_num = page_num.checked_add(1).unwrap()
|
||||
)
|
||||
.context("failed to write header to stdout")?;
|
||||
print_title_prompt(option_counter, "Let's choose a flag!");
|
||||
println!("Available flag presets:\nPage: {page_num} of {num_pages}\n", page_num = page_num + 1);
|
||||
for &row in page {
|
||||
print_flag_row(row, color_mode).context("failed to print flag row")?;
|
||||
}
|
||||
writeln!(io::stdout()).context("failed to write to stdout")?;
|
||||
println!();
|
||||
Ok(())
|
||||
};
|
||||
|
||||
@@ -541,7 +517,7 @@ fn create_config(
|
||||
}
|
||||
printc(line.join(" "), color_mode).context("failed to print line")?;
|
||||
}
|
||||
writeln!(io::stdout()).context("failed to write to stdout")?;
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -566,16 +542,9 @@ fn create_config(
|
||||
let mut opts: Vec<&str> = <Preset as VariantNames>::VARIANTS.into();
|
||||
opts.extend(["next", "n", "prev", "p"]);
|
||||
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"Enter '[n]ext' to go to the next page and '[p]rev' to go to the previous page."
|
||||
)
|
||||
.context("failed to write message to stdout")?;
|
||||
println!("Enter '[n]ext' to go to the next page and '[p]rev' to go to the previous page.");
|
||||
let selection = literal_input(
|
||||
format!(
|
||||
"Which {preset} do you want to use? ",
|
||||
preset = preset_default_colored
|
||||
),
|
||||
format!("Which {preset_default_colored} do you want to use? "),
|
||||
&opts[..],
|
||||
Preset::Rainbow.as_ref(),
|
||||
false,
|
||||
@@ -624,22 +593,15 @@ fn create_config(
|
||||
|
||||
let select_lightness = || -> Result<Lightness> {
|
||||
clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
|
||||
print_title_prompt(
|
||||
option_counter,
|
||||
"Let's adjust the color brightness!",
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to print title prompt")?;
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
print_title_prompt(option_counter, "Let's adjust the color brightness!");
|
||||
println!(
|
||||
"The colors might be a little bit too {bright_dark} for {light_dark} mode.\n",
|
||||
bright_dark = match theme {
|
||||
TerminalTheme::Light => "bright",
|
||||
TerminalTheme::Dark => "dark",
|
||||
},
|
||||
light_dark = theme.as_ref()
|
||||
)
|
||||
.context("failed to write message to stdout")?;
|
||||
);
|
||||
|
||||
let color_align = ColorAlignment::Horizontal;
|
||||
|
||||
@@ -697,14 +659,10 @@ fn create_config(
|
||||
}
|
||||
|
||||
loop {
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"\nWhich brightness level looks the best? (Default: {default:.0}% for \
|
||||
{light_dark} mode)",
|
||||
println!("\nWhich brightness level looks the best? (Default: {default:.0}% for {light_dark} mode)",
|
||||
default = f32::from(default_lightness) * 100.0,
|
||||
light_dark = theme.as_ref()
|
||||
)
|
||||
.context("failed to write prompt to stdout")?;
|
||||
);
|
||||
let lightness = input(Some("> "))
|
||||
.context("failed to read input")?
|
||||
.trim()
|
||||
@@ -716,12 +674,7 @@ fn create_config(
|
||||
},
|
||||
Err(err) => {
|
||||
debug!(%err, "could not parse lightness");
|
||||
printc(
|
||||
"&cUnable to parse lightness value, please enter a lightness value such \
|
||||
as 45%, .45, or 45",
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to print message")?;
|
||||
printc!("&cUnable to parse lightness value, please enter a lightness value such as 45%, .45, or 45");
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -845,16 +798,11 @@ fn create_config(
|
||||
}
|
||||
printc(line.join(" "), color_mode).context("failed to print ascii line")?;
|
||||
}
|
||||
|
||||
writeln!(io::stdout()).context("failed to write to stdout")?;
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
print_title_prompt(
|
||||
option_counter,
|
||||
"Do you want the default logo, or the small logo?",
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to print title prompt")?;
|
||||
print_title_prompt(option_counter, "Do you want the default logo, or the small logo?");
|
||||
let opts: Vec<Cow<str>> = ["default", "small"].map(Into::into).into();
|
||||
let choice = literal_input("Your choice?", &opts[..], "default", true, color_mode)
|
||||
.context("failed to ask for choice input")
|
||||
@@ -969,21 +917,11 @@ fn create_config(
|
||||
}
|
||||
printc(line.join(" "), color_mode).context("failed to print ascii line")?;
|
||||
}
|
||||
writeln!(io::stdout()).context("failed to write to stdout")?;
|
||||
println!();
|
||||
}
|
||||
|
||||
print_title_prompt(
|
||||
option_counter,
|
||||
"Let's choose a color arrangement!",
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to print title prompt")?;
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"You can choose standard horizontal or vertical alignment, or use one of the random \
|
||||
color schemes.\nYou can type \"roll\" to randomize again.\n"
|
||||
)
|
||||
.context("failed to write message to stdout")?;
|
||||
print_title_prompt(option_counter, "Let's choose a color arrangement!");
|
||||
println!("You can choose standard horizontal or vertical alignment, or use one of the random color schemes.\nYou can type \"roll\" to randomize again.\n");
|
||||
let mut opts: Vec<Cow<str>> = ["horizontal", "vertical", "roll"].map(Into::into).into();
|
||||
opts.extend((0..random_count).map(|i| format!("random{i}").into()));
|
||||
let choice = literal_input("Your choice?", &opts[..], "horizontal", true, color_mode)
|
||||
@@ -1022,42 +960,27 @@ fn create_config(
|
||||
|
||||
let select_backend = || -> Result<Backend> {
|
||||
clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
|
||||
print_title_prompt(option_counter, "Select a *fetch backend", color_mode)
|
||||
.context("failed to print title prompt")?;
|
||||
print_title_prompt(option_counter, "Select a *fetch backend");
|
||||
|
||||
// Check if fastfetch is installed
|
||||
let fastfetch_path = fastfetch_path().ok();
|
||||
|
||||
// Check if macchina is installed
|
||||
#[cfg(feature = "macchina")]
|
||||
let macchina_path = macchina_path().context("failed to get macchina path")?;
|
||||
let macchina_path = macchina_path().unwrap_or(None);
|
||||
|
||||
printc(
|
||||
"- &bneofetch&r: Written in bash, &nbest compatibility&r on Unix systems",
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to print message")?;
|
||||
printc(
|
||||
format!(
|
||||
"- &bfastfetch&r: Written in C, &nbest performance&r {installed_not_installed}",
|
||||
installed_not_installed = fastfetch_path
|
||||
.map(|path| format!("&a(Installed at {path})", path = path.display()))
|
||||
.unwrap_or_else(|| "&c(Not installed)".to_owned())
|
||||
),
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to print message")?;
|
||||
printc!("- &bneofetch&r: Written in bash, &nbest compatibility&r on Unix systems");
|
||||
printc!("- &bfastfetch&r: Written in C, &nbest performance&r {}",
|
||||
fastfetch_path
|
||||
.map(|path| format!("&a(Installed at {path})", path = path.display()))
|
||||
.unwrap_or_else(|| "&c(Not installed)".to_owned())
|
||||
);
|
||||
#[cfg(feature = "macchina")]
|
||||
printc(
|
||||
format!(
|
||||
"- &bmacchina&r: Written in Rust, &nbest performance&r {installed_not_installed}\n",
|
||||
installed_not_installed = macchina_path
|
||||
.map(|path| format!("&a(Installed at {path})", path = path.display()))
|
||||
.unwrap_or_else(|| "&c(Not installed)".to_owned())
|
||||
),
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to print message")?;
|
||||
printc!("- &bmacchina&r: Written in Rust, &nbest performance&r {}\n",
|
||||
macchina_path
|
||||
.map(|path| format!("&a(Installed at {path})", path = path.display()))
|
||||
.unwrap_or_else(|| "&c(Not installed)".to_owned())
|
||||
);
|
||||
|
||||
let choice = literal_input(
|
||||
"Your choice?",
|
||||
@@ -1078,10 +1001,56 @@ fn create_config(
|
||||
backend.as_ref(),
|
||||
);
|
||||
|
||||
//////////////////////////////
|
||||
// 8. Custom ASCII file
|
||||
let mut custom_ascii_path: Option<String> = None;
|
||||
clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
|
||||
let choice = literal_input(
|
||||
"Do you want to specify a custom ASCII file?",
|
||||
&["y", "n"],
|
||||
"n",
|
||||
true,
|
||||
color_mode,
|
||||
)
|
||||
.context("failed to ask for choice input")?;
|
||||
|
||||
if choice == "y" {
|
||||
loop {
|
||||
let pth = input(Some("Path to custom ASCII file (must be UTF-8 encoded, empty to skip): "))?.trim().to_owned();
|
||||
if pth.is_empty() {
|
||||
printc!("&cNo path entered. Skipping custom ASCII file.");
|
||||
break;
|
||||
}
|
||||
|
||||
let pth_buf = PathBuf::from(&pth);
|
||||
if !pth_buf.is_file() {
|
||||
printc!("&cError: File not found at {pth}");
|
||||
continue;
|
||||
}
|
||||
|
||||
match fs::read_to_string(&pth_buf) {
|
||||
Ok(_) => {
|
||||
custom_ascii_path = Some(pth);
|
||||
update_title(
|
||||
&mut title,
|
||||
&mut option_counter,
|
||||
"Custom ASCII file",
|
||||
custom_ascii_path.as_ref().unwrap(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
printc!("&cError: File is not UTF-8 encoded or an unexpected error occurred: {e}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create config
|
||||
clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
|
||||
let config = Config {
|
||||
preset,
|
||||
preset: preset.as_ref().to_string(),
|
||||
mode: color_mode,
|
||||
light_dark: Some(theme),
|
||||
auto_detect_light_dark: Some(det_bg.is_some()),
|
||||
@@ -1091,6 +1060,7 @@ fn create_config(
|
||||
args: None,
|
||||
distro: logo_chosen,
|
||||
pride_month_disable: false,
|
||||
custom_ascii_path,
|
||||
};
|
||||
debug!(?config, "created config");
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use bpaf::ShellComp;
|
||||
use bpaf::{construct, long, OptionParser, Parser as _};
|
||||
use directories::BaseDirs;
|
||||
use itertools::Itertools as _;
|
||||
use strum::{VariantArray, VariantNames};
|
||||
use strum::VariantNames;
|
||||
|
||||
use crate::color_util::{color, Lightness};
|
||||
use crate::presets::Preset;
|
||||
@@ -18,7 +18,7 @@ use crate::types::{AnsiMode, Backend};
|
||||
pub struct Options {
|
||||
pub config: bool,
|
||||
pub config_file: PathBuf,
|
||||
pub preset: Option<Preset>,
|
||||
pub preset: Option<String>,
|
||||
pub mode: Option<AnsiMode>,
|
||||
pub backend: Option<Backend>,
|
||||
pub args: Option<Vec<String>>,
|
||||
@@ -55,7 +55,7 @@ pub fn options() -> OptionParser<Options> {
|
||||
let preset = long("preset")
|
||||
.short('p')
|
||||
.help(&*format!(
|
||||
"Use preset
|
||||
"Use preset or comma-separated color list or comma-separated hex colors (e.g., \"#ff0000,#00ff00,#0000ff\")
|
||||
PRESET={{{presets}}}",
|
||||
presets = <Preset as VariantNames>::VARIANTS
|
||||
.iter()
|
||||
@@ -65,30 +65,7 @@ PRESET={{{presets}}}",
|
||||
.argument::<String>("PRESET");
|
||||
#[cfg(feature = "autocomplete")]
|
||||
let preset = preset.complete(complete_preset);
|
||||
let preset = preset
|
||||
.parse(|s| {
|
||||
Preset::from_str(&s)
|
||||
.or_else(|e| {
|
||||
if s == "random" {
|
||||
let mut rng = fastrand::Rng::new();
|
||||
Ok(*rng
|
||||
.choice(<Preset as VariantArray>::VARIANTS)
|
||||
.expect("preset iterator should not be empty"))
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"PRESET should be one of {{{presets}}}",
|
||||
presets = <Preset as VariantNames>::VARIANTS
|
||||
.iter()
|
||||
.chain(iter::once(&"random"))
|
||||
.join(",")
|
||||
)
|
||||
})
|
||||
})
|
||||
.optional();
|
||||
let preset = preset.optional();
|
||||
let mode = long("mode")
|
||||
.short('m')
|
||||
.help(&*format!(
|
||||
|
||||
@@ -403,18 +403,20 @@ where
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! printc {
|
||||
($($arg:tt)*) => {
|
||||
println!("{}", color(format!("{}&r", format!($($arg)*)), AnsiMode::Rgb).expect("failed to color message"));
|
||||
};
|
||||
}
|
||||
|
||||
/// Prints with color.
|
||||
pub fn printc<S>(msg: S, mode: AnsiMode) -> Result<()>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"{msg}",
|
||||
msg = color(format!("{msg}&r", msg = msg.as_ref()), mode)
|
||||
.context("failed to color message")?
|
||||
)
|
||||
.context("failed to write message to stdout")
|
||||
println!("{msg}", msg = color(format!("{msg}&r", msg = msg.as_ref()), mode).context("failed to color message")?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clears screen using ANSI escape codes.
|
||||
|
||||
@@ -2,12 +2,11 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::color_util::Lightness;
|
||||
use crate::neofetch_util::ColorAlignment;
|
||||
use crate::presets::Preset;
|
||||
use crate::types::{AnsiMode, Backend, TerminalTheme};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub preset: Preset,
|
||||
pub preset: String,
|
||||
pub mode: AnsiMode,
|
||||
pub auto_detect_light_dark: Option<bool>,
|
||||
pub light_dark: Option<TerminalTheme>,
|
||||
@@ -19,6 +18,7 @@ pub struct Config {
|
||||
pub args: Option<Vec<String>>,
|
||||
pub distro: Option<String>,
|
||||
pub pride_month_disable: bool,
|
||||
pub custom_ascii_path: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
||||
@@ -113,16 +113,12 @@ where
|
||||
};
|
||||
|
||||
if let Some(selected) = find_selection(&selection, options) {
|
||||
writeln!(io::stdout()).context("failed to write to stdout")?;
|
||||
println!();
|
||||
|
||||
return Ok(selected);
|
||||
} else {
|
||||
let options_text = options.iter().map(AsRef::as_ref).join("|");
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"Invalid selection! {selection} is not one of {options_text}"
|
||||
)
|
||||
.context("failed to write message to stdout")?;
|
||||
println!("Invalid selection! {selection} is not one of {options_text}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +281,7 @@ pub fn run(asc: RecoloredAsciiArt, backend: Backend, args: Option<&Vec<String>>)
|
||||
}
|
||||
|
||||
/// Gets distro ascii width and height, ignoring color code.
|
||||
pub fn ascii_size<S>(asc: S) -> Result<(u8, u8)>
|
||||
pub fn ascii_size<S>(asc: S) -> Result<(u16, u16)>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
@@ -307,25 +303,11 @@ where
|
||||
return Ok((0, 0));
|
||||
}
|
||||
|
||||
let width = asc
|
||||
.lines()
|
||||
.map(|line| line.graphemes(true).count())
|
||||
.max()
|
||||
let width = asc.lines()
|
||||
.map(|line| line.graphemes(true).count()).max()
|
||||
.expect("line iterator should not be empty");
|
||||
let width: u8 = width.try_into().with_context(|| {
|
||||
format!(
|
||||
"`asc` should not have more than {limit} characters per line",
|
||||
limit = u8::MAX
|
||||
)
|
||||
})?;
|
||||
let height = asc.lines().count();
|
||||
let height: u8 = height.try_into().with_context(|| {
|
||||
format!(
|
||||
"`asc` should not have more than {limit} lines",
|
||||
limit = u8::MAX
|
||||
)
|
||||
})?;
|
||||
|
||||
let width: u16 = width.try_into().context("ascii art width should fit in u16")?;
|
||||
let height: u16 = asc.lines().count().try_into().context("ascii art height should fit in u16")?;
|
||||
Ok((width, height))
|
||||
}
|
||||
|
||||
|
||||
@@ -229,6 +229,10 @@ pub enum Preset {
|
||||
|
||||
/// Meme flag
|
||||
Band,
|
||||
|
||||
Libragender, Librafeminine, Libramasculine, Libraandrogyne, Libranonbinary,
|
||||
|
||||
Fluidfluxa, Fluidfluxb,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
@@ -687,6 +691,33 @@ impl Preset {
|
||||
"#2670C0", "#F5BD00", "#DC0045", "#E0608E"
|
||||
]),
|
||||
|
||||
Self::Libragender => ColorProfile::from_hex_colors(vec![
|
||||
"#000000", "#808080", "#92D8E9", "#FFF544", "#FFB0CA", "#808080", "#000000"
|
||||
]),
|
||||
|
||||
Self::Librafeminine => ColorProfile::from_hex_colors(vec![
|
||||
"#000000", "#A3A3A3", "#FFFFFF", "#C6568F", "#FFFFFF", "#A3A3A3", "#000000"
|
||||
]),
|
||||
|
||||
Self::Libramasculine => ColorProfile::from_hex_colors(vec![
|
||||
"#000000", "#A3A3A3", "#FFFFFF", "#56C5C5", "#FFFFFF", "#A3A3A3", "#000000"
|
||||
]),
|
||||
|
||||
Self::Libraandrogyne => ColorProfile::from_hex_colors(vec![
|
||||
"#000000", "#A3A3A3", "#FFFFFF", "#9186B1", "#FFFFFF", "#A3A3A3", "#000000"
|
||||
]),
|
||||
|
||||
Self::Libranonbinary => ColorProfile::from_hex_colors(vec![
|
||||
"#000000", "#A3A3A3", "#FFFFFF", "#FFF987", "#FFFFFF", "#A3A3A3", "#000000"
|
||||
]),
|
||||
|
||||
Self::Fluidfluxa => ColorProfile::from_hex_colors(vec![
|
||||
"#ff115f", "#a34aa3", "#00a4e7", "#ffdf00", "#000000", "#ffed71", "#85daff", "#dbadda", "#fe8db1"
|
||||
]),
|
||||
|
||||
Self::Fluidfluxb => ColorProfile::from_hex_colors(vec![
|
||||
"#c6d1d2", "#f47b9d", "#f09f9b", "#e3f09e", "#75eeea", "#52d2ed", "#c6d1d2"
|
||||
]),
|
||||
})
|
||||
.expect("preset color profiles should be valid")
|
||||
}
|
||||
|
||||
@@ -47,58 +47,32 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
|
||||
};
|
||||
|
||||
let text = &TEXT_ASCII[1..TEXT_ASCII.len().checked_sub(1).unwrap()];
|
||||
let (text_width, text_height) =
|
||||
ascii_size(text).expect("text ascii should have valid width and height");
|
||||
let (text_w, text_h) = ascii_size(text)?;
|
||||
let (text, text_width, text_height) = {
|
||||
const TEXT_BORDER_WIDTH: u16 = 2;
|
||||
const NOTICE_BORDER_WIDTH: u16 = 1;
|
||||
const VERTICAL_MARGIN: u16 = 1;
|
||||
let notice_w = NOTICE.len();
|
||||
let notice_w: u8 = notice_w
|
||||
.try_into()
|
||||
.expect("`NOTICE` width should fit in `u8`");
|
||||
let notice_h = NOTICE.lines().count();
|
||||
let notice_h: u8 = notice_h
|
||||
.try_into()
|
||||
.expect("`NOTICE` height should fit in `u8`");
|
||||
let notice_w: u16 = NOTICE.len().try_into()?;
|
||||
let notice_h: u16 = NOTICE.lines().count().try_into()?;
|
||||
let term_w_min = cmp::max(
|
||||
u16::from(text_width)
|
||||
.checked_add(TEXT_BORDER_WIDTH.checked_mul(2).unwrap())
|
||||
.unwrap(),
|
||||
u16::from(notice_w)
|
||||
.checked_add(NOTICE_BORDER_WIDTH.checked_mul(2).unwrap())
|
||||
.unwrap(),
|
||||
text_w + TEXT_BORDER_WIDTH * 2,
|
||||
notice_w + NOTICE_BORDER_WIDTH * 2,
|
||||
);
|
||||
let term_h_min = u16::from(text_height)
|
||||
.checked_add(notice_h.into())
|
||||
.unwrap()
|
||||
.checked_add(VERTICAL_MARGIN.checked_mul(2).unwrap())
|
||||
.unwrap();
|
||||
let term_h_min = u16::from(text_h) + notice_h + VERTICAL_MARGIN * 2;
|
||||
if w.get() >= term_w_min && h.get() >= term_h_min {
|
||||
(text, text_width, text_height)
|
||||
(text, text_w, text_h)
|
||||
} else {
|
||||
let text = &TEXT_ASCII_SMALL[1..TEXT_ASCII_SMALL.len().checked_sub(1).unwrap()];
|
||||
let (text_width, text_height) =
|
||||
ascii_size(text).expect("text ascii should have valid width and height");
|
||||
let (text_w, text_h) = ascii_size(text)?;
|
||||
let term_w_min = cmp::max(
|
||||
u16::from(text_width)
|
||||
.checked_add(TEXT_BORDER_WIDTH.checked_mul(2).unwrap())
|
||||
.unwrap(),
|
||||
u16::from(notice_w)
|
||||
.checked_add(NOTICE_BORDER_WIDTH.checked_mul(2).unwrap())
|
||||
.unwrap(),
|
||||
text_w + TEXT_BORDER_WIDTH * 2,
|
||||
notice_w + NOTICE_BORDER_WIDTH * 2,
|
||||
);
|
||||
let term_h_min = u16::from(text_height)
|
||||
.checked_add(notice_h.into())
|
||||
.unwrap()
|
||||
.checked_add(VERTICAL_MARGIN.checked_mul(2).unwrap())
|
||||
.unwrap();
|
||||
let term_h_min = text_h + notice_h + VERTICAL_MARGIN * 2;
|
||||
if w.get() < term_w_min || h.get() < term_h_min {
|
||||
return Err(anyhow!(
|
||||
"terminal size should be at least ({term_w_min} * {term_h_min})"
|
||||
));
|
||||
return Err(anyhow!("terminal size should be at least ({term_w_min} * {term_h_min})"));
|
||||
}
|
||||
(text, text_width, text_height)
|
||||
(text, text_w, text_h)
|
||||
}
|
||||
};
|
||||
let text_lines: Vec<&str> = text.lines().collect();
|
||||
@@ -165,8 +139,7 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
|
||||
.rem_euclid(colors.len())]
|
||||
.to_ansi_string(color_mode, ForegroundBackground::Background),
|
||||
fg = fg.to_ansi_string(color_mode, ForegroundBackground::Foreground)
|
||||
)
|
||||
.unwrap();
|
||||
)?;
|
||||
|
||||
// Loop over the width
|
||||
for x in 0..w.get() {
|
||||
@@ -176,27 +149,11 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
|
||||
.wrapping_add_signed((2.0 * (y as f64 + 0.5 * frame as f64).sin()) as isize);
|
||||
let y_text = text_start_y <= y && y < text_end_y;
|
||||
|
||||
let border = 1u16
|
||||
.checked_add(
|
||||
if y == text_start_y || y == text_end_y.checked_sub(1).unwrap() {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let text_bounds_x1 = text_start_x
|
||||
.checked_sub(border)
|
||||
.expect("`text_start_x - border` should not underflow `u16`");
|
||||
let text_bounds_x2 = text_end_x
|
||||
.checked_add(border)
|
||||
.expect("`text_end_x + border` should not overflow `u16`");
|
||||
let notice_bounds_x1 = notice_start_x
|
||||
.checked_sub(1)
|
||||
.expect("`notice_start_x - 1` should not underflow `u16`");
|
||||
let notice_bounds_x2 = notice_end_x
|
||||
.checked_add(1)
|
||||
.expect("`notice_end_x + 1` should not overflow `u16`");
|
||||
let border = 1u16 + if y == text_start_y || y == (text_end_y - 1) { 0 } else { 1 };
|
||||
let text_bounds_x1 = text_start_x - border;
|
||||
let text_bounds_x2 = text_end_x - border;
|
||||
let notice_bounds_x1 = notice_start_x - 1;
|
||||
let notice_bounds_x2 = notice_end_x - 1;
|
||||
|
||||
// If it's a switching point
|
||||
if idx.rem_euclid(NonZeroUsize::from(block_width).get()) == 0
|
||||
@@ -215,19 +172,9 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
|
||||
{
|
||||
let c: LinSrgba = c.with_alpha(1.0).into_linear();
|
||||
let c = Srgb::<u8>::from_linear(c.overlay(black).without_alpha());
|
||||
write!(
|
||||
buf,
|
||||
"{bg}",
|
||||
bg = c.to_ansi_string(color_mode, ForegroundBackground::Background),
|
||||
)
|
||||
.unwrap();
|
||||
write!(buf, "{bg}", bg = c.to_ansi_string(color_mode, ForegroundBackground::Background))?;
|
||||
} else {
|
||||
write!(
|
||||
buf,
|
||||
"{bg}",
|
||||
bg = c.to_ansi_string(color_mode, ForegroundBackground::Background),
|
||||
)
|
||||
.unwrap();
|
||||
write!(buf, "{bg}", bg = c.to_ansi_string(color_mode, ForegroundBackground::Background))?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,8 +187,7 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
|
||||
.chars()
|
||||
.nth(usize::from(x.checked_sub(text_start_x).unwrap()))
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
)?;
|
||||
} else if y == notice_y && notice_start_x <= x && x < notice_end_x {
|
||||
write!(
|
||||
buf,
|
||||
@@ -250,21 +196,15 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
|
||||
.chars()
|
||||
.nth(usize::from(x.checked_sub(notice_start_x).unwrap()))
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
)?;
|
||||
} else {
|
||||
write!(buf, " ").unwrap();
|
||||
write!(buf, " ")?;
|
||||
}
|
||||
}
|
||||
|
||||
// New line if it isn't the last line
|
||||
if y != h.get().checked_sub(1).unwrap() {
|
||||
writeln!(
|
||||
buf,
|
||||
"{reset}",
|
||||
reset = color("&r", color_mode).expect("reset should be valid"),
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(buf, "{reset}", reset = color("&r", color_mode)?)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-3
@@ -135,9 +135,17 @@ class RGB:
|
||||
:return: RGB object
|
||||
"""
|
||||
hex = hex.lstrip("#")
|
||||
r = int(hex[0:2], 16)
|
||||
g = int(hex[2:4], 16)
|
||||
b = int(hex[4:6], 16)
|
||||
|
||||
if len(hex) == 6:
|
||||
r = int(hex[0:2], 16)
|
||||
g = int(hex[2:4], 16)
|
||||
b = int(hex[4:6], 16)
|
||||
elif len(hex) == 3:
|
||||
r = int(hex[0], 16)
|
||||
g = int(hex[1], 16)
|
||||
b = int(hex[2], 16)
|
||||
else:
|
||||
raise ValueError(f"Error: invalid hex length")
|
||||
return cls(r, g, b)
|
||||
|
||||
def to_ansi_rgb(self, foreground: bool = True) -> str:
|
||||
|
||||
+66
-5
@@ -5,6 +5,7 @@ import argparse
|
||||
import datetime
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import traceback
|
||||
from itertools import permutations, islice
|
||||
@@ -17,7 +18,7 @@ from .constants import *
|
||||
from .font_logo import get_font_logo
|
||||
from .models import Config
|
||||
from .neofetch_util import *
|
||||
from .presets import PRESETS
|
||||
from .presets import PRESETS, ColorProfile
|
||||
|
||||
|
||||
def check_config(path) -> Config:
|
||||
@@ -318,9 +319,49 @@ def create_config() -> Config:
|
||||
backend = select_backend()
|
||||
update_title('Selected backend', backend)
|
||||
|
||||
##############################
|
||||
# 7. Custom ASCII file
|
||||
custom_ascii_path = None
|
||||
clear_screen(title)
|
||||
if literal_input('Do you want to specify a custom ASCII file?', ['y', 'n'], 'n') == 'y':
|
||||
while True:
|
||||
path_input = input('Path to custom ASCII file (must be .txt and UTF-8 encoded): ').strip()
|
||||
if not path_input:
|
||||
printc('&cNo path entered. Skipping custom ASCII file.')
|
||||
break
|
||||
|
||||
custom_path = Path(path_input)
|
||||
if not custom_path.is_file():
|
||||
printc(f'&cError: File not found at {path_input}')
|
||||
if literal_input('Try again?', ['y', 'n'], 'y') == 'n':
|
||||
break
|
||||
continue
|
||||
|
||||
if not custom_path.suffix == '.txt':
|
||||
printc(f'&cError: File must have a .txt extension. Found {custom_path.suffix}')
|
||||
if literal_input('Try again?', ['y', 'n'], 'y') == 'n':
|
||||
break
|
||||
continue
|
||||
|
||||
try:
|
||||
custom_path.read_text('utf-8')
|
||||
custom_ascii_path = str(custom_path)
|
||||
update_title('Custom ASCII file', custom_ascii_path)
|
||||
break
|
||||
except UnicodeDecodeError:
|
||||
printc(f'&cError: File is not UTF-8 encoded.')
|
||||
if literal_input('Try again?', ['y', 'n'], 'y') == 'n':
|
||||
break
|
||||
continue
|
||||
except Exception as e:
|
||||
printc(f'&cAn unexpected error occurred: {e}')
|
||||
if literal_input('Try again?', ['y', 'n'], 'y') == 'n':
|
||||
break
|
||||
continue
|
||||
|
||||
# Create config
|
||||
clear_screen(title)
|
||||
c = Config(preset, color_system, light_dark, lightness, color_alignment, backend)
|
||||
c = Config(preset, color_system, light_dark, lightness, color_alignment, backend, custom_ascii_path=custom_ascii_path)
|
||||
|
||||
# Save config
|
||||
print()
|
||||
@@ -338,7 +379,7 @@ def create_parser() -> argparse.ArgumentParser:
|
||||
|
||||
parser.add_argument('-c', '--config', action='store_true', help=color(f'Configure hyfetch'))
|
||||
parser.add_argument('-C', '--config-file', dest='config_file', default=CONFIG_PATH, help=f'Use another config file')
|
||||
parser.add_argument('-p', '--preset', help=f'Use preset', choices=list(PRESETS.keys()) + ['random'])
|
||||
parser.add_argument('-p', '--preset', help=f'Use preset or comma-separated hex color list (e.g., "#ff0000,#00ff00,#0000ff")')
|
||||
parser.add_argument('-m', '--mode', help=f'Color mode', choices=['8bit', 'rgb'])
|
||||
parser.add_argument('-b', '--backend', help=f'Choose a *fetch backend', choices=['qwqfetch', 'neofetch', 'fastfetch', 'fastfetch-old'])
|
||||
parser.add_argument('--args', help=f'Additional arguments pass-through to backend')
|
||||
@@ -451,7 +492,21 @@ def run():
|
||||
GLOBAL_CFG.is_light = config.light_dark == 'light'
|
||||
|
||||
# Get preset
|
||||
preset = PRESETS.get(config.preset)
|
||||
preset = None
|
||||
if config.preset in PRESETS:
|
||||
preset = PRESETS.get(config.preset)
|
||||
elif '#' in config.preset:
|
||||
colors = [color.strip() for color in config.preset.split(',')]
|
||||
|
||||
for color in colors:
|
||||
if not (color.startswith('#') and len(color) in [4, 7] and all(c in '0123456789abcdefABCDEF' for c in color[1:])):
|
||||
print(f'Error: invalid hex color "{color}"')
|
||||
preset = ColorProfile(colors)
|
||||
else:
|
||||
print(f'Preset should be a comma-separated list of hex colors, or one of the following: {', '.join(sorted(PRESETS.keys()))}')
|
||||
|
||||
if preset is None:
|
||||
exit(1)
|
||||
|
||||
# Lighten (args > config)
|
||||
if args.scale:
|
||||
@@ -463,8 +518,14 @@ def run():
|
||||
|
||||
# Run
|
||||
try:
|
||||
asc = get_distro_ascii() if not args.ascii_file else Path(args.ascii_file).read_text("utf-8")
|
||||
if config.custom_ascii_path:
|
||||
asc = Path(config.custom_ascii_path).read_text("utf-8")
|
||||
elif args.ascii_file:
|
||||
asc = Path(args.ascii_file).read_text("utf-8")
|
||||
else:
|
||||
asc = get_distro_ascii()
|
||||
asc = config.color_align.recolor_ascii(asc, preset)
|
||||
asc = '\n'.join(asc.split('\n')[1:])
|
||||
neofetch_util.run(asc, config.backend, config.args or '')
|
||||
except Exception as e:
|
||||
print(f'Error: {e}')
|
||||
|
||||
@@ -20,6 +20,7 @@ class Config:
|
||||
distro: str | None = None
|
||||
pride_month_shown: list[int] = field(default_factory=list) # This is deprecated, see issue #136
|
||||
pride_month_disable: bool = False
|
||||
custom_ascii_path: str | None = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
|
||||
@@ -1011,4 +1011,85 @@ PRESETS: dict[str, ColorProfile] = {
|
||||
"#dc0045",
|
||||
"#e0608e"
|
||||
]),
|
||||
|
||||
# Adding libragender flags https://lgbtqia.wiki/wiki/Libragender
|
||||
# Sourced from https://lgbtqia.wiki/wiki/Libragender
|
||||
'libragender': ColorProfile([
|
||||
"#000000",
|
||||
"#808080",
|
||||
"#92D8E9",
|
||||
"#FFF544",
|
||||
"#FFB0CA",
|
||||
"#808080",
|
||||
"#000000"
|
||||
]),
|
||||
|
||||
# Sourced from https://lgbtqia.wiki/wiki/Librafeminine
|
||||
'librafeminine': ColorProfile([
|
||||
"#000000",
|
||||
"#A3A3A3",
|
||||
"#FFFFFF",
|
||||
"#C6568F",
|
||||
"#FFFFFF",
|
||||
"#A3A3A3",
|
||||
"#000000"
|
||||
]),
|
||||
|
||||
# Sourced from https://lgbtqia.wiki/wiki/Libramasculine
|
||||
'libramasculine': ColorProfile([
|
||||
"#000000",
|
||||
"#A3A3A3",
|
||||
"#FFFFFF",
|
||||
"#56C5C5",
|
||||
"#FFFFFF",
|
||||
"#A3A3A3",
|
||||
"#000000"
|
||||
]),
|
||||
|
||||
# Sourced from https://lgbtqia.wiki/wiki/Librandrogyne
|
||||
'libraandrogyne': ColorProfile([
|
||||
"#000000",
|
||||
"#A3A3A3",
|
||||
"#FFFFFF",
|
||||
"#9186B1",
|
||||
"#FFFFFF",
|
||||
"#A3A3A3",
|
||||
"#000000"
|
||||
]),
|
||||
|
||||
# Sourced from https://lgbtqia.wiki/wiki/Libranonbinary
|
||||
'libranonbinary': ColorProfile([
|
||||
"#000000",
|
||||
"#A3A3A3",
|
||||
"#FFFFFF",
|
||||
"#FFF987",
|
||||
"#FFFFFF",
|
||||
"#A3A3A3",
|
||||
"#000000"
|
||||
]),
|
||||
|
||||
# Adding Fluidflux flags - ObsoleteDev
|
||||
# Sourced from https://gender.fandom.com/wiki/Fluidflux?file=FC90B24D-CA36-4FE2-A752-C9ABFC65E332.jpeg
|
||||
|
||||
'fluidflux A': ColorProfile([
|
||||
"#ff115f",
|
||||
"#a34aa3",
|
||||
"#00a4e7",
|
||||
"#ffdf00",
|
||||
"#000000",
|
||||
"#ffed71",
|
||||
"#85daff",
|
||||
"#dbadda",
|
||||
"#fe8db1"
|
||||
]),
|
||||
|
||||
'fluidflux B': ColorProfile([
|
||||
"#c6d1d2",
|
||||
"#f47b9d",
|
||||
"#f09f9b",
|
||||
"#e3f09e",
|
||||
"#75eeea",
|
||||
"#52d2ed",
|
||||
"#c6d1d2"
|
||||
]),
|
||||
}
|
||||
|
||||
@@ -1823,6 +1823,10 @@ get_model() {
|
||||
iPod5,1): "iPod touch 5G" ;;
|
||||
iPod7,1): "iPod touch 6G" ;;
|
||||
iPod9,1): "iPod touch 7G" ;;
|
||||
|
||||
AppleTV2,1): "Apple TV 2" ;;
|
||||
AppleTV3,1): "Apple TV 3" ;;
|
||||
AppleTV3,2): "Apple TV 3 (2013)" ;;
|
||||
esac
|
||||
|
||||
model=$_
|
||||
@@ -3232,6 +3236,10 @@ END
|
||||
"iPad7,"[1-4]): "Apple A10X Fusion (6) @ 2.39GHz" ;;
|
||||
"iPad8,"[1-8]): "Apple A12X Bionic (8) @ 2.49GHz" ;;
|
||||
"iPad8,9" | "iPad8,1"[0-2]): "Apple A12Z Bionic (8) @ 2.49GHz" ;;
|
||||
|
||||
"AppleTV2,1"): "Apple A4 (1) @ 1GHz" ;;
|
||||
"AppleTV3,1"): "Apple A5 (1) @ 1GHz" ;;
|
||||
"AppleTV3,2"): "Apple A5 (1) @ 1GHz" ;;
|
||||
esac
|
||||
cpu="$_"
|
||||
;;
|
||||
@@ -3537,10 +3545,12 @@ get_gpu() {
|
||||
"iPhone OS")
|
||||
case $kernel_machine in
|
||||
"iPhone1,"[1-2]): "PowerVR MBX Lite 3D" ;;
|
||||
"iPhone2,1" | "iPhone3,"[1-3] | "iPod3,1" | "iPod4,1" | "iPad1,"[1-2]):
|
||||
"iPhone2,1" | "iPhone3,"[1-3] | "iPod3,1" | "iPod4,1" | "iPad1,"[1-2] | "AppleTV2,1"):
|
||||
"PowerVR SGX535"
|
||||
;;
|
||||
"iPhone4,1" | "iPad2,"[1-7] | "iPod5,1"): "PowerVR SGX543MP2" ;;
|
||||
"iPhone4,1" | "iPad2,"[1-7] | "iPod5,1" | "AppleTV3,"[1-2]):
|
||||
"PowerVR SGX543MP2"
|
||||
;;
|
||||
"iPhone5,"[1-4]): "PowerVR SGX543MP3" ;;
|
||||
"iPhone6,"[1-2] | "iPad4,"[1-9]): "PowerVR G6430" ;;
|
||||
"iPhone7,"[1-2] | "iPod7,1" | "iPad5,"[1-2]): "PowerVR GX6450" ;;
|
||||
@@ -4167,6 +4177,7 @@ get_resolution() {
|
||||
"iPad13,"[1-2] | "iPad13,1"[6-9]): "1640x2360" ;;
|
||||
"iPad8,"[1-4] | "iPad8,"[9-10] | "iPad13,"[4-7] | "iPad14,"[3-6]): "1668x2388" ;;
|
||||
"iPad6,"[7-8] | "iPad7,"[1-2] | "iPad8,"[5-8] | "iPad8,1"[1-2] | "iPad13,"[8-9] | "iPad13,1"[0-1] | "iPad14,"[5-6]): "2048x2732" ;;
|
||||
"AppleTV"*) return ;;
|
||||
esac
|
||||
resolution="$_"
|
||||
;;
|
||||
|
||||
Reference in New Issue
Block a user