1use crate::error::LoadPaletteErr;
7use crate::palette;
8use crate::palette::{Colors, Palette};
9use ratatui_core::style::Color;
10use std::borrow::Cow;
11use std::{array, io};
12
13pub fn store_palette(pal: &Palette, mut buf: impl io::Write) -> Result<(), io::Error> {
15 writeln!(buf, "[theme]")?;
16 writeln!(buf, "name={}", pal.theme_name)?;
17 writeln!(buf, "theme={}", pal.theme)?;
18 writeln!(buf)?;
19 writeln!(buf, "[palette]")?;
20 writeln!(buf, "name={}", pal.name)?;
21 writeln!(buf, "docs={}", pal.doc.replace('\n', "\\n"))?;
22 writeln!(buf, "generator={}", pal.generator)?;
23 writeln!(buf,)?;
24 writeln!(buf, "[color]")?;
25 if pal.generator.starts_with("light-dark") {
26 for c in Colors::array() {
27 writeln!(
28 buf,
29 "{}={}, {}",
30 *c, pal.color[*c as usize][0], pal.color[*c as usize][3]
31 )?;
32 }
33 } else if pal.generator.starts_with("color-1") {
34 for c in Colors::array() {
35 writeln!(buf, "{}={}", *c, pal.color[*c as usize][0])?;
36 }
37 } else if pal.generator.starts_with("color-2") {
38 for c in Colors::array() {
39 writeln!(
40 buf,
41 "{}={}, {}",
42 *c, pal.color[*c as usize][0], pal.color[*c as usize][4]
43 )?;
44 }
45 } else if pal.generator.starts_with("color-4") {
46 for c in Colors::array() {
47 writeln!(
48 buf,
49 "{}={}, {}, {}, {}",
50 *c,
51 pal.color[*c as usize][0],
52 pal.color[*c as usize][1],
53 pal.color[*c as usize][2],
54 pal.color[*c as usize][3]
55 )?;
56 }
57 } else if pal.generator.starts_with("color-4-dark") {
58 for c in Colors::array() {
59 writeln!(
60 buf,
61 "{}={}, {}, {}, {}",
62 *c,
63 pal.color[*c as usize][0],
64 pal.color[*c as usize][1],
65 pal.color[*c as usize][2],
66 pal.color[*c as usize][3]
67 )?;
68 }
69 } else if pal.generator.starts_with("color-8") {
70 for c in Colors::array() {
71 writeln!(
72 buf,
73 "{}={}, {}, {}, {}, {}, {}, {}, {}",
74 *c,
75 pal.color[*c as usize][0],
76 pal.color[*c as usize][1],
77 pal.color[*c as usize][2],
78 pal.color[*c as usize][3],
79 pal.color[*c as usize][4],
80 pal.color[*c as usize][5],
81 pal.color[*c as usize][6],
82 pal.color[*c as usize][7],
83 )?;
84 }
85 } else {
86 return Err(io::Error::other(LoadPaletteErr(format!(
87 "Invalid generator format {:?}",
88 pal.generator
89 ))));
90 }
91 writeln!(buf,)?;
92 writeln!(buf, "[reference]")?;
93 for (r, i) in pal.aliased.as_ref() {
94 writeln!(buf, "{}={}", r, i)?;
95 }
96 Ok(())
97}
98
99pub fn load_palette(mut r: impl io::Read) -> Result<Palette, io::Error> {
101 let mut buf = String::new();
102 r.read_to_string(&mut buf)?;
103
104 enum S {
105 Start,
106 Theme,
107 Palette,
108 Color,
109 Reference,
110 Fail(String),
111 }
112
113 let mut pal = Palette::default();
114 let mut dark = 63u8;
115
116 let mut state = S::Start;
117 'm: for l in buf.lines() {
118 let l = l.trim();
119 match state {
120 S::Start => {
121 if l == "[theme]" {
122 state = S::Theme;
123 } else if l == "[palette]" {
124 state = S::Palette;
125 } else {
126 state = S::Fail("No a valid pal-file".to_string());
127 break 'm;
128 }
129 }
130 S::Theme => {
131 if l == "[palette]" {
132 state = S::Palette;
133 } else if l.is_empty() || l.starts_with("#") {
134 } else if l.starts_with("name") {
136 if let Some(s) = l.split('=').nth(1) {
137 pal.theme_name = Cow::Owned(s.trim().to_string());
138 }
139 } else if l.starts_with("theme") {
140 if let Some(s) = l.split('=').nth(1) {
141 pal.theme = Cow::Owned(s.trim().to_string());
142 }
143 } else {
144 state = S::Fail(format!("Invalid theme property {:?}", l));
145 break 'm;
146 }
147 }
148 S::Palette => {
149 if l == "[color]" {
150 state = S::Color;
151 } else if l.is_empty() || l.starts_with("#") {
152 } else if l.starts_with("name") {
154 if let Some(s) = l.split('=').nth(1) {
155 pal.name = Cow::Owned(s.trim().to_string());
156 }
157 } else if l.starts_with("docs") {
158 if let Some(s) = l.split('=').nth(1) {
159 let doc = s.trim().replace("\\n", "\n");
160 pal.doc = Cow::Owned(doc);
161 }
162 } else if l.starts_with("generator") {
163 if let Some(s) = l.split('=').nth(1) {
164 pal.generator = Cow::Owned(s.trim().to_string());
165 if s.starts_with("light-dark") {
166 if let Some(s) = l.split(':').nth(1) {
167 dark = s.trim().parse::<u8>().unwrap_or(63);
168 }
169 } else if s.starts_with("color-1") {
170 } else if s.starts_with("color-2") {
171 } else if s.starts_with("color-4") {
172 } else if s.starts_with("color-4-dark") {
173 if let Some(s) = l.split(':').nth(1) {
174 dark = s.trim().parse::<u8>().unwrap_or(63);
175 }
176 } else if s.starts_with("color-8") {
177 } else {
178 state = S::Fail(format!("Unknown generator format {:?}", s));
179 break 'm;
180 }
181 }
182 } else if l.starts_with("dark") {
183 if let Some(s) = l.split('=').nth(1) {
184 if let Ok(v) = s.trim().parse::<u8>() {
185 dark = v;
186 } else {
187 }
189 }
190 } else {
191 state = S::Fail(format!("Invalid palette property {:?}", l));
192 break 'm;
193 }
194 }
195 S::Color => {
196 if l == "[reference]" {
197 state = S::Reference;
198 } else if l.is_empty() || l.starts_with("#") {
199 } else {
201 let mut kv = l.split('=');
202 let cn = if let Some(v) = kv.next() {
203 let Ok(c) = v.trim().parse::<Colors>() else {
204 state = S::Fail(format!("Invalid property format {:?}", l));
205 break 'm;
206 };
207 c
208 } else {
209 state = S::Fail(format!("Invalid property format {:?}", l));
210 break 'm;
211 };
212 if let Some(v) = kv.next() {
213 if pal.generator.starts_with("light-dark") {
214 let color = split_comma::<2>(v)?;
215 if cn == Colors::TextLight || cn == Colors::TextDark {
216 pal.color[cn as usize] = Palette::interpolatec2(
217 color[0],
218 color[1],
219 Color::default(),
220 Color::default(),
221 )
222 } else {
223 pal.color[cn as usize] =
224 Palette::interpolatec(color[0], color[1], dark);
225 }
226 } else if pal.generator.starts_with("color-1") {
227 let color = split_comma::<1>(v)?;
228 pal.color[cn as usize] = array::from_fn(|_| color[0]);
229 } else if pal.generator.starts_with("color-2") {
230 let color = split_comma::<2>(v)?;
231 pal.color[cn as usize][0..=3]
232 .copy_from_slice(&array::from_fn::<_, 4, _>(|_| color[0]));
233 pal.color[cn as usize][4..=7]
234 .copy_from_slice(&array::from_fn::<_, 4, _>(|_| color[0]));
235 } else if pal.generator.starts_with("color-4") {
236 let color = split_comma::<4>(v)?;
237 pal.color[cn as usize][0..=3].copy_from_slice(&color);
238 pal.color[cn as usize][4..=7].copy_from_slice(&color);
239 } else if pal.generator.starts_with("color-4-dark") {
240 let color = split_comma::<4>(v)?;
241 pal.color[cn as usize][0..=3].copy_from_slice(&color);
242 pal.color[cn as usize][4] =
243 Palette::scale_color_to(pal.color[cn as usize][0], dark);
244 pal.color[cn as usize][5] =
245 Palette::scale_color_to(pal.color[cn as usize][1], dark);
246 pal.color[cn as usize][6] =
247 Palette::scale_color_to(pal.color[cn as usize][2], dark);
248 pal.color[cn as usize][7] =
249 Palette::scale_color_to(pal.color[cn as usize][3], dark);
250 } else if pal.generator.starts_with("color-8") {
251 let color = split_comma::<8>(v)?;
252 pal.color[cn as usize] = color;
253 }
254 } else {
255 state = S::Fail(format!("Invalid property format {:?}", l));
256 break 'm;
257 };
258 }
259 }
260 S::Reference => {
261 let mut kv = l.split('=');
262 let rn = if let Some(v) = kv.next() {
263 v
264 } else {
265 state = S::Fail(format!("Invalid property format {:?}", l));
266 break 'm;
267 };
268 let ci = if let Some(v) = kv.next() {
269 if let Ok(ci) = v.parse::<palette::ColorIdx>() {
270 ci
271 } else {
272 state = S::Fail(format!("Invalid color reference {:?}", l));
273 break 'm;
274 }
275 } else {
276 state = S::Fail(format!("Invalid property format {:?}", l));
277 break 'm;
278 };
279 pal.add_aliased(rn, ci);
280 }
281 S::Fail(_) => {
282 unreachable!()
283 }
284 }
285 }
286
287 match state {
288 S::Fail(n) => Err(io::Error::other(LoadPaletteErr(n))),
289 S::Start => Err(io::Error::other(LoadPaletteErr(
290 "Missing [theme]. Invalid format or truncated.".to_string(),
291 ))),
292 S::Theme => Err(io::Error::other(LoadPaletteErr(
293 "Missing [palette]. Invalid format or truncated.".to_string(),
294 ))),
295 S::Palette => Err(io::Error::other(LoadPaletteErr(
296 "Missing [reference]. Invalid format or truncated.".to_string(),
297 ))),
298 S::Color | S::Reference => Ok(pal),
299 }
300}
301
302fn split_comma<const N: usize>(s: &str) -> Result<[Color; N], io::Error> {
303 let mut r: [Color; N] = array::from_fn(|_| Color::default());
304 let mut vv = s.split(',');
305 for i in 0..N {
306 r[i] = if let Some(v) = vv.next() {
307 let Ok(v) = v.trim().parse::<Color>() else {
308 return Err(io::Error::other(LoadPaletteErr(
309 format!("Invalid color[{}] {:?}", i, s).to_string(),
310 )));
311 };
312 v
313 } else {
314 return Err(io::Error::other(LoadPaletteErr(
315 format!("Invalid color[{}] {:?}", i, s).to_string(),
316 )));
317 }
318 }
319
320 if let Some(v) = vv.next()
321 && !v.trim().is_empty()
322 {
323 return Err(io::Error::other(LoadPaletteErr(
324 format!("Too many colors (max {}) {:?}", N, s).to_string(),
325 )));
326 }
327
328 Ok(r)
329}