Evaluator API
pkl-go provides a rich API for evaluating Pkl files in Go via pkl.Evaluator
. It can be configured with custom readers, have its own security settings, control
external property inputs, and more.
Evaluation occurs by spawning the pkl
cli as a child process, and communicates via message passing.
Initializing an evaluator
There are two methods for initializing a pkl.Evaluator
:
These two constructors are distinguished by how they manage the lifecycle of the underlying pkl
child process.
pkl.NewEvaluator
will spawn a child process per-evaluator, whereas pkl.EvaluatorManager.NewEvalutor
will spawn a
child process for the lifetime of the evaluator manager.
For most use-cases, it is sufficient to use the pkl.NewEvaluator
constructor. If multiple evaluators are desired,
for example, to maintain separate caches or to have different settings, the evaluator manager should be used.
Evaluating modules
With code generation
Go packages generated from Pkl modules may be evaluated simply through the generated LoadFromPath
and
Load
methods.
For example, a generated package github.com/myapp/myteam/appconfig
can be used for evaluation like so:
package main
import (
"context"
"fmt"
"github.com/apple/pkl-go/pkl"
"github.com/myapp/myteam/appconfig" (1)
)
func main() {
cfg, err := appconfig.LoadFromPath(context.Background(), "myconfig.pkl")
if err != nil {
panic(err)
}
fmt.Printf("Got config: %+v\n", cfg)
}
1 | The generated package |
Without code generation
Pkl modules may be evaluated directly into Go structs without using code generation.
For example, given the Pkl module:
foo: String = "hello"
bar: Int = 5
This may be evaluated into a Go struct via pkl.Evaluator.EvaluateModule
.
To run evaluation, pass a struct pointer that matches the structure of the module.
If the Go property name doesn’t match the Pkl name, the pkl
struct tag should be used to identify the Pkl property name.
Example Go:
package main
import (
"context"
"fmt"
"github.com/apple/pkl-go/pkl"
)
type MyConfig struct {
Foo string `pkl:"foo"`
Bar int `pkl:"bar"`
}
func main() {
evaluator, err := pkl.NewEvaluator(context.Background(), pkl.PreconfiguredOptions)
if err != nil {
panic(err)
}
defer evaluator.Close()
var cfg MyConfig
if err = evaluator.EvaluateModule(context.Background(), pkl.FileSource("foo.pkl"), &cfg); err != nil {
panic(err)
}
fmt.Printf("Got module: %+v", cfg)
}
Alternative evaluation modes
Textual and multiple file output
In addition to modules, an evaluator may also evaluate a module’s output.text
and output.files
properties. This mimick the behavior of the CLI.
Take this Pkl module:
foo = "foo"
bar = "bar"
The textual output of this module may be evaluated via Evaluator.EvaluateOutputText
.
package main
import (
"context"
"fmt"
"github.com/apple/pkl-go/pkl"
)
func main() {
evaluator, err := pkl.NewEvaluator(context.Background(), pkl.PreconfiguredOptions)
if err != nil {
panic(err)
}
defer evaluator.Close()
textOutput, err := evaluator.EvaluateOutputText(context.Background(), pkl.FileSource("foo.pkl"))
fmt.Println(textOutput)
}
Expressions
In addition to evaluating modules and textual/file output, any arbitrary expression may be evaluated within a module. In fact, the textual and file output of a module are implemented in terms of evaluating an expression.
package main
import (
"context"
"fmt"
"github.com/apple/pkl-go/pkl"
)
func main() {
evaluator, err := pkl.NewEvaluator(context.Background(), pkl.PreconfiguredOptions)
if err != nil {
panic(err)
}
defer evaluator.Close()
var res string
err = evaluator.EvaluateExpression(
context.Background(),
pkl.TextSource("foo = 5"), (1)
`"foo is \(foo)"`, (2)
&res,
)
if err != nil {
panic(err)
}
fmt.Println(res) // prints "foo is 5"
}
1 | TextSource causes Pkl to evaluate foo = 5 as a module |
2 | The expression to be evaluated |
Evaluator options
pkl-go provides pkl.PreconfiguredOptions
, which serves as a simple way to construct an evaluator with sensible defaults.
Additional options may be provided via the functional options pattern:
pkl.NewEvaluator(context.Background(), pkl.PreconfiguredOptions, func(opts *EvaluatorOptions) {
opts.Logger = pkl.StderrLogger (1)
})
1 | Log warn/trace messages to stderr |
Custom readers
It is possible to use a custom reader for resources and modules by implementing the pkl.ResourceReader
and pkl.ModuleReader
interfaces.
Custom readers must identify the scheme that they are responsible for reading via the Scheme()
method. For example,
a reader may be registered to resolve the Pkl expression read("secret:FOO")
by registering "secret"
as its scheme.
If a resource matches a scheme identified by a custom reader, its Read()
method will be called to retrieve the contents.