use std::sync::Arc;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DbIncludeFrame {
pub path: Option<String>,
pub filename: Option<String>,
pub line: u32,
}
impl DbIncludeFrame {
pub fn file(filename: impl Into<String>, line: u32) -> Self {
Self {
path: None,
filename: Some(filename.into()),
line,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct DbSource {
lines: Vec<String>,
frames: Vec<Arc<[DbIncludeFrame]>>,
}
impl DbSource {
pub fn new(lines: Vec<String>, frames: Vec<Arc<[DbIncludeFrame]>>) -> Self {
debug_assert_eq!(lines.len(), frames.len());
Self { lines, frames }
}
pub fn single_file(filename: Option<&str>, text: &str) -> Self {
let lines: Vec<String> = text.split_inclusive('\n').map(str::to_string).collect();
let frames = (1..=lines.len() as u32)
.map(|line| {
Arc::from(vec![DbIncludeFrame {
path: None,
filename: filename.map(str::to_string),
line,
}])
})
.collect();
Self { lines, frames }
}
pub fn at(&self, line: u32) -> Option<(&[DbIncludeFrame], &str)> {
let i = (line as usize).checked_sub(1)?;
Some((self.frames.get(i)?, self.lines.get(i)?.as_str()))
}
pub fn is_empty(&self) -> bool {
self.lines.is_empty()
}
}
pub fn include_print(frames: &[DbIncludeFrame]) -> String {
let mut out = String::new();
for frame in frames {
out.push_str(" in");
if let Some(path) = &frame.path {
out.push_str(&format!(" path \"{path}\" "));
}
match &frame.filename {
Some(name) => out.push_str(&format!(" file \"{name}\"")),
None => out.push_str(" standard input"),
}
out.push_str(&format!(" line {}\n", frame.line));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn include_print_spacing_follows_c() {
assert_eq!(
include_print(&[DbIncludeFrame::file("/tmp/x.db", 7)]),
" in file \"/tmp/x.db\" line 7\n"
);
assert_eq!(
include_print(&[DbIncludeFrame {
path: Some(".".into()),
filename: Some("badtype.db".into()),
line: 1,
}]),
" in path \".\" file \"badtype.db\" line 1\n"
);
assert_eq!(
include_print(&[DbIncludeFrame {
path: None,
filename: None,
line: 3,
}]),
" in standard input line 3\n"
);
}
#[test]
fn include_print_walks_every_frame() {
assert_eq!(
include_print(&[
DbIncludeFrame::file("inner.db", 2),
DbIncludeFrame::file("outer.db", 9),
]),
" in file \"inner.db\" line 2\n in file \"outer.db\" line 9\n"
);
}
#[test]
fn single_file_keeps_the_newline_c_echoes() {
let src = DbSource::single_file(Some("x.db"), "a\nb\n");
let (frames, text) = src.at(2).expect("line 2");
assert_eq!(text, "b\n");
assert_eq!(frames, [DbIncludeFrame::file("x.db", 2)]);
assert!(src.at(3).is_none());
}
}