guix-scheme 0.2.0

Read, edit, and generate Guix Scheme files (channels.scm, system config.scm) from Rust
Documentation
use guix_scheme::record::{RecordView, RecordViewMut};
use scheme_edit::{string_lit, Document};

const CH: &str = "(channel\n  (name 'guix)\n  ;; mirror\n  (url \"https://a.example/guix.git\")\n  (branch \"master\"))";

#[test]
fn reads_fields() {
    let doc = Document::parse(CH).unwrap();
    let v = RecordView::new(doc.forms().next().unwrap()).unwrap();
    assert_eq!(v.record_name(), "channel");
    assert_eq!(v.symbol_field("name"), Some("guix"));
    assert_eq!(
        v.string_field("url").as_deref(),
        Some("https://a.example/guix.git")
    );
    assert_eq!(v.field("commit"), None);
}

#[test]
fn set_field_replaces_in_place_preserving_rest() {
    let mut doc = Document::parse(CH).unwrap();
    let mut v = RecordViewMut::new(doc.forms_mut().next().unwrap()).unwrap();
    v.set_field("branch", string_lit("stable"));
    let out = doc.to_string();
    assert!(out.contains("(branch \"stable\")"));
    assert!(out.contains(";; mirror"), "comment must survive");
    assert!(out.contains("(url \"https://a.example/guix.git\")"));
}

#[test]
fn set_field_appends_when_missing() {
    let mut doc = Document::parse("(channel\n  (name 'guix))").unwrap();
    let mut v = RecordViewMut::new(doc.forms_mut().next().unwrap()).unwrap();
    v.set_field("branch", string_lit("master"));
    assert_eq!(
        doc.to_string(),
        "(channel\n  (name 'guix)\n  (branch \"master\"))"
    );
}

#[test]
fn operating_system_host_name_edit() {
    let src = std::fs::read_to_string(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/../scheme-edit/tests/fixtures/system/bare-bones.tmpl"
    ))
    .unwrap();
    let mut doc = Document::parse(&src).unwrap();
    let os = doc
        .forms_mut()
        .find(|n| n.head_symbol() == Some("operating-system"))
        .unwrap();
    let mut v = RecordViewMut::new(os).unwrap();
    v.set_field("host-name", string_lit("renamed"));
    let out = doc.to_string();
    assert!(out.contains("(host-name \"renamed\")"));
    // everything else byte-identical: only the host-name value changed
    let diff_lines = src.lines().zip(out.lines()).filter(|(a, b)| a != b).count();
    assert_eq!(diff_lines, 1);
}