fits-header 0.1.0

Pure-Rust, MSVC-safe FITS header reader/writer: parse every card from a FITS file, CRUD single or multiple header keywords, then serialize back to a valid FITS object.
Documentation
# fits-header

A pure-Rust library for reading and writing the header of a
[FITS](https://fits.gsfc.nasa.gov/fits_standard.html) file.

- Pure Rust, no C or system libraries. Builds with the MSVC toolchain.
- Every card is retained on parse; untouched cards (including long-string runs)
  re-serialize byte-for-byte. Only created or edited cards are re-rendered.
- Keyword access is strict: a bare name addresses the sole occurrence of a keyword and
  errors when it is duplicated; `("NAME", n)` selects the n-th occurrence.
- Create, read, update, and delete single or multiple keywords; batch mutations are
  atomic (all or nothing).
- Typed reads and writes: `get::<T>` covers strings, numbers, booleans, and date/times;
  `Literal`/`Fixed`/`Sci` wrappers control number formatting on write.
- Long strings use the `CONTINUE` convention on read and write (with `LONGSTRN`).
- The API is an ordered header of `(keyword, value, comment)` cards; it contains no
  application types.

## Usage

```rust
use fits_header::{parse, FitsError, StructuralHints};

fn demo(bytes: &[u8]) -> Result<(), FitsError> {
    // Read every card from a FITS header unit.
    let mut header = parse(bytes)?;

    // Typed reads through one generic accessor. Access is strict: a bare
    // name errors if the keyword occurs more than once.
    let exptime: Option<f64> = header.get("EXPTIME")?;
    let object: Option<&str> = header.get_str("OBJECT")?;
    let gain: Option<i64> = header.get(("GAIN", 1))?; // second occurrence

    // Create, update, delete.
    header.set("OBJECT", "M31")?;
    header.set("EXPTIME", 300.0)?;
    header.remove("AIRMASS")?;

    // Batch mutations are atomic — all or nothing.
    header.set_many([("FILTER", "Ha"), ("TELESCOP", "EdgeHD 8")])?;

    // Serialize. Untouched cards come back byte-for-byte identical.
    let block: Vec<u8> = header.to_header_bytes();
    let whole_file = header.to_bytes(&StructuralHints::default())?;
    Ok(())
}
```

## Serialization outputs

- `to_header_bytes()` — the header block only (cards + `END`, padded to a 2880-byte
  multiple). The primary path when editing a real file: splice it onto the file's data.
- `to_bytes(&hints)` — a standalone FITS object. Missing `SIMPLE`/`BITPIX`/`NAXIS*`
  cards are synthesized from the hints, and the declared data segment is zero-filled.
  Data larger than `MAX_ZERO_FILL` (1 GiB) returns `FitsError::DataTooLarge` instead of
  allocating.

## Documentation

API documentation is generated from the crate's rustdoc and published at
[docs.rs/fits-header](https://docs.rs/fits-header). Every public item is documented;
the examples are compiled and run as part of the test suite. Build the docs locally
with `cargo doc --no-deps --all-features --open`.

## Features

- `serde` *(off by default)* — derive `Serialize`/`Deserialize` on `Header`, `Record`,
  `Value`, and `StructuralHints`.
- `coords` *(off by default)* — astronomy helpers: sexagesimal RA/Dec parsing and
  formatting, MJD ↔ calendar conversion. No extra dependencies.

## Development

```sh
just verify                  # fmt-check + clippy (-D warnings) + tests
cargo test --all-features    # includes the serde and coords suites
just doc
```

## License

Licensed under the [Apache License, Version 2.0](LICENSE).