use std::{path, str::FromStr};
use indexmap::IndexMap;
use itertools::Itertools;
#[derive(Debug, Clone)]
pub struct SourceTree {
root: path::PathBuf,
sources: IndexMap<path::PathBuf, String>,
}
impl SourceTree {
pub fn empty() -> Self {
SourceTree {
sources: Default::default(),
root: path::PathBuf::new(),
}
}
pub fn single(path: path::PathBuf, content: String) -> Self {
SourceTree {
sources: [(path.clone(), content)].into(),
root: path::PathBuf::new(),
}
}
pub fn new<I>(iter: I, root: path::PathBuf) -> Self
where
I: IntoIterator<Item = (path::PathBuf, String)>,
{
SourceTree {
sources: IndexMap::from_iter(iter),
root,
}
}
pub fn is_empty(&self) -> bool {
self.sources.len() == 0
}
pub fn insert(&mut self, path: path::PathBuf, content: String) {
self.sources.insert(path, content);
}
pub fn replace(&mut self, path: &path::Path, content: String) -> Option<String> {
let source = self.sources.get_mut(path)?;
Some(std::mem::replace(source, content))
}
pub fn get_ids(&self) -> impl Iterator<Item = u16> {
0..self.sources.len() as u16
}
pub fn get_sources(&self) -> impl Iterator<Item = (&path::PathBuf, &String)> {
self.sources.iter()
}
pub fn get_root(&self) -> &std::path::Path {
self.root.as_path()
}
pub fn get_by_id(&self, source_id: u16) -> Option<(&path::Path, &str)> {
self.sources
.get_index(source_id as usize)
.map(|(p, c)| (p.as_path(), c.as_str()))
}
pub fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
self.sources
.get_full(path)
.map(|(i, _, content)| (i as u16, content.as_str()))
}
pub fn get_source_display_paths(&self) -> impl Iterator<Item = &path::Path> {
self.sources
.keys()
.map(|path| self.get_display_path(path).unwrap())
}
pub fn get_absolute_path(&self, path: impl AsRef<path::Path>) -> path::PathBuf {
let path = path.as_ref();
if path.as_os_str().is_empty() {
self.root.to_path_buf()
} else {
self.get_project_dir().join(path)
}
}
pub fn get_relative_path<'a>(
&self,
absolute_path: &'a path::Path,
) -> Result<&'a path::Path, path::StripPrefixError> {
absolute_path.strip_prefix(&self.root)
}
pub fn get_project_dir(&self) -> &path::Path {
if self.root.extension().is_some_and(|e| e == "lt") {
self.root.parent().unwrap()
} else {
&self.root
}
}
pub fn get_display_path<'a>(&'a self, path: &'a path::Path) -> Option<&'a path::Path> {
if path.is_absolute() {
path.strip_prefix(self.get_project_dir()).ok()
} else if path.as_os_str().is_empty() {
self.root.file_name().map(path::Path::new)
} else {
Some(path)
}
}
}
impl std::fmt::Display for SourceTree {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut r = format!("path: {}\nsources:\n", self.root.to_string_lossy());
for source in self.sources.keys().sorted() {
r += "- ";
r += &source.to_string_lossy();
r += "\n";
}
f.write_str(&r)
}
}
pub struct SourceOverlay<'a> {
tree: &'a SourceTree,
snippet_path: path::PathBuf,
snippet: &'a str,
}
impl<'a> SourceOverlay<'a> {
pub fn new(tree: &'a SourceTree, snippet: &'a str, snippet_path: Option<&str>) -> Self {
Self {
tree,
snippet,
snippet_path: snippet_path
.map(|s| path::PathBuf::from_str(s).unwrap())
.unwrap_or_default(),
}
}
pub const fn overlay_id() -> u16 {
u16::MAX
}
}
pub(crate) trait SourceProvider {
fn get_root(&self) -> &path::Path;
fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)>;
fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)>;
}
impl SourceProvider for SourceTree {
fn get_root(&self) -> &path::Path {
&self.root
}
fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)> {
SourceTree::get_by_id(self, id)
}
fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
SourceTree::get_by_path(self, path)
}
}
impl<'a> SourceProvider for SourceOverlay<'a> {
fn get_root(&self) -> &path::Path {
&self.tree.root
}
fn get_by_id(&self, id: u16) -> Option<(&path::Path, &str)> {
if id == SourceOverlay::overlay_id() {
Some((self.snippet_path.as_path(), self.snippet))
} else {
self.tree.get_by_id(id)
}
}
fn get_by_path(&self, path: &path::Path) -> Option<(u16, &str)> {
if path == self.snippet_path {
Some((SourceOverlay::overlay_id(), self.snippet))
} else {
self.tree.get_by_path(path)
}
}
}