debug_canvas/
lib.rs

1use std::{
2    collections::HashMap,
3    fmt::Display,
4    ops::{Index, IndexMut},
5};
6
7#[derive(Default, Debug, Clone, Copy)]
8pub struct Options {
9    pub bottom_oriented: bool,
10    pub filler: char,
11}
12
13#[derive(Debug)]
14pub struct DebugCanvas {
15    points: HashMap<(i64, i64), char>,
16    min_row: i64,
17    max_row: i64,
18    min_col: i64,
19    max_col: i64,
20    options: Options,
21}
22impl DebugCanvas {
23    fn is_empty(&self) -> bool {
24        self.points.is_empty()
25    }
26    pub fn size(&self) -> (u64, u64) {
27        if self.is_empty() {
28            (0, 0)
29        } else {
30            (
31                (self.max_row - self.min_row + 1) as u64,
32                (self.max_col - self.min_col + 1) as u64,
33            )
34        }
35    }
36
37    pub fn new() -> Self {
38        Self::with_options(Options::default())
39    }
40    pub fn with_options(options: Options) -> Self {
41        Self {
42            options,
43            points: HashMap::new(),
44            min_row: i64::MAX,
45            max_row: i64::MIN,
46            min_col: i64::MAX,
47            max_col: i64::MIN,
48        }
49    }
50
51    pub fn clear(&mut self) {
52        *self = Self::with_options(self.options);
53    }
54
55    pub fn remove(&mut self, (row, col): (i64, i64)) {
56        self.points.remove(&(row, col));
57        self.max_col = self.points.keys().map(|&f| f.1).max().unwrap_or(i64::MIN);
58        self.min_col = self.points.keys().map(|&f| f.1).min().unwrap_or(i64::MAX);
59        self.max_row = self.points.keys().map(|&f| f.0).max().unwrap_or(i64::MIN);
60        self.min_row = self.points.keys().map(|&f| f.0).min().unwrap_or(i64::MAX);
61    }
62}
63
64impl Default for DebugCanvas {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl Display for DebugCanvas {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        if self.is_empty() {
73            return Ok(());
74        }
75        let (n, m) = self.size();
76        for i in 0..n {
77            if i > 0 {
78                writeln!(f)?;
79            }
80            for j in 0..m {
81                let row = if self.options.bottom_oriented {
82                    self.min_row + i as i64
83                } else {
84                    self.max_row - i as i64
85                };
86                let col = self.min_col + j as i64;
87                write!(f, "{}", self[(row, col)])?;
88            }
89        }
90        writeln!(f)?;
91        Ok(())
92    }
93}
94
95impl<T> Index<(T, T)> for DebugCanvas
96where
97    T: Into<i64>,
98{
99    type Output = char;
100
101    fn index(&self, (row, col): (T, T)) -> &Self::Output {
102        self.points
103            .get(&(row.into(), col.into()))
104            .unwrap_or(&self.options.filler)
105    }
106}
107
108impl<T> IndexMut<(T, T)> for DebugCanvas
109where
110    T: Into<i64>,
111{
112    fn index_mut(&mut self, (row, col): (T, T)) -> &mut Self::Output {
113        let (row, col) = (row.into(), col.into());
114        self.max_col = self.max_col.max(col);
115        self.min_col = self.min_col.min(col);
116        self.max_row = self.max_row.max(row);
117        self.min_row = self.min_row.min(row);
118        self.points.entry((row, col)).or_insert(' ')
119    }
120}
121
122#[cfg(test)]
123mod tests {
124
125    use super::*;
126
127    #[test]
128    fn test_blank_canvas() {
129        let mut canvas = DebugCanvas::new();
130        assert_eq!(format!("{canvas}"), "");
131        canvas[(3, 2)] = '#';
132        assert_eq!(format!("{canvas}"), "#");
133        canvas[(3, 2)] = '.';
134        assert_eq!(format!("{canvas}"), ".");
135        canvas[(3, 3)] = '-';
136        assert_eq!(format!("{canvas}"), ".-");
137        canvas[(2, 3)] = 'a';
138        assert_eq!(
139            format!("{canvas}"),
140            "
141.-
142 a"
143            .trim_start()
144        );
145        canvas[(2, 5)] = 'b';
146        assert_eq!(
147            format!("{canvas}"),
148            "
149.-  
150 a b"
151            .trim_start()
152        );
153        canvas.remove((2, 5));
154        assert_eq!(
155            format!("{canvas}"),
156            "
157.-
158 a"
159            .trim_start()
160        );
161        canvas.remove((2, 8));
162        assert_eq!(
163            format!("{canvas}"),
164            "
165.-
166 a"
167            .trim_start()
168        );
169    }
170}