packages feed

yamlet-1.0.0.0: src/Yamlet/Internal/View.hs

{-# OPTIONS_HADDOCK not-home #-}

-- | The values of the nodes of a syntax tree.
--
-- This module is intended for internal use only, and may change without warning
-- in subsequent releases.
module Yamlet.Internal.View
  ( View (..)
  , view
  , scalarValue
  , describeNode
  , isNullNode
  , stringValue
  , inputText
  ) where

import Data.Maybe
import Data.Text qualified as T
import GHC.Generics

import Yamlet.Internal.Schema
import Yamlet.Internal.Syntax qualified as S
import Yamlet.Internal.Utils
import Yamlet.Value

-- | The value of a node with its tag resolved. The items and the entries of a
-- collection stay nodes of the syntax tree. A tag that the schema does not
-- know does not matter, e.g. @!secret abc@ is a string.
data View
  = NullView
  | BoolView !Bool
  | IntView !Integer
  | FloatView !FloatValue
  | StringView !T.Text
  | SequenceView ![S.Node]
  | MappingView ![(S.Node, S.Node)]
  | -- | An alias, e.g. in a node from 'Yamlet.Syntax.parseDocuments'.
    -- 'Yamlet.Decode.runParser' replaces the aliases, so a parser never sees
    -- one.
    AliasView !T.Text
  deriving stock (Eq, Show, Generic)

-- | The view of a node.
view :: S.Node -> View
view n = case n.content of
  S.ScalarContent style t -> case scalarValue n.props.tag style t of
    Null -> NullView
    Bool b -> BoolView b
    Int i -> IntView i
    Float f -> FloatView f
    _ -> StringView t
  S.SequenceContent _ xs -> SequenceView xs
  S.MappingContent _ kvs -> MappingView kvs
  S.AliasContent name -> AliasView name
-- GHC does not inline it without the pragma. Inlined, a match on the view
-- allocates no view. Without it, the decode benchmarks and the parseYaml
-- benchmarks of the derived instances allocated more.
{-# INLINE view #-}

-- | The value of a scalar with the tag and the style, without the tag.
scalarValue :: S.Tag -> S.ScalarStyle -> T.Text -> Value
scalarValue tag style t = case tag of
  S.NoTag
    | style == S.Plain -> resolvePlain t
    | otherwise -> String t
  S.NonSpecificTag -> String t
  -- 'Yamlet.Decode.runParser' rejects a value that is not valid for its tag.
  S.Tag tag' -> fromMaybe (String t) (resolveTagged tag' t)
-- Inlined, it saves little of the allocation of a decoder in the decode
-- benchmarks, but each match on 'view' gets a copy of it, and a small
-- instance grows much.
{-# NOINLINE scalarValue #-}

-- | The kind of a node in plain words, e.g. "a list".
--
-- >>> map describeNode <$> decodeText @[Node] "- [1, 2]\n- 3.5\n- ~\n- !!str 12\n"
-- Right ["a list","a floating-point number","null","a string"]
describeNode :: S.Node -> String
describeNode n = case n.content of
  S.ScalarContent style t -> describe (scalarValue n.props.tag style t)
  S.SequenceContent _ _ -> "a list"
  S.MappingContent _ _ -> "a mapping"
  S.AliasContent _ -> "an alias"

-- | The node is null.
isNullNode :: S.Node -> Bool
isNullNode n = case view n of
  NullView -> True
  _ -> False

-- | The text of a string node.
stringValue :: S.Node -> Maybe T.Text
stringValue n = case view n of
  StringView t -> Just t
  _ -> Nothing

-- | A key or an item as the input writes it, for an error: a string in
-- quotes, an alias with its @*@ and another scalar as it is. A collection
-- and an empty scalar have no text.
inputText :: S.Node -> Maybe String
inputText n = case n.content of
  S.AliasContent name -> Just ('*' : T.unpack name)
  S.ScalarContent _ t
    | Just s <- stringValue n -> Just (showText s)
    | not (T.null t) -> Just (T.unpack t)
  _ -> Nothing

-- $setup
-- >>> import Yamlet