tact 0.1.1

Terminal interface for Nanocodex
use chrono::{DateTime, Utc};
use std::{
    env,
    process::Command,
    time::{SystemTime, UNIX_EPOCH},
};

fn main() {
    println!("cargo::rerun-if-changed=.git/HEAD");
    println!("cargo::rerun-if-changed=.git/index");
    println!("cargo::rerun-if-env-changed=SOURCE_DATE_EPOCH");
    println!("cargo::rerun-if-env-changed=TACT_RELEASE_BUILD");

    set("TACT_GIT_SHA", git(&["rev-parse", "--short=12", "HEAD"]));
    set(
        "TACT_GIT_BRANCH",
        git(&["branch", "--show-current"]).filter(|branch| !branch.is_empty()),
    );
    set(
        "TACT_GIT_COMMIT_TIMESTAMP",
        git(&["log", "-1", "--format=%cI"]),
    );
    set("TACT_GIT_DIRTY", dirty_state());
    set("TACT_BUILD_TIMESTAMP", Some(build_timestamp()));
    set("TACT_BUILD_TARGET", env::var("TARGET").ok());
    set("TACT_BUILD_PROFILE", env::var("PROFILE").ok());
    println!(
        "cargo::rustc-env=TACT_RELEASE_BUILD={}",
        env::var("TACT_RELEASE_BUILD").is_ok_and(|value| value == "1")
    );
    set(
        "TACT_RUSTC_VERSION",
        command_output(
            env::var("RUSTC").as_deref().unwrap_or("rustc"),
            &["--version"],
        ),
    );
}

fn git(arguments: &[&str]) -> Option<String> {
    command_output("git", arguments)
}

fn command_output(program: &str, arguments: &[&str]) -> Option<String> {
    let output = Command::new(program).args(arguments).output().ok()?;
    output
        .status
        .success()
        .then(|| String::from_utf8_lossy(&output.stdout).trim().to_owned())
}

fn dirty_state() -> Option<String> {
    let output = git(&["status", "--porcelain", "--untracked-files=no"])?;
    Some(if output.is_empty() { "clean" } else { "dirty" }.to_owned())
}

fn build_timestamp() -> String {
    let unix_timestamp = env::var("SOURCE_DATE_EPOCH")
        .map(|value| {
            value
                .parse()
                .expect("SOURCE_DATE_EPOCH must be a Unix timestamp")
        })
        .unwrap_or_else(|_| {
            SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .expect("system clock is before the Unix epoch")
                .as_secs()
                .try_into()
                .expect("build timestamp exceeds the supported range")
        });

    DateTime::<Utc>::from_timestamp(unix_timestamp, 0)
        .expect("build timestamp exceeds the supported range")
        .to_rfc3339()
}

fn set(name: &str, value: Option<String>) {
    println!(
        "cargo::rustc-env={name}={}",
        value.as_deref().unwrap_or("unknown")
    );
}