kv

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 8 Imported by: 0

README

kv

Command-line tool and Go library for the keyval format: flat key-value pairs whose dotted paths build an implicit tree, a nested associative array keyed by names or indices.

build.00 ./configure --prefix=/usr
build.01 make
build.parallel yes
person.0.email \0
person.0.name Charles Ingvar Jönsson
person.1.name Anna
planets

The format is specified at github.com/unixfile/keyval. This implementation is strictly downstream from the spec.

Usage

usage: kv [-f file] [-F file] <command> [args]

  -f file   use file for this command only
  -F file   set the tty session file; -F prints it, -F- detaches
  -v        print the version

  create      c  <key> [value]  write a new leaf
  read        r  [key]          print a value, all pairs under a prefix, or all
  update      u  <key> [value]  overwrite a leaf
  delete      d  <key>          delete a leaf
  rmtree      D  <key>          delete a subtree
  push        p  <key> [value]  append to a sequence
  pop         P  <key>          remove and print the last item
  insert      i  <key> [value]  insert at an index, siblings shift up
  keys        k  [key]          list child keys, containers dotted
  edit        e                 open in $EDITOR, values aligned
  fmt         f                 sort and validate
  tree        t  [key]          print the tree, or a subtree under a key
  kv                            convert JSON (stdin) to keyval
  json                          convert keyval to JSON
  completion     bash           print the bash completion script
  magic                         print a file(1) magic block for ~/.magic

  <key>=<value>              create or update a leaf
  <key>+=<value>             append to a sequence
  <key>$                     remove and print the last item
  <key>!                     delete a leaf
  <key>                      print a value or a subtree

Operators are the short form for daily use. The commands name the same operations and add strictness: create fails on an existing key, update on a missing one. insert takes an index from 0 to the sequence length; the named sibling and everything above it shift up, the mirror of the shift down on delete. A command name wins over a key spelled the same, so a key literally named tree needs kv r tree.

The file is resolved in order: -f, the KV_FILE environment variable, the session file, and finally a lone .kv file in the current directory; JSON never resolves implicitly. -F binds a file to the current terminal, so a session needs no flag at all:

$ kv -F todo.kv
$ kv title="Spring cleaning"
$ kv steps+="buy boxes"
$ kv steps+="pack books"
$ kv t
steps
  0 buy boxes
  1 pack books
title Spring cleaning
$ kv steps$
pack books
$ kv title
Spring cleaning

-F identifies the terminal through /proc, so the session binding is Linux-only; elsewhere use -f, KV_FILE, or a lone .kv file.

Mutations are atomic: write to a temp file, validate, rename. A failed operation leaves the file untouched. Before each overwrite the old content is copied to ${XDG_STATE_HOME:-$HOME/.local/state}/kv, ten copies deep per file.

Install

go install github.com/unixfile/kv/cmd/kv@latest

Or clone and run make install.

Edit

kv e opens the file in $VISUAL, $EDITOR or vi. The view puts a dotted leader after every key so the values line up in one column, like a table of contents:

build.00......... ./configure --prefix=/usr
build.01......... make
build.parallel... yes
person.0.email... \0
planets..........
title............ Spring cleaning

The dots are presentation only and are stripped on save. New lines may be typed with or without them. Blank lines vanish, out of order keys sort themselves. A parse error appears as a comment at the top and the editor reopens; decline and the draft survives in /tmp. Exiting the editor with a nonzero status, :cq in vim, aborts without writing. A file that changed on disk while the editor was open prompts before being overwritten.

JSON

kv kv and kv json convert on stdin and stdout. A file named *.json converts on the fly, which turns every command into a JSON editor:

$ kv -f config.json update db.port 9090
$ kv -f config.json push tags staging

Conversion is heuristic and lossy at the edges. A leaf spelled exactly like a JSON number, true, false or null converts to that type; everything else stays a string. Scalars therefore round-trip, including null. Markers become {}, so an empty JSON array returns as an empty object.

Keys follow the document. A .kv file allows only the strict grammar: lowercase names and indices. When the document is JSON, a .json file or the json verbs, keys are taken verbatim, so maxRetries, Content-Type and $schema all work. Keys are never folded or renamed. Names with dots, spaces or control characters cannot be represented, and neither can numeric names that do not count from zero. A key holding an operator character needs the verb form on the command line: kv r 'a=b'.

File type

kv magic prints a file(1) magic block that recognises keyval files by the hash line kv writes. Append it to your ~/.magic to teach file the format:

$ kv magic >> ~/.magic
$ file todo.kv
todo.kv: keyval data, github.com/unixfile/keyval
$ file --mime-type todo.kv
todo.kv: text/x-keyval

The block is a fenced section keyed by the spec URL, so it removes cleanly and never collides with other rules. The uninstall command rides along as a comment inside it:

sed -i '\|^# >>> github.com/unixfile/keyval >>>$|,\|^# <<< github.com/unixfile/keyval <<<$|d' ~/.magic

Detection needs file/libmagic, present on Linux, macOS and the BSDs; the !:ext line needs file 5.23 or newer. Where file is absent nothing is lost, the format does not depend on it.

Library

import "github.com/unixfile/kv"

f, err := kv.Parse(data) // strict; kv.ParseLenient sorts and repairs
val, err := f.Read(key)
err = f.Push(key, "value")
out := f.String()

Structs marshal to lowercased field names. A kv tag renames a field; kv:"-" skips it:

type config struct {
	Host    string
	Port    int
	BaseURL string `kv:"base_url"`
}

f, err := kv.Marshal(config{Host: "localhost", Port: 8080})
var c config
err = kv.Unmarshal(f, &c)

kv.FromJSON and kv.ToJSON expose the converter in Go. The package-level functions use the strict key grammar; the JSON-keys dialect is explicit, as in kv.JSONKeys.FromJSON, kv.JSONKeys.Parse and kv.JSONKeys.ParseKey. The library never touches the filesystem.

Completion

Bash completion for verbs, flags, files and keys:

. <(kv completion bash)

Add that to .bashrc for every shell, or install it once:

kv completion bash > \
  ${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions/kv

License

MIT, see LICENSE.

Documentation

Overview

Package kv models, parses and serializes the keyval (.kv) format, spec v1.0. The library never touches the filesystem; callers own all file I/O.

Index

Constants

View Source
const HashLine = "# github.com/unixfile/keyval"

HashLine identifies the format, pointing at its spec. Any first line starting with # is accepted on read; this exact line is written when none exists.

Variables

This section is empty.

Functions

func ToJSON

func ToJSON(f *File) ([]byte, error)

ToJSON converts a keyval file to indented JSON. A node with only indexed children becomes an array; any named child makes it an object, with indexed siblings keyed by their integer. Markers become {}, so an empty array does not survive a round trip. A leaf spelled exactly like a JSON number, true, false or null is emitted as that type, everything else as a string; the guessing mirrors FromJSON and is the documented edge of this lossy conversion.

func Unmarshal

func Unmarshal(f *File, v any) error

Unmarshal reads a keyval file into the Go value pointed to by v. Unknown keys are ignored; a leaf must match a scalar field, a branch a struct, map, slice or array. A marker yields the empty container. An empty interface target gets nested map[string]any, []any and string.

Types

type Dialect

type Dialect int

Dialect selects the key grammar. Strict is the format's own grammar and the only one valid in a .kv file. JSONKeys also accepts the wider names of foreign JSON documents, kept verbatim: anything without a space, dot or control character, not starting with #. It exists for the JSON bridge — .json files and the JSON conversions — and must never reach a .kv file. The package-level functions are the Strict shorthand.

const (
	Strict Dialect = iota
	JSONKeys
)

func (Dialect) FromJSON

func (d Dialect) FromJSON(data []byte) (*File, error)

FromJSON converts a JSON document to a keyval file. Objects become named children, arrays indexed children, empty containers markers. Scalars keep their literal JSON spelling — 8080, true, null — which ToJSON recognizes on the way back, so scalar types round-trip. Object names must fit the dialect's key grammar and may never hold a dot, the path separator. The top-level value must be an object or an array; an empty one yields an empty file.

func (Dialect) Parse

func (d Dialect) Parse(data string) (*File, error)

Parse is Parse under the dialect's key grammar.

func (Dialect) ParseKey

func (d Dialect) ParseKey(s string) (Key, error)

ParseKey parses a dotted path under the dialect's key grammar.

func (Dialect) ParseLenient

func (d Dialect) ParseLenient(data string) (*File, error)

ParseLenient is ParseLenient under the dialect's key grammar.

type File

type File struct {
	Hash  string // first line if it starts with #, verbatim
	Pairs []Pair // kept sorted by key string
}

func FromJSON

func FromJSON(data []byte) (*File, error)

FromJSON converts under the strict key grammar; see Dialect.FromJSON.

func Marshal

func Marshal(v any) (*File, error)

Marshal renders a Go value as a keyval file. Structs and maps become named children, slices and arrays indexed children, scalars leaves. Struct fields map to their lowercased name, overridden by a `kv:"name"` tag; `kv:"-"` skips a field. Nil pointers are omitted; empty maps and slices become markers. The top-level value must be a struct, map, slice or array.

func Parse

func Parse(data string) (*File, error)

Parse parses file content strictly: out-of-order lines, stale markers and every other spec violation are errors.

func ParseLenient

func ParseLenient(data string) (*File, error)

ParseLenient parses file content the way fmt does: lines are sorted and stale markers dropped before validation, so an appended file normalizes instead of failing.

func (*File) Create

func (f *File) Create(key Key, val string) error

func (*File) CreateMarker

func (f *File) CreateMarker(key Key) error

opCreateMarker declares an empty container at key. The marker is removed automatically when the first child is written.

func (*File) Delete

func (f *File) Delete(key Key) error

func (*File) DeleteTree

func (f *File) DeleteTree(key Key) error

func (*File) Flat

func (f *File) Flat() string

Flat returns all leaf pairs as flat key-value lines in sorted order, the grep-friendly view of the file.

func (*File) Insert added in v0.3.0

func (f *File) Insert(key Key, val string) error

opInsert writes a new leaf at the index naming the last segment of key, first shifting that sibling and every higher one up by one. An index equal to the member count appends, exactly like push; a higher one would leave a gap and fails.

func (*File) Keys

func (f *File) Keys(prefix Key) (string, error)

Keys lists the immediate children of the node at prefix as full dotted paths in file spelling, one per line in sorted order, with a trailing dot on containers. A nil prefix lists the top level; a leaf prefix prints the leaf itself.

func (*File) Pop

func (f *File) Pop(key Key) (string, error)

opPop removes the highest-indexed child of the container at key and returns what was removed: the value for a leaf, the raw lines for a branch.

func (*File) Push

func (f *File) Push(key Key, val string) error

opPush appends a new leaf to the indexed children of the container at key. Missing intermediate nodes are created implicitly; validation keeps every sequence consecutive.

func (*File) Read

func (f *File) Read(key Key) (string, error)

func (*File) Set

func (f *File) Set(key Key, val string) error

Set creates the leaf at key or overwrites an existing one, the upsert behind the = operator.

func (*File) String

func (f *File) String() string

func (*File) Tree

func (f *File) Tree(prefix Key) (string, error)

Tree renders the file as an indented tree, two spaces per level, with escaped values on the leaves. A non-empty prefix scopes the output to that subtree, re-rooted so the named node sits in the first column; a leaf prefix prints its single line. An unknown prefix is an error.

func (*File) Update

func (f *File) Update(key Key, val string) error

type Key

type Key []Segment

func ParseKey

func ParseKey(s string) (Key, error)

func (Key) String

func (k Key) String() string

type Pair

type Pair struct {
	Key    Key
	Value  string // unescaped; empty and ignored for markers
	Marker bool   // container marker: no value in file, auto-removed when children arrive
}

type Segment

type Segment struct {
	Raw     string
	Name    string // name segments only
	Index   int    // index segments only
	IsIndex bool
}

Segment is one dotted-path component: a name or an index. The first character decides which. An index segment's digits denote its integer value; leading zeros are padding.

Directories

Path Synopsis
cmd
kv command
kv is a CLI for the keyval (.kv) format.
kv is a CLI for the keyval (.kv) format.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL