use std::fs;
use std::io::{self, Read};
use std::path::PathBuf;
pub fn read_input(file: &Option<PathBuf>) -> io::Result<(String, String)> {
match file {
Some(path) => {
let content = fs::read_to_string(path)?;
let label = path.to_string_lossy().to_string();
Ok((content, label))
}
None => {
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
Ok((buffer, "<stdin>".to_string()))
}
}
}
pub fn byte_offset_to_line_col(input: &str, offset: usize) -> (usize, usize) {
let mut line = 1;
let mut col = 1;
let limit = offset.min(input.len());
for (i, c) in input.char_indices() {
if i >= limit {
break;
}
if c == '\n' {
line += 1;
col = 1;
} else {
col += 1;
}
}
(line, col)
}
pub fn print_diagnostics(input: &str, diagnostics: &[jsonette::Diagnostic], file_label: &str) {
for diag in diagnostics {
let (line, col) = byte_offset_to_line_col(input, diag.span.start);
eprintln!("Error in {}:{}:{}: {}", file_label, line, col, diag.message);
let lines: Vec<&str> = input.lines().collect();
if !lines.is_empty() {
let visual_line = line.min(lines.len());
let error_line = lines[visual_line - 1];
eprintln!(" |");
eprintln!("{:>3} | {}", visual_line, error_line);
let caret_col = if line > lines.len() {
error_line.len() + 1
} else {
col
};
let span_len = (diag.span.end.saturating_sub(diag.span.start)).max(1);
let indent = " ".repeat(caret_col.saturating_sub(1));
let caret = "^".repeat(span_len);
eprintln!(" | {}{}", indent, caret);
}
}
}