recutils-rs 0.1.1

Rust FFI bindings to GNU recutils (librec) with an optional Arrow conversion layer
Documentation
use std::fs;
use std::process::ExitCode;

use recutils_rs::{Db, SelectionExpression};

fn usage() -> ! {
    eprintln!(
        "usage:\n  \
         modify_records <file.rec> <Type> <expr> set <Field>=<Value>\n  \
         modify_records <file.rec> <Type> <expr> delete"
    );
    std::process::exit(2);
}

fn main() -> ExitCode {
    let mut args = std::env::args().skip(1);
    let path = args.next().unwrap_or_else(|| usage());
    let rset_type = args.next().unwrap_or_else(|| usage());
    let expr = args.next().unwrap_or_else(|| usage());
    let action = args.next().unwrap_or_else(|| usage());

    let text = fs::read_to_string(&path).expect("read file");
    let mut db = Db::parse_str(&text).expect("parse");
    let selection_expression =
        SelectionExpression::compile(&expr, false).expect("compile selection expression");

    let summary = {
        let mut rset = db
            .rset_by_type(&rset_type)
            .unwrap_or_else(|| panic!("no record set of type {rset_type:?}"));

        match action.as_str() {
            "set" => {
                let pair = args.next().unwrap_or_else(|| usage());
                let (field, value) = pair
                    .split_once('=')
                    .unwrap_or_else(|| panic!("expected Field=Value, got {pair:?}"));
                let mut updated = 0usize;
                let mut missing = 0usize;
                for mut record in rset.records().filter(|r| selection_expression.matches(r)) {
                    match record.set_field(field, value).expect("set field") {
                        true => updated += 1,
                        false => missing += 1,
                    }
                }
                format!(
                    "set {field}={value} on {updated} record(s); {missing} matching record(s) had no such field"
                )
            }
            "delete" => {
                let removed = rset.remove_matching(|r| selection_expression.matches(r));
                format!("deleted {removed} record(s)")
            }
            other => {
                eprintln!("unknown action {other:?}");
                return ExitCode::from(2);
            }
        }
    };

    let serialized = db.to_rec_string().expect("serialize");
    fs::write(&path, serialized).expect("write file");
    println!("{summary}");
    ExitCode::SUCCESS
}