oas3-gen 0.23.6

A rust type generator for OpenAPI v3.1.x specification.
use std::io::IsTerminal;

use clap::{ValueEnum, builder::styling::Ansi256Color};
use comfy_table::Color as ComfyColor;
use crossterm::style::Color;

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum ColorMode {
  Always,
  Auto,
  Never,
}

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum ThemeMode {
  Dark,
  Light,
  Auto,
}

pub enum Theme {
  Dark,
  Light,
}

pub struct Colors {
  enabled: bool,
  theme: Theme,
}

pub trait IntoComfyColor {
  fn into(self) -> ComfyColor;
}

impl IntoComfyColor for Color {
  fn into(self) -> ComfyColor {
    match self {
      Color::Reset => ComfyColor::Reset,
      Color::Black => ComfyColor::Black,
      Color::DarkGrey => ComfyColor::DarkGrey,
      Color::Red => ComfyColor::Red,
      Color::DarkRed => ComfyColor::DarkRed,
      Color::Green => ComfyColor::Green,
      Color::DarkGreen => ComfyColor::DarkGreen,
      Color::Yellow => ComfyColor::Yellow,
      Color::DarkYellow => ComfyColor::DarkYellow,
      Color::Blue => ComfyColor::Blue,
      Color::DarkBlue => ComfyColor::DarkBlue,
      Color::Magenta => ComfyColor::Magenta,
      Color::DarkMagenta => ComfyColor::DarkMagenta,
      Color::Cyan => ComfyColor::Cyan,
      Color::DarkCyan => ComfyColor::DarkCyan,
      Color::White => ComfyColor::White,
      Color::Grey => ComfyColor::Grey,
      Color::Rgb { r, g, b } => ComfyColor::Rgb { r, g, b },
      Color::AnsiValue(val) => ComfyColor::AnsiValue(val),
    }
  }
}

impl Colors {
  pub const fn new(enabled: bool, theme: Theme) -> Self {
    Self { enabled, theme }
  }

  pub const fn timestamp(&self) -> Color {
    if !self.enabled {
      return Color::Reset;
    }

    match self.theme {
      Theme::Dark => Color::Rgb { r: 118, g: 166, b: 166 },
      Theme::Light => Color::Rgb { r: 92, g: 62, b: 38 },
    }
  }

  pub const fn primary(&self) -> Color {
    if !self.enabled {
      return Color::Reset;
    }

    match self.theme {
      Theme::Dark => Color::Rgb { r: 191, g: 126, b: 4 },
      Theme::Light => Color::Rgb { r: 70, g: 42, b: 25 },
    }
  }

  pub const fn accent(&self) -> Color {
    if !self.enabled {
      return Color::Reset;
    }

    match self.theme {
      Theme::Dark => Color::Rgb { r: 166, g: 84, b: 55 },
      Theme::Light => Color::Rgb { r: 211, g: 99, b: 70 },
    }
  }

  pub const fn info(&self) -> Color {
    if !self.enabled {
      return Color::Reset;
    }

    match self.theme {
      Theme::Dark => Color::Rgb { r: 118, g: 166, b: 166 },
      Theme::Light => Color::Rgb { r: 40, g: 111, b: 170 },
    }
  }

  pub const fn success(&self) -> Color {
    if !self.enabled {
      return Color::Reset;
    }

    match self.theme {
      Theme::Dark => Color::Rgb { r: 118, g: 166, b: 166 },
      Theme::Light => Color::Rgb { r: 34, g: 142, b: 90 },
    }
  }

  pub const fn label(&self) -> Color {
    if !self.enabled {
      return Color::Reset;
    }

    match self.theme {
      Theme::Dark => Color::Rgb { r: 217, g: 164, b: 4 },
      Theme::Light => Color::Rgb { r: 176, g: 103, b: 66 },
    }
  }

  pub const fn value(&self) -> Color {
    if !self.enabled {
      return Color::Reset;
    }

    match self.theme {
      Theme::Dark => Color::Rgb { r: 242, g: 211, b: 56 },
      Theme::Light => Color::Rgb { r: 199, g: 146, b: 76 },
    }
  }

  const fn to_clap(color: Color) -> Option<clap::builder::styling::Color> {
    use clap::builder::styling::{AnsiColor, Color as ClapColor, RgbColor};

    match color {
      Color::Black => Some(ClapColor::Ansi(AnsiColor::Black)),
      Color::Blue | Color::DarkBlue => Some(ClapColor::Ansi(AnsiColor::Blue)),
      Color::Cyan | Color::DarkCyan => Some(ClapColor::Ansi(AnsiColor::Cyan)),
      Color::DarkGreen | Color::Green => Some(ClapColor::Ansi(AnsiColor::Green)),
      Color::DarkGrey | Color::Grey => Some(ClapColor::Ansi(AnsiColor::BrightBlack)),
      Color::DarkMagenta | Color::Magenta => Some(ClapColor::Ansi(AnsiColor::Magenta)),
      Color::DarkRed | Color::Red => Some(ClapColor::Ansi(AnsiColor::Red)),
      Color::DarkYellow | Color::Yellow => Some(ClapColor::Ansi(AnsiColor::Yellow)),
      Color::White => Some(ClapColor::Ansi(AnsiColor::White)),
      Color::AnsiValue(val) => Some(ClapColor::Ansi256(Ansi256Color(val))),
      Color::Rgb { r, g, b } => Some(ClapColor::Rgb(RgbColor(r, g, b))),
      Color::Reset => None,
    }
  }

  pub const fn clap_styles() -> clap::builder::Styles {
    use clap::builder::styling::{Style, Styles};

    let colors = Self::new(true, Theme::Dark);

    Styles::styled()
      .header(Style::new().bold().underline().fg_color(Self::to_clap(colors.label())))
      .usage(Style::new().bold().fg_color(Self::to_clap(colors.label())))
      .literal(Style::new().fg_color(Self::to_clap(colors.success())))
      .placeholder(Style::new().fg_color(Self::to_clap(colors.info())))
      .error(Style::new().bold().fg_color(Self::to_clap(colors.accent())))
      .valid(Style::new().fg_color(Self::to_clap(colors.success())))
      .invalid(Style::new().bold().fg_color(Self::to_clap(colors.accent())))
  }
}

pub fn colors_enabled(mode: ColorMode) -> bool {
  match mode {
    ColorMode::Always => true,
    ColorMode::Never => false,
    ColorMode::Auto => std::io::stdout().is_terminal(),
  }
}

pub fn detect_theme(mode: ThemeMode) -> Theme {
  match mode {
    ThemeMode::Dark => Theme::Dark,
    ThemeMode::Light => Theme::Light,
    ThemeMode::Auto => detect_terminal_theme(),
  }
}

fn detect_terminal_theme() -> Theme {
  if let Ok(colorfgbg) = std::env::var("COLORFGBG")
    && let Some(bg) = colorfgbg.split(';').next_back()
    && let Ok(bg_num) = bg.parse::<u8>()
  {
    return if bg_num >= 8 { Theme::Light } else { Theme::Dark };
  }

  if let Ok(term_program) = std::env::var("TERM_PROGRAM")
    && (term_program == "Apple_Terminal" || term_program == "iTerm.app")
    && let Ok(theme) = std::env::var("ITERM_PROFILE")
    && theme.to_lowercase().contains("light")
  {
    return Theme::Light;
  }

  Theme::Dark
}