bkl

package module
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2023 License: Apache-2.0 Imports: 13 Imported by: 0

README

bkl

Layered configuration language implementation

Paths

/         Go parser library source
/cmd/bkl  Evaluation commandline utility source
/docs     https://bkl.gopatchy.io/ website
/tests    Input/output test cases

Documentation

Overview

Package bkl implements a layered configuration language parser.

Example
package main

import (
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	// import "github.com/gopatchy/bkl"

	b := bkl.New()

	err := b.MergeFileLayers("tests/example1/service.test.toml")
	if err != nil {
		panic(err)
	}

	err = b.OutputToWriter(os.Stdout, "json")
	if err != nil {
		panic(err)
	}
}
Output:

{"addr":"127.0.0.1","name":"myService","port":8081}

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// Base error; every error in bkl inherits from this
	Err = fmt.Errorf("bkl error")

	// Format and system errors
	ErrDecode          = fmt.Errorf("decoding error (%w)", Err)
	ErrEncode          = fmt.Errorf("encoding error (%w)", Err)
	ErrOutputFile      = fmt.Errorf("error opening output file (%w)", Err)
	ErrInvalidIndex    = fmt.Errorf("invalid index (%w)", Err)
	ErrInvalidFilename = fmt.Errorf("invalid filename (%w)", Err)
	ErrInvalidType     = fmt.Errorf("invalid type (%w)", Err)
	ErrMissingFile     = fmt.Errorf("missing file (%w)", Err)
	ErrNoMatchFound    = fmt.Errorf("no document matched $match (%w)", Err)
	ErrRequiredField   = fmt.Errorf("required field not set (%w)", Err)
	ErrUnknownFormat   = fmt.Errorf("unknown format (%w)", Err)

	// Base language directive error
	ErrInvalidDirective = fmt.Errorf("invalid directive (%w)", Err)

	// Specific language directive errors
	ErrInvalidMergeType   = fmt.Errorf("invalid $merge type (%w)", ErrInvalidDirective)
	ErrInvalidParentType  = fmt.Errorf("invalid $parent type (%w)", ErrInvalidDirective)
	ErrInvalidPatchType   = fmt.Errorf("invalid $patch type (%w)", ErrInvalidDirective)
	ErrInvalidPatchValue  = fmt.Errorf("invalid $patch value (%w)", ErrInvalidDirective)
	ErrInvalidReplaceType = fmt.Errorf("invalid $replace type (%w)", ErrInvalidDirective)
	ErrMergeRefNotFound   = fmt.Errorf("$merge reference not found (%w)", ErrInvalidDirective)
	ErrReplaceRefNotFound = fmt.Errorf("$replace reference not found (%w)", ErrInvalidDirective)
)

Functions

func FileMatch added in v1.0.2

func FileMatch(path string) (string, string, error)

FileMatch attempts to find a file with the same base name as path, but possibly with a different supported extension. It is intended to support "virtual" filenames that auto-convert from the format of the underlying real file.

Returns the real filename and the requested output format, or ("", "", error).

Types

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

A Parser reads input documents, merges layers, and generates outputs.

Terminology

  • Each Parser can read multiple files
  • Each file represents a single layer
  • Each file contains one or more documents
  • Each document generates one or more outputs

Directive Evaluation Order

Directive evaluation order can matter, e.g. if you $merge a subtree that contains an $output directive.

Merge phase 1 (load)

  • $parent

Merge phase 2 (evaluate)

  • $env

Merge phase 3 (merge)

  • $patch

Output phase 1 (process)

  • $merge
  • $replace

Output phase 2 (output)

  • $output
Example
package main

import (
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	b := bkl.New()

	// Also parses tests/example1/service.yaml
	err := b.MergeFileLayers("tests/example1/service.test.toml")
	if err != nil {
		panic(err)
	}

	if err = b.OutputToWriter(os.Stdout, "json"); err != nil {
		panic(err)
	}
}
Output:

{"addr":"127.0.0.1","name":"myService","port":8081}

func New

func New() *Parser

New creates and returns a new Parser with an empty starting document set.

New always succeeds and returns a Parser instance.

Example
package main

import (
	"fmt"

	"github.com/gopatchy/bkl"
)

func main() {
	b := bkl.New()

	fmt.Println(b.NumDocuments())
}
Output:

0

func (*Parser) Document

func (p *Parser) Document(index int) (any, error)

Document returns the parsed, merged tree for the document at index.

Example
package main

import (
	"fmt"

	"github.com/gopatchy/bkl"
)

func main() {
	b := bkl.New()

	if err := b.MergeFileLayers("tests/example1/service.test.toml"); err != nil {
		panic(err)
	}

	doc, err := b.Document(0)
	if err != nil {
		panic(err)
	}

	fmt.Println(doc)
}
Output:

map[addr:127.0.0.1 name:myService port:8081]

func (*Parser) MergeFile

func (p *Parser) MergeFile(path string) error

MergeFile parses the file at path and merges its contents into the Parser's document state using bkl's merge semantics.

Example
package main

import (
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	// Compare to Parser.MergeFileLayers example.

	b := bkl.New()

	// Does *not* parse tests/example1/service.yaml
	err := b.MergeFile("tests/example1/service.test.toml")
	if err != nil {
		panic(err)
	}

	if err = b.OutputToWriter(os.Stdout, "json"); err != nil {
		panic(err)
	}
}
Output:

{"port":8081}

func (*Parser) MergeFileLayers

func (p *Parser) MergeFileLayers(path string) error

MergeFileLayers determines relevant layers from the supplied path and merges them in order.

Example
package main

import (
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	// Compare to Parser.MergeFile example.

	b := bkl.New()

	// Also parses tests/example1/service.yaml
	err := b.MergeFileLayers("tests/example1/service.test.toml")
	if err != nil {
		panic(err)
	}

	if err = b.OutputToWriter(os.Stdout, "json"); err != nil {
		panic(err)
	}
}
Output:

{"addr":"127.0.0.1","name":"myService","port":8081}

func (*Parser) MergeParser

func (p *Parser) MergeParser(other *Parser) error

MergeParser applies other's internal document state to ours using bkl's merge semantics.

Example
package main

import (
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	b1 := bkl.New()
	b2 := bkl.New()

	if err := b1.MergeFileLayers("tests/tree/a.b.yaml"); err != nil {
		panic(err)
	}

	if err := b2.MergeFileLayers("tests/tree/c.d.yaml"); err != nil {
		panic(err)
	}

	err := b2.MergeParser(b1)
	if err != nil {
		panic(err)
	}

	if err = b2.OutputToWriter(os.Stdout, "json"); err != nil {
		panic(err)
	}
}
Output:

{"a":1,"b":2,"c":3,"d":4}

func (*Parser) MergePatch

func (p *Parser) MergePatch(index int, patch any) error

MergePatch applies the supplied patch to the Parser's current internal document state at the specified document index using bkl's merge semantics.

index is only a hint; if the patch contains a $match entry, that is used instead.

Example
package main

import (
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	b := bkl.New()

	err := b.MergePatch(0, map[string]any{"a": 1})
	if err != nil {
		panic(err)
	}

	err = b.MergePatch(0, map[string]any{"b": 2})
	if err != nil {
		panic(err)
	}

	if err = b.OutputToWriter(os.Stdout, "json"); err != nil {
		panic(err)
	}
}
Output:

{"a":1,"b":2}

func (*Parser) NumDocuments

func (p *Parser) NumDocuments() int

NumDocuments returns the number of documents in the Parser's internal state.

Example
package main

import (
	"fmt"

	"github.com/gopatchy/bkl"
)

func main() {
	b := bkl.New()

	if err := b.MergeFileLayers("tests/example1/service.test.toml"); err != nil {
		panic(err)
	}

	fmt.Println(b.NumDocuments())
}
Output:

1

func (*Parser) Output

func (p *Parser) Output(ext string) ([]byte, error)

Output returns all documents encoded in the specified format and merged into a stream with ---.

Example
package main

import (
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	b := bkl.New()

	if err := b.MergeFileLayers("tests/output-multi/a.yaml"); err != nil {
		panic(err)
	}

	blob, err := b.Output("yaml")
	if err != nil {
		panic(err)
	}

	os.Stdout.Write(blob)
}
Output:

a: 1
b: 2
---
c: 3

func (*Parser) OutputIndex

func (p *Parser) OutputIndex(index int, ext string) ([][]byte, error)

OutputIndex returns the outputs generated by the document at the specified index, encoded in the specified format.

Example
package main

import (
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	b := bkl.New()

	if err := b.MergeFileLayers("tests/stream-add/a.b.yaml"); err != nil {
		panic(err)
	}

	blobs, err := b.OutputIndex(1, "yaml") // second document
	if err != nil {
		panic(err)
	}

	os.Stdout.Write(blobs[0]) // first output of second document
}
Output:

c: 3

func (*Parser) OutputToFile

func (p *Parser) OutputToFile(path, format string) error

OutputToFile encodes all documents in the specified format and writes them to the specified output path.

If format is "", it is inferred from path's file extension.

Example
package main

import (
	"fmt"
	"io"
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	b := bkl.New()

	if err := b.MergeFileLayers("tests/output-multi/a.yaml"); err != nil {
		panic(err)
	}

	f, err := os.CreateTemp("", "example")
	if err != nil {
		panic(err)
	}

	defer os.Remove(f.Name())

	err = b.OutputToFile(f.Name(), "toml")
	if err != nil {
		panic(err)
	}

	blob, err := io.ReadAll(f)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(blob))
}
Output:

a = 1
b = 2
---
c = 3

func (*Parser) OutputToWriter

func (p *Parser) OutputToWriter(fh io.Writer, format string) error

OutputToWriter encodes all documents in the specified format and writes them to the specified io.Writer.

If format is "", it defaults to "json-pretty".

Example
package main

import (
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	b := bkl.New()

	if err := b.MergeFileLayers("tests/output-multi/a.yaml"); err != nil {
		panic(err)
	}

	err := b.OutputToWriter(os.Stdout, "yaml")
	if err != nil {
		panic(err)
	}
}
Output:

a: 1
b: 2
---
c: 3

func (*Parser) Outputs

func (p *Parser) Outputs(ext string) ([][]byte, error)

Outputs returns all outputs from all documents encoded in the specified format.

Example
package main

import (
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	b := bkl.New()

	if err := b.MergeFileLayers("tests/stream-add/a.b.yaml"); err != nil {
		panic(err)
	}

	blobs, err := b.Outputs("yaml")
	if err != nil {
		panic(err)
	}

	os.Stdout.Write(blobs[1]) // second overall output
}
Output:

c: 3

func (*Parser) SetDebug

func (p *Parser) SetDebug(debug bool)

SetDebug enables or disables debug log output to stderr.

Example
package main

import (
	"log"
	"os"

	"github.com/gopatchy/bkl"
)

func main() {
	log.Default().SetFlags(0)
	log.Default().SetOutput(os.Stdout)

	b := bkl.New()

	b.SetDebug(true)

	if err := b.MergeFileLayers("tests/example1/service.test.toml"); err != nil {
		panic(err)
	}
}
Output:

[tests/example1/service.test.toml] loading
[tests/example1/service.yaml] loading
[tests/example1/service.yaml] merging
[tests/example1/service.test.toml] merging

Directories

Path Synopsis
cmd
bkl

Jump to

Keyboard shortcuts

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