defmodule Jsonnex do @moduledoc """ The Jsonnex library allows you to compile Jsonet files into JSON right from Elixir. ## Design Decisions Writing a Jsonnet compiler from scratch is a large undertaking and not something that I have the time to do, so instead I decided to use the [Golang reference implementation from Google](https://github.com/google/go-jsonnet), build a WASM binary encapsulating that library and then executing that WASM binary via `Wasmex`. There are Rust Jsonnet compilers available, but some of them use `unsafe` in their code and so I did not want to risk crashing the BEAM and others seem to be unmaintained. Leveraging the Golang reference implementation seemed like the best way forward. ## Usage The Jsonnex library comes bundled with the WASM binary for compiling Jsonnet files, but you can instead use your own version of the WASM binary if you want by running the included `build.sh` script, copying the output WASM binary to a directory of your chosing and then overriding the binary path by providing `wasm_path: "your_new_path"` to the `compile/2` or `compile!/2` functions. The Jsonnex library provides two main components: - `Jsonnex.Compiler` - This enables you to compile a Jsonnet snippet into JSON. - `Jsonnex.Format` - This enables you to format Jsonnet snippets (and also `~JSONNET` sigil calls if you leverage the `Jsonnex.MixFormatter` plugin). The Jsonnet compiler can take a standalone snippet of Jsonnet code and compile it into JSON, or you can pass in virtual files to enable `import` statements to work, or you can even pass in paths to directories to mount to the Wasmex so that the WASM binary has access to those directories. ## Examples iex> {:ok, %{"person1" => _, "person2" => _}} = ...> Jsonnex.compile(~S[ ...> // A function that returns an object. ...> local Person(name='Alice') = { ...> name: name, ...> welcome: 'Hello ' + name + '!', ...> }; ...> { ...> person1: Person(), ...> person2: Person('Bob'), ...> }]) iex> {:ok, "{a: 1 + 2, b: \\"hello\\"}\\n"} = ...> Jsonnex.format(~S[{ a: 1 + 2, b: "hello" }]) iex> {:ok, %{"Manhattan" => _, "Vodka Martini" => _}} = ...> Jsonnex.compile(\""" ...> local martinis = import 'martinis.libsonnet'; ...> ...> { ...> 'Vodka Martini': martinis['Vodka Martini'], ...> Manhattan: { ...> ingredients: [ ...> { kind: 'Rye', qty: 2.5 }, ...> { kind: 'Sweet Red Vermouth', qty: 1 }, ...> { kind: 'Angostura', qty: 'dash' }, ...> ], ...> garnish: importstr 'garnish.txt', ...> served: 'Straight Up', ...> }, ...> } ...> \""", ...> virtual_files: %{ ...> "martinis.libsonnet" => \""" ...> { ...> 'Vodka Martini': { ...> ingredients: [ ...> { kind: 'Vodka', qty: 2 }, ...> { kind: 'Dry White Vermouth', qty: 1 }, ...> ], ...> garnish: 'Olive', ...> served: 'Straight Up', ...> }, ...> Cosmopolitan: { ...> ingredients: [ ...> { kind: 'Vodka', qty: 2 }, ...> { kind: 'Triple Sec', qty: 0.5 }, ...> { kind: 'Cranberry Juice', qty: 0.75 }, ...> { kind: 'Lime Juice', qty: 0.5 }, ...> ], ...> garnish: 'Orange Peel', ...> served: 'Straight Up', ...> }, ...> } ...> \""", ...> "garnish.txt" => "Maraschino Cherry" ...> } ...> ) """ @doc false def wasm_path do Application.get_env( :jsonnex, :wasm_path, Path.join(:code.priv_dir(:jsonnex), "jsonnex.wasm") ) end defdelegate compile(input, opts \\ []), to: Jsonnex.Compiler defdelegate compile!(input, opts \\ []), to: Jsonnex.Compiler defdelegate format(input, opts \\ []), to: Jsonnex.Format defdelegate format!(input, opts \\ []), to: Jsonnex.Format end