My own corner of monopam
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

ocaml-dune: per-module encoding API, abstract types, no Sexp.t leaks

Each file kind (File, Project, Workspace, Package) is now a self-
contained module with abstract t and the standard six-verb IO shape
(of_string / of_string_exn / to_string / of_reader / of_reader_exn /
to_writer). Sexp.t no longer appears in any signature; Dune.Error is
aliased from Sexp.Error.

- Project.t, Workspace.t, File.t made abstract; their old record
exposures (packages : Sexp.t list, env : Sexp.t option, etc.) are
now internal. Round-trip via the codec preserves unrecognized
stanzas.
- Project.Package is a typed view of a (package ...) stanza
(name / synopsis / depends).
- Workspace.Context.kind drops the (Other of Sexp.t) leak; unrecognized
context shapes still round-trip via the codec but are filtered out
of Workspace.contexts.
- File.t internally classifies stanzas (Library / Executable / Test /
Other) so library_names / private_library_names / executable_names /
test_names are typed projections, not Sexp tree walks.
- Dune.Package.libraries reads every (library ...) stanza from a
dune-package file; Sexp.Codec.decode_value stays internal.
- ?indent:int now visibly affects to_string: lists with non-atom
children wrap, single-child and atom-only lists <=4 stay inline.

Drops the old public Sexp-level helpers (parse, to_sexps, per-module
to_string, field / fields / field_atom / field_list / field_atoms /
set_field / remove_field, the library/executable/test builders,
pp_dune, to_string_dune, find_stanza, ...). The functionality lives
internally where each module needs it.

Caller migrations:
- monopam/lib/lint.ml: Dune.File.parse -> Dune.File.of_string.
- ocaml-dune/test/test_dune.ml rewritten against the new API.

README rewritten using the new API; mdx libraries reduces to
(libraries nox-dune) -- no nox-sexp leak.

+781 -696
+4 -3
monopam/lib/lint.ml
··· 348 348 |> List.concat_map (fun df -> 349 349 match load_file fs df with 350 350 | None -> [] 351 - | Some content -> 352 - parse_sexps content |> Dune.File.parse 353 - |> Dune.File.private_library_names) 351 + | Some content -> ( 352 + match Dune.File.of_string content with 353 + | Ok t -> Dune.File.private_library_names t 354 + | Stdlib.Error _ -> [])) 354 355 |> String_set.of_list 355 356 356 357 (** Collect all packages referenced via [(libraries ...)] in any dune file in a
+230 -6
ocaml-dune/README.md
··· 3 3 Type-safe codecs for the dune-format file family, built on 4 4 [nox-sexp](https://tangled.org/gazagnaire.org/ocaml-sexp). 5 5 6 - Each file kind lives in its own submodule: 6 + Each file kind is its own self-contained module: 7 7 8 + - `Dune.File` — `dune` build files 8 9 - `Dune.Project` — `dune-project` files 9 10 - `Dune.Workspace` — `dune-workspace` files 10 - - `Dune.File` — `dune` build files 11 11 - `Dune.Package` — installed `dune-package` metadata 12 12 13 - Plus the shared field accessors, stanza-kind tagging, dune-style pretty 14 - printing, and `%{var}` expansion in `Dune.Var`. 13 + Every module exposes the same six-verb IO shape — `of_string`, 14 + `of_string_exn`, `to_string`, `of_reader`, `of_reader_exn`, 15 + `to_writer` — plus typed accessors over an abstract `t`. 16 + 17 + ## Installation 18 + 19 + Install with opam: 20 + 21 + <!-- $MDX skip --> 22 + ```sh 23 + opam install nox-dune 24 + ``` 25 + 26 + If opam cannot find the package, it may not yet be released in the 27 + public `opam-repository`. Add the overlay repository, then install: 28 + 29 + <!-- $MDX skip --> 30 + ```sh 31 + opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git 32 + opam update 33 + opam install nox-dune 34 + ``` 35 + 36 + ## Quick start 37 + 38 + ### dune build files 39 + 40 + ```ocaml 41 + let dune_text = {| 42 + (library (name foo) (libraries fmt)) 43 + (library (name bar)) 44 + (executable (name main)) 45 + (test (name test_foo) (libraries alcotest)) 46 + |} 47 + 48 + let file = Dune.File.of_string_exn dune_text 49 + ``` 50 + 51 + ```ocaml 52 + # Dune.File.library_names file 53 + - : string list = ["foo"; "bar"] 54 + 55 + # Dune.File.private_library_names file 56 + - : string list = ["foo"; "bar"] 15 57 16 - The split mirrors `nox-sexp` (generic sexp AST + codec primitives) and 17 - `nox-opam` (opam-format codecs). 58 + # Dune.File.executable_names file 59 + - : string list = ["main"] 60 + 61 + # Dune.File.test_names file 62 + - : string list = ["test_foo"] 63 + ``` 64 + 65 + ### dune-project files 66 + 67 + ```ocaml 68 + let project_text = {| 69 + (lang dune 3.17) 70 + (name my-project) 71 + (version 1.0.0) 72 + (license ISC) 73 + (authors "Alice" "Bob") 74 + (source (github owner/repo)) 75 + (package 76 + (name my-project) 77 + (synopsis "A demo") 78 + (depends ocaml dune (alcotest :with-test))) 79 + |} 80 + 81 + let proj = Dune.Project.of_string_exn project_text 82 + ``` 83 + 84 + ```ocaml 85 + # Dune.Project.name proj 86 + - : string option = Some "my-project" 87 + 88 + # Dune.Project.license proj 89 + - : string option = Some "ISC" 90 + 91 + # Dune.Project.authors proj 92 + - : string list = ["Alice"; "Bob"] 93 + 94 + # Dune.Project.source proj 95 + - : string option = Some "github:owner/repo" 96 + 97 + # List.map Dune.Project.Package.name (Dune.Project.packages proj) 98 + - : string list = ["my-project"] 99 + 100 + # List.map Dune.Project.Package.depends (Dune.Project.packages proj) 101 + - : string list list = [["ocaml"; "dune"; "alcotest"]] 102 + ``` 103 + 104 + `Dune.Project.make` builds a value from typed fields: 105 + 106 + ```ocaml 107 + let proj = 108 + Dune.Project.make 109 + ~dune_version:"3.17" 110 + ~name:"demo" 111 + ~version:"0.1" 112 + ~license:"ISC" 113 + ~authors:["Alice"] 114 + () 115 + ``` 116 + 117 + ### dune-workspace files 118 + 119 + ```ocaml 120 + let ws = 121 + Dune.Workspace.of_string_exn 122 + {|(lang dune 3.17) (context default)|} 123 + ``` 124 + 125 + ```ocaml 126 + # Dune.Workspace.lang ws 127 + - : string * string = ("dune", "3.17") 128 + 129 + # List.length (Dune.Workspace.contexts ws) 130 + - : int = 1 131 + ``` 132 + 133 + ### dune-package metadata 134 + 135 + `Dune.Package.libraries` decodes every `(library ...)` stanza in an 136 + installed `_opam/lib/<pkg>/dune-package` file, skipping the 137 + `(lang ...)` header: 138 + 139 + ```ocaml 140 + let dune_package_text = {| 141 + (lang dune 3.0) 142 + (library 143 + (name helix.jx) 144 + (kind virtual) 145 + (main_module_name Jx)) 146 + (library 147 + (name helix.jx.jsoo) 148 + (kind normal) 149 + (implements helix.jx) 150 + (main_module_name Jx) 151 + (modules 152 + (wrapped 153 + (group 154 + (alias (obj_name jx__jx_jsoo__)) 155 + (name Jx) 156 + (modules 157 + (module (obj_name jx__Jx_ffi))))))) 158 + |} 159 + 160 + let libs = Dune.Package.libraries dune_package_text 161 + ``` 162 + 163 + ```ocaml 164 + # List.map Dune.Package.name libs 165 + - : string list = ["helix.jx"; "helix.jx.jsoo"] 166 + 167 + # Dune.Package.implements (List.nth libs 1) 168 + - : string option = Some "helix.jx" 169 + 170 + # List.sort String.compare (Dune.Package.modules (List.nth libs 1)) 171 + - : string list = ["Jx"; "Jx__Jx_ffi"] 172 + ``` 173 + 174 + ## IO entry points 175 + 176 + The same six verbs appear on every file-kind module: 177 + 178 + ```ocaml 179 + # #show Dune.File.of_string 180 + val of_string : string -> (Dune.File.t, Sexp.Error.t) result 181 + 182 + # #show Dune.File.to_string 183 + val to_string : ?indent:int -> ?preserve:bool -> Dune.File.t -> string 184 + ``` 185 + 186 + Omit `?indent` for compact output (one stanza per line, single-space 187 + atoms). Pass `~indent:n` to wrap multi-field stanzas with `n` spaces 188 + per nesting level — `~indent:1` matches dune's own layout: 189 + 190 + ```ocaml 191 + # print_endline (Dune.File.to_string file) 192 + (library (name foo) (libraries fmt)) 193 + (library (name bar)) 194 + (executable (name main)) 195 + (test (name test_foo) (libraries alcotest)) 196 + - : unit = () 197 + 198 + # print_endline (Dune.File.to_string ~indent:1 file) 199 + (library 200 + (name foo) 201 + (libraries fmt)) 202 + 203 + (library (name bar)) 204 + 205 + (executable (name main)) 206 + 207 + (test 208 + (name test_foo) 209 + (libraries alcotest)) 210 + - : unit = () 211 + ``` 212 + 213 + For streaming I/O, every module also has `of_reader` / 214 + `of_reader_exn` / `to_writer` over `Bytesrw.Bytes.Reader.t` / 215 + `Bytesrw.Bytes.Writer.t`. 216 + 217 + ## Variables 218 + 219 + Dune embeds `%{name}` and `%{name:default}` references in atoms. 220 + `Dune.Var` parses and expands them: 221 + 222 + ```ocaml 223 + # Dune.Var.to_string (Dune.Var.v "name") 224 + - : string = "%{name}" 225 + 226 + # Dune.Var.to_string (Dune.Var.v ~default:"foo" "name") 227 + - : string = "%{name:foo}" 228 + 229 + # let env = function "name" -> Some "Alice" | _ -> None 230 + val env : string -> string option = <fun> 231 + 232 + # Dune.expand_atom ~env "Hello %{name}!" 233 + - : string = "Hello Alice!" 234 + 235 + # Dune.has_variables "no vars here" 236 + - : bool = false 237 + ``` 238 + 239 + ## License 240 + 241 + ISC.
+349 -268
ocaml-dune/lib/dune.ml
··· 3 3 SPDX-License-Identifier: ISC 4 4 ---------------------------------------------------------------------------*) 5 5 6 + module Error = Sexp.Error 7 + 8 + exception Error = Sexp.Error 9 + 6 10 (* ---- Variable expansion ---- *) 7 11 8 12 module Var = struct ··· 96 100 let segments = text_of_string s in 97 101 expand_text ~env segments 98 102 99 - let rec expand ~env = function 100 - | Sexp.Atom s -> Sexp.Atom (expand_atom ~env s) 101 - | Sexp.List l -> Sexp.List (List.map (expand ~env) l) 102 - 103 103 let has_variables s = 104 104 let len = String.length s in 105 105 let rec find_close i = ··· 111 111 else loop (i + 1) 112 112 in 113 113 loop 0 114 - 115 - let rec variables = function 116 - | Sexp.Atom s -> 117 - let segments = text_of_string s in 118 - List.filter_map 119 - (function Text _ -> None | Variable v -> Some v.name) 120 - segments 121 - | Sexp.List l -> List.concat_map variables l 122 114 123 115 (* ---- Dune-style pretty printing ---- *) 124 116 ··· 148 140 let pp_atom ppf s = 149 141 if needs_quoting s then pp_quoted_atom ppf s else Fmt.string ppf s 150 142 151 - let pp_dune ppf sexp = 152 - let rec pp_sexp indent ppf = function 143 + let pp_compact ppf sexp = 144 + let rec p ppf = function 145 + | Sexp.Atom s -> pp_atom ppf s 146 + | Sexp.List items -> 147 + Fmt.char ppf '('; 148 + (match items with 149 + | [] -> () 150 + | first :: rest -> 151 + p ppf first; 152 + List.iter 153 + (fun item -> 154 + Fmt.char ppf ' '; 155 + p ppf item) 156 + rest); 157 + Fmt.char ppf ')' 158 + in 159 + p ppf sexp 160 + 161 + let pp_dune_indent ~indent ppf sexp = 162 + let pad d = String.make (d * indent) ' ' in 163 + let inlinable = function 164 + | [] | [ _ ] -> true 165 + | items -> 166 + List.for_all (function Sexp.Atom _ -> true | _ -> false) items 167 + && List.length items <= 4 168 + in 169 + let rec p depth ppf = function 153 170 | Sexp.Atom s -> pp_atom ppf s 154 171 | Sexp.List [] -> Fmt.string ppf "()" 155 - | Sexp.List ((Sexp.Atom _ as head) :: rest) when List.length rest <= 3 -> 172 + | Sexp.List ((Sexp.Atom _ as head) :: rest) when inlinable rest -> 156 173 Fmt.char ppf '('; 157 - pp_sexp indent ppf head; 174 + p depth ppf head; 158 175 List.iter 159 176 (fun item -> 160 177 Fmt.char ppf ' '; 161 - pp_sexp indent ppf item) 178 + p depth ppf item) 162 179 rest; 163 180 Fmt.char ppf ')' 164 181 | Sexp.List ((Sexp.Atom _ as head) :: rest) -> 165 182 Fmt.char ppf '('; 166 - pp_sexp indent ppf head; 167 - let new_indent = indent + 1 in 183 + p depth ppf head; 184 + let new_depth = depth + 1 in 168 185 List.iter 169 186 (fun item -> 170 187 Fmt.char ppf '\n'; 171 - Fmt.string ppf (String.make new_indent ' '); 172 - pp_sexp new_indent ppf item) 188 + Fmt.string ppf (pad new_depth); 189 + p new_depth ppf item) 173 190 rest; 174 191 Fmt.char ppf ')' 175 192 | Sexp.List items -> 176 193 Fmt.char ppf '('; 177 - let new_indent = indent + 1 in 194 + let new_depth = depth + 1 in 178 195 (match items with 179 196 | [] -> () 180 197 | first :: rest -> 181 - pp_sexp new_indent ppf first; 198 + p new_depth ppf first; 182 199 List.iter 183 200 (fun item -> 184 201 Fmt.char ppf '\n'; 185 - Fmt.string ppf (String.make new_indent ' '); 186 - pp_sexp new_indent ppf item) 202 + Fmt.string ppf (pad new_depth); 203 + p new_depth ppf item) 187 204 rest); 188 205 Fmt.char ppf ')' 189 206 in 190 - pp_sexp 0 ppf sexp 207 + p 0 ppf sexp 208 + 209 + (* IO helpers shared across file-kind modules. Each module supplies its own 210 + internal codec and to_sexps, then the helpers below produce the 211 + {!of_string}/{!to_string}/{!of_reader}/{!to_writer} entry points. *) 191 212 192 - let to_string_dune sexp = Fmt.str "%a" pp_dune sexp 213 + let render_sexps ?indent sexps = 214 + match indent with 215 + | None -> String.concat "\n" (List.map (Fmt.str "%a" pp_compact) sexps) 216 + | Some n -> 217 + let pp = pp_dune_indent ~indent:n in 218 + let buf = Buffer.create 256 in 219 + let ppf = Format.formatter_of_buffer buf in 220 + let rec loop = function 221 + | [] -> () 222 + | [ s ] -> Format.fprintf ppf "%a" pp s 223 + | s :: rest -> 224 + Format.fprintf ppf "%a\n\n" pp s; 225 + loop rest 226 + in 227 + loop sexps; 228 + Format.pp_print_flush ppf (); 229 + Buffer.contents buf 193 230 194 - let pp_dune_file ppf sexps = 195 - let rec loop = function 196 - | [] -> () 197 - | [ sexp ] -> pp_dune ppf sexp 198 - | sexp :: rest -> 199 - pp_dune ppf sexp; 200 - Fmt.string ppf "\n\n"; 201 - loop rest 231 + let read_all r = 232 + let buf = Buffer.create 1024 in 233 + let rec loop () = 234 + match Bytesrw.Bytes.Reader.read r with 235 + | s when Bytesrw.Bytes.Slice.is_eod s -> () 236 + | s -> 237 + Buffer.add_string buf (Bytesrw.Bytes.Slice.to_string s); 238 + loop () 202 239 in 203 - loop sexps 240 + loop (); 241 + Buffer.contents buf 204 242 205 - let to_string_dune_file sexps = Fmt.str "%a@." pp_dune_file sexps 243 + let exn_of_result = function Ok v -> v | Stdlib.Error e -> raise (Error e) 206 244 207 245 (* ---- Field accessors ---- *) 208 246 ··· 217 255 items 218 256 | Sexp.Atom _ -> None 219 257 220 - let fields name sexp = 221 - match sexp with 222 - | Sexp.List items -> 223 - List.filter_map 224 - (function 225 - | Sexp.List (Sexp.Atom n :: rest) when String.equal n name -> ( 226 - match rest with [ v ] -> Some v | _ -> Some (Sexp.List rest)) 227 - | _ -> None) 228 - items 229 - | Sexp.Atom _ -> [] 230 - 231 258 let field_atom name sexp = 232 259 match field name sexp with Some (Sexp.Atom s) -> Some s | _ -> None 233 260 ··· 241 268 (List.filter_map (function Sexp.Atom s -> Some s | _ -> None) items) 242 269 | None -> None 243 270 244 - let set_field name value sexp = 245 - match sexp with 246 - | Sexp.List (head :: items) -> 247 - let field = Sexp.List [ Sexp.Atom name; value ] in 248 - let found = ref false in 249 - let items = 250 - List.map 251 - (function 252 - | Sexp.List (Sexp.Atom n :: _) when String.equal n name -> 253 - found := true; 254 - field 255 - | item -> item) 256 - items 257 - in 258 - let items = if !found then items else items @ [ field ] in 259 - Sexp.List (head :: items) 260 - | _ -> sexp 261 - 262 - let remove_field name sexp = 263 - match sexp with 264 - | Sexp.List (head :: items) -> 265 - let items = 266 - List.filter 267 - (function 268 - | Sexp.List (Sexp.Atom n :: _) -> not (String.equal n name) 269 - | _ -> true) 270 - items 271 - in 272 - Sexp.List (head :: items) 273 - | _ -> sexp 274 - 275 271 (* ---- Stanza types ---- *) 276 272 277 273 type stanza_kind = ··· 318 314 | Sexp.List (Sexp.Atom name :: _) -> Some (stanza_kind_of_string name) 319 315 | _ -> None 320 316 321 - (* ---- Common stanza builders ---- *) 322 - 323 - let library ?public_name ?libraries ?preprocess name = 324 - let fields = [ Sexp.List [ Sexp.Atom "name"; Sexp.Atom name ] ] in 325 - let fields = 326 - match public_name with 327 - | Some pn -> 328 - fields @ [ Sexp.List [ Sexp.Atom "public_name"; Sexp.Atom pn ] ] 329 - | None -> fields 330 - in 331 - let fields = 332 - match libraries with 333 - | Some libs when libs <> [] -> 334 - fields 335 - @ [ 336 - Sexp.List 337 - (Sexp.Atom "libraries" :: List.map (fun s -> Sexp.Atom s) libs); 338 - ] 339 - | _ -> fields 340 - in 341 - let fields = 342 - match preprocess with 343 - | Some pp -> fields @ [ Sexp.List [ Sexp.Atom "preprocess"; pp ] ] 344 - | None -> fields 345 - in 346 - Sexp.List (Sexp.Atom "library" :: fields) 347 - 348 - let executable ?public_name ?libraries ?preprocess name = 349 - let fields = [ Sexp.List [ Sexp.Atom "name"; Sexp.Atom name ] ] in 350 - let fields = 351 - match public_name with 352 - | Some pn -> 353 - fields @ [ Sexp.List [ Sexp.Atom "public_name"; Sexp.Atom pn ] ] 354 - | None -> fields 355 - in 356 - let fields = 357 - match libraries with 358 - | Some libs when libs <> [] -> 359 - fields 360 - @ [ 361 - Sexp.List 362 - (Sexp.Atom "libraries" :: List.map (fun s -> Sexp.Atom s) libs); 363 - ] 364 - | _ -> fields 365 - in 366 - let fields = 367 - match preprocess with 368 - | Some pp -> fields @ [ Sexp.List [ Sexp.Atom "preprocess"; pp ] ] 369 - | None -> fields 370 - in 371 - Sexp.List (Sexp.Atom "executable" :: fields) 372 - 373 - let test ?libraries ?modules name = 374 - let fields = [ Sexp.List [ Sexp.Atom "name"; Sexp.Atom name ] ] in 375 - let fields = 376 - match modules with 377 - | Some mods when mods <> [] -> 378 - fields 379 - @ [ 380 - Sexp.List 381 - (Sexp.Atom "modules" :: List.map (fun s -> Sexp.Atom s) mods); 382 - ] 383 - | _ -> fields 384 - in 385 - let fields = 386 - match libraries with 387 - | Some libs when libs <> [] -> 388 - fields 389 - @ [ 390 - Sexp.List 391 - (Sexp.Atom "libraries" :: List.map (fun s -> Sexp.Atom s) libs); 392 - ] 393 - | _ -> fields 394 - in 395 - Sexp.List (Sexp.Atom "test" :: fields) 396 - 397 317 (* ---- dune-project codec ---- *) 398 318 399 319 module Project = struct ··· 432 352 433 353 let make ?(dune_version = "3.0") ?name ?version ?generate_opam_files ?license 434 354 ?(authors = []) ?(maintainers = []) ?source ?bug_reports ?homepage 435 - ?documentation ?(packages = []) () = 355 + ?documentation () = 436 356 { 437 357 lang = ("dune", dune_version); 438 358 name; ··· 445 365 bug_reports; 446 366 homepage; 447 367 documentation; 448 - packages; 368 + packages = []; 449 369 other = []; 450 370 } 451 371 ··· 505 425 in 506 426 loop empty sexps 507 427 508 - let field name value = Sexp.List [ Sexp.Atom name; Sexp.Atom value ] 509 - let field_opt name = function Some v -> [ field name v ] | None -> [] 428 + let mk_field name value = Sexp.List [ Sexp.Atom name; Sexp.Atom value ] 429 + let mk_field_opt name = function Some v -> [ mk_field name v ] | None -> [] 510 430 511 - let field_list name values = 431 + let mk_field_list name values = 512 432 if values = [] then [] 513 433 else 514 434 [ Sexp.List (Sexp.Atom name :: List.map (fun s -> Sexp.Atom s) values) ] ··· 533 453 let to_sexps t = 534 454 let lang, version = t.lang in 535 455 [ Sexp.List [ Sexp.Atom "lang"; Sexp.Atom lang; Sexp.Atom version ] ] 536 - @ field_opt "name" t.name 537 - @ field_opt "version" t.version 456 + @ mk_field_opt "name" t.name 457 + @ mk_field_opt "version" t.version 538 458 @ (match t.generate_opam_files with 539 459 | Some b -> 540 - [ field "generate_opam_files" (if b then "true" else "false") ] 460 + [ mk_field "generate_opam_files" (if b then "true" else "false") ] 541 461 | None -> []) 542 - @ field_opt "license" t.license 543 - @ field_list "authors" t.authors 544 - @ field_list "maintainers" t.maintainers 462 + @ mk_field_opt "license" t.license 463 + @ mk_field_list "authors" t.authors 464 + @ mk_field_list "maintainers" t.maintainers 545 465 @ source_sexp t.source 546 - @ field_opt "bug_reports" t.bug_reports 547 - @ field_opt "homepage" t.homepage 548 - @ field_opt "documentation" t.documentation 466 + @ mk_field_opt "bug_reports" t.bug_reports 467 + @ mk_field_opt "homepage" t.homepage 468 + @ mk_field_opt "documentation" t.documentation 549 469 @ t.packages @ t.other 550 470 551 - let to_string t = to_string_dune_file (to_sexps t) 471 + let lang t = t.lang 472 + let name t = t.name 473 + let version t = t.version 474 + let generate_opam_files t = t.generate_opam_files 475 + let license t = t.license 476 + let authors t = t.authors 477 + let maintainers t = t.maintainers 478 + let source t = t.source 479 + let bug_reports t = t.bug_reports 480 + let homepage t = t.homepage 481 + let documentation t = t.documentation 482 + 483 + module Package = struct 484 + type t = { name : string; synopsis : string option; depends : string list } 485 + 486 + let name t = t.name 487 + let synopsis t = t.synopsis 488 + let depends t = t.depends 489 + 490 + let dep_of_sexp = function 491 + | Sexp.Atom n -> Some n 492 + | Sexp.List (Sexp.Atom n :: _) -> Some n 493 + | _ -> None 494 + 495 + let of_sexp pkg_sexp = 496 + let name = Option.value ~default:"" (field_atom "name" pkg_sexp) in 497 + let synopsis = field_atom "synopsis" pkg_sexp in 498 + let depends = 499 + match field_list "depends" pkg_sexp with 500 + | None -> [] 501 + | Some items -> List.filter_map dep_of_sexp items 502 + in 503 + { name; synopsis; depends } 504 + end 505 + 506 + let packages t = List.map Package.of_sexp t.packages 507 + 508 + let codec : t Sexp.Codec.t = 509 + Sexp.Codec.map ~kind:"dune-project" 510 + ~dec:(function Sexp.List sexps -> parse sexps | _ -> empty) 511 + ~enc:(fun t -> Sexp.List (to_sexps t)) 512 + Sexp.Codec.value 513 + 514 + let of_string s = Sexp.Codec.decode_string_many codec s 515 + let of_string_exn s = exn_of_result (of_string s) 516 + let to_string ?indent ?preserve:_ t = render_sexps ?indent (to_sexps t) 517 + let of_reader r = of_string (read_all r) 518 + let of_reader_exn r = exn_of_result (of_reader r) 519 + 520 + let to_writer ?indent ?preserve t w = 521 + Bytesrw.Bytes.Writer.write_string w (to_string ?indent ?preserve t); 522 + Bytesrw.Bytes.Writer.write_eod w 552 523 end 553 524 554 525 (* ---- dune-workspace codec ---- *) 555 526 556 527 module Workspace = struct 557 - type context_kind = Default | Opam of { switch : string } | Other of Sexp.t 558 - type context = { name : string option; kind : context_kind } 528 + module Context = struct 529 + type kind = Default | Opam of { switch : string } 530 + type t = { name : string option; kind : kind } 531 + end 532 + 533 + (* Internal: keeps unrecognized contexts so the codec can round-trip 534 + them. The public {!contexts} accessor projects to {!Context.t} and 535 + drops [Raw_other] entries. *) 536 + type raw_kind = 537 + | Raw_default 538 + | Raw_opam of { switch : string } 539 + | Raw_other of Sexp.t 540 + 541 + type raw_context = { name : string option; kind : raw_kind } 559 542 560 543 type t = { 561 544 lang : string * string; 562 - contexts : context list; 545 + contexts : raw_context list; 563 546 env : Sexp.t option; 564 547 other : Sexp.t list; 565 548 } ··· 576 559 loop 577 560 { 578 561 acc with 579 - contexts = acc.contexts @ [ { name = None; kind = Default } ]; 562 + contexts = acc.contexts @ [ { name = None; kind = Raw_default } ]; 580 563 } 581 564 rest 582 565 | Sexp.List [ Sexp.Atom "context"; Sexp.List ctx_fields ] :: rest -> ··· 585 568 match field "opam" (Sexp.List ctx_fields) with 586 569 | Some (Sexp.List opam_fields) -> ( 587 570 match field_atom "switch" (Sexp.List opam_fields) with 588 - | Some switch -> Opam { switch } 589 - | None -> Other (Sexp.List ctx_fields)) 571 + | Some switch -> Raw_opam { switch } 572 + | None -> Raw_other (Sexp.List ctx_fields)) 590 573 | _ -> ( 591 574 match field_atom "default" (Sexp.List ctx_fields) with 592 - | Some _ -> Default 593 - | None -> Other (Sexp.List ctx_fields)) 575 + | Some _ -> Raw_default 576 + | None -> Raw_other (Sexp.List ctx_fields)) 594 577 in 595 578 loop { acc with contexts = acc.contexts @ [ { name; kind } ] } rest 596 579 | Sexp.List (Sexp.Atom "env" :: env_fields) :: rest -> ··· 611 594 (fun ctx -> 612 595 let ctx_sexp = 613 596 match ctx.kind with 614 - | Default -> Sexp.Atom "default" 615 - | Opam { switch } -> 597 + | Raw_default -> Sexp.Atom "default" 598 + | Raw_opam { switch } -> 616 599 let fields = 617 600 [ 618 601 Sexp.List ··· 629 612 | None -> fields 630 613 in 631 614 Sexp.List fields 632 - | Other sexp -> sexp 615 + | Raw_other sexp -> sexp 633 616 in 634 617 Sexp.List [ Sexp.Atom "context"; ctx_sexp ]) 635 618 t.contexts ··· 643 626 in 644 627 sexps @ t.other 645 628 646 - let to_string t = to_string_dune_file (to_sexps t) 629 + let lang t = t.lang 630 + 631 + let contexts t = 632 + List.filter_map 633 + (fun rc -> 634 + match rc.kind with 635 + | Raw_default -> Some Context.{ name = rc.name; kind = Default } 636 + | Raw_opam { switch } -> 637 + Some Context.{ name = rc.name; kind = Opam { switch } } 638 + | Raw_other _ -> None) 639 + t.contexts 640 + 641 + let codec : t Sexp.Codec.t = 642 + Sexp.Codec.map ~kind:"dune-workspace" 643 + ~dec:(function Sexp.List sexps -> parse sexps | _ -> empty) 644 + ~enc:(fun t -> Sexp.List (to_sexps t)) 645 + Sexp.Codec.value 646 + 647 + let of_string s = Sexp.Codec.decode_string_many codec s 648 + let of_string_exn s = exn_of_result (of_string s) 649 + let to_string ?indent ?preserve:_ t = render_sexps ?indent (to_sexps t) 650 + let of_reader r = of_string (read_all r) 651 + let of_reader_exn r = exn_of_result (of_reader r) 652 + 653 + let to_writer ?indent ?preserve t w = 654 + Bytesrw.Bytes.Writer.write_string w (to_string ?indent ?preserve t); 655 + Bytesrw.Bytes.Writer.write_eod w 647 656 end 648 657 649 658 (* ---- dune file codec ---- *) 650 659 651 660 module File = struct 652 - type t = Sexp.t list 661 + (* Each top-level stanza is classified for fast typed queries; [raw] 662 + is the untouched [Sexp.t] used by the encoder so round-trip 663 + preserves formatting we did not deliberately rewrite. *) 664 + type stanza = 665 + | Library of { name : string; public_name : string option; raw : Sexp.t } 666 + | Executable of { names : string list; raw : Sexp.t } 667 + | Test of { names : string list; raw : Sexp.t } 668 + | Other of Sexp.t 653 669 654 - let parse = Fun.id 655 - let to_sexps = Fun.id 656 - let to_string = to_string_dune_file 670 + type t = stanza list 657 671 658 - let find_stanza kind stanzas = 659 - List.find_opt (fun s -> stanza_kind s = Some kind) stanzas 672 + let names_of s = 673 + match field_atom "name" s with 674 + | Some n -> [ n ] 675 + | None -> ( match field_atoms "names" s with Some ns -> ns | None -> []) 676 + 677 + let classify s = 678 + match stanza_kind s with 679 + | Some Library -> 680 + Library 681 + { 682 + name = Option.value ~default:"" (field_atom "name" s); 683 + public_name = field_atom "public_name" s; 684 + raw = s; 685 + } 686 + | Some (Executable | Executables) -> 687 + Executable { names = names_of s; raw = s } 688 + | Some (Test | Tests) -> Test { names = names_of s; raw = s } 689 + | _ -> Other s 690 + 691 + let parse sexps = List.map classify sexps 692 + 693 + let to_sexps t = 694 + List.map 695 + (function 696 + | Library { raw; _ } | Executable { raw; _ } | Test { raw; _ } -> raw 697 + | Other s -> s) 698 + t 699 + 700 + let library_names t = 701 + List.filter_map (function Library { name; _ } -> Some name | _ -> None) t 660 702 661 - let find_stanzas kind stanzas = 662 - List.filter (fun s -> stanza_kind s = Some kind) stanzas 703 + let private_library_names t = 704 + List.filter_map 705 + (function 706 + | Library { name; public_name = None; _ } -> Some name | _ -> None) 707 + t 663 708 664 - let library_names stanzas = 665 - find_stanzas Library stanzas |> List.filter_map (field_atom "name") 709 + let executable_names t = 710 + List.concat_map (function Executable { names; _ } -> names | _ -> []) t 666 711 667 - let private_library_names stanzas = 668 - find_stanzas Library stanzas 669 - |> List.filter_map (fun s -> 670 - match (field_atom "name" s, field_atom "public_name" s) with 671 - | Some n, None -> Some n 672 - | _ -> None) 712 + let test_names t = 713 + List.concat_map (function Test { names; _ } -> names | _ -> []) t 673 714 674 - let executable_names stanzas = 675 - find_stanzas Executable stanzas @ find_stanzas Executables stanzas 676 - |> List.filter_map (fun s -> 677 - match field_atom "name" s with 678 - | Some n -> Some n 679 - | None -> ( 680 - match field_atoms "names" s with 681 - | Some names -> Some (String.concat "," names) 682 - | None -> None)) 715 + let codec : t Sexp.Codec.t = 716 + Sexp.Codec.map ~kind:"dune-file" 717 + ~dec:(function Sexp.List sexps -> parse sexps | _ -> []) 718 + ~enc:(fun t -> Sexp.List (to_sexps t)) 719 + Sexp.Codec.value 683 720 684 - let test_names stanzas = 685 - find_stanzas Test stanzas @ find_stanzas Tests stanzas 686 - |> List.filter_map (fun s -> 687 - match field_atom "name" s with 688 - | Some n -> Some n 689 - | None -> ( 690 - match field_atoms "names" s with 691 - | Some names -> Some (String.concat "," names) 692 - | None -> None)) 721 + let of_string s = Sexp.Codec.decode_string_many codec s 722 + let of_string_exn s = exn_of_result (of_string s) 723 + let to_string ?indent ?preserve:_ t = render_sexps ?indent (to_sexps t) 724 + let of_reader r = of_string (read_all r) 725 + let of_reader_exn r = exn_of_result (of_reader r) 726 + 727 + let to_writer ?indent ?preserve t w = 728 + Bytesrw.Bytes.Writer.write_string w (to_string ?indent ?preserve t); 729 + Bytesrw.Bytes.Writer.write_eod w 693 730 end 694 731 695 732 module Package = struct 696 - module Library = struct 697 - type t = { 698 - name : string; 699 - main_module_name : string option; 700 - modules : string list; 701 - implements : string option; 702 - } 733 + type t = { 734 + name : string; 735 + main_module_name : string option; 736 + modules : string list; 737 + implements : string option; 738 + } 703 739 704 - let capitalize s = 705 - if s = "" then s 706 - else 707 - String.uppercase_ascii (String.sub s 0 1) 708 - ^ String.sub s 1 (String.length s - 1) 740 + let name t = t.name 741 + let main_module_name t = t.main_module_name 742 + let modules t = t.modules 743 + let implements t = t.implements 709 744 710 - let rec gather_obj_names acc = function 711 - | Sexp.List (Sexp.Atom "module" :: fs) as m -> 712 - let kind = field_atom "kind" m in 713 - if kind = Some "alias" then List.fold_left gather_obj_names acc fs 714 - else 715 - let acc = 716 - match field_atom "obj_name" m with 717 - | Some n -> capitalize n :: acc 718 - | None -> acc 719 - in 720 - List.fold_left gather_obj_names acc fs 721 - | Sexp.List xs -> List.fold_left gather_obj_names acc xs 722 - | _ -> acc 745 + let capitalize s = 746 + if s = "" then s 747 + else 748 + String.uppercase_ascii (String.sub s 0 1) 749 + ^ String.sub s 1 (String.length s - 1) 723 750 724 - let modules_codec : string list Sexp.Codec.t = 725 - Sexp.Codec.map ~kind:"modules" 726 - ~dec:(fun v -> List.rev (gather_obj_names [] v)) 727 - ~enc:(fun _ -> Sexp.List []) 728 - Sexp.Codec.value 751 + let rec gather_obj_names acc = function 752 + | Sexp.List (Sexp.Atom "module" :: fs) as m -> 753 + let kind = field_atom "kind" m in 754 + if kind = Some "alias" then List.fold_left gather_obj_names acc fs 755 + else 756 + let acc = 757 + match field_atom "obj_name" m with 758 + | Some n -> capitalize n :: acc 759 + | None -> acc 760 + in 761 + List.fold_left gather_obj_names acc fs 762 + | Sexp.List xs -> List.fold_left gather_obj_names acc xs 763 + | _ -> acc 729 764 730 - let record_codec : t Sexp.Codec.t = 731 - Sexp.Codec.Record.( 732 - obj ~kind:"library" (fun name main_module_name modules implements -> 733 - let modules = 734 - match main_module_name with 735 - | Some m when not (List.mem m modules) -> m :: modules 736 - | _ -> modules 737 - in 738 - { name; main_module_name; modules; implements }) 739 - |> mem "name" Sexp.Codec.string ~enc:(fun l -> l.name) 740 - |> opt_mem "main_module_name" Sexp.Codec.string ~enc:(fun l -> 741 - l.main_module_name) 742 - |> mem "modules" modules_codec ~dec_absent:[] ~enc:(fun l -> l.modules) 743 - |> opt_mem "implements" Sexp.Codec.string ~enc:(fun l -> l.implements) 744 - |> skip_unknown |> finish) 765 + let modules_codec : string list Sexp.Codec.t = 766 + Sexp.Codec.map ~kind:"modules" 767 + ~dec:(fun v -> List.rev (gather_obj_names [] v)) 768 + ~enc:(fun _ -> Sexp.List []) 769 + Sexp.Codec.value 770 + 771 + let record_codec : t Sexp.Codec.t = 772 + Sexp.Codec.Record.( 773 + obj ~kind:"library" (fun name main_module_name modules implements -> 774 + let modules = 775 + match main_module_name with 776 + | Some m when not (List.mem m modules) -> m :: modules 777 + | _ -> modules 778 + in 779 + { name; main_module_name; modules; implements }) 780 + |> mem "name" Sexp.Codec.string ~enc:(fun l -> l.name) 781 + |> opt_mem "main_module_name" Sexp.Codec.string ~enc:(fun l -> 782 + l.main_module_name) 783 + |> mem "modules" modules_codec ~dec_absent:[] ~enc:(fun l -> l.modules) 784 + |> opt_mem "implements" Sexp.Codec.string ~enc:(fun l -> l.implements) 785 + |> skip_unknown |> finish) 786 + 787 + let codec : t Sexp.Codec.t = 788 + Sexp.Codec.Variant.( 789 + variant ~kind:"library" 790 + [ case "library" record_codec (fun l -> l) (fun l -> Some l) ]) 791 + 792 + let of_string s = Sexp.Codec.decode_string codec s 793 + let of_string_exn s = exn_of_result (of_string s) 794 + 795 + let to_sexp t = 796 + let modules_sexp = 797 + Sexp.List 798 + (Sexp.Atom "modules" :: List.map (fun m -> Sexp.Atom m) t.modules) 799 + in 800 + let fields = 801 + [ Sexp.List [ Sexp.Atom "name"; Sexp.Atom t.name ] ] 802 + @ (match t.main_module_name with 803 + | Some m -> [ Sexp.List [ Sexp.Atom "main_module_name"; Sexp.Atom m ] ] 804 + | None -> []) 805 + @ [ modules_sexp ] 806 + @ 807 + match t.implements with 808 + | Some i -> [ Sexp.List [ Sexp.Atom "implements"; Sexp.Atom i ] ] 809 + | None -> [] 810 + in 811 + Sexp.List (Sexp.Atom "library" :: fields) 745 812 746 - let codec : t Sexp.Codec.t = 747 - Sexp.Codec.Variant.( 748 - variant ~kind:"library" 749 - [ case "library" record_codec (fun l -> l) (fun l -> Some l) ]) 750 - end 813 + let to_string ?indent ?preserve:_ t = 814 + match indent with 815 + | None -> Fmt.str "%a" pp_compact (to_sexp t) 816 + | Some n -> Fmt.str "%a" (pp_dune_indent ~indent:n) (to_sexp t) 817 + 818 + let of_reader r = of_string (read_all r) 819 + let of_reader_exn r = exn_of_result (of_reader r) 820 + 821 + let to_writer ?indent ?preserve t w = 822 + Bytesrw.Bytes.Writer.write_string w (to_string ?indent ?preserve t); 823 + Bytesrw.Bytes.Writer.write_eod w 824 + 825 + let libraries content = 826 + match Sexp.Value.parse_string_many content with 827 + | Stdlib.Error _ -> [] 828 + | Ok stanzas -> 829 + List.filter_map 830 + (fun s -> Sexp.Codec.decode_value codec s |> Result.to_option) 831 + stanzas 751 832 end 752 833 753 834 module Lib_index = struct ··· 780 861 | Ok stanzas -> 781 862 List.iter 782 863 (fun s -> 783 - match Sexp.Codec.decode_value Package.Library.codec s with 864 + match Sexp.Codec.decode_value Package.codec s with 784 865 | Error _ -> () 785 - | Ok (lib : Package.Library.t) -> 866 + | Ok (lib : Package.t) -> 786 867 if lib.implements <> None then 787 868 Hashtbl.replace t.virtual_impls lib.name (); 788 869 merge_modules t lib.name lib.modules)
+118 -213
ocaml-dune/lib/dune.mli
··· 5 5 6 6 (** Type-safe codecs for the dune-format file family. 7 7 8 - Built on {!Sexp.Codec}; one submodule per file kind plus shared helpers for 9 - variable expansion, dune-style pretty printing, field accessors, and 10 - stanza-kind tagging. 8 + Each file kind has its own typed value and IO verbs: 11 9 12 - {2 File kinds} 10 + - {!module:File} — [dune] build files 11 + - {!module:Project} — [dune-project] files 12 + - {!module:Workspace} — [dune-workspace] files 13 + - {!module:Package} — installed [dune-package] metadata 13 14 14 - - {!module:File} - [dune] build files (libraries, executables, tests) 15 - - {!module:Project} - [dune-project] files with project metadata 16 - - {!module:Workspace} - [dune-workspace] files for multi-context builds 17 - - {!module:Package} - installed [dune-package] metadata files 15 + Plus {!module:Var} for [%{name}] / [%{name:default}] references and 16 + {!module:Lib_index} for cross-package library lookup. *) 18 17 19 - {2 Variable expansion} 18 + (** {1 Errors} *) 20 19 21 - Dune supports variable references like [%{name}] / [%{name:default}]. 22 - {!module:Var} parses and expands them. *) 20 + module Error = Sexp.Error 21 + (** Decode errors. Aliased to [Sexp.Error]; the underlying type is 22 + {!Loc.Error.t}. *) 23 23 24 - (** {1 Variable Expansion} *) 24 + exception Error of Error.t 25 + (** Raised by the [_exn] entry points of every file kind below. *) 26 + 27 + (** {1 Variable expansion} *) 25 28 26 29 module Var : sig 27 30 type t 28 31 (** Dune variable reference like [%{name}] or [%{name:default}]. *) 29 32 30 33 val v : ?default:string -> string -> t 31 - (** [v ?default name] creates a variable reference. *) 32 - 33 34 val name : t -> string 34 - (** [name v] is the variable name. *) 35 - 36 35 val default : t -> string option 37 - (** [default v] is the optional default value. *) 38 - 39 36 val pp : Format.formatter -> t -> unit 40 - (** [pp fmt v] prints the variable in Dune syntax. *) 41 - 42 37 val to_string : t -> string 43 - (** [to_string v] converts to Dune variable syntax. *) 44 38 end 45 39 46 40 (** Text segments that may contain variable references. *) ··· 50 44 (** Text with embedded variable references. *) 51 45 52 46 val text_of_string : string -> text 53 - (** [text_of_string s] parses a string into text segments. *) 54 - 55 47 val text_to_string : text -> string 56 - (** [text_to_string t] converts text back to a string. *) 57 - 58 48 val pp_text : Format.formatter -> text -> unit 59 - (** [pp_text fmt t] prints text with variables. *) 60 - 61 49 val expand_text : env:(string -> string option) -> text -> string 62 - (** [expand_text ~env t] expands variables using [env] lookup. *) 63 50 64 51 val expand_atom : env:(string -> string option) -> string -> string 65 - (** [expand_atom ~env s] expands variables in a string. *) 66 - 67 - val expand : env:(string -> string option) -> Sexp.t -> Sexp.t 68 - (** [expand ~env sexp] recursively expands all variables in atoms. *) 52 + (** [expand_atom ~env s] expands [%{var}] references in [s] using [env]. *) 69 53 70 54 val has_variables : string -> bool 71 - (** [has_variables s] is [true] if [s] contains variable references. *) 72 - 73 - val variables : Sexp.t -> string list 74 - (** [variables sexp] extracts all variable names from an S-expression. *) 75 - 76 - (** {1 Dune-style Pretty Printing} *) 77 - 78 - val pp_dune : Format.formatter -> Sexp.t -> unit 79 - (** [pp_dune fmt sexp] prints an S-expression in Dune style. *) 80 - 81 - val to_string_dune : Sexp.t -> string 82 - (** [to_string_dune sexp] converts to a Dune-formatted string. *) 83 - 84 - val pp_dune_file : Format.formatter -> Sexp.t list -> unit 85 - (** [pp_dune_file fmt sexps] prints multiple stanzas with blank lines. *) 86 - 87 - val to_string_dune_file : Sexp.t list -> string 88 - (** [to_string_dune_file sexps] converts stanzas to a dune file string. *) 89 - 90 - (** {1 Field Accessors} *) 91 - 92 - val field : string -> Sexp.t -> Sexp.t option 93 - (** [field name sexp] gets a field value from a stanza. *) 94 - 95 - val fields : string -> Sexp.t -> Sexp.t list 96 - (** [fields name sexp] gets all values for a repeated field. *) 97 - 98 - val field_atom : string -> Sexp.t -> string option 99 - (** [field_atom name sexp] gets a field's atom value. *) 100 - 101 - val field_list : string -> Sexp.t -> Sexp.t list option 102 - (** [field_list name sexp] gets a field's list value. *) 103 - 104 - val field_atoms : string -> Sexp.t -> string list option 105 - (** [field_atoms name sexp] gets atoms from a list field. *) 55 + (** [has_variables s] is [true] iff [s] contains a [%{...}] reference. *) 106 56 107 - val set_field : string -> Sexp.t -> Sexp.t -> Sexp.t 108 - (** [set_field name value sexp] sets or adds a field. *) 57 + (** {1 Stanza kinds} *) 109 58 110 - val remove_field : string -> Sexp.t -> Sexp.t 111 - (** [remove_field name sexp] removes a field from a stanza. *) 112 - 113 - (** {1 Stanza Types} *) 114 - 59 + (** Closed enumeration of the stanza heads that can appear at the top of a 60 + [dune] build file. *) 115 61 type stanza_kind = 116 62 | Library 117 63 | Executable ··· 126 72 | Other of string 127 73 128 74 val stanza_kind_of_string : string -> stanza_kind 129 - (** [stanza_kind_of_string s] converts a string to a stanza kind. *) 130 - 131 75 val stanza_kind_to_string : stanza_kind -> string 132 - (** [stanza_kind_to_string k] converts a stanza kind to string. *) 133 76 134 - val stanza_kind : Sexp.t -> stanza_kind option 135 - (** [stanza_kind sexp] extracts the kind from a stanza. *) 77 + (** {1 dune-project Files} *) 136 78 137 - (** {1 Stanza Builders} *) 79 + module Project : sig 80 + module Package : sig 81 + type t 82 + (** A typed view of one [(package ...)] stanza. *) 138 83 139 - val library : 140 - ?public_name:string -> 141 - ?libraries:string list -> 142 - ?preprocess:Sexp.t -> 143 - string -> 144 - Sexp.t 145 - (** [library name] builds a [(library ...)] stanza. *) 84 + val name : t -> string 85 + val synopsis : t -> string option 146 86 147 - val executable : 148 - ?public_name:string -> 149 - ?libraries:string list -> 150 - ?preprocess:Sexp.t -> 151 - string -> 152 - Sexp.t 153 - (** [executable name] builds an [(executable ...)] stanza. *) 87 + val depends : t -> string list 88 + (** Package names from [(depends ...)]. Version constraints and conditional 89 + flags are dropped. *) 90 + end 154 91 155 - val test : ?libraries:string list -> ?modules:string list -> string -> Sexp.t 156 - (** [test name] builds a [(test ...)] stanza. *) 157 - 158 - (** {1 dune-project Files} *) 159 - 160 - module Project : sig 161 - type t = { 162 - lang : string * string; 163 - name : string option; 164 - version : string option; 165 - generate_opam_files : bool option; 166 - license : string option; 167 - authors : string list; 168 - maintainers : string list; 169 - source : string option; 170 - bug_reports : string option; 171 - homepage : string option; 172 - documentation : string option; 173 - packages : Sexp.t list; 174 - other : Sexp.t list; 175 - } 176 - (** Parsed dune-project file. *) 92 + type t 93 + (** A parsed [dune-project] file. *) 177 94 178 95 val empty : t 179 - (** [empty] is an empty dune-project with default lang. *) 180 96 181 97 val make : 182 98 ?dune_version:string -> ··· 190 106 ?bug_reports:string -> 191 107 ?homepage:string -> 192 108 ?documentation:string -> 193 - ?packages:Sexp.t list -> 194 109 unit -> 195 110 t 196 - (** [make ()] creates a dune-project with the given fields. *) 197 111 198 - val parse : Sexp.t list -> t 199 - (** [parse sexps] parses S-expressions into a dune-project. *) 112 + val lang : t -> string * string 113 + val name : t -> string option 114 + val version : t -> string option 115 + val generate_opam_files : t -> bool option 116 + val license : t -> string option 117 + val authors : t -> string list 118 + val maintainers : t -> string list 119 + val source : t -> string option 120 + val bug_reports : t -> string option 121 + val homepage : t -> string option 122 + val documentation : t -> string option 123 + val packages : t -> Package.t list 124 + val of_string : string -> (t, Error.t) result 125 + val of_string_exn : string -> t 126 + val to_string : ?indent:int -> ?preserve:bool -> t -> string 127 + val of_reader : Bytesrw.Bytes.Reader.t -> (t, Error.t) result 128 + val of_reader_exn : Bytesrw.Bytes.Reader.t -> t 200 129 201 - val to_sexps : t -> Sexp.t list 202 - (** [to_sexps t] converts to S-expressions. *) 203 - 204 - val to_string : t -> string 205 - (** [to_string t] converts to a dune-project file string. *) 130 + val to_writer : 131 + ?indent:int -> ?preserve:bool -> t -> Bytesrw.Bytes.Writer.t -> unit 206 132 end 207 133 208 134 (** {1 dune-workspace Files} *) 209 135 210 136 module Workspace : sig 211 - type context_kind = Default | Opam of { switch : string } | Other of Sexp.t 212 - type context = { name : string option; kind : context_kind } 137 + module Context : sig 138 + type kind = 139 + | Default 140 + | Opam of { switch : string } 141 + (** Build-context kinds nox-dune currently surfaces. Contexts with 142 + shapes outside this set still round-trip; they just do not appear 143 + in {!contexts}. *) 213 144 214 - type t = { 215 - lang : string * string; 216 - contexts : context list; 217 - env : Sexp.t option; 218 - other : Sexp.t list; 219 - } 220 - (** Parsed dune-workspace file. *) 145 + type t = { name : string option; kind : kind } 146 + end 147 + 148 + type t 149 + (** A parsed [dune-workspace] file. *) 221 150 222 151 val empty : t 223 - (** [empty] is an empty dune-workspace. *) 152 + val lang : t -> string * string 153 + val contexts : t -> Context.t list 154 + val of_string : string -> (t, Error.t) result 155 + val of_string_exn : string -> t 156 + val to_string : ?indent:int -> ?preserve:bool -> t -> string 157 + val of_reader : Bytesrw.Bytes.Reader.t -> (t, Error.t) result 158 + val of_reader_exn : Bytesrw.Bytes.Reader.t -> t 224 159 225 - val parse : Sexp.t list -> t 226 - (** [parse sexps] parses S-expressions into a dune-workspace. *) 227 - 228 - val to_sexps : t -> Sexp.t list 229 - (** [to_sexps t] converts to S-expressions. *) 230 - 231 - val to_string : t -> string 232 - (** [to_string t] converts to a dune-workspace file string. *) 160 + val to_writer : 161 + ?indent:int -> ?preserve:bool -> t -> Bytesrw.Bytes.Writer.t -> unit 233 162 end 234 163 235 - (** {1 dune Files} *) 164 + (** {1 dune Build Files} *) 236 165 237 166 module File : sig 238 - type t = Sexp.t list 239 - (** A dune file is a list of stanzas. *) 240 - 241 - val parse : Sexp.t list -> t 242 - (** [parse sexps] parses S-expressions as a dune file. *) 243 - 244 - val to_sexps : t -> Sexp.t list 245 - (** [to_sexps t] converts to S-expressions. *) 246 - 247 - val to_string : t -> string 248 - (** [to_string t] converts to a dune file string. *) 249 - 250 - val find_stanza : stanza_kind -> t -> Sexp.t option 251 - (** [find_stanza kind t] finds the first stanza of the given kind. *) 252 - 253 - val find_stanzas : stanza_kind -> t -> Sexp.t list 254 - (** [find_stanzas kind t] finds all stanzas of the given kind. *) 167 + type t 168 + (** A parsed [dune] build file. *) 255 169 256 170 val library_names : t -> string list 257 - (** [library_names t] extracts all library names. *) 171 + (** Names declared by [(library (name X))] stanzas, in source order. *) 258 172 259 173 val private_library_names : t -> string list 260 - (** [private_library_names t] extracts the names of [(library (name X))] 261 - stanzas that have no [(public_name ...)]. These are workspace-private 262 - libraries: sibling stanzas can [(libraries X)] them, but they ship nowhere 263 - and must not appear in opam depends. *) 174 + (** Subset of {!library_names} for libraries with no [(public_name ...)]. 175 + These libraries ship nowhere and must not appear in opam depends. *) 264 176 265 177 val executable_names : t -> string list 266 - (** [executable_names t] extracts all executable names. *) 178 + val test_names : t -> string list 179 + val of_string : string -> (t, Error.t) result 180 + val of_string_exn : string -> t 181 + val to_string : ?indent:int -> ?preserve:bool -> t -> string 182 + val of_reader : Bytesrw.Bytes.Reader.t -> (t, Error.t) result 183 + val of_reader_exn : Bytesrw.Bytes.Reader.t -> t 267 184 268 - val test_names : t -> string list 269 - (** [test_names t] extracts all test names. *) 185 + val to_writer : 186 + ?indent:int -> ?preserve:bool -> t -> Bytesrw.Bytes.Writer.t -> unit 270 187 end 271 188 272 189 (** {1 dune-package Files} *) 273 190 274 191 module Package : sig 275 - (** Parsed [_opam/lib/<pkg>/dune-package] / [_build/install/.../dune-package] 276 - metadata. Dune writes one of these per installed package; it lists every 277 - sub-library along with its exposed module names. *) 192 + type t 193 + (** One [(library ...)] stanza from an installed 194 + [_opam/lib/<pkg>/dune-package] file. *) 278 195 279 - module Library : sig 280 - type t = { 281 - name : string; 282 - (** Public library name, e.g. [helix.jx.jsoo]. May contain dots. *) 283 - main_module_name : string option; 284 - (** Top-level wrapper module when the library is wrapped. *) 285 - modules : string list; 286 - (** All exposed module names (capitalised, [obj_name] form). Includes 287 - [main_module_name] when present. *) 288 - implements : string option; 289 - (** When present, this library is the concrete implementation of the 290 - named virtual library. Such libraries are link-time live even if 291 - none of their modules appear in source code. *) 292 - } 293 - (** A single [(library ...)] sub-stanza of a dune-package file. *) 196 + val name : t -> string 197 + (** Public library name, e.g. [helix.jx.jsoo]. May contain dots. *) 198 + 199 + val main_module_name : t -> string option 200 + (** Top-level wrapper module when the library is wrapped. *) 294 201 295 - val codec : t Sexp.codec 296 - (** Codec for a single [(library ...)] stanza. Decoding any other stanza 297 - shape returns an error. *) 298 - end 202 + val modules : t -> string list 203 + (** All exposed module names (capitalised, [obj_name] form). Includes 204 + {!main_module_name} when present. *) 205 + 206 + val implements : t -> string option 207 + (** When set, this library is the concrete implementation of the named virtual 208 + library. Such libraries are link-time live even if none of their modules 209 + appear in source. *) 210 + 211 + val of_string : string -> (t, Error.t) result 212 + val of_string_exn : string -> t 213 + val to_string : ?indent:int -> ?preserve:bool -> t -> string 214 + val of_reader : Bytesrw.Bytes.Reader.t -> (t, Error.t) result 215 + val of_reader_exn : Bytesrw.Bytes.Reader.t -> t 216 + 217 + val to_writer : 218 + ?indent:int -> ?preserve:bool -> t -> Bytesrw.Bytes.Writer.t -> unit 219 + 220 + val libraries : string -> t list 221 + (** [libraries content] decodes every [(library ...)] stanza in the contents 222 + of an installed [dune-package] file, skipping the [(lang ...)] header and 223 + any other shapes. *) 299 224 end 300 225 301 226 (** {1 Library Index} *) ··· 311 236 312 237 I/O is the caller's job. Pass {!add_dune_package} the {e contents} of a 313 238 [dune-package] file you already read, and {!add_cmi_modules} the list of 314 - [*.cmi] basenames you already enumerated. The index then knows which 315 - top-level modules each library exposes and which libraries are concrete 316 - implementations of virtual libraries. *) 239 + [*.cmi] basenames you already enumerated. *) 317 240 318 241 type t 319 242 320 243 val empty : unit -> t 321 - (** [empty ()] is a fresh, mutable index with no libraries. *) 322 - 323 244 val add_dune_package : t -> string -> t 324 - (** [add_dune_package t content] parses [content] as a [dune-package] file and 325 - folds every library it declares into [t]. Updates the virtual-impl set for 326 - any [(implements X)] entries and unions modules under each library's 327 - public name. *) 328 - 329 245 val add_cmi_modules : t -> pkg:string -> modules:string list -> t 330 - (** [add_cmi_modules t ~pkg ~modules] records [pkg]'s exposed top-level 331 - modules, used as a fallback when no [dune-package] file is present. The 332 - caller has already filtered out wrapped-private modules (basenames 333 - containing [__]) and capitalised the remainder. *) 334 - 335 246 val modules : t -> string -> string list 336 - (** [modules t lib] is the list of top-level module names exposed by [lib], or 337 - [[]] if [lib] is unknown. *) 338 - 339 247 val is_virtual_implementation : t -> string -> bool 340 - (** [is_virtual_implementation t lib] is [true] iff [lib] has an 341 - [(implements X)] entry in its [dune-package]. Such libraries are link-time 342 - live even when none of their modules appear in source. *) 343 248 end
+80 -206
ocaml-dune/test/test_dune.ml
··· 5 5 6 6 open Dune 7 7 8 - (* ---- Test helpers ---- *) 9 - 10 - let sexp = Alcotest.testable Sexp.pp Sexp.equal 11 - 12 - let parse s = 13 - match Sexp.Value.parse_string s with 14 - | Ok v -> v 15 - | Error e -> Alcotest.fail (Sexp.Error.to_string e) 16 - 17 - let parse_many s = 18 - match Sexp.Value.parse_string_many s with 19 - | Ok v -> v 20 - | Error e -> Alcotest.fail (Sexp.Error.to_string e) 21 - 22 8 (* ---- Variable tests ---- *) 23 9 24 10 let test_var_simple () = ··· 35 21 36 22 let test_text_parsing () = 37 23 let text = text_of_string "hello %{world} and %{name:default}" in 38 - (* 4 segments: "hello ", %{world}, " and ", %{name:default} *) 39 24 Alcotest.(check int) "4 segments" 4 (List.length text); 40 25 Alcotest.(check string) 41 26 "roundtrip" "hello %{world} and %{name:default}" (text_to_string text) ··· 46 31 Alcotest.(check bool) "no vars" false (has_variables "hello"); 47 32 Alcotest.(check bool) "incomplete" false (has_variables "%{") 48 33 49 - let test_expand () = 34 + let test_expand_atom () = 50 35 let env = function "name" -> Some "Alice" | _ -> None in 51 - let input = parse "(hello %{name})" in 52 - let expected = parse "(hello Alice)" in 53 - Alcotest.(check sexp) "expanded" expected (expand ~env input) 54 - 55 - let test_expand_default () = 56 - let env = function _ -> None in 57 - let input = parse "(hello %{name:World})" in 58 - let expected = parse "(hello World)" in 59 - Alcotest.(check sexp) "use default" expected (expand ~env input) 60 - 61 - let test_variables () = 62 - let input = parse "(foo %{bar} (baz %{qux:default}))" in 63 - let vars = variables input in 64 - Alcotest.(check (list string)) "variables" [ "bar"; "qux" ] vars 65 - 66 - (* ---- Field accessor tests ---- *) 67 - 68 - let test_find_field () = 69 - let stanza = parse "(library (name foo) (libraries bar baz))" in 70 - Alcotest.(check (option string)) 71 - "name" (Some "foo") (field_atom "name" stanza); 72 - Alcotest.(check (option (list string))) 73 - "libraries" 74 - (Some [ "bar"; "baz" ]) 75 - (field_atoms "libraries" stanza); 76 - Alcotest.(check (option string)) 77 - "missing" None 78 - (field_atom "public_name" stanza) 36 + Alcotest.(check string) 37 + "expanded" "hello Alice" 38 + (expand_atom ~env "hello %{name}"); 39 + Alcotest.(check string) 40 + "use default" "hello World" 41 + (expand_atom ~env:(fun _ -> None) "hello %{name:World}") 79 42 80 - let test_set_field () = 81 - let stanza = parse "(library (name foo))" in 82 - let updated = set_field "public_name" (Sexp.Atom "my-lib") stanza in 83 - Alcotest.(check (option string)) 84 - "added" (Some "my-lib") 85 - (field_atom "public_name" updated); 86 - let updated2 = set_field "name" (Sexp.Atom "bar") updated in 87 - Alcotest.(check (option string)) 88 - "replaced" (Some "bar") 89 - (field_atom "name" updated2) 43 + (* ---- File tests ---- *) 90 44 91 - let test_remove_field () = 92 - let stanza = parse "(library (name foo) (public_name bar))" in 93 - let updated = remove_field "public_name" stanza in 94 - Alcotest.(check (option string)) 95 - "removed" None 96 - (field_atom "public_name" updated); 97 - Alcotest.(check (option string)) 98 - "kept" (Some "foo") 99 - (field_atom "name" updated) 100 - 101 - (* ---- Stanza kind tests ---- *) 102 - 103 - let test_stanza_kind () = 104 - Alcotest.(check (option string)) 105 - "library" (Some "library") 106 - (Option.map stanza_kind_to_string 107 - (stanza_kind (parse "(library (name foo))"))); 108 - Alcotest.(check (option string)) 109 - "executable" (Some "executable") 110 - (Option.map stanza_kind_to_string 111 - (stanza_kind (parse "(executable (name main))"))); 112 - Alcotest.(check (option string)) 113 - "test" (Some "test") 114 - (Option.map stanza_kind_to_string 115 - (stanza_kind (parse "(test (name test_foo))"))) 116 - 117 - (* ---- Stanza builder tests ---- *) 118 - 119 - let test_library_builder () = 120 - let lib = library ~public_name:"my-lib" ~libraries:[ "fmt"; "logs" ] "foo" in 121 - Alcotest.(check (option string)) "name" (Some "foo") (field_atom "name" lib); 122 - Alcotest.(check (option string)) 123 - "public_name" (Some "my-lib") 124 - (field_atom "public_name" lib); 125 - Alcotest.(check (option (list string))) 126 - "libraries" 127 - (Some [ "fmt"; "logs" ]) 128 - (field_atoms "libraries" lib) 129 - 130 - let test_executable_builder () = 131 - let exe = executable ~public_name:"my-exe" ~libraries:[ "cmdliner" ] "main" in 132 - Alcotest.(check (option string)) "name" (Some "main") (field_atom "name" exe); 133 - Alcotest.(check (option string)) 134 - "public_name" (Some "my-exe") 135 - (field_atom "public_name" exe) 136 - 137 - let test_test_builder () = 138 - let t = 139 - test ~modules:[ "test_foo"; "test_bar" ] ~libraries:[ "alcotest" ] 140 - "test_main" 141 - in 142 - Alcotest.(check (option string)) 143 - "name" (Some "test_main") (field_atom "name" t); 144 - Alcotest.(check (option (list string))) 145 - "modules" 146 - (Some [ "test_foo"; "test_bar" ]) 147 - (field_atoms "modules" t) 148 - 149 - (* ---- Dune file tests ---- *) 150 - 151 - let test_dune_find_stanzas () = 152 - let dune_content = 45 + let test_dune_file_queries () = 46 + let content = 153 47 {| 154 - (library (name foo)) 48 + (library (name foo) (libraries fmt)) 155 49 (library (name bar)) 156 50 (executable (name main)) 157 51 (test (name test_foo)) 158 52 |} 159 53 in 160 - let stanzas = File.parse (parse_many dune_content) in 54 + let t = File.of_string_exn content in 161 55 Alcotest.(check (list string)) 162 - "library names" [ "foo"; "bar" ] 163 - (File.library_names stanzas); 56 + "library names" [ "foo"; "bar" ] (File.library_names t); 164 57 Alcotest.(check (list string)) 165 - "executable names" [ "main" ] 166 - (File.executable_names stanzas); 58 + "private libs" [ "foo"; "bar" ] 59 + (File.private_library_names t); 167 60 Alcotest.(check (list string)) 168 - "test names" [ "test_foo" ] (File.test_names stanzas) 61 + "executable names" [ "main" ] (File.executable_names t); 62 + Alcotest.(check (list string)) "test names" [ "test_foo" ] (File.test_names t) 169 63 170 - (* ---- Dune-project tests ---- *) 64 + let test_dune_file_roundtrip () = 65 + let content = 66 + "(library (name foo) (libraries fmt))\n(executable (name main))" 67 + in 68 + let t = File.of_string_exn content in 69 + let s = File.to_string ~indent:1 t in 70 + let t2 = File.of_string_exn s in 71 + Alcotest.(check (list string)) 72 + "lib names preserved" (File.library_names t) (File.library_names t2) 73 + 74 + (* ---- Project tests ---- *) 171 75 172 76 let test_dune_project_parse () = 173 77 let content = ··· 178 82 (license ISC) 179 83 (authors "Alice" "Bob") 180 84 (source (github owner/repo)) 85 + (package 86 + (name my-project) 87 + (synopsis "a demo") 88 + (depends ocaml dune (alcotest :with-test))) 181 89 |} 182 90 in 183 - let proj = Project.parse (parse_many content) in 184 - Alcotest.(check (pair string string)) "lang" ("dune", "3.17") proj.lang; 185 - Alcotest.(check (option string)) "name" (Some "my-project") proj.name; 186 - Alcotest.(check (option string)) "version" (Some "1.0.0") proj.version; 187 - Alcotest.(check (option string)) "license" (Some "ISC") proj.license; 188 - Alcotest.(check (list string)) "authors" [ "Alice"; "Bob" ] proj.authors; 91 + let p = Project.of_string_exn content in 92 + Alcotest.(check (pair string string)) "lang" ("dune", "3.17") (Project.lang p); 93 + Alcotest.(check (option string)) "name" (Some "my-project") (Project.name p); 94 + Alcotest.(check (option string)) "version" (Some "1.0.0") (Project.version p); 95 + Alcotest.(check (option string)) "license" (Some "ISC") (Project.license p); 96 + Alcotest.(check (list string)) 97 + "authors" [ "Alice"; "Bob" ] (Project.authors p); 189 98 Alcotest.(check (option string)) 190 - "source" (Some "github:owner/repo") proj.source 99 + "source" (Some "github:owner/repo") (Project.source p); 100 + let pkgs = Project.packages p in 101 + Alcotest.(check int) "one package" 1 (List.length pkgs); 102 + let pkg = List.hd pkgs in 103 + Alcotest.(check string) "package name" "my-project" (Project.Package.name pkg); 104 + Alcotest.(check (option string)) 105 + "package synopsis" (Some "a demo") 106 + (Project.Package.synopsis pkg); 107 + Alcotest.(check (list string)) 108 + "package depends" 109 + [ "ocaml"; "dune"; "alcotest" ] 110 + (Project.Package.depends pkg) 191 111 192 112 let test_dune_project_roundtrip () = 193 - let proj = 113 + let p = 194 114 Project.make ~dune_version:"3.17" ~name:"test" ~version:"0.1" ~license:"MIT" 195 115 ~authors:[ "Test Author" ] ~source:"github:test/repo" () 196 116 in 197 - let sexps = Project.to_sexps proj in 198 - let proj2 = Project.parse sexps in 199 - Alcotest.(check (option string)) "name preserved" proj.name proj2.name; 117 + let s = Project.to_string p in 118 + let p2 = Project.of_string_exn s in 119 + Alcotest.(check (option string)) 120 + "name preserved" (Project.name p) (Project.name p2); 200 121 Alcotest.(check (option string)) 201 - "version preserved" proj.version proj2.version 122 + "version preserved" (Project.version p) (Project.version p2) 202 123 203 - (* ---- Dune-workspace tests ---- *) 124 + (* ---- Workspace tests ---- *) 204 125 205 126 let test_dune_workspace_parse () = 206 127 let content = {| 207 128 (lang dune 3.17) 208 129 (context default) 209 130 |} in 210 - let ws = Workspace.parse (parse_many content) in 211 - Alcotest.(check (pair string string)) "lang" ("dune", "3.17") ws.lang; 212 - Alcotest.(check int) "one context" 1 (List.length ws.contexts) 131 + let ws = Workspace.of_string_exn content in 132 + Alcotest.(check (pair string string)) 133 + "lang" ("dune", "3.17") (Workspace.lang ws); 134 + Alcotest.(check int) "one context" 1 (List.length (Workspace.contexts ws)) 213 135 214 - (* ---- Pretty printing tests ---- *) 215 - 216 - let test_pp_dune () = 217 - let lib = library ~libraries:[ "a"; "b"; "c"; "d"; "e" ] "foo" in 218 - let s = to_string_dune lib in 219 - (* Should have newlines for long list *) 220 - Alcotest.(check bool) "has newlines" true (String.contains s '\n') 136 + (* ---- Package (dune-package metadata) tests ---- *) 221 137 222 - let contains_substring sub s = 223 - let sub_len = String.length sub in 224 - let s_len = String.length s in 225 - if sub_len > s_len then false 226 - else 227 - let rec check i = 228 - if i > s_len - sub_len then false 229 - else if String.sub s i sub_len = sub then true 230 - else check (i + 1) 231 - in 232 - check 0 233 - 234 - let test_pp_dune_file () = 235 - let stanzas = [ library "foo"; executable "main" ] in 236 - let s = to_string_dune_file stanzas in 237 - (* Should have blank lines between stanzas *) 238 - Alcotest.(check bool) "has blank lines" true (contains_substring "\n\n" s) 239 - 240 - (* ---- Package tests ---- *) 241 - 242 - (* Trimmed sample mirroring an installed dune-package: a wrapped library with 243 - one nested module plus a virtual implementation that has [(implements X)] 244 - and an empty module list. *) 138 + (* Trimmed sample mirroring an installed dune-package: a virtual library 139 + plus its concrete implementation (with [(implements X)]). *) 245 140 let dune_package_sample = 246 141 {| 247 142 (lang dune 3.0) ··· 266 161 |} 267 162 268 163 let test_dune_package_libraries () = 269 - (* The codec only decodes (library ...) stanzas; everything else (lang, 270 - etc) is rejected. Filter via decode_value attempts. *) 271 - let libs = 272 - parse_many dune_package_sample 273 - |> List.filter_map (fun s -> 274 - Sexp.Codec.decode_value Package.Library.codec s |> Result.to_option) 275 - in 164 + let libs = Package.libraries dune_package_sample in 276 165 Alcotest.(check int) "two libraries" 2 (List.length libs); 277 166 let virt = List.nth libs 0 in 278 - Alcotest.(check string) "virtual name" "helix.jx" virt.Package.Library.name; 167 + Alcotest.(check string) "virtual name" "helix.jx" (Package.name virt); 279 168 Alcotest.(check (option string)) 280 - "virtual not implementing" None virt.implements; 169 + "virtual not implementing" None (Package.implements virt); 281 170 let impl = List.nth libs 1 in 282 - Alcotest.(check string) "impl name" "helix.jx.jsoo" impl.Package.Library.name; 171 + Alcotest.(check string) "impl name" "helix.jx.jsoo" (Package.name impl); 283 172 Alcotest.(check (option string)) 284 - "impl implements virtual" (Some "helix.jx") impl.implements; 173 + "impl implements virtual" (Some "helix.jx") (Package.implements impl); 285 174 Alcotest.(check (option string)) 286 - "main_module_name" (Some "Jx") impl.main_module_name; 287 - (* Modules pulled from each [(module (obj_name X))] descendant, plus the 288 - [main_module_name] itself. The synthetic [(alias (obj_name X__))] 289 - wrapper is not a module the user can name — skip it. Order doesn't 290 - matter. *) 291 - let sorted = List.sort String.compare impl.modules in 175 + "main_module_name" (Some "Jx") 176 + (Package.main_module_name impl); 177 + let sorted = List.sort String.compare (Package.modules impl) in 292 178 Alcotest.(check (list string)) 293 179 "modules harvested" [ "Jx"; "Jx__Jx_ffi" ] sorted 294 180 ··· 301 187 Alcotest.test_case "var with default" `Quick test_var_with_default; 302 188 Alcotest.test_case "text parsing" `Quick test_text_parsing; 303 189 Alcotest.test_case "has_variables" `Quick test_has_variables; 304 - Alcotest.test_case "expand" `Quick test_expand; 305 - Alcotest.test_case "expand default" `Quick test_expand_default; 306 - Alcotest.test_case "extract variables" `Quick test_variables; 307 - Alcotest.test_case "find_field" `Quick test_find_field; 308 - Alcotest.test_case "set_field" `Quick test_set_field; 309 - Alcotest.test_case "remove_field" `Quick test_remove_field; 310 - Alcotest.test_case "stanza kinds" `Quick test_stanza_kind; 311 - Alcotest.test_case "library builder" `Quick test_library_builder; 312 - Alcotest.test_case "executable builder" `Quick test_executable_builder; 313 - Alcotest.test_case "test builder" `Quick test_test_builder; 314 - Alcotest.test_case "find stanzas" `Quick test_dune_find_stanzas; 315 - Alcotest.test_case "dune_project parse" `Quick test_dune_project_parse; 316 - Alcotest.test_case "dune_project roundtrip" `Quick 317 - test_dune_project_roundtrip; 318 - Alcotest.test_case "dune_workspace parse" `Quick test_dune_workspace_parse; 319 - Alcotest.test_case "pp_dune" `Quick test_pp_dune; 320 - Alcotest.test_case "pp_dune_file" `Quick test_pp_dune_file; 321 - Alcotest.test_case "dune_package libraries" `Quick 322 - test_dune_package_libraries; 190 + Alcotest.test_case "expand_atom" `Quick test_expand_atom; 191 + Alcotest.test_case "file queries" `Quick test_dune_file_queries; 192 + Alcotest.test_case "file roundtrip" `Quick test_dune_file_roundtrip; 193 + Alcotest.test_case "project parse" `Quick test_dune_project_parse; 194 + Alcotest.test_case "project roundtrip" `Quick test_dune_project_roundtrip; 195 + Alcotest.test_case "workspace parse" `Quick test_dune_workspace_parse; 196 + Alcotest.test_case "package libraries" `Quick test_dune_package_libraries; 323 197 ] )