goini: a flag-driven CLI for reading and editing INI files
A small Go utility that inspects and mutates .ini files through command-line flags, using exit codes as answers.
goini is a small command-line utility for interacting with .ini files. The README says exactly that: "This utility is called goini and its designed to help you interact with .ini files." It ships as a single Go binary, about 41 KB across 14 files, Apache-2.0 licensed, at version v1.0.0.
The design philosophy is visible in the flag table: most operations are meant to be *asked* rather than *parsed*. Instead of returning structured results for every query, goini leans on process exit codes — a section exists (exit 0) or it doesn't (exit 1), a key exists in a section (0) or not (1), and a specified value matches the key's current value (0) or not (1). That makes the tool comfortable inside shell scripts and CI checks. For operations that return data, like listing sections or keys, results go to STDOUT in plain text, CSV, JSON, or YAML.
How it is built
The program is deliberately flat. goini.go is five lines:
package main
func main() {
Run(NewConfiguration())
}
Flag definitions live in config.go, which builds a figtree.Plant from the author's own github.com/andreimerlescu/figtree/v2 library. Each flag — -ini, -section, -key, -value, -sections, -add-section, and so on — is registered there with a help string.
run.go does the setup: it loads the flags, requires an INI path, parses the file with github.com/go-ini/ini, then hands control to executePrimaryTask. The dispatch in tasks.go picks one operation based on which flags are set. There is no subcommand structure; you select behavior by combining flags.
if *figs.String(argAddSection) != "" {
ignore(Task7(figs, cfg, filePath))
return
}
The tasks themselves (Task1 through Task9, plus a legacy TaskX) each check their required flags, run against the parsed INI structure, and either print output or call ExitFunc. The exit function is a package-level variable in output.go:
var ExitFunc = os.Exit
That indirection exists so tests could swap it out — though the shipped test suite takes a different approach (below).
What you can do with it
The README documents three families of operations.
Queries that answer through exit codes. -has-section, -has-section-key (needs -section), and -has-section-key-value (needs both -section and -key) return 0 when the thing exists, 1 when it does not. -are-sections-present takes a comma-separated list and returns 0 only if every section is present.
./goini --ini sample.ini --section default --has-section-key user
echo $? # Output: 0 (success, key 'user' exists in 'default')
Queries that print to STDOUT. -sections lists the file's sections; -list-keys and -list-key-values need a -section. Each accepts -csv, -json, or -yaml to change formatting. The JSON output for key/value pairs is marshaled with encoding/json into a single object; the README shows its keys in alphabetical order (country, key, port, user), which matches how Go's encoder sorts map keys rather than any file or insertion order.
Mutations that write back. -add-section creates a new section; -add-key adds a key-value pair inside a given -section (failing if the key already exists); -modify-key changes an existing value. Each saves the file with cfg.SaveTo(filePath) and returns 0 on success, 1 when the target is missing or duplicate.
./goini --ini sample.ini --section default --key new_setting --value 123 --add-key
echo $? # Output: 0 (and new_setting=123 will be added to [default])
Testing
There are no Go unit tests in the repository; instead, test.sh builds the binary and runs twenty end-to-end checks against a bundled sample.ini. Each test invokes the compiled tool with one of the documented flag combinations and compares its exit code against an expected value (0 or 1). Tests marked in the script's ini_reset array first reset sample.ini to a known state before running, so only those tests start from a clean file.
The script keeps four parallel arrays — test names, command arguments, descriptions, and expected exit codes — plus an ini_reset array marking which tests reset the sample file to a known state before running. The README shows all twenty passing:
TEST_01: Relying on exit code, does 'section' have 'key'?...PASSED
...
All 20 tests passed. PASS
The Makefile ties this together with cross-compilation targets (darwin-amd64, linux-arm64, and so on), each of which runs test before building.
Who it is for
This is a scripting tool, not a library. If you need to check whether an INI config has a section or key from a shell script, or add a default value that isn't there yet, goini gives you a binary whose exit code and STDOUT are the interface — no JSON parsing of your own required unless you ask for it with -json. The dependency on figtree/v2 means flags follow that library's conventions rather than Go's standard flag package.
The trade-offs match its scope. There is one operation per invocation, selected by flag combination; there is no interactive mode and no batch processing of multiple changes in a single call. For scripted INI inspection and small edits, the tool stays deliberately narrow — which is exactly what makes it predictable to drive from automation.
andreimerlescu/goini: 41 KB in 14 files, indexed Nov 9, 2026 after 0.00 hours of indexing. License: Apache-2.0. Source: https://github.com/andreimerlescu/goini.
Ask your agent to name it in its tool calls (the repo argument of query_graph, recall_project and read_project_source).
Written by AI models on the operator's own hardware, checked by a second model, from the repository's README and its code. Back to the shelf