packages feed

yaml-unscrambler (empty) → 0.1

raw patch · 19 files changed

+1502/−0 lines, 19 filesdep +QuickCheckdep +accdep +attoparsecsetup-changed

Dependencies added: QuickCheck, acc, attoparsec, attoparsec-data, attoparsec-time, base, base64, bytestring, conduit, containers, foldl, hashable, libyaml, mtl, neat-interpolation, quickcheck-instances, rerebase, scientific, selective, tasty, tasty-hunit, tasty-quickcheck, text, text-builder, time, transformers, unordered-containers, uuid, vector, yaml, yaml-unscrambler

Files

+ LICENSE view
@@ -0,0 +1,22 @@+Copyright (c) 2020 Nikita Volkov++Permission is hereby granted, free of charge, to any person+obtaining a copy of this software and associated documentation+files (the "Software"), to deal in the Software without+restriction, including without limitation the rights to use,+copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the+Software is furnished to do so, subject to the following+conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ library/YamlUnscrambler.hs view
@@ -0,0 +1,610 @@+module YamlUnscrambler+(+  -- * Execution+  parseText,+  parseByteString,+  getExpectations,+  -- * DSL+  -- ** Value+  Value,+  value,+  nullableValue,+  -- *** Helpers+  sequenceValue,+  mappingValue,+  scalarsValue,+  -- ** Scalar+  Scalar,+  stringScalar,+  nullScalar,+  boolScalar,+  scientificScalar,+  doubleScalar,+  rationalScalar,+  boundedIntegerScalar,+  unboundedIntegerScalar,+  timestampScalar,+  dayScalar,+  timeScalar,+  uuidScalar,+  binaryScalar,+  -- ** Mapping+  Mapping,+  foldMapping,+  byKeyMapping,+  -- ** Sequence+  Sequence,+  foldSequence,+  byOrderSequence,+  byKeySequence,+  -- ** String+  String,+  textString,+  enumString,+  formattedString,+  attoparsedString,+  -- ** ByKey+  ByKey,+  atByKey,+  atOneOfByKey,+  -- ** ByOrder+  ByOrder,+  fetchByOrder,+  -- * Value types+  MaxInputSize(..),+  Signed(..),+  NumeralSystem(..),+  CaseSensitive(..),+)+where++import YamlUnscrambler.Prelude hiding (String)+import YamlUnscrambler.Model+import qualified YamlUnscrambler.Err as Err+import qualified Attoparsec.Time.ByteString as AsciiAtto+import qualified Control.Foldl as Fold+import qualified Data.Attoparsec.ByteString.Char8 as AsciiAtto+import qualified Data.Attoparsec.Text as TextAtto+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Base64 as Base64+import qualified Data.HashMap.Strict as HashMap+import qualified Data.List as List+import qualified Data.Map.Strict as Map+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.UUID as UUID+import qualified Data.Vector as Vector+import qualified Data.Vector.Generic as GenericVector+import qualified Data.Yaml.Parser as Yaml+import qualified Text.Libyaml as Libyaml+import qualified YamlUnscrambler.AsciiAtto as AsciiAtto+import qualified YamlUnscrambler.CompactErrRendering as CompactErrRendering+import qualified YamlUnscrambler.Expectations as Ex+import qualified YamlUnscrambler.Util.ByteString as ByteString+import qualified YamlUnscrambler.Util.HashMap as HashMap+import qualified YamlUnscrambler.Util.Text as Text+import qualified YamlUnscrambler.Util.Vector as Vector+import qualified YamlUnscrambler.Util.Yaml as Yaml+++-- * Execution+-------------------------++parseText :: Value a -> Text -> Either Text a+parseText value =+  parseByteString value . Text.encodeUtf8++parseByteString :: Value a -> ByteString -> Either Text a+parseByteString (Value {..}) input =+  do+    Yaml.RawDoc value map <- Yaml.parseByteStringToRawDoc input+    valueParser value map & first CompactErrRendering.renderErrAtPath++{-|+Get a tree of expectations, which can then be converted into+documentation for people working with the YAML document or+into one of the spec formats (e.g., YAML Spec, JSON Spec).+-}+getExpectations :: Value a -> Ex.Value+getExpectations =+  valueExpectation+++-- *+-------------------------++data Value a =+  Value {+    valueExpectation :: Ex.Value,+    valueParser :: Yaml.YamlValue -> Yaml.AnchorMap -> Either Err.ErrAtPath a+  }+  deriving (Functor)++value :: [Scalar a] -> Maybe (Mapping a) -> Maybe (Sequence a) -> Value a+value scalars mappings sequences =+  Value expectations parse+  where+    expectations =+      Ex.Value+        scalarExpectations+        (fmap mappingExpectation mappings)+        (fmap sequenceExpectation sequences)+    scalarExpectations =+      fmap scalarExpectation scalars+    parse input anchorMap =+      case input of+        Yaml.Scalar bytes tag style _ ->+          case scalars of+            [] ->+              Left (Err.ErrAtPath [] (Err.UnexpectedScalarErr expectations))+            _ ->+              runExcept (asum (fmap parse scalars)) &+              first convErr+              where+                parse scalar =+                  except $ first (Last . Just) $ scalarParser scalar bytes tag style+                convErr (Last msg) =+                  Err.ErrAtPath [] (Err.ScalarErr scalarExpectations bytes tag style msg)+        Yaml.Mapping input _ ->+          case mappings of+            Just mapping ->+              mappingParser mapping input anchorMap+            Nothing ->+              Left (Err.ErrAtPath [] (Err.UnexpectedMappingErr expectations))+        Yaml.Sequence input _ ->+          case sequences of+            Just sequence ->+              sequenceParser sequence input anchorMap+            Nothing ->+              Left (Err.ErrAtPath [] (Err.UnexpectedSequenceErr expectations))+        Yaml.Alias anchorName ->+          case Map.lookup anchorName anchorMap of+            Just value ->+              parse value anchorMap+            Nothing ->+              Left (Err.ErrAtPath [] (Err.UnknownAnchorErr (fromString anchorName)))++nullableValue :: [Scalar a] -> Maybe (Mapping a) -> Maybe (Sequence a) -> Value (Maybe a)+nullableValue scalars mappings sequences =+  value+    ((nullScalar Nothing) : fmap (fmap Just) scalars)+    (fmap (fmap Just) mappings)+    (fmap (fmap Just) sequences)++-- ** Helpers+-------------------------++sequenceValue :: Sequence a -> Value a+sequenceValue sequence =+  value [] Nothing (Just sequence)++mappingValue :: Mapping a -> Value a+mappingValue mapping =+  value [] (Just mapping) Nothing++scalarsValue :: [Scalar a] -> Value a+scalarsValue scalars =+  value scalars Nothing Nothing+++-- *+-------------------------++data Scalar a =+  Scalar {+    scalarExpectation :: Ex.Scalar,+    scalarParser :: ByteString -> Libyaml.Tag -> Libyaml.Style -> Either Text a+  }+  deriving (Functor)++bytesParsingScalar :: Ex.Scalar -> (ByteString -> Either Text a) -> Scalar a+bytesParsingScalar expectation parser =+  Scalar expectation (\ bytes _ _ -> parser bytes)++attoparsedScalar :: Ex.Scalar -> AsciiAtto.Parser a -> Scalar a+attoparsedScalar expectation parser =+  bytesParsingScalar expectation $+  first (const "") . AsciiAtto.parseOnly (parser <* AsciiAtto.endOfInput)++sizedScalar :: MaxInputSize -> Scalar a -> Scalar a+sizedScalar (MaxInputSize maxInputSize) (Scalar {..}) =+  Scalar scalarExpectation $ \ bytes tag style ->+    if ByteString.length bytes <= maxInputSize+      then+        scalarParser bytes tag style+      else+        Left ("Input is longer then the expected maximum of " <> showAsText maxInputSize <> " bytes")++stringScalar :: String a -> Scalar a+stringScalar (String exp parse) =+  bytesParsingScalar+    (Ex.StringScalar exp)+    (\ bytes -> first showAsText (Text.decodeUtf8' bytes) >>= parse)++nullScalar :: a -> Scalar a+nullScalar a =+  Scalar Ex.NullScalar $ \ bytes tag _ ->+    if+      tag == Libyaml.NullTag ||+      ByteString.null bytes ||+      bytes == "~" ||+      ByteString.saysNullInCiAscii bytes+      then+        Right a+      else+        Left "Not null"++boolScalar :: Scalar Bool+boolScalar =+  bytesParsingScalar Ex.BoolScalar $ \ bytes ->+    if ByteString.length bytes <= 5+      then let+        lowercased =+          ByteString.lowercaseInAscii bytes+        in if elem lowercased ["y", "yes", "on", "true", "t", "1"]+          then+            return True+          else if elem lowercased ["n", "no", "off", "false", "f", "0"]+            then+              return False+            else+              Left "Not a boolean"+      else+        Left "Not a boolean"++scientificScalar :: Scalar Scientific+scientificScalar =+  attoparsedScalar Ex.ScientificScalar AsciiAtto.scientific++doubleScalar :: Scalar Double+doubleScalar =+  attoparsedScalar Ex.DoubleScalar AsciiAtto.double++rationalScalar :: MaxInputSize -> Scalar Rational+rationalScalar a =+  sizedScalar a $+  attoparsedScalar (Ex.RationalScalar a) AsciiAtto.rational++{-|+E.g., 'Int', 'Int64', 'Word', but not 'Integer'.+-}+boundedIntegerScalar :: (Integral a, FiniteBits a) => Signed -> NumeralSystem -> Scalar a+boundedIntegerScalar a b =+  attoparsedScalar (Ex.BoundedIntegerScalar a b) (AsciiAtto.integralScalar a b)++unboundedIntegerScalar :: MaxInputSize -> Signed -> NumeralSystem -> Scalar Integer+unboundedIntegerScalar a b c =+  sizedScalar a $+  attoparsedScalar (Ex.UnboundedIntegerScalar a b c) (AsciiAtto.integralScalar b c)++timestampScalar :: Scalar UTCTime+timestampScalar =+  attoparsedScalar Ex.Iso8601TimestampScalar AsciiAtto.utcTimeInISO8601++dayScalar :: Scalar Day+dayScalar =+  attoparsedScalar Ex.Iso8601DayScalar AsciiAtto.dayInISO8601++timeScalar :: Scalar TimeOfDay+timeScalar =+  attoparsedScalar Ex.Iso8601TimeScalar AsciiAtto.timeOfDayInISO8601++uuidScalar :: Scalar UUID+uuidScalar =+  bytesParsingScalar Ex.UuidScalar $ \ bytes ->+    case UUID.fromASCIIBytes bytes of+      Just uuid ->+        return uuid+      Nothing ->+        Left "Invalid UUID"++binaryScalar :: Scalar ByteString+binaryScalar =+  bytesParsingScalar Ex.Base64BinaryScalar $ \ bytes ->+    let+      bytesWithoutNewlines =+        ByteString.filter (/= 10) bytes+      in case Base64.decodeBase64 bytesWithoutNewlines of+        Right res ->+          return res+        Left err ->+          Left err+++-- *+-------------------------++data Mapping a =+  Mapping {+    mappingExpectation :: Ex.Mapping,+    mappingParser :: [(Text, Yaml.YamlValue)] -> Yaml.AnchorMap -> Either Err.ErrAtPath a+  }+  deriving (Functor)++foldMapping :: (key -> val -> assoc) -> Fold assoc a -> String key -> Value val -> Mapping a+foldMapping zip (Fold foldStep foldInit foldExtract) key val =+  Mapping+    (Ex.MonomorphicMapping (stringExpectation key) (valueExpectation val))+    parser+  where+    parser input anchorMap =+      foldM step foldInit input &+      fmap foldExtract+      where+        step state (keyInput, valInput) =+          do+            parsedKey <- first keyErr (stringParser key keyInput)+            parsedVal <- first (Err.atSegment keyInput) (valueParser val valInput anchorMap)+            return $! foldStep state (zip parsedKey parsedVal)+          where+            keyErr =+              Err.ErrAtPath [] .+              Err.KeyErr (stringExpectation key) keyInput++byKeyMapping :: CaseSensitive -> ByKey Text a -> Mapping a+byKeyMapping caseSensitive byKey =+  Mapping expectation parser+  where+    expectation =+      Ex.ByKeyMapping caseSensitive (byKeyExpectation byKey)+    parser input =+      either Left (first keysErr) . runExceptT . parser+      where+        parser =+          if coerce caseSensitive+            then let+              map =+                HashMap.fromList input+              lookup k =+                HashMap.lookup k map+              lookupFirst kl =+                HashMap.lookupFirst kl map+              in byKeyParser byKey id lookup lookupFirst+            else let+              map =+                HashMap.fromList (fmap (first Text.toLower) input)+              lookup k =+                HashMap.lookup (Text.toLower k) map+              lookupFirst kl =+                HashMap.lookupFirst (fmap Text.toLower kl) map+              in byKeyParser byKey id lookup lookupFirst+        keysErr keys =+          Err.ErrAtPath [] $+          Err.NoneOfMappingKeysFoundErr (byKeyExpectation byKey) caseSensitive keysAvail (toList keys)+          where+            keysAvail =+              fmap fst input+++-- *+-------------------------++data Sequence a =+  Sequence {+    sequenceExpectation :: Ex.Sequence,+    sequenceParser :: [Yaml.YamlValue] -> Yaml.AnchorMap -> Either Err.ErrAtPath a+  }+  deriving (Functor)++foldSequence :: Fold a b -> Value a -> Sequence b+foldSequence (Fold foldStep foldInit foldExtract) value =+  Sequence+    (Ex.MonomorphicSequence (valueExpectation value))+    parser+  where+    parser input anchorMap =+      foldM step (0 :: Int, foldInit) input &+      fmap (foldExtract . snd)+      where+        step (!index, !state) input =+          valueParser value input anchorMap &+          first (Err.atSegment (showAsText index)) &+          fmap (\ a -> (succ index, foldStep state a))++byOrderSequence :: ByOrder a -> Sequence a+byOrderSequence (ByOrder {..}) =+  Sequence+    (Ex.ByOrderSequence byOrderExpectation)+    parser+    where+      parser input anchorMap =+        runExceptT (runReaderT (evalStateT byOrderParser (0, input)) anchorMap) &+        either Left (first mapErr)+        where+          mapErr =+            \ case+              NotEnoughElementsByOrderErr a ->+                Err.ErrAtPath [] $+                Err.NotEnoughElementsErr byOrderExpectation a++byKeySequence :: ByKey Int a -> Sequence a+byKeySequence (ByKey {..}) =+  Sequence expectation parser+  where+    expectation =+      Ex.ByKeySequence byKeyExpectation+    parser input =+      let+        vector =+          Vector.fromList input+        lookup k =+          vector Vector.!? k+        lookupFirst kl =+          Vector.lookupFirst kl vector+        in \ anchorMap ->+          runExceptT (byKeyParser showAsText lookup lookupFirst anchorMap) &+          either Left (first keysErr)+      where+        keysErr keys =+          Err.ErrAtPath [] $+          Err.NoneOfSequenceKeysFoundErr byKeyExpectation (toList keys)+++-- *+-------------------------++data String a =+  String {+    stringExpectation :: Ex.String,+    stringParser :: Text -> Either Text a+  }+  deriving (Functor)++textString :: String Text+textString =+  String Ex.AnyString return++enumString :: CaseSensitive -> [(Text, a)] -> String a+enumString (CaseSensitive caseSensitive) assocList =+  String expectation parser+  where+    expectation =+      Ex.OneOfString (CaseSensitive caseSensitive) (fmap fst assocList)+    {-# NOINLINE lookup #-}+    lookup =+      if length assocList > 512+        then if caseSensitive+          then let+            hashMap =+              HashMap.fromList assocList+            in flip HashMap.lookup hashMap+          else let+            hashMap =+              HashMap.fromList (fmap (first Text.toLower) assocList)+            in flip HashMap.lookup hashMap . Text.toLower+        else if caseSensitive+          then+            flip List.lookup assocList+          else+            flip List.lookup (fmap (first Text.toLower) assocList) . Text.toLower+    parser text =+      case lookup text of+        Just a -> return a+        _ -> Left "Unexpected value"++formattedString :: Text -> (Text -> Either Text a) -> String a+formattedString format parser =+  String+    (Ex.FormattedString format)+    parser++attoparsedString :: Text -> TextAtto.Parser a -> String a+attoparsedString format parser =+  String+    (Ex.FormattedString format)+    (first fromString . TextAtto.parseOnly parser)+++-- *+-------------------------++data ByKey key a =+  ByKey {+    byKeyExpectation :: Ex.ByKey key,+    byKeyParser ::+      (key -> Text) ->+      (key -> Maybe Yaml.YamlValue) ->+      ([key] -> Maybe (key, Yaml.YamlValue)) ->+      Yaml.AnchorMap ->+      ExceptT (Acc key) (Either Err.ErrAtPath) a+  }+  deriving (Functor)++instance Applicative (ByKey key) where+  pure =+    ByKey Ex.AnyByKey . const . const . const . const . pure+  (<*>) (ByKey le lp) (ByKey re rp) =+    ByKey+      (Ex.BothByKey le re)+      (\ a b c d -> lp a b c d <*> rp a b c d)++instance Selective (ByKey key) where+  select (ByKey le lp) (ByKey re rp) =+    ByKey+      (Ex.BothByKey le re)+      (\ a b c d -> select (lp a b c d) (rp a b c d))++instance Alternative (ByKey key) where+  empty =+    ByKey+      Ex.NoByKey+      (const (const (const (const empty))))+  (<|>) (ByKey le lp) (ByKey re rp) =+    ByKey+      (Ex.EitherByKey le re)+      (\ a b c d -> lp a b c d <|> rp a b c d)++atByKey :: key -> Value a -> ByKey key a+atByKey key valueSpec =+  ByKey+    (Ex.LookupByKey [key] (valueExpectation valueSpec))+    parser+  where+    parser renderKey lookup _ env =+      case lookup key of+        Just val ->+          lift $ first (Err.atSegment (renderKey key)) $+          valueParser valueSpec val env+        Nothing ->+          throwE (pure key)++atOneOfByKey :: [key] -> Value a -> ByKey key a+atOneOfByKey keys valueSpec =+  ByKey+    (Ex.LookupByKey keys (valueExpectation valueSpec))+    parser+  where+    parser renderKey _ lookup env =+      case lookup keys of+        Just (key, val) ->+          lift $ first (Err.atSegment (renderKey key)) $+          valueParser valueSpec val env+        Nothing ->+          throwE (fromList keys)+++-- *+-------------------------++data ByOrderErr =+  NotEnoughElementsByOrderErr+    Int++data ByOrder a =+  ByOrder {+    byOrderExpectation :: Ex.ByOrder,+    byOrderParser :: StateT (Int, [Yaml.YamlValue]) (ReaderT Yaml.AnchorMap (ExceptT ByOrderErr (Either Err.ErrAtPath))) a+  }+  deriving (Functor)++instance Applicative ByOrder where+  pure =+    ByOrder Ex.AnyByOrder . pure+  (<*>) (ByOrder le lp) (ByOrder re rp) =+    ByOrder+      (Ex.BothByOrder le re)+      (lp <*> rp)++instance Selective ByOrder where+  select (ByOrder le lp) (ByOrder re rp) =+    ByOrder+      (Ex.BothByOrder le re)+      (select lp rp)++fetchByOrder :: Value a -> ByOrder a+fetchByOrder value =+  ByOrder+    (Ex.FetchByOrder (valueExpectation value))+    parser+  where+    parser =+      do+        (!offset, list) <- get+        case list of+          h : t ->+            do+              put (succ offset, t)+              anchorMap <- ask+              lift $ lift $ lift $ first (Err.atSegment (showAsText offset)) $ valueParser value h anchorMap+          _ ->+            throwError $ NotEnoughElementsByOrderErr offset
+ library/YamlUnscrambler/AsciiAtto.hs view
@@ -0,0 +1,23 @@+module YamlUnscrambler.AsciiAtto+where++import YamlUnscrambler.Prelude+import YamlUnscrambler.Model+import Data.Attoparsec.ByteString.Char8+import Attoparsec.Time.ByteString+++integralScalar :: (Integral a, Bits a) => Signed -> NumeralSystem -> Parser a+integralScalar (Signed isSigned) numeralSystem =+  if isSigned+    then+      signed numeralParser+    else+      numeralParser+  where+    numeralParser =+      case numeralSystem of+        DecimalNumeralSystem ->+          decimal+        HexadecimalNumeralSystem ->+          hexadecimal
+ library/YamlUnscrambler/CompactErrRendering.hs view
@@ -0,0 +1,128 @@+module YamlUnscrambler.CompactErrRendering+(+  renderErrAtPath,+)+where++import YamlUnscrambler.Prelude hiding (intercalate)+import YamlUnscrambler.Model+import Text.Builder+import qualified YamlUnscrambler.Err as Err+import qualified YamlUnscrambler.Expectations as Ex+import qualified Data.Text.Encoding as Text+import qualified Data.Text as Text+++renderErrAtPath :: Err.ErrAtPath -> Text+renderErrAtPath =+  run . errAtPath++path a =+  "/" <> intercalate "/" (fmap text a)++errAtPath (Err.ErrAtPath a b) =+  "Error at path " <> path a <> ". " <> reason b++reason =+  \ case+    Err.KeyErr a b c ->+      text c <> ". On input: " <> string (show b) <> ". " <>+      "Expecting: " <> stringExpectation a+    Err.NoneOfMappingKeysFoundErr a b c d ->+      "None of keys found " <> caseSensitively b <> ": " <> string (show d) <> ". " <>+      "Keys available: " <> string (show c)+    Err.NoneOfSequenceKeysFoundErr a b ->+      "None of indices found: " <> string (show b)+    Err.ScalarErr a b c d e ->+      foldMap (\ a -> text a <> ". ") (mfilter (not . Text.null) e) <>+      "Expecting one of the following formats: " <>+      intercalate ", " (fmap scalarExpectation a) <>+      foldMap (\ a -> ". Got input: " <> string (show a)) (Text.decodeUtf8' b)+    Err.UnexpectedScalarErr a ->+      "Unexpected scalar value"+    Err.UnexpectedMappingErr a ->+      "Unexpected mapping value"+    Err.UnexpectedSequenceErr a ->+      "Unexpected sequence value"+    Err.UnknownAnchorErr a ->+      "Unknown anchor: " <> text a+    Err.NotEnoughElementsErr a b ->+      "Not enough elements: " <> decimal b <> ". " <>+      "Expecting: " <> byOrderExpectation a++scalarExpectation =+  \ case+    Ex.StringScalar a ->+      stringExpectation a+    Ex.NullScalar ->+      "null"+    Ex.BoolScalar ->+      "boolean"+    Ex.ScientificScalar ->+      "scientific"+    Ex.DoubleScalar ->+      "double"+    Ex.RationalScalar a ->+      "rational of maximum length of " <> maxInputSize a <> " chars"+    Ex.BoundedIntegerScalar a b ->+      signed a <> " " <> numeralSystem b+    Ex.UnboundedIntegerScalar a b c ->+      signed b <> " " <> numeralSystem c <> " of maximum length of " <> maxInputSize a <> " chars"+    Ex.Iso8601TimestampScalar ->+      "timestamp in ISO-8601"+    Ex.Iso8601DayScalar ->+      "date in ISO-8601"+    Ex.Iso8601TimeScalar ->+      "time in ISO-8601"+    Ex.UuidScalar ->+      "UUID"+    Ex.Base64BinaryScalar ->+      "binary data in Base-64"++stringExpectation =+  \ case+    Ex.AnyString ->+      "any string"+    Ex.OneOfString a b ->+      "one of " <> string (show b) <> "(" <> caseSensitive a <> ")"+    Ex.FormattedString a ->+      text a++byOrderExpectation =+  decimal . count 0+  where+    count !a =+      \ case+        Ex.AnyByOrder ->+          a+        Ex.BothByOrder b c ->+          countBoth a b c+        Ex.FetchByOrder _ ->+          succ a+    countBoth a b c =+      case b of+        Ex.BothByOrder d e ->+          countBoth a d (Ex.BothByOrder e c)+        Ex.AnyByOrder ->+          count a c+        Ex.FetchByOrder _ ->+          count (succ a) c++caseSensitive (CaseSensitive a) =+  "case-" <> bool "insensitive" "sensitive" a++caseSensitively (CaseSensitive a) =+  "case-" <> bool "insensitively" "sensitively" a++signed (Signed a) =+  bool "unsigned" "signed" a++numeralSystem =+  \ case+    DecimalNumeralSystem ->+      "decimal"+    HexadecimalNumeralSystem ->+      "hexadecimal"++maxInputSize (MaxInputSize a) =+  decimal a
+ library/YamlUnscrambler/Err.hs view
@@ -0,0 +1,78 @@+module YamlUnscrambler.Err+(+  ErrAtPath(..),+  Err(..),+  atSegment,+  -- *+  MaxInputSize(..),+  Signed(..),+  NumeralSystem(..),+  CaseSensitive(..),+  -- * Reexports from \"libyaml\"+  Libyaml.Tag(..),+  Libyaml.Style(..),+)+where++import YamlUnscrambler.Prelude hiding (String)+import YamlUnscrambler.Model+import qualified YamlUnscrambler.Expectations as Ex+import qualified Text.Libyaml as Libyaml+import qualified Text.Builder as TextBuilder+++data ErrAtPath =+  ErrAtPath [Text] Err++data Err =+  KeyErr+    Ex.String+    {-^ Key expectation. -}+    Text+    {-^ Key input. -}+    Text+    {-^ String parsing error. -}+    |+  NoneOfMappingKeysFoundErr+    (Ex.ByKey Text)+    CaseSensitive+    [Text]+    {-^ Available keys. -}+    [Text]+    {-^ Keys looked up. -}+    |+  NoneOfSequenceKeysFoundErr+    (Ex.ByKey Int)+    [Int]+    |+  ScalarErr+    [Ex.Scalar]+    {-^ Expected formats. -}+    ByteString+    {-^ Input. -}+    Libyaml.Tag+    {-^ Tag. -}+    Libyaml.Style+    {-^ Style. -}+    (Maybe Text)+    {-^ Last error. -}+    |+  UnexpectedScalarErr+    Ex.Value+    |+  UnexpectedMappingErr+    Ex.Value+    |+  UnexpectedSequenceErr+    Ex.Value+    |+  UnknownAnchorErr+    Text+    |+  NotEnoughElementsErr+    Ex.ByOrder+    Int++atSegment :: Text -> ErrAtPath -> ErrAtPath+atSegment seg (ErrAtPath path err) =+  ErrAtPath (seg : path) err
+ library/YamlUnscrambler/Expectations.hs view
@@ -0,0 +1,115 @@+module YamlUnscrambler.Expectations+(+  Value(..),+  Scalar(..),+  Mapping(..),+  Sequence(..),+  String(..),+  ByKey(..),+  ByOrder(..),+  -- *+  MaxInputSize(..),+  Signed(..),+  NumeralSystem(..),+  CaseSensitive(..),+)+where++import YamlUnscrambler.Prelude hiding (String)+import YamlUnscrambler.Model+import qualified YamlUnscrambler.Util.Maybe as Maybe+++-- *+-------------------------++data Value =+  Value+    [Scalar]+    (Maybe Mapping)+    (Maybe Sequence)++data Scalar =+  StringScalar String+    |+  NullScalar+    |+  BoolScalar+    |+  ScientificScalar+    |+  DoubleScalar+    |+  RationalScalar MaxInputSize+    |+  BoundedIntegerScalar Signed NumeralSystem+    |+  UnboundedIntegerScalar MaxInputSize Signed NumeralSystem+    |+  Iso8601TimestampScalar+    |+  Iso8601DayScalar+    |+  Iso8601TimeScalar+    |+  UuidScalar+    |+  Base64BinaryScalar++data Mapping =+  MonomorphicMapping String Value+    |+  ByKeyMapping CaseSensitive (ByKey Text)++data Sequence =+  MonomorphicSequence Value+    |+  ByOrderSequence ByOrder+    |+  ByKeySequence (ByKey Int)++-- *+-------------------------++data String =+  {-| Any string as it is. -}+  AnyString+    |+  {-| One of options. Suitable for enumerations. -}+  OneOfString CaseSensitive [Text] {-^ Options. -}+    |+  {-| Must conform to a textually described format. -}+  FormattedString Text {-^ Description of the format. -}++data ByKey key =+  AnyByKey+    |+  NoByKey+    |+  EitherByKey (ByKey key) (ByKey key)+    |+  BothByKey (ByKey key) (ByKey key)+    |+  LookupByKey [key] {-^ Keys to lookup. -} Value++data ByOrder =+  AnyByOrder+    |+  BothByOrder ByOrder ByOrder+    |+  FetchByOrder Value+++-- *+-------------------------++instance Semigroup Value where+  (<>) (Value lScalars lMappings lSequences) (Value rScalars rMappings rSequences) =+    Value+      (lScalars <> rScalars)+      (Maybe.firstNonEmpty lMappings rMappings)+      (Maybe.firstNonEmpty lSequences rSequences)++instance Monoid Value where+  mempty =+    Value [] Nothing Nothing
+ library/YamlUnscrambler/Model.hs view
@@ -0,0 +1,24 @@+module YamlUnscrambler.Model+where++import YamlUnscrambler.Prelude+++{-|+Specification of the maximum allowed length for the input.+A safety measure to ensure that the parser doesn't exhaust memory+when parsing to unlimited datatypes.+-}+newtype MaxInputSize =+  MaxInputSize Int++newtype Signed =+  Signed Bool++data NumeralSystem =+  DecimalNumeralSystem+    |+  HexadecimalNumeralSystem++newtype CaseSensitive =+  CaseSensitive Bool
+ library/YamlUnscrambler/Prelude.hs view
@@ -0,0 +1,149 @@+module YamlUnscrambler.Prelude+( +  module Exports,+  showAsText,+)+where++-- base+-------------------------+import Control.Applicative as Exports+import Control.Arrow as Exports hiding (first, second)+import Control.Category as Exports+import Control.Concurrent as Exports+import Control.Exception as Exports+import Control.Monad as Exports hiding (fail, mapM_, sequence_, forM_, msum, mapM, sequence, forM)+import Control.Monad.IO.Class as Exports+import Control.Monad.Fail as Exports+import Control.Monad.Fix as Exports hiding (fix)+import Control.Monad.ST as Exports+import Data.Bifunctor as Exports+import Data.Bits as Exports+import Data.Bool as Exports+import Data.Char as Exports+import Data.Coerce as Exports+import Data.Complex as Exports+import Data.Data as Exports+import Data.Dynamic as Exports+import Data.Either as Exports+import Data.Fixed as Exports+import Data.Foldable as Exports hiding (toList)+import Data.Function as Exports hiding (id, (.))+import Data.Functor as Exports+import Data.Functor.Compose as Exports+import Data.Functor.Contravariant as Exports+import Data.Int as Exports+import Data.IORef as Exports+import Data.Ix as Exports+import Data.List as Exports hiding (sortOn, isSubsequenceOf, uncons, concat, foldr, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, find, maximumBy, minimumBy, mapAccumL, mapAccumR, foldl')+import Data.List.NonEmpty as Exports (NonEmpty(..))+import Data.Maybe as Exports+import Data.Monoid as Exports hiding (Alt)+import Data.Ord as Exports+import Data.Proxy as Exports+import Data.Ratio as Exports+import Data.STRef as Exports+import Data.String as Exports+import Data.Traversable as Exports+import Data.Tuple as Exports+import Data.Unique as Exports+import Data.Version as Exports+import Data.Void as Exports+import Data.Word as Exports+import Debug.Trace as Exports+import Foreign.ForeignPtr as Exports+import Foreign.Ptr as Exports+import Foreign.StablePtr as Exports+import Foreign.Storable as Exports+import GHC.Conc as Exports hiding (orElse, withMVar, threadWaitWriteSTM, threadWaitWrite, threadWaitReadSTM, threadWaitRead)+import GHC.Exts as Exports (IsList(..), lazy, inline, sortWith, groupWith)+import GHC.Generics as Exports (Generic)+import GHC.IO.Exception as Exports+import Numeric as Exports+import Prelude as Exports hiding (fail, concat, foldr, mapM_, sequence_, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, mapM, sequence, id, (.))+import System.Environment as Exports+import System.Exit as Exports+import System.IO as Exports (Handle, hClose)+import System.IO.Error as Exports+import System.IO.Unsafe as Exports+import System.Mem as Exports+import System.Mem.StableName as Exports+import System.Timeout as Exports+import Text.ParserCombinators.ReadP as Exports (ReadP, ReadS, readP_to_S, readS_to_P)+import Text.ParserCombinators.ReadPrec as Exports (ReadPrec, readPrec_to_P, readP_to_Prec, readPrec_to_S, readS_to_Prec)+import Text.Printf as Exports (printf, hPrintf)+import Text.Read as Exports (Read(..), readMaybe, readEither)+import Unsafe.Coerce as Exports++-- selective+-------------------------+import Control.Selective as Exports++-- text+-------------------------+import Data.Text as Exports (Text)++-- bytestring+-------------------------+import Data.ByteString as Exports (ByteString)++-- vector+-------------------------+import Data.Vector as Exports (Vector)++-- hashable+-------------------------+import Data.Hashable as Exports (Hashable)++-- containers+-------------------------+import Data.Map.Strict as Exports (Map)++-- unordered-containers+-------------------------+import Data.HashSet as Exports (HashSet)+import Data.HashMap.Strict as Exports (HashMap)++-- uuid+-------------------------+import Data.UUID as Exports (UUID)++-- foldl+-------------------------+import Control.Foldl as Exports (Fold(..))++-- transformers+-------------------------+import Control.Monad.IO.Class as Exports+import Control.Monad.Trans.Class as Exports+import Control.Monad.Trans.Cont as Exports hiding (shift, callCC)+import Control.Monad.Trans.Except as Exports (ExceptT(ExceptT), Except, except, runExcept, runExceptT, mapExcept, mapExceptT, withExcept, withExceptT, throwE, catchE)+import Control.Monad.Trans.Maybe as Exports+import Control.Monad.Trans.Reader as Exports (Reader, runReader, mapReader, withReader, ReaderT(ReaderT), runReaderT, mapReaderT, withReaderT)+import Control.Monad.Trans.State.Strict as Exports (State, runState, evalState, execState, mapState, withState, StateT(StateT), runStateT, evalStateT, execStateT, mapStateT, withStateT)+import Control.Monad.Trans.Writer.Strict as Exports (Writer, runWriter, execWriter, mapWriter, WriterT(..), execWriterT, mapWriterT)++-- mtl+-------------------------+import Control.Monad.Cont.Class as Exports+import Control.Monad.Error.Class as Exports hiding (Error(..))+import Control.Monad.Reader.Class as Exports+import Control.Monad.State.Class as Exports+import Control.Monad.Writer.Class as Exports++-- scientific+-------------------------+import Data.Scientific as Exports (Scientific)++-- time+-------------------------+import Data.Time as Exports+import Data.Time.Clock.System as Exports+import Data.Time.Clock.POSIX as Exports++-- acc+-------------------------+import Acc as Exports (Acc)++showAsText :: Show a => a -> Text+showAsText = show >>> fromString
+ library/YamlUnscrambler/Util/ByteString.hs view
@@ -0,0 +1,19 @@+module YamlUnscrambler.Util.ByteString+where++import YamlUnscrambler.Prelude hiding (map, length)+import Data.ByteString+import qualified YamlUnscrambler.Util.Word8 as Word8+++lowercaseInAscii =+  map Word8.lowercaseInAscii++{-# NOINLINE lowercaseNullInAscii #-}+lowercaseNullInAscii :: ByteString+lowercaseNullInAscii =+  "null"++saysNullInCiAscii :: ByteString -> Bool+saysNullInCiAscii a =+  length a == 4 && lowercaseInAscii a == lowercaseNullInAscii
+ library/YamlUnscrambler/Util/HashMap.hs view
@@ -0,0 +1,10 @@+module YamlUnscrambler.Util.HashMap+where++import YamlUnscrambler.Prelude hiding (lookup)+import Data.HashMap.Strict+++lookupFirst :: (Hashable k, Eq k) => [k] -> HashMap k v -> Maybe (k, v)+lookupFirst keys map =+  getFirst (foldMap (\ k -> First (fmap (k,) (lookup k map))) keys)
+ library/YamlUnscrambler/Util/List.hs view
@@ -0,0 +1,11 @@+module YamlUnscrambler.Util.List+where++import YamlUnscrambler.Prelude hiding (lookup)+++firstNonEmpty :: [a] -> [a] -> [a]+firstNonEmpty a b =+  case a of+    [] -> b+    _ -> a
+ library/YamlUnscrambler/Util/Maybe.hs view
@@ -0,0 +1,11 @@+module YamlUnscrambler.Util.Maybe+where++import YamlUnscrambler.Prelude hiding (lookup)+++firstNonEmpty :: Maybe a -> Maybe a -> Maybe a+firstNonEmpty a b =+  case a of+    Just a -> Just a+    _ -> b
+ library/YamlUnscrambler/Util/Text.hs view
@@ -0,0 +1,18 @@+module YamlUnscrambler.Util.Text+where++import YamlUnscrambler.Prelude+import qualified Data.Attoparsec.Text as Atto+import qualified Attoparsec.Data as Atto+++deshowIfPossible :: Text -> Text+deshowIfPossible a =+  either (const a) id (deshow a)++deshow :: Text -> Either Text Text+deshow =+  first fromString . Atto.parseOnly parser+  where+    parser =+      Atto.show <* Atto.endOfInput
+ library/YamlUnscrambler/Util/Vector.hs view
@@ -0,0 +1,10 @@+module YamlUnscrambler.Util.Vector+where++import YamlUnscrambler.Prelude hiding (lookup)+import Data.Vector+++lookupFirst :: [Int] -> Vector a -> Maybe (Int, a)+lookupFirst keys vector =+  getFirst (foldMap (\ k -> First (fmap (k,) (vector !? k))) keys)
+ library/YamlUnscrambler/Util/Word8.hs view
@@ -0,0 +1,14 @@+module YamlUnscrambler.Util.Word8+where++import YamlUnscrambler.Prelude+++lowercaseInAscii :: Word8 -> Word8+lowercaseInAscii a =+  if+    65 <= a && a <= 90 ||+    192 <= a && a <= 214 ||+    216 <= a && a <= 222+    then a + 32+    else a
+ library/YamlUnscrambler/Util/Yaml.hs view
@@ -0,0 +1,18 @@+module YamlUnscrambler.Util.Yaml+where++import YamlUnscrambler.Prelude+import qualified Conduit+import qualified Data.Yaml as Yaml+import qualified Data.Yaml.Parser as YamlParser+import qualified Data.Text.Encoding as Text+import qualified Text.Libyaml as Libyaml+++parseByteStringToRawDoc :: ByteString -> Either Text YamlParser.RawDoc+parseByteStringToRawDoc input =+  first showAsText $+  unsafePerformIO $+  try @SomeException $+  Conduit.runConduitRes $+  Conduit.fuse (Libyaml.decode input) (YamlParser.sinkRawDoc)
+ test/Main.hs view
@@ -0,0 +1,160 @@+module Main where++import Prelude hiding (assert)+import GHC.Exts (fromList)+import Test.QuickCheck.Instances+import Test.Tasty+import Test.Tasty.Runners+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck+import qualified YamlUnscrambler as U+import qualified Test.QuickCheck as QuickCheck+import qualified Control.Foldl as Fold+import qualified NeatInterpolation as NeatInterpolation+import qualified Data.Text as Text+++main =+  defaultMain $ +  testGroup "All tests" [+    testCase "Should fail on sequence when no sequence is specified" $ let+      unscrambler =+        U.value [] (Just mapping) Nothing+        where+          mapping =+            U.foldMapping (,) Fold.list U.textString value+            where+              value =+                U.value [nullScalar, intScalar] Nothing Nothing+                where+                  nullScalar =+                    U.nullScalar Nothing+                  intScalar =+                    fmap Just $ U.boundedIntegerScalar @Int (U.Signed True) U.DecimalNumeralSystem+      input =+        [NeatInterpolation.text|+          a: 2+          b: 3+          c:+            - 1+            - 2+          |]+      in case U.parseText unscrambler input of+        Right res ->+          assertFailure (show res)+        Left failure ->+          assertEqual (Text.unpack failure) "Error at path /c. Unexpected sequence value" failure+    ,+    testCase "Domain sum-type regression" $ let+      unscrambler =+        doc+        where+          doc =+            U.mappingValue $+            U.byKeyMapping (U.CaseSensitive True) $+            asum [+              Just <$> U.atByKey "sums" sum,+              pure Nothing+              ]+          sum =+            U.mappingValue $+            U.foldMapping (,) Fold.list U.textString sumVariant+          sumVariant =+            U.mappingValue $+            U.foldMapping (,) Fold.list U.textString params+          params =+            U.value [nullScalar, intScalar] Nothing Nothing+            where+              nullScalar =+                U.nullScalar Nothing+              intScalar =+                fmap Just $ U.boundedIntegerScalar @Int (U.Signed True) U.DecimalNumeralSystem+      input =+        [NeatInterpolation.text|+          sums:+            A:+              a:+                - Int+                - Bool+              b: Char, Double+          |]+      in case U.parseText unscrambler input of+        Right res ->+          assertFailure (show res)+        Left failure ->+          assertEqual "" "Error at path /sums/A/a. Unexpected sequence value" failure+    ,+    testCase "Domain sum-type correct" $ let+      unscrambler =+        doc+        where+          doc =+            U.mappingValue $+            U.byKeyMapping (U.CaseSensitive True) $+            asum [+              Just <$> U.atByKey "sums" sum,+              pure Nothing+              ]+          sum =+            U.mappingValue $+            U.foldMapping (,) Fold.list U.textString sumVariant+          sumVariant =+            U.mappingValue $+            U.foldMapping (,) Fold.list U.textString params+          params =+            U.value [nullScalar, valueScalar] Nothing Nothing+            where+              nullScalar =+                U.nullScalar Nothing+              valueScalar =+                fmap Just $ U.stringScalar U.textString+      input =+        [NeatInterpolation.text|+          sums:+            A:+              a: Text+              b: Int+          |]+      in case U.parseText unscrambler input of+        Right res ->+          assertEqual "" (Just [("A", [("a", Just "Text"), ("b", Just "Int")])]) res+        Left failure ->+          assertFailure (Text.unpack failure)+    ,+    testCase "Scalar errors are readable" $ let+      unscrambler =+        doc+        where+          doc =+            U.mappingValue $+            U.byKeyMapping (U.CaseSensitive True) $+            asum [+              Just <$> U.atByKey "sums" sum,+              pure Nothing+              ]+          sum =+            U.mappingValue $+            U.foldMapping (,) Fold.list U.textString sumVariant+          sumVariant =+            U.mappingValue $+            U.foldMapping (,) Fold.list U.textString params+          params =+            U.value [nullScalar, intScalar] Nothing Nothing+            where+              nullScalar =+                U.nullScalar Nothing+              intScalar =+                fmap Just $ U.boundedIntegerScalar @Int (U.Signed True) U.DecimalNumeralSystem+      input =+        [NeatInterpolation.text|+          sums:+            A:+              a: Int+              b: Char+          |]+      in case U.parseText unscrambler input of+        Right res ->+          assertFailure (show res)+        Left failure ->+          assertEqual "" "Error at path /sums/A/a. Expecting one of the following formats: null, signed decimal. Got input: \"Int\"" failure+    ]
+ yaml-unscrambler.cabal view
@@ -0,0 +1,80 @@+name: yaml-unscrambler+version: 0.1+synopsis: Flexible declarative YAML parsing toolkit+stability: Experimental+homepage: https://github.com/nikita-volkov/yaml-unscrambler+bug-reports: https://github.com/nikita-volkov/yaml-unscrambler/issues+author: Nikita Volkov <nikita.y.volkov@mail.ru>+maintainer: Nikita Volkov <nikita.y.volkov@mail.ru>+copyright: (c) 2020 Nikita Volkov+license: MIT+license-file: LICENSE+build-type: Simple+cabal-version: >=1.10++source-repository head+  type: git+  location: git://github.com/nikita-volkov/yaml-unscrambler.git++library+  hs-source-dirs: library+  default-extensions: BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples, ViewPatterns+  default-language: Haskell2010+  exposed-modules:+    YamlUnscrambler+    YamlUnscrambler.Expectations+  other-modules:+    YamlUnscrambler.AsciiAtto+    YamlUnscrambler.CompactErrRendering+    YamlUnscrambler.Err+    YamlUnscrambler.Model+    YamlUnscrambler.Prelude+    YamlUnscrambler.Util.ByteString+    YamlUnscrambler.Util.Maybe+    YamlUnscrambler.Util.HashMap+    YamlUnscrambler.Util.List+    YamlUnscrambler.Util.Yaml+    YamlUnscrambler.Util.Word8+    YamlUnscrambler.Util.Text+    YamlUnscrambler.Util.Vector+  build-depends:+    acc >=0.1.0.2 && <0.2,+    attoparsec >=0.13 && <0.14,+    attoparsec-data >=1.0.5 && <1.1,+    attoparsec-time >=1.0.1.1 && <1.1,+    base >=4.11 && <5,+    base64 >=0.4.2.2 && <0.5,+    bytestring >=0.10 && <0.11,+    conduit >=1.3.2 && <1.4,+    containers >=0.6.2 && <0.7,+    foldl >=1.4 && <2,+    hashable >=1 && <2,+    libyaml >=0.1.2 && <0.2,+    mtl >=2.2 && <3,+    scientific >=0.3.6.2 && <0.4,+    selective >=0.4 && <0.5,+    text >=1 && <2,+    text-builder >=0.6.6.1 && <0.7,+    time >=1.9 && <2,+    transformers >=0.5 && <0.6,+    unordered-containers >=0.2.10 && <0.3,+    uuid >=1.3 && <2,+    vector >=0.12 && <0.13,+    yaml >=0.11.5 && <0.12++test-suite test+  type: exitcode-stdio-1.0+  hs-source-dirs: test+  default-extensions: BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples, ViewPatterns+  default-language: Haskell2010+  main-is: Main.hs+  build-depends:+    foldl >=1.4.9 && <2,+    neat-interpolation >=0.5.1.2 && <0.6,+    QuickCheck >=2.8.1 && <3,+    quickcheck-instances >=0.3.11 && <0.4,+    rerebase >=1.9 && <2,+    tasty >=0.12 && <2,+    tasty-hunit >=0.9 && <0.11,+    tasty-quickcheck >=0.9 && <0.11,+    yaml-unscrambler