use std::{fmt, str::FromStr};
use crate::{continuous, generated, normalize::key_matches, Error, PaletteKind, Rgb, Rgba};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ItermVariant {
Normal,
Bright,
}
impl ItermVariant {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Normal => "normal",
Self::Bright => "bright",
}
}
pub fn parse(input: &str) -> Result<Self, Error> {
let trimmed = input.trim();
if trimmed.eq_ignore_ascii_case("normal") {
Ok(Self::Normal)
} else if trimmed.eq_ignore_ascii_case("bright") {
Ok(Self::Bright)
} else {
Err(Error::UnknownItermVariant {
variant: input.to_owned(),
})
}
}
}
impl fmt::Display for ItermVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for ItermVariant {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::parse(input)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ItermChannel {
Blue,
Yellow,
Red,
Cyan,
Green,
Magenta,
}
impl ItermChannel {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Blue => "Blue",
Self::Yellow => "Yellow",
Self::Red => "Red",
Self::Cyan => "Cyan",
Self::Green => "Green",
Self::Magenta => "Magenta",
}
}
#[must_use]
pub const fn index(self) -> usize {
match self {
Self::Blue => 0,
Self::Yellow => 1,
Self::Red => 2,
Self::Cyan => 3,
Self::Green => 4,
Self::Magenta => 5,
}
}
}
pub const ITERM_CHANNELS: [ItermChannel; 6] = [
ItermChannel::Blue,
ItermChannel::Yellow,
ItermChannel::Red,
ItermChannel::Cyan,
ItermChannel::Green,
ItermChannel::Magenta,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ItermPalette {
name: &'static str,
normal: &'static [Rgb; 6],
bright: &'static [Rgb; 6],
}
impl ItermPalette {
#[must_use]
pub const fn new(
name: &'static str,
normal: &'static [Rgb; 6],
bright: &'static [Rgb; 6],
) -> Self {
Self {
name,
normal,
bright,
}
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub const fn kind(&self) -> PaletteKind {
PaletteKind::Discrete
}
#[must_use]
pub const fn colors(&self, variant: ItermVariant) -> &'static [Rgb; 6] {
match variant {
ItermVariant::Normal => self.normal,
ItermVariant::Bright => self.bright,
}
}
#[must_use]
pub const fn color(&self, variant: ItermVariant, channel: ItermChannel) -> Rgb {
self.colors(variant)[channel.index()]
}
pub fn take(&self, variant: ItermVariant, n: usize) -> Result<Vec<Rgb>, Error> {
let colors = self.colors(variant);
if n > colors.len() {
return Err(Error::TooManyItermColorsRequested {
palette: self.name,
variant: variant.as_str(),
requested: n,
available: colors.len(),
});
}
Ok(colors[..n].to_vec())
}
pub fn take_hex(&self, variant: ItermVariant, n: usize) -> Result<Vec<String>, Error> {
self.take(variant, n)
.map(|colors| colors.into_iter().map(Rgb::to_hex_string).collect())
}
pub fn take_rgba(
&self,
variant: ItermVariant,
n: usize,
alpha: f32,
) -> Result<Vec<Rgba>, Error> {
let alpha = continuous::continuous_alpha(alpha)?;
self.take(variant, n).map(|colors| {
colors
.into_iter()
.map(|color| color.with_alpha_u8(alpha))
.collect()
})
}
pub fn cycle(&self, variant: ItermVariant) -> impl Iterator<Item = Rgb> + '_ {
self.colors(variant).iter().copied().cycle()
}
}
#[must_use]
pub fn iterm_palettes() -> &'static [ItermPalette] {
let palettes = generated::iterm::ITERM_PALETTES;
debug_assert_eq!(palettes.len(), generated::iterm::ITERM_PALETTE_COUNT);
debug_assert_eq!(generated::iterm::ITERM_VARIANT_COUNT, 2);
debug_assert_eq!(generated::iterm::ITERM_COLORS_PER_VARIANT, 6);
debug_assert_eq!(
palettes.len()
* generated::iterm::ITERM_VARIANT_COUNT
* generated::iterm::ITERM_COLORS_PER_VARIANT,
generated::iterm::ITERM_TOTAL_COLOR_COUNT
);
debug_assert_eq!(
generated::iterm::ITERM_DATA_SOURCE,
"ggsci/R/palettes-iterm.R"
);
palettes
}
pub fn iterm_palette(name: &str) -> Result<&'static ItermPalette, Error> {
iterm_palettes()
.iter()
.find(|palette| key_matches(palette.name(), name))
.ok_or_else(|| Error::UnknownItermPalette {
palette: name.to_owned(),
})
}
pub fn iterm_palette_names() -> impl Iterator<Item = &'static str> {
iterm_palettes().iter().map(ItermPalette::name)
}
#[must_use]
pub const fn iterm_palette_count() -> usize {
generated::iterm::ITERM_PALETTE_COUNT
}
#[must_use]
pub const fn iterm_total_color_count() -> usize {
generated::iterm::ITERM_TOTAL_COLOR_COUNT
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::iterm_palette_names;
use crate::normalize::normalize_key;
#[test]
fn normalized_theme_names_are_unique() {
let names = iterm_palette_names().collect::<Vec<_>>();
let normalized = names
.iter()
.map(|name| normalize_key(name))
.collect::<HashSet<_>>();
assert_eq!(normalized.len(), names.len());
}
}