eidos-ekf 0.1.0

EKF package contract support for Eidos.
Documentation
use std::path::{Path, PathBuf};

use crate::model::*;

pub(crate) fn manifest_source_paths(manifest: &Manifest) -> Vec<(String, String, Vec<String>)> {
    let mut out = Vec::new();
    out.extend(
        manifest
            .sources
            .docs
            .iter()
            .cloned()
            .map(|p| ("docs".into(), p, Vec::new())),
    );
    out.extend(
        manifest
            .sources
            .skills
            .iter()
            .cloned()
            .map(|p| ("skills".into(), p, Vec::new())),
    );
    out.extend(
        manifest
            .sources
            .agents
            .iter()
            .cloned()
            .map(|p| ("agents".into(), p, Vec::new())),
    );
    out.extend(
        manifest
            .sources
            .capabilities
            .iter()
            .cloned()
            .map(|p| ("capabilities".into(), p, Vec::new())),
    );
    out.extend(
        manifest
            .sources
            .brief_profiles
            .iter()
            .cloned()
            .map(|p| ("brief_profiles".into(), p, Vec::new())),
    );
    out.extend(
        manifest
            .sources
            .workflows
            .iter()
            .cloned()
            .map(|p| ("workflows".into(), p, Vec::new())),
    );
    out.extend(
        manifest
            .sources
            .evals
            .iter()
            .cloned()
            .map(|p| ("evals".into(), p, Vec::new())),
    );
    out.extend(
        manifest
            .sources
            .code
            .iter()
            .map(|c| ("code".into(), c.path.clone(), c.languages.clone())),
    );
    out
}

pub(crate) fn parse_inline_list(value: &str) -> Vec<String> {
    let value = value.trim();
    if !(value.starts_with('[') && value.ends_with(']')) {
        return if value.is_empty() {
            Vec::new()
        } else {
            vec![unquote(value).to_string()]
        };
    }
    value[1..value.len() - 1]
        .split(',')
        .map(str::trim)
        .filter(|item| !item.is_empty())
        .map(unquote)
        .map(str::to_string)
        .collect()
}

pub(crate) fn unquote(s: &str) -> &str {
    let s = s.trim();
    s.strip_prefix('"')
        .and_then(|s| s.strip_suffix('"'))
        .or_else(|| s.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
        .unwrap_or(s)
}

pub(crate) fn set_source_list(sources: &mut SourceManifest, key: &str, values: Vec<String>) {
    match key {
        "docs" => sources.docs = values,
        "skills" => sources.skills = values,
        "agents" => sources.agents = values,
        "capabilities" => sources.capabilities = values,
        "brief_profiles" => sources.brief_profiles = values,
        "workflows" => sources.workflows = values,
        "evals" => sources.evals = values,
        _ => {}
    }
}

pub(crate) fn unknown_field(line_no: usize, field: &str) -> EkfDiagnostic {
    EkfDiagnostic {
        severity: DiagnosticSeverity::Warning,
        kind: "unknown_field".into(),
        message: format!("unknown EKF field '{field}'"),
        source: Some(format!("line {line_no}")),
    }
}

pub(crate) fn malformed(line_no: usize, detail: &str) -> EkfDiagnostic {
    EkfDiagnostic {
        severity: DiagnosticSeverity::Warning,
        kind: "malformed_line".into(),
        message: format!("could not parse EKF manifest: {detail}"),
        source: Some(format!("line {line_no}")),
    }
}

pub(crate) fn resolve_path(root: &Path, path: &Path) -> PathBuf {
    if path.is_absolute() {
        path.to_path_buf()
    } else {
        root.join(path)
    }
}