tokmd-core 1.14.0

High-level API façade for tokmd. The recommended entry point for library usage.
Documentation
use super::*;
#[cfg(feature = "analysis")]
use crate::settings::AnalyzeSettings;
use crate::settings::ScanSettings;
#[cfg(feature = "analysis")]
use std::fs;
#[cfg(feature = "analysis")]
use std::path::{Path, PathBuf};
#[cfg(feature = "analysis")]
use std::time::{SystemTime, UNIX_EPOCH};

#[cfg(feature = "analysis")]
#[derive(Debug)]
struct TempDirGuard(PathBuf);

#[cfg(feature = "analysis")]
impl Drop for TempDirGuard {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.0);
    }
}

#[test]
fn version_not_empty() {
    assert!(!version().is_empty());
}

#[test]
fn scan_settings_current_dir() {
    let settings = ScanSettings::current_dir();
    assert_eq!(settings.paths, vec!["."]);
}

#[test]
fn scan_settings_for_paths() {
    let settings = ScanSettings::for_paths(vec!["src".to_string(), "lib".to_string()]);
    assert_eq!(settings.paths, vec!["src", "lib"]);
}

#[cfg(feature = "analysis")]
#[test]
fn effort_request_defaults_to_estimate_preset() {
    let analyze = AnalyzeSettings {
        preset: "estimate".to_string(),
        ..Default::default()
    };
    let req = parse_effort_request(&analyze, "estimate").expect("parse effort request");
    let req = req.expect("estimate should imply effort request");
    assert_eq!(
        req.model.as_str(),
        analysis::EffortModelKind::Cocomo81Basic.as_str()
    );
    assert_eq!(req.layer.as_str(), analysis::EffortLayer::Full.as_str());
}

#[cfg(feature = "analysis")]
#[test]
fn effort_request_not_implied_for_non_estimate_without_flags() {
    let analyze = AnalyzeSettings {
        preset: "receipt".to_string(),
        ..Default::default()
    };
    let req = parse_effort_request(&analyze, "receipt").expect("parse effort request");
    assert!(req.is_none());
}

#[cfg(feature = "analysis")]
#[test]
fn effort_request_rejects_unsupported_model() {
    let analyze = AnalyzeSettings {
        preset: "estimate".to_string(),
        effort_model: Some("cocomo2-early".to_string()),
        ..Default::default()
    };
    let err =
        parse_effort_request(&analyze, "estimate").expect_err("unsupported model should fail");
    assert!(err.to_string().contains("only 'cocomo81-basic'"));
}

#[cfg(feature = "analysis")]
fn mk_temp_dir(prefix: &str) -> PathBuf {
    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_nanos();
    let mut root = std::env::temp_dir();
    root.push(format!("{prefix}-{timestamp}-{}", std::process::id()));
    root
}

#[cfg(feature = "analysis")]
fn write_file(path: &Path, contents: &str) {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).unwrap();
    }
    fs::write(path, contents).unwrap();
}

#[cfg(feature = "analysis")]
#[test]
fn analyze_workflow_estimate_preset_populates_effort_and_size_basis_breakdown() {
    let root = mk_temp_dir("tokmd-core-estimate-preset");
    let _guard = TempDirGuard(root.clone());
    write_file(&root.join("src/main.rs"), "fn main() {}\n");
    write_file(
        &root.join("target/generated/bundle.min.js"),
        "console.log(1);\n",
    );
    write_file(
        &root.join("vendor/lib/external.rs"),
        "pub fn external() {}\n",
    );

    let scan = settings::ScanSettings::for_paths(vec![root.display().to_string()]);
    let analyze = AnalyzeSettings {
        preset: "estimate".to_string(),
        ..Default::default()
    };

    let receipt = analyze_workflow(&scan, &analyze).expect("estimate analyze failed");
    let effort = receipt
        .effort
        .as_ref()
        .expect("estimate preset should produce effort");

    assert!(effort.results.effort_pm_p50 > 0.0);
    assert_eq!(
        effort.size_basis.total_lines,
        effort.size_basis.authored_lines
            + effort.size_basis.generated_lines
            + effort.size_basis.vendored_lines
    );
    assert!(effort.size_basis.authored_lines > 0);
    assert!(
        effort.size_basis.generated_lines + effort.size_basis.vendored_lines > 0,
        "expected deterministic generated or vendored lines"
    );
}