use ratatui::style::Color;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum Language {
#[default]
Gleam,
Elixir,
Erlang,
All,
}
impl Language {
pub fn hex_filter(self) -> Option<&'static str> {
match self {
Language::Gleam => Some("build_tool:gleam"),
Language::Elixir => Some("build_tool:mix"),
Language::Erlang => Some("build_tool:rebar3"),
Language::All => None,
}
}
pub fn label(self) -> &'static str {
match self {
Language::Gleam => "Gleam",
Language::Elixir => "Elixir",
Language::Erlang => "Erlang",
Language::All => "All BEAM",
}
}
pub fn badge(self) -> &'static str {
match self {
Language::Gleam => "glm",
Language::Elixir => "ex ",
Language::Erlang => "erl",
Language::All => " ? ",
}
}
pub fn accent(self) -> Color {
match self {
Language::Gleam => Color::Rgb(255, 121, 198), Language::Elixir => Color::Rgb(100, 67, 217), Language::Erlang => Color::Rgb(163, 62, 40), Language::All => Color::Rgb(97, 218, 251), }
}
pub fn cycle(self) -> Self {
match self {
Language::Gleam => Language::Elixir,
Language::Elixir => Language::Erlang,
Language::Erlang => Language::All,
Language::All => Language::Gleam,
}
}
pub fn cycle_back(self) -> Self {
match self {
Language::Gleam => Language::All,
Language::All => Language::Erlang,
Language::Erlang => Language::Elixir,
Language::Elixir => Language::Gleam,
}
}
pub fn all() -> &'static [Language] {
&[
Language::Gleam,
Language::Elixir,
Language::Erlang,
Language::All,
]
}
}
impl std::fmt::Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Language::Gleam => "gleam",
Language::Elixir => "elixir",
Language::Erlang => "erlang",
Language::All => "all",
})
}
}
impl std::str::FromStr for Language {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"gleam" => Ok(Language::Gleam),
"elixir" => Ok(Language::Elixir),
"erlang" => Ok(Language::Erlang),
"all" => Ok(Language::All),
other => Err(format!(
"unknown language: '{other}' (valid: gleam, elixir, erlang, all)"
)),
}
}
}
pub fn infer_language(build_tools: &[String]) -> Language {
for tool in build_tools {
if tool == "gleam" {
return Language::Gleam;
}
}
for tool in build_tools {
match tool.as_str() {
"rebar3" | "erlang.mk" | "erlang" => return Language::Erlang,
_ => {}
}
}
for tool in build_tools {
if tool == "mix" {
return Language::Elixir;
}
}
Language::All
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Sort {
#[default]
RecentDownloads,
Downloads,
Updated,
Newest,
Name,
}
impl Sort {
pub fn api_param(self) -> &'static str {
match self {
Sort::RecentDownloads => "recent_downloads",
Sort::Downloads => "downloads",
Sort::Updated => "updated_at",
Sort::Newest => "inserted_at",
Sort::Name => "name",
}
}
pub fn label(self) -> &'static str {
match self {
Sort::RecentDownloads => "Recent dls",
Sort::Downloads => "All dls",
Sort::Updated => "Last updated",
Sort::Newest => "Newest first",
Sort::Name => "Name A→Z",
}
}
pub fn cycle(self) -> Self {
match self {
Sort::RecentDownloads => Sort::Downloads,
Sort::Downloads => Sort::Updated,
Sort::Updated => Sort::Newest,
Sort::Newest => Sort::Name,
Sort::Name => Sort::RecentDownloads,
}
}
}
impl std::str::FromStr for Sort {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"recent_downloads" | "recent" => Ok(Sort::RecentDownloads),
"downloads" | "all" => Ok(Sort::Downloads),
"updated_at" | "updated" => Ok(Sort::Updated),
"inserted_at" | "newest" => Ok(Sort::Newest),
"name" => Ok(Sort::Name),
other => Err(format!("unknown sort: '{other}'")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum View {
List,
Detail,
Settings,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SettingRow {
GithubToken,
KeepWeeks,
Compress,
ClearGhCache,
}
impl SettingRow {
pub fn all() -> &'static [Self] {
&[
Self::GithubToken,
Self::KeepWeeks,
Self::Compress,
Self::ClearGhCache,
]
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum OutputFormat {
Json,
Compact,
Detail,
}
impl std::str::FromStr for OutputFormat {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"json" => Ok(OutputFormat::Json),
"compact" => Ok(OutputFormat::Compact),
"detail" => Ok(OutputFormat::Detail),
other => Err(format!(
"unknown output format: '{other}' (valid: json, compact, detail)"
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn infer_gleam() {
assert_eq!(infer_language(&["gleam".into()]), Language::Gleam);
}
#[test]
fn infer_elixir() {
assert_eq!(infer_language(&["mix".into()]), Language::Elixir);
}
#[test]
fn infer_erlang_rebar3() {
assert_eq!(infer_language(&["rebar3".into()]), Language::Erlang);
}
#[test]
fn infer_erlang_mk() {
assert_eq!(infer_language(&["erlang.mk".into()]), Language::Erlang);
}
#[test]
fn infer_unknown() {
assert_eq!(infer_language(&["cargo".into()]), Language::All);
}
#[test]
fn infer_empty() {
assert_eq!(infer_language(&[]), Language::All);
}
#[test]
fn infer_gleam_beats_mix() {
assert_eq!(
infer_language(&["mix".into(), "gleam".into()]),
Language::Gleam
);
assert_eq!(
infer_language(&["gleam".into(), "mix".into()]),
Language::Gleam
);
}
#[test]
fn infer_gleam_beats_erlang() {
assert_eq!(
infer_language(&["rebar3".into(), "gleam".into()]),
Language::Gleam
);
}
#[test]
fn cycle_forward() {
assert_eq!(Language::Gleam.cycle(), Language::Elixir);
assert_eq!(Language::Elixir.cycle(), Language::Erlang);
assert_eq!(Language::Erlang.cycle(), Language::All);
assert_eq!(Language::All.cycle(), Language::Gleam);
}
#[test]
fn cycle_back_is_inverse() {
for &lang in Language::all() {
assert_eq!(lang.cycle().cycle_back(), lang);
}
}
#[test]
fn language_display_roundtrip() {
for &lang in Language::all() {
assert_eq!(lang.to_string().parse::<Language>().unwrap(), lang);
}
}
#[test]
fn language_from_str_case_insensitive() {
assert_eq!("Gleam".parse::<Language>().unwrap(), Language::Gleam);
assert_eq!("ELIXIR".parse::<Language>().unwrap(), Language::Elixir);
assert!("unknown".parse::<Language>().is_err());
}
#[test]
fn sort_cycle_wraps() {
assert_eq!(Sort::Name.cycle(), Sort::RecentDownloads);
}
}