akari_theme/
lib.rs

1mod color;
2#[cfg(feature = "generator")]
3mod generator;
4mod palette;
5#[cfg(feature = "generator")]
6pub mod terminal;
7
8pub use color::Rgb;
9#[cfg(feature = "generator")]
10pub use generator::Generator;
11pub use palette::Palette;
12
13#[cfg(feature = "generator")]
14use std::path::PathBuf;
15
16#[derive(Debug, thiserror::Error)]
17pub enum Error {
18    #[error("io error: {0}")]
19    Io(#[from] std::io::Error),
20    #[error("invalid palette: {0}")]
21    ParsePalette(#[from] toml::de::Error),
22    #[error("unresolved reference: {0}")]
23    UnresolvedRef(String),
24    #[cfg(feature = "generator")]
25    #[error("template {context}: {source}")]
26    Template {
27        context: &'static str,
28        #[source]
29        source: tera::Error,
30    },
31    #[error("invalid hex color: {0}")]
32    InvalidHex(String),
33    #[cfg(feature = "generator")]
34    #[error("non-UTF-8 path: {0}")]
35    InvalidPath(PathBuf),
36    #[cfg(feature = "generator")]
37    #[error("project root not found (expected palette/ and Cargo.toml)")]
38    ProjectRootNotFound,
39    #[cfg(feature = "generator")]
40    #[error("plist error: {0}")]
41    Plist(#[from] plist::Error),
42    #[cfg(feature = "generator")]
43    #[error("plist output was not valid UTF-8")]
44    PlistUtf8,
45    #[error("invalid color expression: {0}")]
46    InvalidColorExpr(String),
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum Variant {
51    Night,
52    Dawn,
53}
54
55impl Variant {
56    #[must_use]
57    pub const fn name(self) -> &'static str {
58        match self {
59            Self::Night => "night",
60            Self::Dawn => "dawn",
61        }
62    }
63
64    #[must_use]
65    pub const fn title(self) -> &'static str {
66        match self {
67            Self::Night => "Night",
68            Self::Dawn => "Dawn",
69        }
70    }
71
72    #[must_use]
73    pub const fn palette_filename(self) -> &'static str {
74        match self {
75            Self::Night => "akari-night.toml",
76            Self::Dawn => "akari-dawn.toml",
77        }
78    }
79}
80
81pub const VARIANTS: [Variant; 2] = [Variant::Night, Variant::Dawn];
82
83#[cfg(feature = "generator")]
84/// Content of an artifact
85#[derive(Debug, Clone)]
86pub enum ArtifactContent {
87    /// Text content to be written
88    Text(String),
89    /// Source path to be copied
90    Copy(PathBuf),
91}
92
93#[cfg(feature = "generator")]
94/// A generated or copied file
95#[derive(Debug, Clone)]
96pub struct Artifact {
97    /// Relative path from output root (e.g., "helix/akari-night.toml")
98    pub rel_path: PathBuf,
99    /// Content or source path
100    pub content: ArtifactContent,
101}
102
103#[cfg(feature = "generator")]
104impl Artifact {
105    pub fn text(rel_path: impl Into<PathBuf>, content: impl Into<String>) -> Self {
106        Self {
107            rel_path: rel_path.into(),
108            content: ArtifactContent::Text(content.into()),
109        }
110    }
111
112    pub fn copy(rel_path: impl Into<PathBuf>, src: impl Into<PathBuf>) -> Self {
113        Self {
114            rel_path: rel_path.into(),
115            content: ArtifactContent::Copy(src.into()),
116        }
117    }
118}
119
120#[cfg(feature = "generator")]
121pub fn find_project_root() -> Result<PathBuf, Error> {
122    let mut current = std::env::current_dir()?;
123
124    loop {
125        if current.join("palette").is_dir() && current.join("Cargo.toml").is_file() {
126            return Ok(current);
127        }
128
129        if !current.pop() {
130            return Err(Error::ProjectRootNotFound);
131        }
132    }
133}