diff --git a/data/cookbook/deserialise-post-process-from-yaml/00-yaml.ml b/data/cookbook/deserialise-post-process-from-yaml/00-yaml.ml new file mode 100644 index 0000000000..da5041b557 --- /dev/null +++ b/data/cookbook/deserialise-post-process-from-yaml/00-yaml.ml @@ -0,0 +1,78 @@ +--- +packages: +- name: yaml + tested_version: 3.2.0 + used_libraries: + - yaml +- name: ppx_deriving_yaml + tested_version: 0.2.2 + used_libraries: + - ppx_deriving_yaml +--- + +(* This YAML string contains a list of ingredients where the ingredients are represented as + a YAML object, with keys representing names and values representing amounts. *) +let yaml_string = {| +french name: pâte sucrée +ingredients: +- flour: 250 +- butter: 100 +- sugar: 100 +- egg: 50 +- salt: 5 +steps: +- soften butter +- add sugar +- add egg and salt +- add flour +|} + +(* The `[@@deriving of_yaml]` attribute makes the `ppx_deriving_yaml` library generate the function + ``ingredient_of_yaml : Yaml.value -> (ingredient, [> `Msg of string]) result``. *) +type ingredient = { + name: string; + weight: int; +} [@@deriving of_yaml] + +(* The `[@@deriving of_yaml]` attribute makes the `ppx_deriving_yaml` library generate the function + ``recipe_of_yaml : Yaml.value -> (ingredient, [> `Msg of string]) result``. *) +type recipe = { + name: string; [@key "french name"] + ingredients: ingredient list; + steps: string list; +} [@@deriving of_yaml] + +(* Since the structure of the YAML file does not exactly match the `recipe` type, + we (1) parse the YAML file to the internal representation `Yaml.value` of the `yaml` package, + and then (2) change the structure to match the `recipe` type, so we can use the `recipe_of_yaml` + function. + + The functions `add_keys` and `at_ingredients` perform this post-processing. *) +let add_keys = function + | `O [(name, `Float weight)] -> + `O [ + ("name", `String name); + ("weight", `Float weight); + ] + | v -> v + +let at_ingredients f = function + | `O [ + ("french name", `String name); + ("ingredients", `A ingredients); + ("steps", `A steps) + ] -> `O [ + ("french name", `String name); + ("ingredients", + Yaml.Util.map_exn f (`A ingredients)); + ("steps", `A steps); + ] + | v -> v + +(* Parse, post-process, and convert the YAML string into an OCaml value of type `recipe`. *) +let pate_sucree = + yaml_string + |> Yaml.of_string + |> Result.map (at_ingredients add_keys) + |> fun yaml -> Result.bind yaml recipe_of_yaml + diff --git a/data/cookbook/deserialise-post-process-from-yaml/01-hl_yaml.ml b/data/cookbook/deserialise-post-process-from-yaml/01-hl_yaml.ml new file mode 100644 index 0000000000..f2cbb64b2d --- /dev/null +++ b/data/cookbook/deserialise-post-process-from-yaml/01-hl_yaml.ml @@ -0,0 +1,79 @@ +--- +packages: +- name: hl_yaml + tested_version: 1.0.0 + used_libraries: + - hl_yaml +- name: ppx_deriving_yojson + tested_version: 3.7.0 + used_libraries: + - ppx_deriving_yojson +--- + +(* This YAML string contains a list of ingredients where the ingredients are represented as + a YAML object, with keys representing names and values representing amounts. *) +let yaml = {| +french name: pâte sucrée +ingredients: +- flour: 250 +- butter: 100 +- sugar: 100 +- egg: 50 +- salt: 5 +steps: +- soften butter +- add sugar +- add egg and salt +- add flour +|} + +(* The `[@@deriving of_yojson]` attribute makes the `ppx_deriving_yojson` library generate the function + `ingredient_of_yojson : Yojson.Safe.t -> (ingredient, string) result`. *) +type ingredient = { + name: string; + weight: int; +} [@@deriving of_yojson] + +(* The `[@@deriving of_yojson]` attribute makes the `ppx_deriving_yojson` library generate the function + ``recipe_of_yojson : Yojson.Safe.t -> (ingredient, string) result``. *) +type recipe = { + name: string; [@key "french name"] + ingredients: ingredient list; + steps: string list; +} [@@deriving of_yojson] + +(* Since the structure of the YAML file does not exactly match the `recipe` type, + we (1) parse the YAML file to the representation `Yojson.Safe.t`, + and then (2) modify the `Yojson.Safe.t` value to change the structure to match the `recipe` type, + so we can use the `recipe_of_yojson` function. + + The functions `add_keys` and `at_ingredients` perform this post-processing. *) +let add_keys = function + | `Assoc [(name, `Int weight)] -> + `Assoc [ + ("name", `String name); + ("weight", `Int weight); + ] + | v -> v + +let at_ingredients f = function + | `Assoc [ + ("french name", `String name); + ("ingredients", `List ingredients); + ("steps", `List steps) + ] -> `Assoc [ + ("french name", `String name); + ("ingredients", + Yojson.Safe.Util.map f (`List ingredients)); + ("steps", `List steps); + ] + | v -> v + +(* Parse, post-process, and convert the YAML string into an OCaml value of type `recipe`. *) +let pate_sucree = + let of_yojson json = + json + |> at_ingredients add_keys + |> recipe_of_yojson in + yaml + |> Hl_yaml.Unix.parse ~of_yojson diff --git a/data/cookbook/tasks.yml b/data/cookbook/tasks.yml index 9b03af1af1..ab04538a10 100644 --- a/data/cookbook/tasks.yml +++ b/data/cookbook/tasks.yml @@ -96,8 +96,12 @@ categories: slug: serialise-to-yaml - title: Deserialise YAML Data slug: deserialise-from-yaml + description: | + Deserialise a YAML file whose structure maps to some OCaml types. - title: Deserialise and Post-Process YAML Data slug: deserialise-post-process-from-yaml + description: | + Deserialise a YAML file whose structure does not directly map to some OCaml types. - title: Date and Time tasks: - title: Get Today's Date