#diff #ir #networking #engine #document #markdown #emit #statistics #format-markdown #build-plan

netform_diff

Deterministic diff engine and reporting primitives for lossless network config IR

2 unstable releases

Uses new Rust 2024

0.2.0 Feb 16, 2026
0.1.0 Feb 16, 2026

#998 in Text processing


Used in netform_cli

MIT license

76KB
2K SLoC

netform

vendor-agnostic, lossless config parsing and diffing for network configs.

structure

  • netform_ir: core config intermediate representation (ir), parser, and lossless renderer.
  • netform_diff: normalization, diff engine, report formatting, and plan/report primitives.
  • netform_cli: config-diff and replay binaries.
  • netform_dialect_eos: eos profile for comment/token handling and dialect-aware parsing.
  • netform_dialect_iosxe: iosxe profile for comment/token handling and dialect-aware parsing.
  • netform_dialect_junos: junos profile for comment/token handling and dialect-aware parsing.

features

  • lossless round-trip: parse -> render preserves original text
  • indentation-based structural grouping with conservative fallback
  • stable node ids and path addressing for diff output
  • configurable normalization (comments, blank lines, whitespace)
  • deterministic line-based edits with spans and stats
  • markdown report output plus diff.json / plan.json

docs

install

add to Cargo.toml:

[dependencies]
netform_ir = "0.2.0"
netform_diff = "0.2.0"
netform_dialect_eos = "0.2.0"
netform_dialect_iosxe = "0.2.0"
netform_dialect_junos = "0.2.0"

install the cli binary so you can run config-diff directly:

# from this repo checkout
cargo install --path netform_cli

# or from crates.io (after publish)
cargo install netform_cli

quick start

parse and round-trip:

use netform_dialect_junos::parse_junos;

let input = "interfaces {\n    ge-0/0/0 {\n        disable;\n    }\n}\n";
let doc = parse_junos(input);
assert_eq!(doc.render(), input);

diff two configs:

use netform_diff::{diff_documents, NormalizeOptions};
use netform_ir::parse_generic;

let a = parse_generic("interface Ethernet1\n  description old\n");
let b = parse_generic("interface Ethernet1\n  description new\n");

let diff = diff_documents(&a, &b, NormalizeOptions::default());
assert_eq!(diff.stats.replaces, 1);

cli

config-diff compares two config files and prints a drift report.

usage:

config-diff [OPTIONS] <FILE_A> <FILE_B>

options:

  • --dialect <generic|eos|iosxe|junos>: parser profile to apply (default: generic)
  • --order-policy <ordered|unordered|keyed-stable>: sibling ordering semantics (default: ordered)
  • --ignore-comments: drop comment lines from comparison
  • --ignore-blank-lines: drop blank lines from comparison
  • --normalize-whitespace: collapse internal whitespace in comparison view
  • --json: print machine-readable Diff json instead of markdown
  • --plan-json: print machine-readable Plan json instead of markdown

examples:

cargo run -p netform_cli --bin config-diff -- ./before.cfg ./after.cfg
cargo run -p netform_cli --bin config-diff -- --dialect eos ./intended.conf ./actual.conf
cargo run -p netform_cli --bin config-diff -- --dialect iosxe ./intended.conf ./actual.conf
cargo run -p netform_cli --bin config-diff -- --dialect junos ./intended.conf ./actual.conf
cargo run -p netform_cli --bin config-diff -- --order-policy keyed-stable ./intended.conf ./actual.conf
cargo run -p netform_cli --bin config-diff -- --json ./before.cfg ./after.cfg
cargo run -p netform_cli --bin config-diff -- --plan-json ./before.cfg ./after.cfg

release

releases are tag-driven (v*) via github actions and publish workspace crates to crates.io.


have fun!


lib.rs:

Diff engine and reporting primitives for netform_ir::Document.

This crate builds comparison views from lossless IR documents, applies explicit normalization and ordering policies, and emits deterministic edits.

Primary entrypoints:

Example

use netform_diff::{diff_documents, NormalizeOptions};
use netform_ir::parse_generic;

let left = parse_generic("hostname old\n");
let right = parse_generic("hostname new\n");
let diff = diff_documents(&left, &right, NormalizeOptions::default());
assert!(diff.has_changes);

Dependencies

~0.3–1MB
~21K SLoC