gixor 0.5.2

An API for managing .gitignore files.
Documentation
//! The boilerplates compiled in by the `embedded` feature.
//!
//! Nothing here may touch a clone or the network: that is the whole point of the feature, and
//! it is what lets the library be built for a target with no file system.
#![cfg(feature = "embedded")]

use gixor::{GixorFactory, Name, RepositoryManager};
use std::path::PathBuf;

/// The snapshot the tables were generated from, read directly rather than through gixor, so the
/// assertions compare the compiled-in content against its source.
fn snapshot_dir() -> PathBuf {
    PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/boilerplates/default"))
}

/// The commit recorded in `boilerplates/SOURCES`, which the permalinks have to name.
fn snapshot_commit() -> String {
    let sources = std::fs::read_to_string(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/boilerplates/SOURCES"
    ))
    .unwrap();
    sources
        .lines()
        .find(|line| line.starts_with("default "))
        .and_then(|line| line.split_whitespace().nth(4))
        .expect("SOURCES carries the default repository")
        .to_string()
}

#[test]
fn the_snapshot_repositories_are_configured() {
    let gixor = GixorFactory::embedded();
    assert_eq!(gixor.len(), 1);
    let repo = gixor.repositories().next().unwrap();
    assert_eq!(repo.name, "default");
    assert_eq!(repo.owner, "github");
    assert_eq!(repo.repo_name, "gitignore");
}

#[test]
fn preparing_has_nothing_to_do() {
    // No clone, no network, and no error either: the snapshot is already whatever it will be.
    assert!(GixorFactory::embedded().prepare(false).is_ok());
}

#[test]
fn every_boilerplate_of_the_snapshot_is_found() {
    let gixor = GixorFactory::embedded();
    let repo = gixor.repositories().next().unwrap();
    let found = repo.iter("").count();

    let on_disk = walk(&snapshot_dir());
    assert!(on_disk > 0, "the snapshot is empty");
    assert_eq!(found, on_disk);
}

#[test]
fn the_content_matches_the_snapshot() {
    let gixor = GixorFactory::embedded();
    let boilerplates = gixor.find(Name::parse("rust")).unwrap();
    let rust = boilerplates.first().expect("Rust is in the snapshot");

    let dumped = rust.dump("").unwrap();
    let expected = std::fs::read_to_string(snapshot_dir().join("Rust.gitignore")).unwrap();
    assert!(dumped.ends_with(&format!("{expected}\n")), "{dumped}");
}

/// The permalink cannot be resolved through Git here, so it names the commit the snapshot was
/// taken from. That keeps the generated gitignore pointing at the very content it carries.
#[test]
fn the_permalink_names_the_snapshot_commit() {
    let gixor = GixorFactory::embedded();
    let boilerplates = gixor.find(Name::parse("rust")).unwrap();
    let url = boilerplates.first().unwrap().content_url("").unwrap();

    assert!(url.contains(&snapshot_commit().to_uppercase()), "{url}");
    assert!(url.ends_with("/Rust.gitignore"), "{url}");
}

#[test]
fn building_a_gitignore_keeps_the_given_prologue() {
    let gixor = GixorFactory::embedded();
    let names = Name::parse_all(vec!["rust", "macos"]);

    let content = gixor
        .build_gitignore_with(names, "# my own rules\n*.local\n### Stale.gitignore\ndropped\n")
        .unwrap();

    assert!(content.starts_with("# my own rules\n*.local\n"), "{content}");
    // everything from the first boilerplate onwards is gixor's to write again
    assert!(!content.contains("dropped"), "{content}");
    assert!(content.contains("### Generated by Gixor"), "{content}");
}

#[test]
fn a_name_that_is_not_in_the_snapshot_is_an_error() {
    let gixor = GixorFactory::embedded();
    let r = gixor.find(Name::parse("no-such-boilerplate"));
    assert!(matches!(r, Err(gixor::Error::BoilerplateNotFound(_))));
}

fn walk(dir: &std::path::Path) -> usize {
    std::fs::read_dir(dir)
        .unwrap()
        .flatten()
        .map(|entry| {
            let path = entry.path();
            if path.is_dir() {
                walk(&path)
            } else {
                usize::from(path.extension().is_some_and(|e| e == "gitignore"))
            }
        })
        .sum()
}