use std::fs;
use std::path::{Path, PathBuf};
pub struct LineIndex<'a> {
src: &'a str,
newlines: Vec<usize>,
}
impl<'a> LineIndex<'a> {
pub fn new(src: &'a str) -> Self {
let newlines = src
.bytes()
.enumerate()
.filter_map(|(i, b)| (b == b'\n').then_some(i))
.collect();
Self { src, newlines }
}
pub fn line_at_byte(&self, offset: usize) -> usize {
let offset = offset.min(self.src.len());
self.newlines.partition_point(|&nl| nl < offset) + 1
}
pub fn line_of(&self, sub: &str) -> usize {
let offset = (sub.as_ptr() as usize).wrapping_sub(self.src.as_ptr() as usize);
if offset > self.src.len() {
return 0;
}
self.line_at_byte(offset)
}
}
pub trait Traversable {
fn gather_all_files(
&self,
condition: impl Fn(&Path) -> bool,
) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>>;
}
impl Traversable for Path {
fn gather_all_files(
&self,
condition: impl Fn(&Path) -> bool,
) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
let mut paths = Vec::new();
if self.is_file() && condition(self) {
paths.push(self.to_path_buf());
} else if self.is_dir() {
gather_paths_recursive(self, &mut paths, &condition)?;
}
Ok(paths)
}
}
fn gather_paths_recursive(
dir: &Path,
paths: &mut Vec<PathBuf>,
condition: &impl Fn(&Path) -> bool,
) -> Result<(), Box<dyn std::error::Error>> {
let entries = fs::read_dir(dir)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.is_file() && condition(&path) {
paths.push(path);
} else if path.is_dir() {
gather_paths_recursive(&path, paths, condition)?;
}
}
Ok(())
}
#[test]
fn line_at_byte_accepts_an_offset_inside_a_multibyte_char() {
let src = "café\nx\n";
let lines = LineIndex::new(src);
assert_eq!(lines.line_at_byte(4), 1);
assert_eq!(lines.line_at_byte(6), 2);
assert_eq!(lines.line_at_byte(999), 3);
}