projd-core 0.3.0

Core project scanning data model and analysis helpers for Projd.
Documentation
use std::fs;
use std::path::Path;

use projd_core::{LanguageKind, scan_path};
use tempfile::TempDir;

#[test]
fn counts_code_statistics_by_language() {
    let fixture = ProjectFixture::new();
    fixture.write(
        "Cargo.toml",
        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\n",
    );
    fixture.write("src/lib.rs", "// leading comment\npub fn demo() {\n\n}\n");
    fixture.write("scripts/build.py", "# header\nprint('build')\n\n");

    let scan = scan_path(fixture.path()).expect("scan succeeds");

    assert_eq!(scan.code.total_files, 2);
    assert_eq!(scan.code.total_lines, 7);
    assert_eq!(scan.code.code_lines, 3);
    assert_eq!(scan.code.comment_lines, 2);
    assert_eq!(scan.code.blank_lines, 2);

    let rust = scan
        .code
        .languages
        .iter()
        .find(|language| language.kind == LanguageKind::Rust)
        .expect("rust code stats");
    assert_eq!(rust.files, 1);
    assert_eq!(rust.total_lines, 4);
    assert_eq!(rust.code_lines, 2);
    assert_eq!(rust.comment_lines, 1);
    assert_eq!(rust.blank_lines, 1);

    let python = scan
        .code
        .languages
        .iter()
        .find(|language| language.kind == LanguageKind::Python)
        .expect("python code stats");
    assert_eq!(python.files, 1);
    assert_eq!(python.total_lines, 3);
    assert_eq!(python.code_lines, 1);
    assert_eq!(python.comment_lines, 1);
    assert_eq!(python.blank_lines, 1);
}

#[test]
fn ignores_code_statistics_for_ignored_source_files() {
    let fixture = ProjectFixture::new();
    fixture.write(
        "Cargo.toml",
        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\n",
    );
    fixture.write(".gitignore", "generated/\n");
    fixture.write("src/main.rs", "fn main() {}\n");
    fixture.write("generated/ignored.py", "print('ignore me')\n");

    let scan = scan_path(fixture.path()).expect("scan succeeds");

    assert_eq!(scan.code.total_files, 1);
    assert_eq!(scan.code.total_lines, 1);
    assert_eq!(scan.code.code_lines, 1);
    assert_eq!(scan.code.comment_lines, 0);
    assert_eq!(scan.code.blank_lines, 0);
    assert!(scan.has_language(LanguageKind::Rust));
    assert!(!scan.has_language(LanguageKind::Python));
}

struct ProjectFixture {
    temp_dir: TempDir,
}

impl ProjectFixture {
    fn new() -> Self {
        Self {
            temp_dir: tempfile::tempdir().expect("create temp dir"),
        }
    }

    fn path(&self) -> &Path {
        self.temp_dir.path()
    }

    fn write(&self, relative: &str, content: &str) {
        let path = self.path().join(relative);
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).expect("create fixture parent");
        }
        fs::write(path, content).expect("write fixture file");
    }
}