artifactize-core 0.13.1

The library behind artifactize: Artifacts, evals, reviews, the monitor and review models, and the shared operations every interface calls.
Documentation
//! Program lookup metadata policies shared by scoped execution and preflight.

use std::path::{Path, PathBuf};

pub(crate) fn reject_link_ancestors(path: &Path) -> Result<(), String> {
    for ancestor in path.ancestors() {
        if super::path_kind(ancestor).is_ok_and(|kind| kind == super::FileKind::Symlink) {
            return Err("Artifact symlinks are not supported.".into());
        }
    }
    Ok(())
}

/// The folder-relative path a declared command names, without a leading `./`: a command
/// with a `/` that is not an absolute path on this system. A bare name (looked up on `PATH`)
/// or an absolute path stays literal, so it is `None`.
pub(crate) fn folder_relative(command: &str) -> Option<&str> {
    (!Path::new(command).is_absolute() && command.contains('/'))
        .then(|| command.strip_prefix("./").unwrap_or(command))
}

pub(crate) fn candidates(path: &Path) -> Vec<PathBuf> {
    artifactize_tools::program::candidates(path, super::environment::var)
}

/// Lookup-added suffixes use actual entry spelling; caller-authored parents remain exact.
pub(crate) fn suffix_spelling(directory: &Path, candidate: &Path) -> Option<PathBuf> {
    let requested = candidate.file_name()?.to_str()?;
    std::fs::read_dir(directory)
        .ok()?
        .filter_map(Result::ok)
        .find_map(|entry| {
            let name = entry.file_name();
            name.to_str()?
                .eq_ignore_ascii_case(requested)
                .then(|| candidate.with_file_name(name))
        })
}

/// Find `program` as a child whose environment `variable` reads would: by `PATH` (and on
/// Windows `PATHEXT`) for a bare name, below `cwd` for a path.
pub(crate) fn resolve_with(
    program: &std::ffi::OsStr,
    cwd: &Path,
    variable: impl Fn(&str) -> Option<std::ffi::OsString>,
) -> std::io::Result<PathBuf> {
    artifactize_tools::program::resolve_with(program, cwd, variable)
}

/// Find `program` as a child with this process's environment would.
pub(crate) fn resolve(program: &std::ffi::OsStr, cwd: &Path) -> std::io::Result<PathBuf> {
    resolve_with(program, cwd, super::environment::var)
}

/// Hand `target`, a file or URL, to the desktop's opener.
pub(crate) async fn open_desktop(target: &std::ffi::OsStr) -> std::io::Result<()> {
    artifactize_tools::opener::open(target).await
}

/// A short, synchronous host-tool probe; no shell is added.
pub(crate) fn tool_output<'a>(
    program: &str,
    args: impl IntoIterator<Item = &'a std::ffi::OsStr>,
) -> Option<Vec<u8>> {
    let cwd = std::env::current_dir().ok()?;
    let program = resolve(std::ffi::OsStr::new(program), &cwd).ok()?;
    let output = std::process::Command::new(program)
        .args(args)
        .stdin(std::process::Stdio::null())
        .output()
        .ok()?;
    output.status.success().then_some(output.stdout)
}