nox-dune #
Typed codecs for dune's configuration files, built on nox-sexp.
Linters, release scripts and tools that manage many packages at once all
need the same answer: which libraries and packages does each dune and
dune-project file declare? Parsing the S-expressions by hand yields an
untyped tree, and every such tool then re-derives the same fields from it.
This library decodes each kind of file into typed values once, and prints
them back.
Dune.Codec holds one codec per kind of file. file () reads dune build
files, project reads dune-project, workspace reads dune-workspace, and
dune_package reads the installed metadata under _opam/lib/<pkg>/. Any of
them can be passed to the flat entry points Dune.of_string,
of_string_exn, to_string, of_reader, of_reader_exn and to_writer.
The decoded values are read through Dune.File, Dune.Project,
Dune.Workspace and Dune.Package.
Installation #
Install with opam:
$ opam install nox-dune
If opam cannot find the package, it may not yet be released in the public
opam-repository. Add the overlay repository, then install it:
$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install nox-dune
Quick start #
Read a dune file and list what it defines:
let dune_text = {|
(library (name foo) (libraries fmt))
(library (name bar))
(executable (name main))
(test (name test_foo) (libraries alcotest))
|}
let file = Dune.of_string_exn (Dune.Codec.file ()) dune_text
# Dune.File.library_names file
- : string list = ["foo"; "bar"]
# Dune.File.executable_names file
- : string list = ["main"]
# Dune.File.test_names file
- : string list = ["test_foo"]
A dune-project file decodes the same way, into project and package fields:
let project_text = {|
(lang dune 3.17)
(name my-project)
(version 1.0.0)
(license ISC)
(authors "Alice" "Bob")
(source (github owner/repo))
(package
(name my-project)
(synopsis "A demo")
(depends ocaml dune (alcotest :with-test)))
|}
let proj = Dune.of_string_exn Dune.Codec.project project_text
# Dune.Project.name proj
- : string option = Some "my-project"
# Dune.Project.authors proj
- : string list = ["Alice"; "Bob"]
# Dune.Project.source proj
- : string option = Some "github:owner/repo"
# Dune.Project.packages proj
|> List.map (fun p ->
Dune.Project.Package.dependencies p
|> List.map Dune.Project.Dep.to_string)
- : string list list = [["ocaml"; "dune"; "(alcotest :with-test)"]]
# List.map Dune.Project.Package.depends (Dune.Project.packages proj)
- : string list list = [["ocaml"; "dune"; "alcotest"]]
A Dep.t keeps a (depends ...) entry as it was written, with the package
name and its constraint in dune's own grammar. Reading a dune-project and
writing it back therefore leaves the constraints as they were.
Package.depends returns just the names.
To build a project without parsing one, call Dune.Project.v with the
fields, then print it with Dune.to_string Dune.Codec.project. By default the
output puts each stanza on one line. With ~indent:n, stanzas that have
several fields wrap, indenting n spaces per nesting level; ~indent:1 gives
the layout dune itself writes.
Atoms can contain %{name} and %{name:default} references. Dune.Var
parses one, and Dune.expand_atom substitutes them from an environment:
# Dune.Var.to_string (Dune.Var.v ~default:"foo" "name")
- : string = "%{name:foo}"
# let env = function "name" -> Some "Alice" | _ -> None
val env : string -> string option = <fun>
# Dune.expand_atom ~env "Hello %{name}!"
- : string = "Hello Alice!"
API #
dune.mli has the full signatures of the file codecs, the
modules that read their values, Dune.Var, Dune.expand_atom and
Dune.has_variables. An error is a Dune.Error.t, which carries the source
location that nox-sexp
reported.
Licence #
ISC. See LICENSE.md.
- OCaml 99%
- Raku <1%