1use crate::prelude::*;
7
8use alloc::collections::BTreeMap;
9
10use crate::error::ContentError;
11use crate::palette::{PaletteDef, line_of_key};
12use crate::ron::parse_ron;
13
14#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
17pub enum Ink {
18 White,
20 Black,
22 BlackEdged,
25 Grey,
27 Dots,
29 Diagonals,
31 Dashes,
34 Invert,
37}
38
39impl Ink {
40 pub const ALL: [Self; 8] = [
42 Self::White,
43 Self::Black,
44 Self::BlackEdged,
45 Self::Grey,
46 Self::Dots,
47 Self::Diagonals,
48 Self::Dashes,
49 Self::Invert,
50 ];
51
52 pub const fn name(self) -> &'static str {
54 match self {
55 Self::White => "white",
56 Self::Black => "black",
57 Self::BlackEdged => "black_edged",
58 Self::Grey => "grey",
59 Self::Dots => "dots",
60 Self::Diagonals => "diagonals",
61 Self::Dashes => "dashes",
62 Self::Invert => "invert",
63 }
64 }
65
66 pub fn parse(name: &str) -> Option<Self> {
68 Self::ALL.into_iter().find(|ink| ink.name() == name)
69 }
70
71 pub const fn code(self) -> [u8; 3] {
75 match self {
76 Self::White => [0xff, 0xff, 0xff],
77 Self::Black => [0x00, 0x00, 0x00],
78 Self::BlackEdged => [0x01, 0x01, 0x01],
79 Self::Grey => [0x80, 0x80, 0x80],
80 Self::Dots => [0xe0, 0xe0, 0xe0],
81 Self::Diagonals => [0xc0, 0xc0, 0xc0],
82 Self::Dashes => [0x40, 0x40, 0x40],
83 Self::Invert => [0xff, 0x00, 0xff],
84 }
85 }
86
87 pub fn from_code(rgb: [u8; 3]) -> Option<Self> {
89 Self::ALL.into_iter().find(|ink| ink.code() == rgb)
90 }
91}
92
93impl PaletteDef {
94 pub fn from_ink_source(
98 file: &str,
99 source: &str,
100 base: &PaletteDef,
101 ) -> Result<Self, Vec<ContentError>> {
102 let raw: BTreeMap<String, String> = parse_ron(file, source).map_err(|e| vec![e])?;
103 let mut errors = Vec::new();
104 let mut colors = BTreeMap::new();
105 for (name, value) in &raw {
106 if let Some(ink) = Ink::parse(value) {
107 colors.insert(name.clone(), ink.code());
108 } else {
109 let inks: Vec<&str> = Ink::ALL.iter().map(|ink| ink.name()).collect();
110 let mut e = ContentError::new(
111 file,
112 format!(
113 "colour \"{name}\": no ink \"{value}\"; the inks are {}",
114 inks.join(", ")
115 ),
116 );
117 if let Some(line) = line_of_key(source, name) {
118 e = e.at(line, None);
119 }
120 errors.push(e);
121 }
122 }
123 for name in base.colors.keys().filter(|n| !raw.contains_key(*n)) {
124 errors.push(ContentError::new(
125 file,
126 format!("missing colour \"{name}\""),
127 ));
128 }
129 if errors.is_empty() {
130 Ok(Self { colors })
131 } else {
132 Err(errors)
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 fn base(names: &[&str]) -> PaletteDef {
142 PaletteDef {
143 colors: names.iter().map(|&n| (n.to_owned(), [9, 9, 9])).collect(),
144 }
145 }
146
147 #[test]
148 fn every_ink_has_its_own_name_and_code() {
149 for ink in Ink::ALL {
150 assert_eq!(Ink::parse(ink.name()), Some(ink));
151 assert_eq!(Ink::from_code(ink.code()), Some(ink));
152 }
153 assert_eq!(Ink::parse("gray"), None);
154 assert_eq!(Ink::from_code([0x80, 0x80, 0x81]), None);
155 assert_eq!(Ink::Grey.code(), [0x80; 3]);
156 assert_eq!(Ink::Invert.name(), "invert");
157 }
158
159 #[test]
160 fn a_palette_of_inks_loads_as_their_codes() {
161 let src =
162 "{\n \"text\": \"black\",\n \"enemy\": \"grey\",\n \"own\": \"dots\",\n}";
163 let p = PaletteDef::from_ink_source("p.ron", src, &base(&["text", "enemy"]));
164 let p = p.unwrap_or_default();
165 assert_eq!(p.get("text"), Some(Ink::Black.code()));
166 assert_eq!(p.get("enemy"), Some(Ink::Grey.code()));
167 assert_eq!(p.get("own"), Some(Ink::Dots.code()));
169 assert_eq!(p.colors.len(), 3);
170 }
171
172 #[test]
173 fn an_unknown_ink_and_a_missing_colour_are_errors() {
174 let src = "{\n \"text\": \"black\",\n \"enemy\": \"#808080\",\n}";
175 let errs = PaletteDef::from_ink_source("p.ron", src, &base(&["text", "enemy", "cursor"]))
176 .err()
177 .unwrap_or_default();
178 let shown: Vec<String> = errs.iter().map(ToString::to_string).collect();
179 assert_eq!(shown.len(), 2, "{shown:?}");
180 assert_eq!(errs[0].line, Some(3));
181 assert!(
182 shown[0].contains("colour \"enemy\": no ink \"#808080\"; the inks are white, black,"),
183 "{}",
184 shown[0]
185 );
186 assert_eq!(shown[1], "p.ron: missing colour \"cursor\"");
187 assert!(PaletteDef::from_ink_source("p.ron", "nope", &base(&[])).is_err());
188 }
189}