use std::fmt;
use std::path::Path;
use crate::colors::Colors;
use anyhow::Result;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct WalSpecial {
pub background: String,
pub foreground: String,
pub cursor: String,
}
#[derive(Deserialize)]
pub struct WalColors {
pub color0 : String,
pub color1 : String,
pub color2 : String,
pub color3 : String,
pub color4 : String,
pub color5 : String,
pub color6 : String,
pub color7 : String,
pub color8 : String,
pub color9 : String,
pub color10: String,
pub color11: String,
pub color12: String,
pub color13: String,
pub color14: String,
pub color15: String,
}
#[derive(Deserialize)]
pub struct WalTheme {
pub special: WalSpecial,
pub colors: WalColors,
}
#[derive(Deserialize)]
pub struct TerminalSexy {
pub name: String,
pub author: String,
pub color: Vec<String>,
pub foreground: String,
pub background: String,
}
#[derive(Debug, Clone, clap::ValueEnum)]
pub enum Schemes {
Pywal,
TerminalSexy,
Wallust,
}
use glob::glob;
use crate::config::WalStr;
pub fn search_theme_or_cs(name: &str, quiet: bool, confpath: &Path, schemes: Option<Schemes>) -> Result<(WalStr, Colors)> {
let p = confpath.join("colorschemes").join(name);
if p.exists() { return Ok((WalStr::Path(p.clone()), try_all_schemes(&p, quiet)?)); }
let myglob = glob(&format!("{}*", p.display())).expect("glob pattern is ok");
let count = myglob.count();
if count == 1 { for i in glob(&format!("{}*", p.display())).expect("glob pattern is ok") {
match i {
Ok(o) => {
let cs = match schemes {
Some(s) => read_scheme(&o, &s),
None => try_all_schemes(&o, quiet),
};
return Ok((WalStr::Path(o), cs?))
},
Err(e) => anyhow::bail!("Found match for '{name}', but could not opened: {e}"),
}
}
anyhow::bail!("Should be unreacheable");
} else if count == 0 { match built_in_theme(name, quiet) {
Some(s) => Ok((WalStr::Theme(name.to_owned()), s)),
None => anyhow::bail!("No matches for '{name}'.") }
} else { anyhow::bail!("There are many matches for '{name}', consider renaming.") }
}
pub fn read_scheme(f: &Path, format: &Schemes) -> Result<Colors> {
let contents = std::fs::read_to_string(f)?;
deser_scheme(&contents, format)
}
fn deser_scheme(contents: &str, format: &Schemes) -> Result<Colors> {
match format {
Schemes::Pywal => {
let ser: WalTheme = serde_json::from_str(contents)?;
ser.to_colors()
},
Schemes::TerminalSexy => {
let ser: TerminalSexy = serde_json::from_str(contents)?;
ser.to_colors()
},
Schemes::Wallust => {
let ser: Colors = serde_json::from_str(contents)?;
Ok(ser)
},
}
}
use owo_colors::{OwoColorize, AnsiColors};
pub fn try_all_schemes(file: &Path, quiet: bool) -> Result<Colors> {
let info = "I".blue().bold().to_string();
let cs = "colorscheme format".magenta().bold().to_string();
let a = [
Schemes::Pywal,
Schemes::TerminalSexy,
Schemes::Wallust,
];
let contents = std::fs::read_to_string(file)?;
for i in &a {
match deser_scheme(&contents, i) {
Ok(o) => {
if ! quiet { println!("[{info}] {cs}: Using {}", i.to_string().to_ascii_lowercase().color(i.col())); }
return Ok(o);
},
Err(_) => { continue; },
}
}
let (themes, last) = a.split_at(a.len() - 1);
let themes = themes.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(", ");
anyhow::bail!("{} was not in the {themes} or {} format.", file.display(), last[0])
}
#[cfg(feature = "themes")]
pub const RAND: &str = "random";
#[cfg(feature = "themes")]
pub const LIST: &str = "list";
#[cfg(feature = "themes")]
pub fn list_themes() {
let mut cols = wallust_themes::COLS_KEY;
cols.sort();
let cols = cols
.iter()
.map(|x| format!("- {x}"))
.collect::<Vec<_>>()
.join("\n")
;
println!("\
{themes}:
{cols}
{extra}:
- {RAND} (select a random theme)
- {LIST} (lists available themes) \
",
extra = "Extra".bold().green(),
themes = "Available themes".bold().green(),
)
}
#[cfg(feature = "themes")]
pub fn built_in_theme(theme_key: &str, quiet: bool) -> Option<Colors> {
use wallust_themes::COLS_KEY;
use wallust_themes::COLS_VALUE;
use crate::colors::Myrgb;
let index = if theme_key == RAND {
let i = fastrand::usize(0..COLS_KEY.len());
if ! quiet { println!("[{info}] {theme}: randomly selected {name}", theme = "theme".magenta().bold(), name = COLS_KEY[i], info = "I".blue().bold()); }
Some(i)
} else {
COLS_KEY.iter().position(|&x| x == theme_key)
};
match index {
Some(s) => {
let c = COLS_VALUE[s];
let c = c
.iter()
.map(|x| {
let [b, g, r, _a] = x.to_le_bytes();
let s = Srgb::<u8>::new(r, g, b);
Myrgb(s.into_format())
})
.collect::<Vec<_>>();
Some(
Colors {
color0: c[0],
color1: c[1],
color2: c[2],
color3: c[3],
color4: c[4],
color5: c[5],
color6: c[6],
color7: c[7],
color8: c[8],
color9: c[9],
color10: c[10],
color11: c[11],
color12: c[12],
color13: c[13],
color14: c[14],
color15: c[15],
background: c[16],
foreground: c[17],
cursor: c[18],
}
)
},
None => None,
}
}
impl Schemes {
pub fn col(&self) -> AnsiColors {
match self {
Schemes::Pywal => AnsiColors::Blue,
Schemes::TerminalSexy => AnsiColors::Magenta,
Schemes::Wallust => AnsiColors::Red,
}
}
}
impl fmt::Display for Schemes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Schemes::Pywal => write!(f, "Pywal"),
Schemes::TerminalSexy => write!(f, "Terminal-Sexy"),
Schemes::Wallust => write!(f, "Wallust"),
}
}
}
use palette::Srgb;
impl WalTheme {
fn to_colors(&self) -> Result<Colors> {
let c = &self.colors;
let s = &self.special;
Ok(
Colors {
cursor: s.cursor.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
background: s.background.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
foreground: s.foreground.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color0 : c.color0 .parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color1 : c.color1 .parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color2 : c.color2 .parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color3 : c.color3 .parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color4 : c.color4 .parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color5 : c.color5 .parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color6 : c.color6 .parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color7 : c.color7 .parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color8 : c.color8 .parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color9 : c.color9 .parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color10: c.color10.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color11: c.color11.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color12: c.color12.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color13: c.color13.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color14: c.color14.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color15: c.color15.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
}
)
}
}
impl TerminalSexy {
fn to_colors(&self) -> Result<Colors> {
let c = &self.color;
let fg = &self.foreground;
let bg = &self.background;
Ok(
Colors {
cursor: fg.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
background: bg.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
foreground: fg.parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color0 : c[0 ].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color1 : c[1 ].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color2 : c[2 ].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color3 : c[3 ].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color4 : c[4 ].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color5 : c[5 ].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color6 : c[6 ].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color7 : c[7 ].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color8 : c[8 ].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color9 : c[9 ].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color10: c[10].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color11: c[11].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color12: c[12].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color13: c[13].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color14: c[14].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
color15: c[15].parse::<Srgb<u8>>()?.into_format::<u8>().into(),
}
)
}
}