a-agent 0.2.1

Fast, terminal-native coding agent with progressive context
Documentation
use std::path::{Component, Path, PathBuf};

use anyhow::{Context, Result};

pub fn unrestricted_path(root: &Path, requested: &str, must_exist: bool) -> Result<PathBuf> {
    let root = root
        .canonicalize()
        .with_context(|| format!("resolve workspace {}", root.display()))?;
    let requested = Path::new(requested);
    let candidate = if requested.is_absolute() {
        normalize(requested)?
    } else {
        normalize(&root.join(requested))?
    };
    if must_exist {
        candidate
            .canonicalize()
            .with_context(|| format!("resolve path {}", requested.display()))
    } else {
        Ok(candidate)
    }
}

fn normalize(path: &Path) -> Result<PathBuf> {
    let mut result = PathBuf::new();
    for component in path.components() {
        match component {
            Component::Prefix(prefix) => result.push(prefix.as_os_str()),
            Component::RootDir => result.push(component.as_os_str()),
            Component::CurDir => {}
            Component::ParentDir => {
                if !result.pop() {
                    anyhow::bail!("path escapes its root: {}", path.display());
                }
            }
            Component::Normal(value) => result.push(value),
        }
    }
    Ok(result)
}