siderust-archive 0.1.0

Reusable Rust bindings for the Siderust Archive: manifests, checksums, provenance, and runtime download of scientific datasets (IERS time data, kernels, planetary theories).
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (C) 2026 Vallés Puig, Ramon

//! Build-time pipeline for the IAU 2000A/2000B nutation coefficient tables.
//!
//! Reads three CSVs from `raw/`:
//!
//! * `nut00a_ls.csv` — IAU 2000A luni-solar terms (678 rows, 11 columns)
//! * `nut00a_pl.csv` — IAU 2000A planetary terms (687 rows, 17 columns)
//! * `nut00b_ls.csv` — IAU 2000B luni-solar terms (77 rows, 11 columns)
//!
//! Emits `nutation_data.rs` into `OUT_DIR` with `NUT00A_LS`, `NUT00A_PL`,
//! `NUT00B_LS`, and `NUT00B_NTERMS`.

use anyhow::{Context, Result};
use std::fmt::Write as FmtWrite;
use std::fs;
use std::path::Path;

fn parse_csv_rows(path: &Path) -> Result<Vec<Vec<String>>> {
    let csv = fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
    let mut rows = Vec::new();
    for (i, line) in csv.lines().enumerate() {
        if i == 0 {
            continue; // skip header
        }
        let line = line.trim();
        if line.is_empty() {
            continue;
        }
        let cols: Vec<String> = line.split(',').map(|s| s.trim().to_string()).collect();
        rows.push(cols);
    }
    Ok(rows)
}

pub(crate) fn run_regen(raw_dir: &Path, out_dir: &Path) -> Result<()> {
    let ls_a_path = raw_dir.join("nut00a_ls.csv");
    let pl_a_path = raw_dir.join("nut00a_pl.csv");
    let ls_b_path = raw_dir.join("nut00b_ls.csv");

    for p in [&ls_a_path, &pl_a_path, &ls_b_path] {
        println!("cargo:rerun-if-changed={}", p.display());
    }

    let ls_a = parse_csv_rows(&ls_a_path)?;
    let pl_a = parse_csv_rows(&pl_a_path)?;
    let ls_b = parse_csv_rows(&ls_b_path)?;

    anyhow::ensure!(
        ls_a.len() == 678,
        "nut00a_ls: expected 678 rows, got {}",
        ls_a.len()
    );
    anyhow::ensure!(
        pl_a.len() == 687,
        "nut00a_pl: expected 687 rows, got {}",
        pl_a.len()
    );
    anyhow::ensure!(
        ls_b.len() == 77,
        "nut00b_ls: expected 77 rows, got {}",
        ls_b.len()
    );

    let n_ls_b = ls_b.len();

    let mut out = String::new();
    writeln!(
        out,
        "// AUTOGENERATED by siderust-archive build.rs — DO NOT EDIT BY HAND"
    )?;
    writeln!(
        out,
        "// Source: src/nutation/raw/nut00a_ls.csv, nut00a_pl.csv, nut00b_ls.csv"
    )?;
    writeln!(out)?;

    // NUT00A_LS — [[f64; 11]; 678]
    writeln!(out, "/// IAU 2000A luni-solar nutation series (678 terms).")?;
    writeln!(
        out,
        "/// Columns: nl, nlp, nf, nd, nom, sp, spt, cp, ce, cet, se"
    )?;
    writeln!(out, "#[rustfmt::skip]")?;
    writeln!(out, "pub static NUT00A_LS: [[f64; 11]; 678] = [")?;
    for row in &ls_a {
        anyhow::ensure!(row.len() == 11, "nut00a_ls row has {} columns", row.len());
        writeln!(out, "    [{}],", row.join(", "))?;
    }
    writeln!(out, "];")?;
    writeln!(out)?;

    // NUT00A_PL — [[i32; 17]; 687]
    writeln!(out, "/// IAU 2000A planetary nutation series (687 terms).")?;
    writeln!(
        out,
        "/// Columns: nl, nf, nd, nom, nme, nve, nea, nma, nju, nsa, nur, nne, npa, sp, cp, se, ce"
    )?;
    writeln!(out, "#[rustfmt::skip]")?;
    writeln!(out, "pub static NUT00A_PL: [[i32; 17]; 687] = [")?;
    for row in &pl_a {
        anyhow::ensure!(row.len() == 17, "nut00a_pl row has {} columns", row.len());
        writeln!(out, "    [{}],", row.join(", "))?;
    }
    writeln!(out, "];")?;
    writeln!(out)?;

    // NUT00B_NTERMS
    writeln!(out, "pub const NUT00B_NTERMS: usize = {n_ls_b};")?;
    writeln!(out)?;

    // NUT00B_LS — [[f64; 11]; 77]
    writeln!(
        out,
        "/// IAU 2000B luni-solar nutation series ({n_ls_b} terms)."
    )?;
    writeln!(
        out,
        "/// Columns: nl, nlp, nf, nd, nom, sp, spt, cp, ce, cet, se"
    )?;
    writeln!(out, "#[rustfmt::skip]")?;
    writeln!(out, "pub static NUT00B_LS: [[f64; 11]; {n_ls_b}] = [")?;
    for row in &ls_b {
        anyhow::ensure!(row.len() == 11, "nut00b_ls row has {} columns", row.len());
        writeln!(out, "    [{}],", row.join(", "))?;
    }
    writeln!(out, "];")?;

    let out_path = out_dir.join("nutation_data.rs");
    fs::write(&out_path, &out).with_context(|| format!("writing {}", out_path.display()))?;

    Ok(())
}