kdl

package module
v0.0.0-...-9edc83d Latest Latest
Warning

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

Go to latest
Published: Mar 4, 2026 License: MIT Imports: 11 Imported by: 0

README

go2kdl

A Go library for KDL v2 with full spec compliance, format-preserving editing, and rich error diagnostics.

Forked from sblinch/kdl-go and upgraded to KDLv2 from the ground up.

Features

  • Full KDLv2 spec compliance — passes all 336 official test cases
  • Format-preserving editing — parse a config, change one value, write it back without disturbing whitespace, comments, or formatting
  • Rich error diagnostics — structured errors with line, column, byte offset, and source context snippets
  • Familiar APIMarshal/Unmarshal with struct tags, similar to encoding/json
  • Custom (un)marshalersMarshaler/Unmarshaler interfaces plus generic AddCustomMarshaler[T]/AddCustomUnmarshaler[T]
  • Format optionstime.Time, time.Duration, []byte, float32/64 with format tag support
  • StreamingEncoder/Decoder for reading and writing KDL from io.Reader/io.Writer
  • Comment preservation — parse and round-trip comments
  • Relaxed parsing modes — nginx-style configs, YAML/TOML-style assignments, multiplier suffixes
  • Node lookup helpersFindNode, FindNodeRecursive, RemoveNode on both Document and Node

Install

go get github.com/stream-enterer/go2kdl

Import

import kdl "github.com/stream-enterer/go2kdl"

Quick start

Parse and generate
data := `
name "Bob"
age 76
active #true
`

doc, err := kdl.Parse(strings.NewReader(data))
if err != nil {
    log.Fatal(kdl.FormatError(err))
}

for _, node := range doc.Nodes {
    fmt.Println(node.Name.ValueString())
}
// name
// age
// active
Unmarshal into a struct
type Person struct {
    Name   string `kdl:"name"`
    Age    int    `kdl:"age"`
    Active bool   `kdl:"active"`
}

data := []byte(`
name "Bob"
age 76
active #true
`)

var p Person
if err := kdl.Unmarshal(data, &p); err != nil {
    log.Fatal(err)
}
fmt.Printf("%+v\n", p)
// {Name:Bob Age:76 Active:true}
Marshal from a struct
person := Person{Name: "Bob", Age: 32, Active: true}

data, err := kdl.Marshal(person)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))
// name "Bob"
// age 32
// active #true

Format-preserving editing

Parse a document, modify it, and write it back. Untouched nodes are emitted byte-for-byte identical to the original source.

input := `
// Database settings
host "localhost"
port 5432
max-connections 100
`

doc, _ := kdl.Parse(strings.NewReader(input))

// Change one value
node := doc.FindNode("port")
node.Arguments[0].SetValue(int64(3306))

// Write it back — only the changed value differs
kdl.GenerateWithOptions(doc, os.Stdout, kdl.GenerateOptions{
    PreserveFormatting: true,
})

The setter methods (SetValue, SetType, SetName, AddArgument, etc.) automatically mark elements as dirty so the generator knows to re-format them. Everything else is emitted verbatim from the original source.

Use Autoformat when you want to reformat an entire document:

kdl.Autoformat(reader, writer)

Error diagnostics

Parse errors include source location and context:

_, err := kdl.Parse(strings.NewReader(`node {`))
if err != nil {
    fmt.Println(kdl.FormatError(err))
}
error: unexpected EOF in state stateChildren
 --> 1:7
 |
 1 | node {
 |       ^

Errors implement error and can be inspected programmatically:

var kdlErr *kdl.Error
if errors.As(err, &kdlErr) {
    fmt.Println(kdlErr.Span.Line, kdlErr.Span.Column)
}

Decoder with options

var person Person
dec := kdl.NewDecoder(strings.NewReader(data))
dec.Options.AllowUnhandledNodes = true
if err := dec.Decode(&person); err != nil {
    log.Fatal(err)
}

Encoder with options

enc := kdl.NewEncoder(os.Stdout)
if err := enc.Encode(person); err != nil {
    log.Fatal(err)
}

Relaxed parsing modes

nginx-style syntax
data := `
location / {
    root /var/www/html;
}
location /missing {
    return 404;
}
`

type Location struct {
    Root   string `kdl:"root,omitempty,child"`
    Return int    `kdl:"return,omitempty,child"`
}
type Server struct {
    Locations map[string]Location `kdl:"location,multiple"`
}

var srv Server
dec := kdl.NewDecoder(strings.NewReader(data))
dec.Options.RelaxedNonCompliant |= relaxed.NGINXSyntax
dec.Decode(&srv)

Document model

The AST is fully accessible for programmatic manipulation:

doc := document.New()

node := document.NewNode()
node.SetName("server")
node.AddArgument("localhost", "")
node.AddProperty("port", int64(8080), "")
doc.AddNode(node)

// Find nodes
n := doc.FindNode("server")
n = doc.FindNodeRecursive("nested-node")

// Remove nodes
doc.Nodes[0].RemoveNode("child-name")

Spec compliance

go2kdl passes all 336 official KDLv2 test cases from the kdl-org test suite:

go test -run TestKDLv2Compliance -v
=== RUN   TestKDLv2Compliance
--- PASS: TestKDLv2Compliance (0.01s)
    336/336 tests passed

KDLv2 syntax at a glance

For users coming from KDLv1, the main changes are:

KDLv1 KDLv2
true / false / null #true / #false / #null
r"raw string" #"raw string"#
N/A #inf / #-inf / #nan
N/A """multiline"""
N/A Bare identifiers as values
N/A Whitespace in type annotations: ( u8 )
N/A Space around =: key = value

See the KDLv2 spec for the full details.

License

go2kdl is released under the MIT license. See LICENSE for details.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultGenerateOptions = generator.DefaultOptions
View Source
var DefaultParseOptions = parser.ParseContextOptions{}

Functions

func AddCustomMarshaler

func AddCustomMarshaler[T any](marshal func(v reflect.Value, node *document.Node) error)

func AddCustomUnmarshaler

func AddCustomUnmarshaler[T any](unmarshal func(node *document.Node, v reflect.Value) error)

func AddCustomValueMarshaler

func AddCustomValueMarshaler[T any](marshal func(v reflect.Value, value *document.Value, format string) error)

func AddCustomValueUnmarshaler

func AddCustomValueUnmarshaler[T any](unmarshal func(value *document.Value, v reflect.Value, format string) error)

func Autoformat

func Autoformat(r io.Reader, w io.Writer) error

Autoformat parses a KDL document from r and writes a freshly formatted version to w.

func AutoformatWithOptions

func AutoformatWithOptions(r io.Reader, w io.Writer, opts GenerateOptions) error

AutoformatWithOptions parses a KDL document from r and writes a freshly formatted version to w using the given options. PreserveFormatting is forced to false.

func FormatError

func FormatError(err error) string

FormatError returns a multi-line, human-readable diagnostic string for err. If err is an *Error or Errors with source text, the output includes a numbered source line with a caret pointing at the error column:

error: unexpected } before node entry
  --> input.kdl:3:5
   |
 3 | foo { }
   |       ^

If err is any other error, FormatError returns err.Error().

func Generate

func Generate(doc *document.Document, w io.Writer) error

Generate writes to w a well-formatted KDL document generated from doc, or a non-nil error on failure

func GenerateWithOptions

func GenerateWithOptions(doc *document.Document, w io.Writer, opts GenerateOptions) error

GenerateWithOptions writes to w a well-formatted KDL document generated from doc, or a non-nil error on failure

func Marshal

func Marshal(v interface{}) ([]byte, error)

Marshal returns the KDL representation of v, or a non-nil error on failure

func MarshalDocument

func MarshalDocument(v interface{}, doc *document.Document) error

func MarshalDocumentWithOptions

func MarshalDocumentWithOptions(v interface{}, doc *document.Document, opts marshaler.MarshalOptions) error

func MarshalNode

func MarshalNode(v interface{}) (*document.Node, error)

func MarshalNodeWithOptions

func MarshalNodeWithOptions(v interface{}, opts marshaler.MarshalOptions) (*document.Node, error)

func MarshalWithOptions

func MarshalWithOptions(v interface{}, opts MarshalOptions) ([]byte, error)

MarshalWithOptions returns the KDL representation of v using the specified Options, or a non-nil error on failure

func Parse

func Parse(r io.Reader) (*document.Document, error)

Parse parses a KDL document from r and returns the parsed Document, or a non-nil error on failure

func ParseWithOptions

func ParseWithOptions(r io.Reader, opts ParseOptions) (*document.Document, error)

func Unmarshal

func Unmarshal(data []byte, v interface{}) error

Unmarshal unmarshals KDL from data into v; v must contain a pointer type. Returns a non-nil error on failure.

func UnmarshalDocument

func UnmarshalDocument(doc *document.Document, v interface{}) error

func UnmarshalDocumentWithOptions

func UnmarshalDocumentWithOptions(doc *document.Document, v interface{}, opts UnmarshalOptions) error

func UnmarshalNode

func UnmarshalNode(node *document.Node, v interface{}) error

func UnmarshalNodeWithOptions

func UnmarshalNodeWithOptions(node *document.Node, v interface{}, opts UnmarshalOptions) error

func UnmarshalWithOptions

func UnmarshalWithOptions(data []byte, v interface{}, opts UnmarshalOptions) error

UnmarshalWithOptions unmarshals KDL from data into v with the specified options; v must contain a pointer type. Returns a non-nil error on failure.

Types

type Decoder

type Decoder struct {
	Options marshaler.UnmarshalOptions
	// contains filtered or unexported fields
}

Decoder implements a decoder for KDL

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder returns a Decoder that reads from r

func (*Decoder) Decode

func (d *Decoder) Decode(v interface{}) error

Decode decodes KDL from the Decoder's reader into v; v must contain a pointer type. Returns a non-nil error on failure.

type Encoder

type Encoder struct {
	Options MarshalOptions
	// contains filtered or unexported fields
}

Encoder implements an encoder for KDL

func NewEncoder

func NewEncoder(w io.Writer) *Encoder

NewEncoder creates a new Encoder that writes to w

func (*Encoder) Encode

func (e *Encoder) Encode(v interface{}) error

Encode encodes v into KDL and writes it to the Encoder's writer, and returns a non-nil error on failure

type Error

type Error struct {
	// Message is the human-readable error description.
	Message string
	// Span locates the error in the source text. Zero value if unknown.
	Span document.Span
	// Source is the original input text (or nil if unavailable).
	// Retained so callers can render context snippets.
	Source []byte
}

Error is a structured parse error with source location and context.

func (*Error) Error

func (e *Error) Error() string

Error returns a compact diagnostic string suitable for logs.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns nil (leaf error).

type Errors

type Errors []Error

Errors collects one or more parse errors.

func (Errors) Error

func (e Errors) Error() string

Error joins messages, one per line.

type GenerateOptions

type GenerateOptions = generator.Options

type GeneratorOptions

type GeneratorOptions = generator.Options

type MarshalOptions

type MarshalOptions struct {
	MarshalerOptions
	GeneratorOptions
}

type Marshaler

type Marshaler interface {
	MarshalKDL(node *document.Node) error
}

Marshaler provides an interface for custom marshaling of a Go type into a Node

type MarshalerOptions

type MarshalerOptions = marshaler.MarshalOptions

type ParseOptions

type ParseOptions = parser.ParseContextOptions

type UnmarshalOptions

type UnmarshalOptions = marshaler.UnmarshalOptions

type Unmarshaler

type Unmarshaler interface {
	UnmarshalKDL(node *document.Node) error
}

Unmarshaler provides an interface for custom unmarshaling of a node into a Go type

type ValueMarshaler

type ValueMarshaler interface {
	MarshalKDLValue(value *document.Value) error
}

ValueMarshaler provides an interface for custom marshaling of a Go type into a Value (such as a node argument or property)

type ValueUnmarshaler

type ValueUnmarshaler interface {
	UnmarshalKDLValue(value *document.Value) error
}

ValueUnmarshaler provides an interface for custom unmarshaling of a Value (such as a node argument or property) into a Go type

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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