Skip to content

jhheider/edikt

Repository files navigation

edikt

CI Coverage Status crates.io License

Edit config files without reflowing them.

edit, meets edict. A lossless, format-preserving config editor for JSONC/JSON5, INI, TOML, YAML, KDL, and sectionless key-value files (.env, .properties). It edits with a jq-flavored expression language and a sed-flavored execution model, changing only the bytes you target - comments, indentation, quoting, and trailing commas in every untouched region survive byte-for-byte.

# query - reads like jq
edikt '.compilerOptions.strict' tsconfig.json

# edit in place - comments, indent, comma style all preserved
edikt -i '.compilerOptions.target = "ES2022"' tsconfig.json

# edit YAML in place - anchors, flow style, and comments survive
edikt -i '.services.web.replicas = 3' compose.yaml

# compute, not just place
edikt -i '.version |= . + "-dev"' package.jsonc

# stream-first, like sed
cat settings.jsonc | edikt 'del(.telemetry) | .theme = "dark"'

# script from a file
edikt -f release.edk -i config.jsonc

# convert, where feasible - comments carried across, in the target's syntax
edikt -T yaml tsconfig.jsonc

What it does: edits commented, hand-formatted config - settings.json, tsconfig.json, devcontainer.json, compose.yaml, Cargo.toml - and writes back a file that is byte-identical except for the one value you changed. Comments, trailing commas, indentation, and quoting all survive. Query it like jq; convert between formats with -T.

Install

brew install jhheider/tap/edikt   # Homebrew
cargo install edikt               # Cargo (crates.io)
pkgx install edikt                # pkgx (builds from source)

Or grab a prebuilt binary (Linux, macOS, and Windows; x86_64 and arm64) from the releases page.

A worked example: bumping a Cargo workspace

edikt is happy editing the config it ships in. Here is the whole version bump for a release - a Cargo.toml per crate plus the internal dependency pins in the workspace root - done losslessly:

# each crate's own version
for c in crates/*/Cargo.toml; do
  edikt -i '.package.version = "0.2.0"' "$c"
done

# the [workspace.dependencies] pins (inline tables), chained with -e
edikt -i \
  -e '.workspace.dependencies."edikt-core".version   = "0.2.0"' \
  -e '.workspace.dependencies."edikt-syntax".version = "0.2.0"' \
  Cargo.toml

Each edit moves exactly one version = "...". The # Internal crates comment above the dependency table, the path = "crates/edikt-core" sitting next to each version, the brace-and-space style of every inline table, and every other byte come back unchanged - the kind of diff a reviewer reads at a glance.

One quoting note: a key containing - is written ."edikt-core" (or .["edikt-core"]), because a bare .edikt-core would read the - as subtraction. edikt says so with a clear error rather than guessing.

Why edikt?

Honestly, a weekend project - one that started with a real itch. The problem crystallized while reading "Respectful" YAML Patching in Rust, which surveys the Rust libraries for patching YAML and lands on the gap in one line: none of them preserve both the formatting and the comments. That's the exact thing I kept wanting - surgically change one value (or one comment) and leave every other byte, comment, and blank line alone - and not just for YAML but for the whole pile of config formats a project accumulates.

The good tools each own their corner: jq and yq for querying, taplo and prettier for formatting, and the excellent toml_edit and kdl-rs crates for lossless edits (edikt is built on those last two). edikt isn't trying to replace them - it's the piece I couldn't find off the shelf: one jq-flavored tool that edits and queries and converts across JSONC, INI, TOML, YAML, KDL, and .env, touching only the bytes you point at. If your need is single-format, reach for the specialist; if it's "the same surgical edit, across all of these," that's the gap this fills.

Status: v0.2.0 - seven formats with lossless in-place edit, query, and conversion (comments carried across), on crates.io, Homebrew, and pkgx. See CLAUDE.md for the build contract.

Scripting notes

  • Exit codes are sed-shaped: 0 = success - a query that matches nothing is a silent no-op (safe under set -e); 2 = parse or evaluation error. For presence tests, --exit-status opts into jq's 1 on zero matches; for defaults, use //: edikt '.maybe.key // "fallback"' f.yaml.
  • The expression language is deliberately capped in v1. jq's navigation, mutation, arithmetic, // defaults, and a curated builtin registry (including regex test/match/capture/sub/gsub, split/join) are in; variables (as $x), if/then, reduce, and user-defined functions are not (yet).
  • Many files, sed-style: edikt -i '.v = 9' a.json b.json (let the shell glob: edikt -i 'del(.telemetry)' config/*.jsonc). Queries over several files concatenate results in order.
  • .env is flat and string-valued - no arrays or nesting, ever - but string computation on values works fine: edikt -i '.VERSION |= sub("^v"; "")' .env.

License

Licensed under either of Apache License, Version 2.0 or MIT license, at your option.

About

No description, website, or topics provided.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors