knave 0.14.14

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use std::path::{Component, Path, PathBuf};

use super::program_file::ProgramFile;

pub enum RepositoryAddress<'u> {
    Remote(&'u str),
    Local(PathBuf),
}

#[derive(Clone)]
pub struct CheckoutBounds {
    root: PathBuf,
}

impl<'u> RepositoryAddress<'u> {
    const SCHEME_SEPARATOR: &'static str = "://";
    const FILE_SCHEME: &'static str = "file";
    const HOST_SEPARATOR: char = ':';
    const PATH_SEPARATOR: char = '/';

    pub fn of(url: &'u str) -> Self {
        if let Some((scheme, rest)) = url.split_once(Self::SCHEME_SEPARATOR) {
            return match scheme {
                Self::FILE_SCHEME => Self::Local(PathBuf::from(rest)),
                _ => Self::Remote(url),
            };
        }
        if Self::is_host_and_path(url) {
            return Self::Remote(url);
        }
        Self::Local(PathBuf::from(url))
    }

    fn is_host_and_path(url: &str) -> bool {
        let Some(host_end) = url.find(Self::HOST_SEPARATOR) else {
            return false;
        };
        !url[..host_end].contains(Self::PATH_SEPARATOR)
    }
}

impl CheckoutBounds {
    pub fn of(checkout: &Path) -> Self {
        Self {
            root: Self::resolved(checkout),
        }
    }

    pub fn root(&self) -> &Path {
        &self.root
    }

    pub fn contains(&self, path: &Path) -> bool {
        Self::resolved(path).starts_with(&self.root)
    }

    fn resolved(path: &Path) -> PathBuf {
        path.ancestors()
            .find_map(|ancestor| {
                let canonical = ProgramFile::canonical(ancestor)?;
                let rest = path.strip_prefix(ancestor).ok()?;
                Some(Self::without_parent_steps(&canonical.join(rest)))
            })
            .unwrap_or_else(|| Self::without_parent_steps(path))
    }

    fn without_parent_steps(path: &Path) -> PathBuf {
        let mut resolved = PathBuf::new();
        for component in path.components() {
            match component {
                Component::ParentDir => {
                    resolved.pop();
                }
                Component::CurDir => {}
                other => resolved.push(other),
            }
        }
        resolved
    }
}