1use std::convert::{TryFrom, Into};
18use std::cmp::PartialEq;
19use regex::Regex;
20use std::{fs, io, fmt};
21use std::sync::Arc;
22#[macro_use]
23extern crate lazy_static;
24#[cfg(test)]
25mod tests;
26
27lazy_static! {
28 pub static ref COLORS: Vec<Color> = vec![
29 Color::BLACK,
30 Color::BLUE,
31 Color::GREEN,
32 Color::CYAN,
33 Color::RED,
34 Color::MAGENTA,
35 Color::YELLOW,
36 Color::WHITE,
37 Color::GRAY,
38 Color::BRIGHT_BLUE,
39 Color::BRIGHT_GREEN,
40 Color::BRIGHT_CYAN,
41 Color::BRIGHT_RED,
42 Color::BRIGHT_MAGENTA,
43 Color::BRIGHT_YELLOW,
44 Color::BRIGHT_WHITE,
45 ];
46}
47
48const DEFAULT_DELAY: u16 = 50;
49const DEFAULT_PREVIEW: u16 = 0;
50const DEFAULT_LOOP: bool = true;
51const DEFAULT_COLORS: ColorMod = ColorMod::None;
52const DEFAULT_UTF8: bool = false;
53
54#[derive(Debug, Clone)]
55pub enum ParcingError{
56 UnknownColor(char),
57 UnknownColorMod(String),
58 InvalidWidth,
59 InvalidHeight,
60 ThereIsNoBody,
61}
62
63impl fmt::Display for ParcingError {
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 match self{
66 Self::UnknownColor(c) => {write!(f, "UnknownColor: {}", c)}
67 Self::UnknownColorMod(s) => {write!(f, "UnknownColorMod: {}", s)}
68 Self::InvalidWidth => {write!(f, "InvalidWidth")}
69 Self::InvalidHeight => {write!(f, "InvalidHeight")}
70 Self::ThereIsNoBody => {write!(f, "There is no body found")}
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
76pub enum ReadingError{
77 ParcingError(ParcingError),
78 IOError(Arc<io::Error>),
79}
80
81impl fmt::Display for ReadingError {
82 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83 match self{
84 Self::ParcingError(e) => {e.fmt(f)}
85 Self::IOError(e) => {e.fmt(f)}
86 }
87 }
88}
89
90pub fn escape_comments(s: &str) -> String{
91 let re1 = Regex::new(r"(?m)^\t.*?(\n|$)").unwrap();
92 let re2 = Regex::new(r"\t.*?(\n|$)").unwrap();
93 let s = re1.replace_all(s, "").to_string();
94 let s = re2.replace_all(&s, "\n").to_string();
95 s
96}
97
98pub fn load(s: String) -> Result<Art, ParcingError>{
99 let s = escape_comments(&s);
100 let fragments: Vec<&str> = s.splitn(2, "\n\n").collect();
101 if fragments.len() < 2 {
102 return Err(ParcingError::ThereIsNoBody);
103 }
104 let header: Header = match Header::try_from(fragments[0].to_string()){
105 Ok(v) => {v}
106 Err(e) => {return Err(e);}
107 };
108 let body: Body = match Body::from_string(fragments[1].to_string(), header.clone()){
109 Ok(v) => {v}
110 Err(e) => {return Err(e);}
111 };
112 Ok(Art{header, body})
113}
114
115pub fn save(art: Art, pretify: bool) -> String{
116 let mut ret: String = art.header.into();
117 ret += "\n";
118 ret += &art.body.to_string(pretify);
119 ret
120}
121
122pub fn load_file(path: String) -> Result<Art, ReadingError>{
123 let s = match fs::read_to_string(path) {
124 Ok(s) => {s}
125 Err(ie) => {return Err(ReadingError::IOError(Arc::new(ie)))}
126 };
127 match load(s) {
128 Ok(v) => {Ok(v)}
129 Err(e) => {Err(ReadingError::ParcingError(e))}
130 }
131}
132
133pub fn save_file(art: Art, path: String, pretify: bool) -> Result<(), io::Error>{
134 let s = save(art, pretify);
135 match fs::write(path, s) {
136 Ok(_) => {Ok(())}
137 Err(e) => {Err(e)}
138 }
139}
140
141#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
142#[allow(non_camel_case_types)]
143pub enum Color{
144 BLACK,
145 BLUE,
146 GREEN,
147 CYAN,
148 RED,
149 MAGENTA,
150 YELLOW,
151 WHITE,
152 GRAY,
153 BRIGHT_BLUE,
154 BRIGHT_GREEN,
155 BRIGHT_CYAN,
156 BRIGHT_RED,
157 BRIGHT_MAGENTA,
158 BRIGHT_YELLOW,
159 BRIGHT_WHITE
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Hash)]
163pub struct RowFragment{
164 pub text: String,
165 pub fg_color: Option<Color>,
166 pub bg_color: Option<Color>,
167}
168
169pub type Row = Vec<RowFragment>;
170
171pub type Frame = Vec<Row>;
172
173#[derive(Debug, Clone, PartialEq, Eq, Hash)]
174pub struct Body{
175 pub frames: Vec<Frame>
176}
177
178#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
179pub enum ColorMod{
180 None,
181 Fg,
182 Bg,
183 Full
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Hash)]
187pub struct Header{
188 pub width: u16,
189 pub height: u16,
190 pub delay: u16,
191 pub loop_enable: bool,
192 pub color_mod: ColorMod,
193 pub utf8: bool,
194 pub datacols: u16,
195 pub preview: u16,
196 pub audio: Option<String>,
197 pub title: Option<String>,
198 pub author: Option<String>,
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Hash)]
202pub struct Art{
203 pub header: Header,
204 pub body: Body,
205}
206
207impl Body{
208 pub fn from_string(s: String, h: Header) -> Result<Self, ParcingError> {
209 let re = Regex::new(r"(\n|\t)").unwrap();
210 let s = re.replace_all(&s, "").to_string();
211 let char_vec: Vec<char> = s.chars().collect();
212 let len = char_vec.len();
213 let mut frm: usize = 0; let width = h.width as usize;
215 let height = h.height as usize;
216 let datacols = h.datacols as usize;
217 let mut frames: Vec<Frame> = Vec::new();
218 let mut next = true;
219 'outer: while next {
220 let mut frame: Frame = Vec::new();
221 for y in 0..height{
222 let mut row: Row = Vec::new();
223 let mut row_fragment = RowFragment{
224 text: "".to_string(),
225 fg_color: None,
226 bg_color: None,
227 };
228 for x in 0..width{
229 let symbol_pos: usize = (frm*width*datacols*height)+(y*width*datacols)+x;
230 if symbol_pos >= len{
231 next = false;
232 break;
233 }
234 let symbol: char = char_vec[symbol_pos];
235 let mut fg_color: Option<Color> = None;
236 let mut bg_color: Option<Color> = None;
237 match h.color_mod {
238 ColorMod::None => {}
239 ColorMod::Fg => {
240 let fg_color_position = (frm*width*datacols*height)+(y*width*datacols)+width+x;
241 if fg_color_position >= len{
242 next = false;
243 break;
244 }
245 fg_color = Some(match Color::try_from(char_vec[fg_color_position]) {
246 Ok(c) => {c}
247 Err(e) => {return Err(e);}
248 });
249 }
250 ColorMod::Bg => {
251 let bg_color_position = (frm*width*datacols*height)+(y*width*datacols)+width+x;
252 if bg_color_position >= len{
253 next = false;
254 break;
255 }
256 bg_color = Some(match Color::try_from(char_vec[bg_color_position]) {
257 Ok(c) => {c}
258 Err(e) => {return Err(e);}
259 });
260 }
261 ColorMod::Full => {
262 let fg_color_position = (frm*width*datacols*height)+(y*width*datacols)+width+x;
263 let bg_color_position = (frm*width*datacols*height)+(y*width*datacols)+width*2+x;
264 if fg_color_position >= len || bg_color_position >= len {
265 next = false;
266 break;
267 }
268 fg_color = Some(match Color::try_from(char_vec[fg_color_position]) {
269 Ok(c) => {c}
270 Err(e) => {return Err(e);}
271 });
272 bg_color = Some(match Color::try_from(char_vec[bg_color_position]) {
273 Ok(c) => {c}
274 Err(e) => {return Err(e);}
275 });
276 }
277 }
278 if x == 0 {
279 row_fragment.fg_color = fg_color;
280 row_fragment.bg_color = bg_color;
281 }else{
282 if row_fragment.fg_color != fg_color || row_fragment.bg_color != bg_color {
283 row.push(row_fragment);
284 row_fragment = RowFragment{
285 text: symbol.to_string(),
286 fg_color: fg_color,
287 bg_color: bg_color,
288 };
289 continue;
290 }
291 }
292 row_fragment.text.push(symbol)
293 }
294 if row_fragment.text.len() > 0 {
295 row.push(row_fragment);
296 }
297 if row.len() < 1 {
298 break 'outer;
299 }
300 frame.push(row);
301 }
302 frames.push(frame);
303 frm += 1;
304 }
305 Ok(Body{frames})
306 }
307 fn generate_color_fragment(color: Option<Color>, count: usize) -> String{
308 let mut ret: String = "".to_string();
309 if let Some(color) = color {
310 let c: char = color.into();
311 for _ in 0..count {
312 ret.push(c);
313 }
314 }
315 ret
316 }
317 pub fn to_string(self, pretify: bool) -> String{
318 let mut ret: String = "".to_string();
319 for (frm, frame) in self.frames.iter().enumerate(){
320 for row in frame{
321 let mut text_col: String = "".to_string();
322 let mut color1_col: String = "".to_string();
323 let mut color2_col: String = "".to_string();
324 for fragment in row{
325 text_col += &fragment.text;
326 color1_col += &Self::generate_color_fragment(fragment.fg_color, fragment.text.len());
327 color2_col += &Self::generate_color_fragment(fragment.bg_color, fragment.text.len());
328 }
329 ret += &text_col;
330 ret += &color1_col;
331 ret += &color2_col;
332 if pretify {
333 ret.push('\n');
334 }
335 }
336 if frm < self.frames.len()-1 {
337 ret.push('\n');
338 }
339 }
340 ret
341 }
342}
343
344impl ColorMod{
345 fn to_datacols(self) -> u16{
346 match self{
347 ColorMod::None => {1}
348 ColorMod::Fg => {2}
349 ColorMod::Bg => {2}
350 ColorMod::Full => {3}
351 }
352 }
353}
354
355impl TryFrom<&str> for ColorMod{
356 type Error = ParcingError;
357 fn try_from(value: &str) -> Result<Self, Self::Error> {
358 match value{
359 "none" => {Ok(Self::None)}
360 "fg" => {Ok(Self::Fg)}
361 "bg" => {Ok(Self::Bg)}
362 "full" => {Ok(Self::Full)}
363 _ => {Err(ParcingError::UnknownColorMod(value.to_string()))}
364 }
365 }
366}
367
368impl Into<String> for ColorMod{
369 fn into(self) -> String{
370 match self{
371 ColorMod::None => {"none"}
372 ColorMod::Fg => {"fg"}
373 ColorMod::Bg => {"bg"}
374 ColorMod::Full => {"full"}
375 }.to_string()
376 }
377}
378
379impl TryFrom<char> for Color{
380 type Error = ParcingError;
381 fn try_from(value: char) -> Result<Self, Self::Error> {
382 match value{
383 '0' => {Ok(Self::BLACK)}
384 '1' => {Ok(Self::BLUE)}
385 '2' => {Ok(Self::GREEN)}
386 '3' => {Ok(Self::CYAN)}
387 '4' => {Ok(Self::RED)}
388 '5' => {Ok(Self::MAGENTA)}
389 '6' => {Ok(Self::YELLOW)}
390 '7' => {Ok(Self::WHITE)}
391 '8' => {Ok(Self::GRAY)}
392 '9' => {Ok(Self::BRIGHT_BLUE)}
393 'a' => {Ok(Self::BRIGHT_GREEN)}
394 'b' => {Ok(Self::BRIGHT_CYAN)}
395 'c' => {Ok(Self::BRIGHT_RED)}
396 'd' => {Ok(Self::BRIGHT_MAGENTA)}
397 'e' => {Ok(Self::BRIGHT_YELLOW)}
398 'f'=> {Ok(Self::BRIGHT_WHITE)}
399 _ => {Err(ParcingError::UnknownColor(value))}
400 }
401 }
402}
403
404impl Into<char> for Color{
405 fn into(self) -> char{
406 match self{
407 Self::BLACK => {'0'}
408 Self::BLUE => {'1'}
409 Self::GREEN => {'2'}
410 Self::CYAN => {'3'}
411 Self::RED => {'4'}
412 Self::MAGENTA => {'5'}
413 Self::YELLOW => {'6'}
414 Self::WHITE => {'7'}
415 Self::GRAY => {'8'}
416 Self::BRIGHT_BLUE => {'9'}
417 Self::BRIGHT_GREEN => {'a'}
418 Self::BRIGHT_CYAN => {'b'}
419 Self::BRIGHT_RED => {'c'}
420 Self::BRIGHT_MAGENTA => {'d'}
421 Self::BRIGHT_YELLOW => {'e'}
422 Self::BRIGHT_WHITE => {'f'}
423 }
424 }
425}
426
427fn only_payload(v: Vec<&str>) -> Vec<&str>{
428 let mut ret: Vec<&str> = Vec::new();
429 for s in v {
430 if s != ""{
431 ret.push(s);
432 }
433 }
434 ret
435}
436
437
438impl TryFrom<String> for Header{
439 type Error = ParcingError;
440 fn try_from(s: String) -> Result<Self, Self::Error> {
441 let mut width: u16 = 0; let mut w_set = false;
442 let mut height: u16 = 0; let mut h_set = false;
443 let mut delay: u16 = DEFAULT_DELAY;
444 let mut loop_enable: bool = DEFAULT_LOOP;
445 let mut color_mod: ColorMod = DEFAULT_COLORS;
446 let mut utf8: bool = DEFAULT_UTF8;
447 let mut datacols: u16 = 0; let mut d_set = false;
448 let mut preview: u16 = DEFAULT_PREVIEW;
449 let mut audio: Option<String> = None;
450 let mut title: Option<String> = None;
451 let mut author: Option<String> = None;
452 let rows = s.split("\n").collect::<Vec<&str>>();
453 for row in rows{
454 let tokens = row.split(" ").collect::<Vec<&str>>();
455 if tokens.len() < 1 {continue;}
456 let tokens = only_payload(tokens);
457 if tokens.len() < 1 {continue;}
458 match tokens[0]{
459 "width" => {
460 if tokens.len() < 2 {continue;}
461 width = match tokens[1].parse::<u16>(){
462 Ok(v) => {v}
463 Err(_) => {continue;}
464 };
465 w_set = true;
466 }
467 "height" => {
468 if tokens.len() < 2 {continue;}
469 height = match tokens[1].parse::<u16>(){
470 Ok(v) => {v}
471 Err(_) => {continue;}
472 };
473 h_set = true;
474 }
475 "delay" => {
476 if tokens.len() < 2 {continue;}
477 delay = match tokens[1].parse::<u16>(){
478 Ok(v) => {v}
479 Err(_) => {continue;}
480 };
481 }
482 "loop" => {
483 if tokens.len() < 2 {continue;}
484 loop_enable = match tokens[1] {
485 "true" => {true}
486 "false" => {false}
487 _ => {continue;}
488 };
489 }
490 "colors" => {
491 if tokens.len() < 2 {continue;}
492 color_mod = match ColorMod::try_from(tokens[1]){
493 Ok(v) => {v}
494 _ => {continue;}
495 };
496 }
497 "utf8" => {
498 utf8 = true;
499 }
500 "datacols" => {
501 if tokens.len() < 2 {continue;}
502 datacols = match tokens[1].parse::<u16>(){
503 Ok(v) => {v}
504 Err(_) => {continue;}
505 };
506 d_set = true;
507 }
508 "preview" => {
509 if tokens.len() < 2 {continue;}
510 preview = match tokens[1].parse::<u16>(){
511 Ok(v) => {v}
512 Err(_) => {continue;}
513 };
514 }
515 "audio" => {
516 if tokens.len() < 2 {continue;}
517 audio = match tokens[1]{
518 "" => {continue;}
519 a => {Some(a.to_string())}
520 };
521 }
522 "title" => {
523 if tokens.len() < 2 {continue;}
524 let mut s = "".to_string();
525 for i in 1..tokens.len() {
526 if i > 1 {s.push_str(" ")}
527 s.push_str(tokens[i])
528 }
529 title = Some(s);
530 }
531 "author" => {
532 if tokens.len() < 2 {continue;}
533 let mut s = "".to_string();
534 for i in 1..tokens.len() {
535 if i > 1 {s.push_str(" ")}
536 s.push_str(tokens[i])
537 }
538 author = Some(s);
539 }
540 _ => {}
541 }
542 }
543 if !w_set {return Err(ParcingError::InvalidWidth);}
544 if !h_set {return Err(ParcingError::InvalidHeight);}
545 if !d_set {
546 datacols = color_mod.to_datacols();
547 }
548 Ok(Self{width, height, delay, loop_enable, color_mod, utf8, datacols, preview, audio, title, author})
549 }
550}
551
552impl Into<String> for Header{
553 fn into(self) -> String{
554 let mut ret = "".to_string();
555 ret.push_str("width ");
556 ret.push_str(&self.width.to_string());
557 ret.push_str("\nheight ");
558 ret.push_str(&self.height.to_string());
559 if self.delay != DEFAULT_DELAY {
560 ret.push_str("\ndelay ");
561 ret.push_str(&self.delay.to_string());
562 }
563 if self.loop_enable != DEFAULT_LOOP {
564 ret.push_str("\nloop ");
565 ret.push_str(match self.loop_enable{
566 true => {"true"}
567 false => {"false"}
568 });
569 }
570 if self.color_mod != DEFAULT_COLORS {
571 ret.push_str("\ncolors ");
572 let s: String = self.color_mod.into();
573 ret.push_str(&s);
574 }
575 if self.utf8 {
576 ret.push_str("\nutf8");
577 }
578 if self.color_mod.to_datacols() != self.datacols{
579 ret.push_str("\ndatacols ");
580 ret.push_str(&self.datacols.to_string());
581 }
582 if self.preview != DEFAULT_PREVIEW {
583 ret.push_str("\npreview ");
584 ret.push_str(&self.preview.to_string());
585 }
586 if let Some(a) = self.audio {
587 ret.push_str("\naudio ");
588 ret.push_str(&a);
589 }
590 if let Some(a) = self.title {
591 ret.push_str("\ntitle ");
592 ret.push_str(&a);
593 }
594 if let Some(a) = self.author {
595 ret.push_str("\nauthor ");
596 ret.push_str(&a);
597 }
598 ret.push_str("\n\n");
599 ret
600 }
601}