rpk_config/
lib.rs

1use std::fmt::Write;
2use std::{ops::Range, path::Path};
3
4pub mod compiler;
5pub mod globals;
6pub mod keycodes;
7pub mod vendor_coms;
8pub mod builder;
9
10#[derive(Debug)]
11pub struct ConfigError {
12    pub message: String,
13    pub span: Option<Range<usize>>,
14}
15
16impl std::fmt::Display for ConfigError {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        write!(f, "{}\n    at: ({:?})", &self.message, &self.span)
19    }
20}
21
22impl std::error::Error for ConfigError {}
23
24impl From<std::io::Error> for ConfigError {
25    fn from(err: std::io::Error) -> Self {
26        Self {
27            message: err.to_string(),
28            span: None,
29        }
30    }
31}
32
33impl From<&str> for ConfigError {
34    fn from(err: &str) -> Self {
35        Self {
36            message: err.to_string(),
37            span: None,
38        }
39    }
40}
41
42impl ConfigError {
43    pub fn new(message: String, span: Range<usize>) -> Self {
44        Self {
45            message,
46            span: Some(span),
47        }
48    }
49
50    fn char_span(&self, source: &str) -> Option<Range<usize>> {
51        self.span.clone().map(|b: Range<usize>| {
52            let mut s = usize::MAX;
53            let mut j = 0;
54            for (i, c) in source.chars().enumerate() {
55                if j >= b.start {
56                    if s == usize::MAX {
57                        s = i;
58                    }
59                    if j >= b.end {
60                        return Some(s..i);
61                    }
62                }
63                j += c.len_utf8();
64            }
65            Some(s..j - 1)
66        })?
67    }
68
69    pub fn long_format(&self, source_file: &Path, source: &str) -> String {
70        let (line, col, slice) = self.line_col_slice(source);
71        let width = format!("{}", line + 10).len();
72        format!(
73            "error: {} \n   --> {}:{}:{}\n{}",
74            self.message,
75            source_file.display(),
76            line,
77            col,
78            source[slice.0..slice.1]
79                .split('\n')
80                .skip(1)
81                .enumerate()
82                .fold(String::new(), |mut output, l| {
83                    let _ = writeln!(output, " {:>width$} | {}", line + l.0, l.1);
84                    output
85                })
86        )
87    }
88
89    pub fn line_col_slice(&self, source: &str) -> (usize, usize, (usize, usize)) {
90        let mut line = 1;
91        let mut col = 0;
92        let mut sol = 0;
93        let Some(span) = self.span.clone() else {
94            return (0, 0, (0, 0));
95        };
96
97        for (i, c) in source.char_indices() {
98            match c {
99                '\n' => {
100                    if i <= span.start {
101                        line += 1;
102                        col = 0;
103                        sol = i;
104                    } else {
105                        return (line, col, (sol, i));
106                    }
107                }
108                _ if i <= span.start => {
109                    col += 1;
110                }
111                _ => {}
112            }
113        }
114        (line, col, (sol, source.len()))
115    }
116}
117
118pub fn pretty_compile<'s>(
119    file: &Path,
120    src: &'s str,
121) -> Result<compiler::KeyboardConfig<'s>, ConfigError> {
122    match compiler::compile(src) {
123        Ok(config) => Ok(config),
124        Err(err) => {
125            use ariadne::{ColorGenerator, Label, Report, ReportKind, Source};
126            let filename = file.to_str().unwrap_or("<unknown>");
127            let mut colors = ColorGenerator::new();
128
129            let a = colors.next();
130            if let Some(span) = err.char_span(src) {
131                Report::build(ReportKind::Error, filename, 12)
132                    .with_message("Invalid config".to_string())
133                    .with_label(
134                        Label::new((filename, span))
135                            .with_message(&err.message)
136                            .with_color(a),
137                    )
138                    .finish()
139                    .eprint((filename, Source::from(src)))
140                    .unwrap();
141            }
142            Err(err)
143        }
144    }
145}
146
147pub fn text_to_binary(source: &str) -> Result<Vec<u16>, ConfigError> {
148    let file = Path::new("<unknown>");
149    let config = pretty_compile(file, source)?;
150    Ok(config.serialize())
151}
152
153pub fn f32_to_u16(n: f32) -> ByteToU16IntoIter<4> {
154    bytes_to_u16(n.to_le_bytes())
155}
156pub fn bytes_to_u16<const N: usize>(bytes: [u8; N]) -> ByteToU16IntoIter<N> {
157    ByteToU16IntoIter::new(bytes)
158}
159pub struct ByteToU16IntoIter<const N: usize>([u8; N], usize);
160impl<const N: usize> ByteToU16IntoIter<N> {
161    pub fn new(bytes: [u8; N]) -> Self {
162        Self(bytes, 0)
163    }
164}
165impl<const N: usize> Iterator for ByteToU16IntoIter<N> {
166    type Item = u16;
167
168    fn next(&mut self) -> Option<Self::Item> {
169        let i = self.1;
170        if i + 1 < self.0.len() {
171            self.1 += 2;
172            Some(self.0[i] as u16 | ((self.0[i + 1] as u16) << 8))
173        } else {
174            None
175        }
176    }
177}
178
179#[macro_export]
180macro_rules! fixme {
181    ($a:expr) => {{
182        extern crate std;
183        std::eprintln!(
184            // split so that not found when looking for the word in an editor
185            "FIXME\
186             ! at ./{}:{}:{}\n{:?}",
187            file!(),
188            line!(),
189            column!(),
190            $a,
191        )
192    }};
193}
194
195#[cfg(test)]
196#[path = "lib_test.rs"]
197mod test;