code_minimap/
core.rs

1use std::{
2    io::{self, BufRead, Write},
3    ops::Range,
4};
5
6use itertools::Itertools;
7
8/// Write minimap to the writer.
9pub fn write(
10    mut writer: impl Write,
11    reader: impl BufRead,
12    hscale: f64,
13    vscale: f64,
14    padding: Option<usize>,
15) -> io::Result<()> {
16    let mut frame = [0..0, 0..0, 0..0, 0..0];
17    reader
18        .lines()
19        .map(|line| {
20            line.map(|line| {
21                let beg = line.find(|c: char| !c.is_whitespace()).unwrap_or(usize::MAX);
22                let end = line.rfind(|c: char| !c.is_whitespace()).unwrap_or(0);
23                (beg, end)
24            })
25        })
26        .enumerate()
27        .map(|(i, line)| (scale(i, vscale), line))
28        .chunk_by(|(i, _)| *i)
29        .into_iter()
30        .chunks(4)
31        .into_iter()
32        .try_for_each(|chunk| {
33            let mut chunk_size = 0;
34            for (i, (_, group)) in chunk.enumerate() {
35                let (beg, end) = group.into_iter().try_fold((usize::MAX, 0), |(beg, end), (_, line)| {
36                    line.map(|(b, e)| (beg.min(b), end.max(e)))
37                })?;
38                frame[i] = beg..(end + 1);
39                chunk_size += 1;
40            }
41            frame.iter_mut().skip(chunk_size).for_each(|row| *row = 0..0);
42            scale_frame(&mut frame, hscale);
43            write_frame(&mut writer, &frame, padding)
44        })
45}
46
47/// Print minimap to the stdout.
48///
49/// # Examples
50///
51/// Basic usage:
52///
53/// ```
54/// use std::{io, io::BufReader};
55///
56/// let stdin = io::stdin();
57/// code_minimap::print(stdin.lock(), 1.0, 1.0, None).unwrap();
58/// ```
59pub fn print(reader: impl BufRead, hscale: f64, vscale: f64, padding: Option<usize>) -> io::Result<()> {
60    write(io::stdout(), reader, hscale, vscale, padding)
61}
62
63/// Write minimap to a string.
64///
65/// # Examples
66///
67/// Basic usage:
68///
69/// ```
70/// use std::{io, io::BufReader};
71///
72/// let stdin = io::stdin();
73/// let s =
74///     code_minimap::write_to_string(stdin.lock(), 1.0, 1.0, None).unwrap();
75/// print!("{}", s);
76/// ```
77pub fn write_to_string(reader: impl BufRead, hscale: f64, vscale: f64, padding: Option<usize>) -> io::Result<String> {
78    let mut buf = Vec::new();
79    write(&mut buf, reader, hscale, vscale, padding)?;
80    Ok(String::from_utf8(buf).unwrap())
81}
82
83fn write_frame(mut writer: impl Write, frame: &[Range<usize>], padding: Option<usize>) -> std::io::Result<()> {
84    let idx = |pos| {
85        frame
86            .iter()
87            .enumerate()
88            .fold(0, |acc, (i, x)| if x.contains(&pos) { acc + (1 << i) } else { acc })
89    };
90    let end = frame.iter().max_by_key(|range| range.end).unwrap().end;
91    let line: String = (0..end)
92        .step_by(2)
93        .map(|i| BRAILLE_MATRIX[(idx(i)) + (idx(i + 1) << 4)])
94        .collect();
95    match padding {
96        Some(padding) => writeln!(writer, "{0:<1$}", line, padding),
97        None => writeln!(writer, "{}", line),
98    }
99}
100
101fn scale_frame(frame: &mut [Range<usize>], factor: f64) {
102    for x in frame {
103        *x = scale(x.start, factor)..scale(x.end, factor);
104    }
105}
106
107fn scale(x: usize, factor: f64) -> usize {
108    (x as f64 * factor) as usize
109}
110
111#[rustfmt::skip]
112const BRAILLE_MATRIX : [char; 256] = [
113    '⠀', '⠁', '⠂', '⠃', '⠄', '⠅', '⠆', '⠇', '⡀', '⡁', '⡂', '⡃', '⡄', '⡅', '⡆', '⡇',
114    '⠈', '⠉', '⠊', '⠋', '⠌', '⠍', '⠎', '⠏', '⡈', '⡉', '⡊', '⡋', '⡌', '⡍', '⡎', '⡏',
115    '⠐', '⠑', '⠒', '⠓', '⠔', '⠕', '⠖', '⠗', '⡐', '⡑', '⡒', '⡓', '⡔', '⡕', '⡖', '⡗',
116    '⠘', '⠙', '⠚', '⠛', '⠜', '⠝', '⠞', '⠟', '⡘', '⡙', '⡚', '⡛', '⡜', '⡝', '⡞', '⡟',
117    '⠠', '⠡', '⠢', '⠣', '⠤', '⠥', '⠦', '⠧', '⡠', '⡡', '⡢', '⡣', '⡤', '⡥', '⡦', '⡧',
118    '⠨', '⠩', '⠪', '⠫', '⠬', '⠭', '⠮', '⠯', '⡨', '⡩', '⡪', '⡫', '⡬', '⡭', '⡮', '⡯',
119    '⠰', '⠱', '⠲', '⠳', '⠴', '⠵', '⠶', '⠷', '⡰', '⡱', '⡲', '⡳', '⡴', '⡵', '⡶', '⡷',
120    '⠸', '⠹', '⠺', '⠻', '⠼', '⠽', '⠾', '⠿', '⡸', '⡹', '⡺', '⡻', '⡼', '⡽', '⡾', '⡿',
121    '⢀', '⢁', '⢂', '⢃', '⢄', '⢅', '⢆', '⢇', '⣀', '⣁', '⣂', '⣃', '⣄', '⣅', '⣆', '⣇',
122    '⢈', '⢉', '⢊', '⢋', '⢌', '⢍', '⢎', '⢏', '⣈', '⣉', '⣊', '⣋', '⣌', '⣍', '⣎', '⣏',
123    '⢐', '⢑', '⢒', '⢓', '⢔', '⢕', '⢖', '⢗', '⣐', '⣑', '⣒', '⣓', '⣔', '⣕', '⣖', '⣗',
124    '⢘', '⢙', '⢚', '⢛', '⢜', '⢝', '⢞', '⢟', '⣘', '⣙', '⣚', '⣛', '⣜', '⣝', '⣞', '⣟',
125    '⢠', '⢡', '⢢', '⢣', '⢤', '⢥', '⢦', '⢧', '⣠', '⣡', '⣢', '⣣', '⣤', '⣥', '⣦', '⣧',
126    '⢨', '⢩', '⢪', '⢫', '⢬', '⢭', '⢮', '⢯', '⣨', '⣩', '⣪', '⣫', '⣬', '⣭', '⣮', '⣯',
127    '⢰', '⢱', '⢲', '⢳', '⢴', '⢵', '⢶', '⢷', '⣰', '⣱', '⣲', '⣳', '⣴', '⣵', '⣶', '⣷',
128    '⢸', '⢹', '⢺', '⢻', '⢼', '⢽', '⢾', '⢿', '⣸', '⣹', '⣺', '⣻', '⣼', '⣽', '⣾', '⣿',
129];
130
131#[cfg(test)]
132mod test {
133    use rstest::*;
134
135    use super::*;
136
137    #[rstest(
138        input,
139        expected,
140        case("", ""),
141        case("a", "⠁"),
142        case("aaaa\nbbbb\ncccc\ndddd", "⣿⣿"),
143        case("aaa\n aa\n  a\n   a", "⠙⢇"),
144        case("  a  b c\n d efg  \n    h  i\n jk", "⢐⡛⠿⠭")
145    )]
146    fn test_write_to_string(input: &'static str, expected: &str) {
147        let actual = write_to_string(input.as_bytes(), 1.0, 1.0, None).unwrap();
148        assert_eq!(expected, actual.trim());
149    }
150}