From e18f420f97e191357e893015c8f750e116cc30e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Sat, 22 Feb 2020 10:55:53 +0100 Subject: [PATCH 1/2] added more primitive functions --- src/Yaml/Decode.elm | 677 ++++++++++++++++++++++++++++++-------------- 1 file changed, 457 insertions(+), 220 deletions(-) diff --git a/src/Yaml/Decode.elm b/src/Yaml/Decode.elm index 1f4ac84..f43519f 100644 --- a/src/Yaml/Decode.elm +++ b/src/Yaml/Decode.elm @@ -1,147 +1,204 @@ -module Yaml.Decode exposing - ( Decoder, Error(..), fromString - , string, bool, int, float - , nullable, list - , field, at - , Value, value, sometimes, fail, succeed, andThen - , map, map2, map3, map4, map5, map6, map7, map8 - ) - -{-| - -Turn YAML values into Elm values. The library is structured the same way -as a `Json.Decode` in `elm/json`, so if you haven't worked with decoders +module Yaml.Decode exposing + ( Decoder, Error(..), fromString + , string, bool, int, float, null + , nullable, list, keyValuePairs + , field, at, or, oneOf + , map, map2, map3, map4, map5, map6, map7, map8 + , Value, value, sometimes, fail, succeed, andThen, lazy + ) + +{-| Turn YAML values into Elm values. The library is structured the same way +as a `Json.Decode` in `elm/json`, so if you haven't worked with decoders before, reading through [the guide](https://guide.elm-lang.org/effects/json.html) maybe be helpful. @docs Decoder, Error, fromString + # Primitives -@docs string, bool, int, float + +@docs string, bool, int, float, null + # Data Structures -@docs nullable, list + +@docs nullable, list, keyValuePairs + # Object Primitives -@docs field, at + +@docs field, at, or, oneOf + # Maps + @docs map, map2, map3, map4, map5, map6, map7, map8 + # Special -@docs Value, value, sometimes, fail, succeed, andThen +@docs Value, value, sometimes, fail, succeed, andThen, lazy -} +import Dict import Yaml.Parser as Yaml import Yaml.Parser.Ast as Ast -import Dict {-| A value that knows how to decode YAML values. -There is a whole section in guide.elm-lang.org about decoders, -so [check it out](https://guide.elm-lang.org/effects/json.html) +There is a whole section in guide.elm-lang.org about decoders, +so [check it out](https://guide.elm-lang.org/effects/json.html) for a more comprehensive introduction! -} -type Decoder a = - Decoder (Yaml.Value -> Result Error a) +type Decoder a + = Decoder (Yaml.Value -> Result Error a) {-| Represents a YAML value. -} type alias Value = - Yaml.Value + Yaml.Value {-| -} -type Error -- TODO - = Parsing String - | Decoding String +type + Error + -- TODO + = Parsing String + | Decoding String {-| -} fromString : Decoder a -> String -> Result Error a fromString decoder raw = - case Yaml.fromString raw of - Ok v -> fromValue decoder v - Err error -> Err (Parsing error) + case Yaml.fromString raw of + Ok v -> + fromValue decoder v + + Err error -> + Err (Parsing error) fromValue : Decoder a -> Yaml.Value -> Result Error a fromValue (Decoder decoder) v = - decoder v + decoder v -- PRIMITIVES -{-| Decode a YAML string into an Elm `String`. +{-| A faked lazy function that enables recursive decoders. +-} +lazy : (() -> Decoder a) -> Decoder a +lazy t = + succeed () |> andThen t + + +{-| Decode a YAML string into an Elm `String`. -} string : Decoder String string = - Decoder <| \v -> - case v of - Ast.String_ string_ -> Ok string_ - _ -> Err (Decoding "Expected string") + Decoder <| + \v -> + case v of + Ast.String_ string_ -> + Ok string_ + + _ -> + Err (Decoding "Expected string") {-| Decode a YAML boolean into an Elm `Bool`. -} bool : Decoder Bool bool = - Decoder <| \v -> - case v of - Ast.Bool_ bool_ -> Ok bool_ - _ -> Err (Decoding "Expected bool") + Decoder <| + \v -> + case v of + Ast.Bool_ bool_ -> + Ok bool_ + + _ -> + Err (Decoding "Expected bool") {-| Decode a YAML number into an Elm `Int`. -} int : Decoder Int int = - Decoder <| \v -> - case v of - Ast.Int_ int_ -> Ok int_ - _ -> Err (Decoding "Expected int") + Decoder <| + \v -> + case v of + Ast.Int_ int_ -> + Ok int_ + + _ -> + Err (Decoding "Expected int") {-| Decode a YAML number into an Elm `Float`. -} float : Decoder Float float = - Decoder <| \v -> - case v of - Ast.Float_ float_ -> Ok float_ - _ -> Err (Decoding "Expected float") + Decoder <| + \v -> + case v of + Ast.Float_ float_ -> + Ok float_ + + _ -> + Err (Decoding "Expected float") + + +{-| Sometimes you just need a null value. +-} +null : Decoder (Maybe a) +null = + Decoder <| + \v -> + case v of + Ast.Null_ -> + Ok Nothing + + _ -> + Err (Decoding "Expected null") {-| Decode a nullable YAML value into an Elm value. -} nullable : Decoder a -> Decoder (Maybe a) nullable decoder = - Decoder <| \v -> - case v of - Ast.Null_ -> Ok Nothing - other -> Result.map Just (fromValue decoder other) + Decoder <| + \v -> + case v of + Ast.Null_ -> + Ok Nothing + + other -> + Result.map Just (fromValue decoder other) {-| Decode a YAML array into an Elm `List`. -} list : Decoder a -> Decoder (List a) list decoder = - Decoder <| \v -> - case v of - Ast.List_ list_ -> singleResult (List.map (fromValue decoder) list_) - _ -> Err (Decoding "Expected list") + Decoder <| + \v -> + case v of + Ast.List_ list_ -> + singleResult (List.map (fromValue decoder) list_) + + _ -> + Err (Decoding "Expected list") {-| Decode a YAML object, requiring a particular field. -The object can have other fields. Lots of them! The only thing this decoder +The object can have other fields. Lots of them! The only thing this decoder cares about is if x is present and that the value there is an Int. Check out [map2](#map2) to see how to decode multiple fields! @@ -149,54 +206,63 @@ Check out [map2](#map2) to see how to decode multiple fields! -} field : String -> Decoder a -> Decoder a field name decoder = - Decoder <| \v -> - find [ name ] decoder v + Decoder <| + \v -> + find [ name ] decoder v -{-| Decode a nested YAML object, requiring certain fields. +{-| Decode a nested YAML object, requiring certain fields. -} at : List String -> Decoder a -> Decoder a at names decoder = - Decoder <| \v -> - find names decoder v + Decoder <| + \v -> + find names decoder v -- SPECIAL -{-| Do not do anything with a YAML value, just bring it into -Elm as a `Value`. This can be useful if you have particularly +{-| Do not do anything with a YAML value, just bring it into +Elm as a `Value`. This can be useful if you have particularly complex data that you would like to deal with later. -} value : Decoder Value value = - Decoder <| \v -> - Ok v + Decoder <| + \v -> + Ok v {-| A decoder which returns `Nothing` when it fails. Note: This is equivalent to `maybe` from `Json.Decode`. + -} sometimes : Decoder a -> Decoder (Maybe a) sometimes decoder = - Decoder <| \v -> - case fromValue decoder v of - Ok a -> Ok (Just a) - Err _ -> Ok Nothing + Decoder <| + \v -> + case fromValue decoder v of + Ok a -> + Ok (Just a) + + Err _ -> + Ok Nothing {-| Ignore the YAML and produce a certain Elm value. -} succeed : a -> Decoder a succeed v = - Decoder <| \_ -> - Ok v + Decoder <| + \_ -> + Ok v -{-| Ignore the YAML and make the decoder fail. This is handy -when used with `oneOf` or `andThen` where you want to give a +{-| Ignore the YAML and make the decoder fail. This is handy +when used with `oneOf` or `andThen` where you want to give a custom error message in some case. See the [andThen](#andThen) docs for an example. @@ -204,18 +270,94 @@ See the [andThen](#andThen) docs for an example. -} fail : String -> Decoder a fail error = - Decoder <| \_ -> - Err (Decoding error) + Decoder <| + \_ -> + Err (Decoding error) {-| Create decoders that depend on previous results. -} andThen : (a -> Decoder b) -> Decoder a -> Decoder b andThen next decoder = - Decoder <| \v0 -> - case fromValue decoder v0 of - Ok a -> fromValue (next a) v0 - Err err -> Err err + Decoder <| + \v0 -> + case fromValue decoder v0 of + Ok a -> + fromValue (next a) v0 + + Err err -> + Err err + + +{-| Pass decoders in a list and the first working one defines the result. +-} +oneOf : List (Decoder a) -> Decoder a +oneOf ds = + List.foldr or (fail "Empty") ds + + +{-| Chose between (try out) two decoders. +-} +or : Decoder a -> Decoder a -> Decoder a +or lp rp = + Decoder <| + \yaml -> + case fromValue lp yaml of + Ok a -> + Ok a + + Err lms -> + case fromValue rp yaml of + Ok a -> + Ok a + + Err rms -> + Err rms + + +{-| This can be used to decode a dictionary, the result is a key value pair. +This function can be called in conjunction with `oneOf` to define more complex +decoders. +-} +keyValuePairs : Decoder a -> Decoder (List ( String, a )) +keyValuePairs decoder = + Decoder <| + \v -> + case v of + Ast.Record_ properties -> + let + rslt = + properties + |> Dict.toList + |> List.map (\( key, val ) -> ( key, fromValue decoder val )) + in + case + rslt + |> List.filter + (Tuple.second + >> Result.toMaybe + >> (==) Nothing + ) + |> List.head + of + Just err -> + Err (Decoding "Expected record") + + _ -> + rslt + |> List.filterMap + (\( key, val ) -> + case val of + Ok v_ -> + Just ( key, v_ ) + + _ -> + Nothing + ) + |> Ok + + _ -> + Err (Decoding "Expected record") @@ -226,163 +368,252 @@ andThen next decoder = -} map : (a -> b) -> Decoder a -> Decoder b map func (Decoder a) = - Decoder <| \v0 -> - case a v0 of - Err err -> Err err - Ok av -> Ok (func av) + Decoder <| + \v0 -> + case a v0 of + Err err -> + Err err + + Ok av -> + Ok (func av) + {-| Try two decoders and then combine the result. -} map2 : (a -> b -> c) -> Decoder a -> Decoder b -> Decoder c map2 func (Decoder a) (Decoder b) = - Decoder <| \v0 -> - case a v0 of - Err err1 -> Err err1 - Ok av -> - case b v0 of - Err err2 -> Err err2 - Ok bv -> Ok (func av bv) + Decoder <| + \v0 -> + case a v0 of + Err err1 -> + Err err1 + + Ok av -> + case b v0 of + Err err2 -> + Err err2 + + Ok bv -> + Ok (func av bv) {-| Try three decoders and then combine the result. -} map3 : (a -> b -> c -> d) -> Decoder a -> Decoder b -> Decoder c -> Decoder d map3 func (Decoder a) (Decoder b) (Decoder c) = - Decoder <| \v0 -> - case a v0 of - Err err1 -> Err err1 - Ok av -> - case b v0 of - Err err2 -> Err err2 - Ok bv -> - case c v0 of - Err err3 -> Err err3 - Ok cv -> Ok (func av bv cv) + Decoder <| + \v0 -> + case a v0 of + Err err1 -> + Err err1 + + Ok av -> + case b v0 of + Err err2 -> + Err err2 + + Ok bv -> + case c v0 of + Err err3 -> + Err err3 + + Ok cv -> + Ok (func av bv cv) {-| Try four decoders and then combine the result. -} map4 : (a -> b -> c -> d -> e) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e map4 func (Decoder a) (Decoder b) (Decoder c) (Decoder d) = - Decoder <| \v0 -> - case a v0 of - Err err1 -> Err err1 - Ok av -> - case b v0 of - Err err2 -> Err err2 - Ok bv -> - case c v0 of - Err err3 -> Err err3 - Ok cv -> - case d v0 of - Err err4 -> Err err4 - Ok dv -> Ok (func av bv cv dv) + Decoder <| + \v0 -> + case a v0 of + Err err1 -> + Err err1 + + Ok av -> + case b v0 of + Err err2 -> + Err err2 + + Ok bv -> + case c v0 of + Err err3 -> + Err err3 + + Ok cv -> + case d v0 of + Err err4 -> + Err err4 + + Ok dv -> + Ok (func av bv cv dv) {-| Try five decoders and then combine the result. -} map5 : (a -> b -> c -> d -> e -> f) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f map5 func (Decoder a) (Decoder b) (Decoder c) (Decoder d) (Decoder e) = - Decoder <| \v0 -> - case a v0 of - Err err1 -> Err err1 - Ok av -> - case b v0 of - Err err2 -> Err err2 - Ok bv -> - case c v0 of - Err err3 -> Err err3 - Ok cv -> - case d v0 of - Err err4 -> Err err4 - Ok dv -> - case e v0 of - Err err5 -> Err err5 - Ok ev -> Ok (func av bv cv dv ev) + Decoder <| + \v0 -> + case a v0 of + Err err1 -> + Err err1 + + Ok av -> + case b v0 of + Err err2 -> + Err err2 + + Ok bv -> + case c v0 of + Err err3 -> + Err err3 + + Ok cv -> + case d v0 of + Err err4 -> + Err err4 + + Ok dv -> + case e v0 of + Err err5 -> + Err err5 + + Ok ev -> + Ok (func av bv cv dv ev) {-| Try six decoders and then combine the result. -} map6 : (a -> b -> c -> d -> e -> f -> g) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g map6 func (Decoder a) (Decoder b) (Decoder c) (Decoder d) (Decoder e) (Decoder f) = - Decoder <| \v0 -> - case a v0 of - Err err1 -> Err err1 - Ok av -> - case b v0 of - Err err2 -> Err err2 - Ok bv -> - case c v0 of - Err err3 -> Err err3 - Ok cv -> - case d v0 of - Err err4 -> Err err4 - Ok dv -> - case e v0 of - Err err5 -> Err err5 - Ok ev -> - case f v0 of - Err err6 -> Err err6 - Ok fv -> Ok (func av bv cv dv ev fv) + Decoder <| + \v0 -> + case a v0 of + Err err1 -> + Err err1 + + Ok av -> + case b v0 of + Err err2 -> + Err err2 + + Ok bv -> + case c v0 of + Err err3 -> + Err err3 + + Ok cv -> + case d v0 of + Err err4 -> + Err err4 + + Ok dv -> + case e v0 of + Err err5 -> + Err err5 + + Ok ev -> + case f v0 of + Err err6 -> + Err err6 + + Ok fv -> + Ok (func av bv cv dv ev fv) {-| Try seven decoders and then combine the result. -} map7 : (a -> b -> c -> d -> e -> f -> g -> h) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h map7 func (Decoder a) (Decoder b) (Decoder c) (Decoder d) (Decoder e) (Decoder f) (Decoder g) = - Decoder <| \v0 -> - case a v0 of - Err err1 -> Err err1 - Ok av -> - case b v0 of - Err err2 -> Err err2 - Ok bv -> - case c v0 of - Err err3 -> Err err3 - Ok cv -> - case d v0 of - Err err4 -> Err err4 - Ok dv -> - case e v0 of - Err err5 -> Err err5 - Ok ev -> - case f v0 of - Err err6 -> Err err6 - Ok fv -> - case g v0 of - Err err7 -> Err err7 - Ok gv -> Ok (func av bv cv dv ev fv gv) + Decoder <| + \v0 -> + case a v0 of + Err err1 -> + Err err1 + + Ok av -> + case b v0 of + Err err2 -> + Err err2 + + Ok bv -> + case c v0 of + Err err3 -> + Err err3 + + Ok cv -> + case d v0 of + Err err4 -> + Err err4 + + Ok dv -> + case e v0 of + Err err5 -> + Err err5 + + Ok ev -> + case f v0 of + Err err6 -> + Err err6 + + Ok fv -> + case g v0 of + Err err7 -> + Err err7 + + Ok gv -> + Ok (func av bv cv dv ev fv gv) {-| Try seven decoders and then combine the result. -} map8 : (a -> b -> c -> d -> e -> f -> g -> h -> i) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h -> Decoder i map8 func (Decoder a) (Decoder b) (Decoder c) (Decoder d) (Decoder e) (Decoder f) (Decoder g) (Decoder h) = - Decoder <| \v0 -> - case a v0 of - Err err1 -> Err err1 - Ok av -> - case b v0 of - Err err2 -> Err err2 - Ok bv -> - case c v0 of - Err err3 -> Err err3 - Ok cv -> - case d v0 of - Err err4 -> Err err4 - Ok dv -> - case e v0 of - Err err5 -> Err err5 - Ok ev -> - case f v0 of - Err err6 -> Err err6 - Ok fv -> - case g v0 of - Err err7 -> Err err7 - Ok gv -> - case h v0 of - Err err8 -> Err err8 - Ok hv -> Ok (func av bv cv dv ev fv gv hv) + Decoder <| + \v0 -> + case a v0 of + Err err1 -> + Err err1 + + Ok av -> + case b v0 of + Err err2 -> + Err err2 + + Ok bv -> + case c v0 of + Err err3 -> + Err err3 + + Ok cv -> + case d v0 of + Err err4 -> + Err err4 + + Ok dv -> + case e v0 of + Err err5 -> + Err err5 + + Ok ev -> + case f v0 of + Err err6 -> + Err err6 + + Ok fv -> + case g v0 of + Err err7 -> + Err err7 + + Ok gv -> + case h v0 of + Err err8 -> + Err err8 + + Ok hv -> + Ok (func av bv cv dv ev fv gv hv) @@ -391,32 +622,38 @@ map8 func (Decoder a) (Decoder b) (Decoder c) (Decoder d) (Decoder e) (Decoder f singleResult : List (Result Error a) -> Result Error (List a) singleResult = - let - each v r = - case r of - Err _ -> r - Ok vs -> - case v of - Ok vok -> Ok (vok :: vs) - Err err -> Err err - in - List.foldl each (Ok []) >> Result.map List.reverse + let + each v r = + case r of + Err _ -> + r + + Ok vs -> + case v of + Ok vok -> + Ok (vok :: vs) + + Err err -> + Err err + in + List.foldl each (Ok []) >> Result.map List.reverse find : List String -> Decoder a -> Ast.Value -> Result Error a find names decoder v0 = - case names of - name :: rest -> - case v0 of - Ast.Record_ properties -> - case Dict.get name properties of - Just v1 -> find rest decoder v1 - Nothing -> Err (Decoding <| "Expected property: " ++ name) - - _ -> - Err (Decoding "Expected record") - - [] -> - fromValue decoder v0 - - + case names of + name :: rest -> + case v0 of + Ast.Record_ properties -> + case Dict.get name properties of + Just v1 -> + find rest decoder v1 + + Nothing -> + Err (Decoding <| "Expected property: " ++ name) + + _ -> + Err (Decoding "Expected record") + + [] -> + fromValue decoder v0 From 51cf7c7346fa536db3c16f690ef5c0d1033dd5c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Dietrich?= Date: Sat, 22 Feb 2020 10:56:47 +0100 Subject: [PATCH 2/2] this additional newline solves the dead-end problem at least for some cases --- src/Yaml/Decode.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Yaml/Decode.elm b/src/Yaml/Decode.elm index f43519f..b7f0f96 100644 --- a/src/Yaml/Decode.elm +++ b/src/Yaml/Decode.elm @@ -74,7 +74,7 @@ type {-| -} fromString : Decoder a -> String -> Result Error a fromString decoder raw = - case Yaml.fromString raw of + case Yaml.fromString (raw ++ "\n") of Ok v -> fromValue decoder v