use std::io;
use std::path::{Path, PathBuf};
use std::fs;
#[derive(Debug)]
pub enum StackError {
Io(io::Error),
StackEmpty,
NoParentDirectory(PathBuf),
}
impl std::fmt::Display for StackError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StackError::Io(err) => write!(f, "IO error: {}", err),
StackError::StackEmpty => write!(f, "Directory stack is empty"),
StackError::NoParentDirectory(path) => write!(f, "Path '{}' has no parent directory", path.display()),
}
}
}
impl std::error::Error for StackError {}
impl From<io::Error> for StackError {
fn from(err: io::Error) -> Self {
StackError::Io(err)
}
}
#[derive(Debug)]
pub struct DirectoryStack {
stack: Vec<PathBuf>,
}
impl Default for DirectoryStack {
fn default() -> Self {
Self::new(None).expect("Failed to create DirectoryStack from the current working directory.")
}
}
impl DirectoryStack {
pub fn new(path: Option<&Path>) -> Result<Self, io::Error> {
let initial_path = match path {
Some(p) => fs::canonicalize(p)?,
None => fs::canonicalize(".")?,
};
Ok(Self {
stack: vec![initial_path],
})
}
pub fn push(&mut self, path: &Path) -> Result<(), StackError> {
let base_dir = self.stack.last().ok_or(StackError::StackEmpty)?;
let new_path = base_dir.join(path);
let absolute_path = fs::canonicalize(new_path)?;
self.stack.push(absolute_path);
Ok(())
}
pub fn push_file(&mut self, file_path: &Path) -> Result<(), StackError> {
let base_dir = self.stack.last().ok_or(StackError::StackEmpty)?;
let resolved_file_path = base_dir.join(file_path);
let parent_dir = resolved_file_path.parent()
.ok_or_else(|| StackError::NoParentDirectory(resolved_file_path.clone()))?;
let absolute_parent_path = fs::canonicalize(parent_dir)?;
self.stack.push(absolute_parent_path);
Ok(())
}
pub fn pop(&mut self) -> Option<PathBuf> {
if self.stack.len() > 1 {
self.stack.pop()
} else {
None
}
}
pub fn translate(&self, path: &Path) -> Result<PathBuf, StackError> {
let base_dir = self.stack.last().ok_or(StackError::StackEmpty)?;
let resolved_path = base_dir.join(path);
let absolute_path = fs::canonicalize(resolved_path)?;
Ok(absolute_path)
}
pub fn current_base(&self) -> Option<&Path> {
self.stack.last().map(|p| p.as_path())
}
}