Packages

Pkl language integration for Elixir — evaluator client and code generation.

Current section

Files

Jump to
pkl_elixir lib pkl_elixir.ex
Raw

lib/pkl_elixir.ex

defmodule PklElixir do
@moduledoc """
Pkl language integration for Elixir.
Provides an evaluator client that communicates with the `pkl server`
subprocess via MessagePack over stdin/stdout.
## Quick start
# Evaluate Pkl text
{:ok, result} = PklElixir.evaluate_text(~S'name = "hello"')
result["name"] #=> "hello"
# Evaluate a .pkl file
{:ok, result} = PklElixir.evaluate("config.pkl")
# Evaluate a specific expression
{:ok, name} = PklElixir.evaluate("config.pkl", expr: "name")
## Custom readers
defmodule MyReader do
@behaviour PklElixir.ModuleReader
@impl true
def scheme, do: "myapp"
@impl true
def is_local, do: false
@impl true
def is_globbable, do: false
@impl true
def has_hierarchical_uris, do: false
@impl true
def read("myapp:" <> path), do: {:ok, "value = 42"}
@impl true
def list_elements(_uri), do: {:ok, []}
end
PklElixir.evaluate_text(
~S'x = import("myapp:config").value',
module_readers: [MyReader]
)
## Managed lifecycle
For evaluating multiple modules efficiently, reuse a server:
{:ok, server} = PklElixir.Server.start_link()
{:ok, eval_id} = PklElixir.Server.create_evaluator(server)
{:ok, r1} = PklElixir.Server.evaluate(server, eval_id, PklElixir.ModuleSource.text("x = 1"))
{:ok, r2} = PklElixir.Server.evaluate(server, eval_id, PklElixir.ModuleSource.text("y = 2"))
PklElixir.Server.close_evaluator(server, eval_id)
GenServer.stop(server)
## Requirements
The `pkl` binary must be available. Either install it on PATH or set
the `PKL_EXEC` environment variable to the path of the pkl executable.
"""
alias PklElixir.{Server, ModuleSource}
@doc """
Evaluate a Pkl file at the given path.
## Options
* `:expr` — evaluate a specific expression instead of the whole module
* `:module_readers` — list of modules implementing `PklElixir.ModuleReader`
* `:resource_readers` — list of modules implementing `PklElixir.ResourceReader`
* `:timeout` — timeout in milliseconds (default: 60_000)
Returns `{:ok, result}` or `{:error, exception}`.
"""
@spec evaluate(String.t(), keyword()) :: {:ok, term()} | {:error, Exception.t()}
def evaluate(path, opts \\ []) when is_binary(path) do
source = ModuleSource.file(path)
do_evaluate(source, opts)
end
@doc """
Evaluate Pkl source text directly.
## Options
* `:expr` — evaluate a specific expression instead of the whole module
* `:module_readers` — list of modules implementing `PklElixir.ModuleReader`
* `:resource_readers` — list of modules implementing `PklElixir.ResourceReader`
* `:timeout` — timeout in milliseconds (default: 60_000)
Returns `{:ok, result}` or `{:error, exception}`.
"""
@spec evaluate_text(String.t(), keyword()) :: {:ok, term()} | {:error, Exception.t()}
def evaluate_text(text, opts \\ []) when is_binary(text) do
source = ModuleSource.text(text)
do_evaluate(source, opts)
end
defp do_evaluate(source, opts) do
{:ok, server} = Server.start_link()
try do
with {:ok, evaluator_id} <- Server.create_evaluator(server, opts),
result <- Server.evaluate(server, evaluator_id, source, opts) do
Server.close_evaluator(server, evaluator_id)
result
end
after
GenServer.stop(server)
end
end
end