Documentation ¶
Overview ¶
Package bkl implements a layered configuration language parser.
- Language & tool documentation: https://bkl.gopatchy.io/
- Go library source: https://github.com/gopatchy/bkl
- Go library documentation: https://pkg.go.dev/github.com/gopatchy/bkl
Example ¶
package main import ( "os" "github.com/gopatchy/bkl" ) func main() { // import "github.com/gopatchy/bkl" b := bkl.New() err := b.MergeFileLayers("tests/example1/a.b.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 ¶
- Variables
- func FileMatch(path string) (string, string, error)
- type Document
- func (d *Document) AddParents(parents ...*Document)
- func (d *Document) AllParents() map[string]*Document
- func (d *Document) Clone(suffix string) (*Document, error)
- func (d *Document) DataAsMap() map[string]any
- func (d *Document) PopMapValue(key string) (bool, any)
- func (d *Document) Process(mergeFromDocs []*Document) ([]*Document, error)
- func (d *Document) String() string
- type Format
- type Parser
- func (p *Parser) Documents() []*Document
- func (p *Parser) MergeDocument(patch *Document) error
- func (p *Parser) MergeFile(path string) error
- func (p *Parser) MergeFileLayers(path string) error
- func (p *Parser) Output(format string) ([]byte, error)
- func (p *Parser) OutputDocuments() ([]any, error)
- func (p *Parser) OutputToFile(path, format string) error
- func (p *Parser) OutputToWriter(fh io.Writer, format string) error
- func (p *Parser) SetDebug(debug bool)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // Base error; every error in bkl inherits from this Err = fmt.Errorf("bkl error") // Format and system errors ErrCircularRef = fmt.Errorf("circular reference (%w)", Err) ErrConflictingParent = fmt.Errorf("conflicting $parent (%w)", Err) ErrExtraEntries = fmt.Errorf("extra entries (%w)", Err) ErrExtraKeys = fmt.Errorf("extra keys (%w)", Err) ErrInvalidArguments = fmt.Errorf("invalid arguments (%w)", Err) ErrInvalidDirective = fmt.Errorf("invalid directive (%w)", Err) ErrInvalidIndex = fmt.Errorf("invalid index (%w)", Err) ErrInvalidFilename = fmt.Errorf("invalid filename (%w)", Err) ErrInvalidType = fmt.Errorf("invalid type (%w)", Err) ErrInvalidParent = fmt.Errorf("invalid $parent (%w)", Err) ErrInvalidRepeat = fmt.Errorf("invalid $repeat (%w)", Err) ErrMarshal = fmt.Errorf("encoding error (%w)", Err) ErrRefNotFound = fmt.Errorf("reference not found (%w)", Err) ErrMissingEnv = fmt.Errorf("missing environment variable (%w)", Err) ErrMissingFile = fmt.Errorf("missing file (%w)", Err) ErrMissingMatch = fmt.Errorf("missing $match (%w)", Err) ErrMultiMatch = fmt.Errorf("multiple documents $match (%w)", Err) ErrNoMatchFound = fmt.Errorf("no document/entry matched $match (%w)", Err) ErrNoCloneFound = fmt.Errorf("no document/entry matched $clone (%w)", Err) ErrOutputFile = fmt.Errorf("error opening output file (%w)", Err) ErrRequiredField = fmt.Errorf("required field not set (%w)", Err) ErrUnknownFormat = fmt.Errorf("unknown format (%w)", Err) ErrUnmarshal = fmt.Errorf("decoding error (%w)", Err) ErrUselessOverride = fmt.Errorf("useless override (%w)", Err) ErrVariableNotFound = fmt.Errorf("variable not found (%w)", Err) )
Functions ¶
func FileMatch ¶ added in v1.0.2
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 Document ¶ added in v1.0.25
func NewDocument ¶ added in v1.0.25
func NewDocumentWithData ¶ added in v1.0.25
func (*Document) AddParents ¶ added in v1.0.25
func (*Document) AllParents ¶ added in v1.0.25
func (*Document) PopMapValue ¶ added in v1.0.25
type Format ¶ added in v1.0.7
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.
Phase 1
- $parent
Phase 2
- $delete
- $replace: true
Phase 3
- $merge
- $replace: map
- $replace: string
Phase 4
- $repeat: int
Phase 5
- $""
- $encode
- $env
- $repeat
- $value
Phase 6
- $output
Document Layer Matching Logic ¶
When applying a new document to internal state, it may be merged into one or more existing documents or appended as a new document. To select merge targets, Parser considers (in order):
- If $match:
- $match: null -> append
- $match within parent documents -> merge
- $match any documents -> merge
- No matching documents -> error
- If parent documents -> merge into all parents
- If no parent documents -> append
Example ¶
package main import ( "os" "github.com/gopatchy/bkl" ) func main() { b := bkl.New() // Also parses tests/example1/a.yaml err := b.MergeFileLayers("tests/example1/a.b.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() docs := b.Documents() fmt.Println(docs) }
Output: []
func (*Parser) Documents ¶ added in v1.0.14
Documents returns the parsed, merged (but not processed) trees for all documents.
Example ¶
package main import ( "fmt" "github.com/gopatchy/bkl" ) func main() { b := bkl.New() if err := b.MergeFileLayers("tests/example1/a.b.toml"); err != nil { panic(err) } docs := b.Documents() fmt.Println(docs[0].Data) }
Output: map[addr:127.0.0.1 name:myService port:8081]
func (*Parser) MergeDocument ¶ added in v1.0.25
MergeDocument applies the supplied Document to the Parser's current internal document state using bkl's merge semantics. If expand is true, documents without $match will append; otherwise this is an error.
Example ¶
package main import ( "os" "github.com/gopatchy/bkl" ) func main() { b := bkl.New() doc1 := bkl.NewDocumentWithData("a", map[string]any{"a": 1}) doc2 := bkl.NewDocumentWithData("b", map[string]any{"b": 2}) doc2.AddParents(doc1) err := b.MergeDocument(doc1) if err != nil { panic(err) } err = b.MergeDocument(doc2) if err != nil { panic(err) } if err = b.OutputToWriter(os.Stdout, "json"); err != nil { panic(err) } }
Output: {"a":1,"b":2}
func (*Parser) MergeFile ¶
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/a.yaml err := b.MergeFile("tests/example1/a.b.toml") if err != nil { panic(err) } if err = b.OutputToWriter(os.Stdout, "json"); err != nil { panic(err) } }
Output: {"port":8081}
func (*Parser) MergeFileLayers ¶
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/a.yaml err := b.MergeFileLayers("tests/example1/a.b.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) Output ¶
Output returns all documents encoded in the specified format and merged into a stream.
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
Example (Literal_dollar) ¶
package main import ( "os" "github.com/gopatchy/bkl" ) func main() { b := bkl.New() if err := b.MergeFileLayers("tests/literal-dollar/a.yaml"); err != nil { panic(err) } blob, err := b.Output("json") if err != nil { panic(err) } os.Stdout.Write(blob) }
Output: {"listKey":["$instance",{"nested":{"$key":"$value"}}],"mapKey":"$pod"}
func (*Parser) OutputDocuments ¶ added in v1.0.7
OutputDocuments returns the output objects generated by all documents.
func (*Parser) OutputToFile ¶
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 ¶
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) SetDebug ¶
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/a.b.toml"); err != nil { panic(err) } }
Output: