rust-stdf 1.1.0

A library for parsing Standard Test Data Format (STDF) files of version V4 and V4-2007.
Documentation
# rust-stdf

[Documentation](https://docs.rs/rust-stdf/)

A Rust library for reading and writing STDF V4 and V4-2007 test data logs.

```
# Cargo.toml
[dependencies]
rust-stdf = "1.1.0"
```
## Features

Available features:

- `gzip`: Read gzip-compressed (`.gz`) files with `flate2`.
- `bzip`: Read bzip2-compressed (`.bz2`) files with `bzip2`.
- `zipfile`: Read zip archives with `zip`.
- `atdf`: Read ATDF files and convert STDF to ATDF. This feature is under development.
- `serialize`: Serialize STDF records with `serde`.

> [!NOTE]
> `zipfile` contains unsafe Rust. The reader opens only the first unencrypted file in an archive.

The `gzip` and `bzip` features are enabled by default. Disable default features to choose them explicitly:

```
rust-stdf = { version="1.1.0", default-features = false, features = ["gzip", ...]}
```

---

## Reading STDF

This example iterates over records in an STDF V4 file. More examples are available in the [example folder](https://github.com/noonchen/rust-stdf/tree/main/example).

```rust
use rust_stdf::{stdf_file::*, stdf_record_type::*, StdfRecord};

fn main() {
    let stdf_path = "demo_file.stdf";   // "demo_file.stdf.gz" "demo_file.stdf.bz2"
    let mut reader = match StdfReader::new(&stdf_path) {
        Ok(r) => r,
        Err(e) => {
            println!("{}", e);
            return;
        }
    };

    // we will count total DUT# in the file
    // and put test result of PTR named
    // "continuity test" in a vector.
    let mut dut_count: u64 = 0;
    let mut continuity_rlt = vec![];

    // use type filter to work on certain types,
    // use `|` to combine multiple typs
    let rec_types = REC_PIR | REC_PTR;
    // iterator starts from current file position,
    // if file hits EOF, it will NOT redirect to 0.
    for rec in reader
        .get_record_iter()
        .map(|x| x.unwrap())
        .filter(|x| x.is_type(rec_types))
    {
        match rec {
            StdfRecord::PIR(_) => {dut_count += 1;}
            StdfRecord::PTR(ref ptr_rec) => {
                if ptr_rec.test_txt == "continuity test" {
                    continuity_rlt.push(ptr_rec.result);
                }
            }
            _ => {}
        }
    }
    println!("Total duts {} \n continuity result {:?}",
            dut_count,
            continuity_rlt);
}
```

### Choosing an Iterator

`StdfReader` provides three ways to read records. Each starts at the current file position:

- `get_record_iter()` returns fully parsed, owned `StdfRecord` values. It implements `Iterator`.
- `get_rawdata_iter()` returns unparsed `RawDataElement` values with owned payloads. It implements `Iterator`.
- `get_rawdata_view_iter()` returns unparsed `RawDataElementView` values that borrow a reused buffer. It avoids heap allocation, but does not implement `Iterator`; call `next()` to advance it.

When you need only a few fields, convert a raw view to a zero-copy `StdfRecordView`. Its field getters parse data on demand instead of parsing the entire record:

```rust
use rust_stdf::{stdf_file::*, stdf_record_type::*, StdfRecordView};

let mut reader = StdfReader::new("demo_file.stdf").unwrap();

// Read two unparsed records with get_rawdata_iter().
let mut count = 0;
for raw in reader
    .get_rawdata_iter()
    .map(|x| x.unwrap())
    .filter(|x| x.is_type(REC_PTR))
{
    if count >= 2 { break; }

    // Convert an owned raw record to either representation,
    // or send it to another thread.
    let _rec: StdfRecord = (&raw).into();
    let _view: StdfRecordView = (&raw).into();

    count += 1;
}

// Continue reading with get_rawdata_view_iter().
let mut iter = reader.get_rawdata_view_iter();
while let Some(item) = iter.next() {
    // This is not a standard iterator, so it has no map/filter methods.
    let raw = item.unwrap();
    if !raw.is_type(REC_PTR) {
        continue;
    }
    // The raw/view is valid only within this scope.
    let rec_view: StdfRecordView = (&raw).into();
    // It can be converted into StdfRecord and RawDataElement
    // if you need it live longer.

    // This iterator is the most efficient option.
    if let StdfRecordView::PTR(ptr) = rec_view {
        println!("{}", ptr.result());
    }
}
```

## Writing STDF

Use `StdfWriter` from `stdf_file` to write STDF bytes. Typed records are validated before writing. When using `BufWriter`, call `flush()` or drop the writer when finished. See [`example/stdf_write_example.rs`](https://github.com/noonchen/rust-stdf/tree/main/example/stdf_write_example.rs).

`StdfWriter` provides these methods:

- `write_record`: Writes a validated typed record such as `FAR` or `PTR`. It does not accept reserved or unknown records.
- `write_stdf_record`: Writes any owned `StdfRecord`, including `ReservedRec` and `UnknownRec`.
- `write_stdf_record_view`: Writes any borrowed `StdfRecordView`. Known records are re-encoded when their byte order differs from the writer's.
- `write_raw` and `write_raw_view`: Write an encoded `RawDataElement` or `RawDataElementView` without changing its bytes.

```rust
use rust_stdf::{stdf_file::StdfWriter, ByteOrder, FAR, PTR};
use std::fs::File;
use std::io::BufWriter;

fn main() -> Result<(), rust_stdf::StdfError> {
    let file = File::create("out.stdf").unwrap();
    let mut writer = StdfWriter::new(BufWriter::new(file), ByteOrder::LittleEndian);

    let mut far = FAR::new();
    far.cpu_type = 2;
    far.stdf_ver = 4;
    writer.write_record(&far)?;

    let mut ptr = PTR::new();
    ptr.test_num = 1000;
    ptr.head_num = 1;
    ptr.site_num = 1;
    ptr.result = 1.234;
    ptr.test_txt = "continuity".to_string();
    writer.write_record(&ptr)?;

    writer.flush()?;
    Ok(())
}
```

For a byte-exact filter or copy, such as removing selected record types, read borrowed views with `get_rawdata_view_iter()` and write them with `write_raw_view`. This avoids decoding and re-encoding. Match `err.kind()` to handle errors such as `ByteOrderMismatch` and `RecordTooLarge`. The following example edits selected records while copying the rest verbatim; see [`example/stdf_edit_example.rs`](https://github.com/noonchen/rust-stdf/tree/main/example/stdf_edit_example.rs).

```rust
use rust_stdf::{
    stdf_file::{StdfReader, StdfWriter},
    stdf_record_type::*,
    StdfRecordView, PTR,
};
use std::fs::File;
use std::io::BufWriter;

fn main() -> Result<(), rust_stdf::StdfError> {
    let mut reader = StdfReader::new("in.stdf").unwrap();
    let file = File::create("out.stdf").unwrap();
    let mut writer = StdfWriter::new(BufWriter::new(file), reader.get_byte_order());

    let mut iter = reader.get_rawdata_view_iter();
    while let Some(item) = iter.next() {
        let raw = item.unwrap();

        // Copy every non-PTR record unchanged.
        if !raw.is_type(REC_PTR) {
            writer.write_raw_view(&raw)?;
            continue;
        }

        let rec_view: StdfRecordView = (&raw).into();
        if let StdfRecordView::PTR(ptr) = rec_view {
            // Copy PTRs whose test number is not 1000 unchanged.
            if ptr.test_num() != 1000 {
                writer.write_raw_view(&raw)?;
                continue;
            }
            // Otherwise add 0.5 to the result and write the edited record.
            let mut rec: PTR = ptr.to_owned();
            rec.result += 0.5;
            writer.write_record(&rec)?;
        }
    }

    writer.flush()?;
    Ok(())
}
```