packages feed

hydra-0.15.0: src/main/haskell/Hydra/Yaml/Serde.hs

-- Note: this is an automatically generated file. Do not edit.

-- | Native YAML serialization: YAML Node to String

module Hydra.Yaml.Serde where

import qualified Hydra.Lib.Chars as Chars
import qualified Hydra.Lib.Equality as Equality
import qualified Hydra.Lib.Lists as Lists
import qualified Hydra.Lib.Literals as Literals
import qualified Hydra.Lib.Logic as Logic
import qualified Hydra.Lib.Maps as Maps
import qualified Hydra.Lib.Maybes as Maybes
import qualified Hydra.Lib.Pairs as Pairs
import qualified Hydra.Lib.Strings as Strings
import qualified Hydra.Yaml.Model as Model
import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
import qualified Data.Scientific as Sci

-- | Escape single quotes by doubling them
escapeSingleQuotes :: String -> String
escapeSingleQuotes s =

      let squote = 39
      in (Strings.fromList (Lists.bind (Strings.toList s) (\c -> Logic.ifElse (Equality.equal c squote) [
        squote,
        squote] [
        c])))

-- | Check if a string has leading or trailing whitespace
hasLeadingTrailingSpace :: String -> Bool
hasLeadingTrailingSpace s =

      let chars = Strings.toList s
      in (Logic.or (Maybes.fromMaybe False (Maybes.map (\c -> Chars.isSpace c) (Lists.maybeHead chars))) (Maybes.fromMaybe False (Maybes.map (\c -> Chars.isSpace c) (Lists.maybeLast chars))))

-- | Serialize a YAML node to a string
hydraYamlToString :: Model.Node -> String
hydraYamlToString node = writeNode node

-- | Indent all lines of a string by 2 spaces
indentString :: String -> String
indentString s =
    Strings.cat (Lists.map (\line -> Logic.ifElse (Strings.null line) "" (Strings.cat [
      "  ",
      line,
      "\n"])) (Strings.lines s))

-- | Check if character codes represent a decimal number
isDecimalString :: [Int] -> Bool
isDecimalString chars =

      let dotCode = 46
          parts = Lists.span (\c -> Logic.not (Equality.equal c dotCode)) chars
          before = Pairs.first parts
          afterWithDot = Pairs.second parts
      in (Logic.ifElse (Lists.null before) False (Logic.ifElse (Lists.null afterWithDot) False (
        let after = Lists.drop 1 afterWithDot
        in (Logic.ifElse (Lists.null after) False (
          let isDigitFn = \c -> Logic.and (Equality.gte c 48) (Equality.lte c 57)
          in (Logic.and (Lists.null (Lists.filter (\c -> Logic.not (isDigitFn c)) before)) (Lists.null (Lists.filter (\c -> Logic.not (isDigitFn c)) after))))))))

-- | Check if a string looks like a number
looksLikeNumber :: String -> Bool
looksLikeNumber s =

      let chars = Strings.toList s
      in (Maybes.fromMaybe False (Maybes.map (\p ->
        let firstCh = Pairs.first p
            tailCh = Pairs.second p
            rest = Logic.ifElse (Equality.equal firstCh 45) tailCh chars
            isDigitFn = \c -> Logic.and (Equality.gte c 48) (Equality.lte c 57)
            allDigits = Logic.and (Logic.not (Lists.null rest)) (Lists.null (Lists.filter (\c -> Logic.not (isDigitFn c)) rest))
        in (Logic.ifElse allDigits True (isDecimalString rest))) (Lists.uncons chars)))

-- | Check if a string needs quoting in YAML
needsQuoting :: String -> Bool
needsQuoting s =
    Logic.ifElse (Strings.null s) True (Logic.ifElse (Lists.elem s yamlReservedWords) True (Logic.ifElse (looksLikeNumber s) True (
      let chars = Strings.toList s
          specials = Strings.toList yamlSpecialChars
          hasSpecial = Logic.not (Lists.null (Lists.filter (\c -> Lists.elem c specials) chars))
      in (Logic.ifElse hasSpecial True (hasLeadingTrailingSpace s)))))

-- | Write a mapping entry in block style
writeMappingEntry :: (Model.Node, Model.Node) -> String
writeMappingEntry entry =

      let key = Pairs.first entry
          value = Pairs.second entry
      in case value of
        Model.NodeScalar v0 -> Strings.cat [
          writeNodeInline key,
          ": ",
          (writeScalar v0),
          "\n"]
        Model.NodeSequence v0 -> Logic.ifElse (Lists.null v0) (Strings.cat [
          writeNodeInline key,
          ": []\n"]) (Strings.cat [
          writeNodeInline key,
          ":\n",
          (indentString (writeNode value))])
        Model.NodeMapping v0 -> Logic.ifElse (Equality.equal (Maps.size v0) 0) (Strings.cat [
          writeNodeInline key,
          ": {}\n"]) (Strings.cat [
          writeNodeInline key,
          ":\n",
          (indentString (writeNode value))])

-- | Write a mapping entry for the first item of a sequence element
writeMappingEntryInline :: (Model.Node, Model.Node) -> String
writeMappingEntryInline entry =

      let key = Pairs.first entry
          value = Pairs.second entry
      in case value of
        Model.NodeScalar v0 -> Strings.cat [
          writeNodeInline key,
          ": ",
          (writeScalar v0),
          "\n"]
        Model.NodeSequence v0 -> Logic.ifElse (Lists.null v0) (Strings.cat [
          writeNodeInline key,
          ": []\n"]) (Strings.cat [
          writeNodeInline key,
          ":\n",
          (indentString (writeNode value))])
        Model.NodeMapping v0 -> Logic.ifElse (Equality.equal (Maps.size v0) 0) (Strings.cat [
          writeNodeInline key,
          ": {}\n"]) (Strings.cat [
          writeNodeInline key,
          ":\n",
          (indentString (writeNode value))])

-- | Write a YAML node as a top-level value in block style
writeNode :: Model.Node -> String
writeNode node =
    case node of
      Model.NodeScalar v0 -> Strings.cat2 (writeScalar v0) "\n"
      Model.NodeSequence v0 -> Logic.ifElse (Lists.null v0) "[]\n" (Strings.cat (Lists.map (\item -> writeSequenceItem item) v0))
      Model.NodeMapping v0 -> Logic.ifElse (Equality.equal (Maps.size v0) 0) "{}\n" (Strings.cat (Lists.map (\e -> writeMappingEntry e) (Maps.toList v0)))

-- | Write a node inline (for use as a mapping key)
writeNodeInline :: Model.Node -> String
writeNodeInline node =
    case node of
      Model.NodeScalar v0 -> writeScalar v0
      Model.NodeSequence v0 -> Strings.cat [
        "[",
        (Strings.intercalate ", " (Lists.map (\item -> writeNodeInline item) v0)),
        "]"]
      Model.NodeMapping v0 ->
        let writeFlowEntry =
                \e -> Strings.cat [
                  writeNodeInline (Pairs.first e),
                  ": ",
                  (writeNodeInline (Pairs.second e))]
        in (Strings.cat [
          "{",
          (Strings.intercalate ", " (Lists.map writeFlowEntry (Maps.toList v0))),
          "}"])

-- | Write a scalar value
writeScalar :: Model.Scalar -> String
writeScalar s =
    case s of
      Model.ScalarBool v0 -> Logic.ifElse v0 "true" "false"
      Model.ScalarDecimal v0 -> Literals.showDecimal v0
      Model.ScalarFloat v0 -> Literals.showBigfloat v0
      Model.ScalarInt v0 -> Literals.showBigint v0
      Model.ScalarNull -> "null"
      Model.ScalarStr v0 -> writeString v0

-- | Write a sequence item in block style
writeSequenceItem :: Model.Node -> String
writeSequenceItem node =
    case node of
      Model.NodeScalar v0 -> Strings.cat [
        "- ",
        (writeScalar v0),
        "\n"]
      Model.NodeSequence v0 -> Logic.ifElse (Lists.null v0) "- []\n" (Strings.cat2 "-\n" (indentString (writeNode node)))
      Model.NodeMapping v0 -> Logic.ifElse (Equality.equal (Maps.size v0) 0) "- {}\n" (
        let entries = Maps.toList v0
        in (Maybes.fromMaybe "" (Maybes.map (\p ->
          let firstEntry = Pairs.first p
              restEntries = Pairs.second p
              firstStr = writeMappingEntryInline firstEntry
              restStr = Strings.cat (Lists.map (\e -> writeMappingEntry e) restEntries)
          in (Strings.cat [
            "- ",
            firstStr,
            (indentString restStr)])) (Lists.uncons entries))))

-- | Write a string value, quoting if necessary
writeString :: String -> String
writeString s =
    Logic.ifElse (needsQuoting s) (Strings.cat [
      "'",
      (escapeSingleQuotes s),
      "'"]) s

-- | YAML reserved words that need quoting
yamlReservedWords :: [String]
yamlReservedWords =
    [
      "true",
      "false",
      "null",
      "~",
      "yes",
      "no",
      "on",
      "off",
      "True",
      "False",
      "Null",
      "Yes",
      "No",
      "On",
      "Off",
      "TRUE",
      "FALSE",
      "NULL",
      "YES",
      "NO",
      "ON",
      "OFF"]

-- | YAML special characters that trigger quoting
yamlSpecialChars :: String
yamlSpecialChars = ": {}[]#,&*!|>'\"%@`"