packages feed

language-puppet-1.5.1: src/Puppet/Runner/Erb/Evaluate.hs

-- | Private module. Evaluates a ruby template from what's generated by "Erb.Parser".
module Puppet.Runner.Erb.Evaluate
  ( rubyEvaluate,
  )
where

import qualified Data.Char as Char
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as Text
import qualified Data.Vector as V
import Erb.Ruby
import Puppet.Interpreter
import XPrelude

type ScopeName = Text

-- | Evaluate a list of ruby statements.
rubyEvaluate ::
  Container ScopeInformation ->
  ScopeName ->
  [RubyStatement] ->
  Either Doc Text
rubyEvaluate vars ctx = foldl (evalruby vars ctx) (Right "") . optimize
  where
    optimize [] = []
    optimize (Puts x : DropPrevSpace' : xs) = optimize $ DropPrevSpace (Puts x) : xs
    optimize (x : xs) = x : optimize xs

spaceNotCR :: Char -> Bool
spaceNotCR c = Char.isSpace c && c /= '\n' && c /= '\r'

evalruby :: Container ScopeInformation -> ScopeName -> Either Doc Text -> RubyStatement -> Either Doc Text
evalruby _ _ (Left err) _ = Left err
evalruby _ _ (Right _) DropPrevSpace' = Left "Could not evaluate a non optimize DropPrevSpace'"
evalruby mp ctx (Right curstr) (DropNextSpace x) =
  case evalruby mp ctx (Right curstr) x of
    Left err -> Left err
    Right y -> Right (Text.dropWhile spaceNotCR y)
evalruby mp ctx (Right curstr) (DropPrevSpace x) =
  case evalruby mp ctx (Right curstr) x of
    Left err -> Left err
    Right y -> Right (Text.dropWhileEnd spaceNotCR y)
evalruby mp ctx (Right curstr) (Puts e) =
  case evalExpression mp ctx e >>= evalValue of
    Left err -> Left err
    Right ex -> Right (curstr <> ex)

evalExpression :: Container ScopeInformation -> ScopeName -> Expression -> Either Doc PValue
evalExpression mp ctx (LookupOperation expvar expidx) = do
  val <- evalExpression mp ctx expvar
  idx <- evalExpression mp ctx expidx
  case val of
    PArray arr ->
      case idx ^? _PValueInteger of
        Nothing -> Left $ "Can't convert index to integer when resolving" <+> pretty val <> brackets (pretty idx)
        Just i ->
          if fromIntegral (V.length arr) <= i
            then Left $ "Array out of bound" <+> pretty val <> brackets (pretty idx)
            else Right (arr V.! fromIntegral i)
    PHash hs ->
      case idx of
        PString idx' ->
          case hs ^. at idx' of
            Just x' -> Right x'
            _ -> Left $ "Can't index variable" <+> pretty val <+> ", it is " <+> pretty (PHash hs)
        _ -> Left $ "Can't index variable" <+> pretty val <+> ", it is " <+> pretty (PHash hs)
    unexpectedval -> Left $ "Can't index variable" <+> pretty val <+> ", it is " <+> pretty unexpectedval
evalExpression _ _ (Value (Literal x)) = Right (PString x)
evalExpression mp ctx (ScopeObject (Value (Literal x))) = getVariable mp ctx x
evalExpression mp ctx (Object (Value (Literal x))) = do
  case Text.stripPrefix "@" x of
    Nothing -> Left $ "Erb variables '" <> ppline x <> "' should be prefixed by '@' in puppet version 4 and above."
    Just x' -> getVariable mp ctx x'
evalExpression _ _ x = Left $ "Can't evaluate" <+> pretty x

evalValue :: PValue -> Either Doc Text
evalValue = go False
  where
    go escaped p = case p of
      PString x -> Right $ if escaped then show x else x
      PNumber x -> Right (scientific2text x)
      PUndef -> Right "nil"
      PBoolean True -> Right "true"
      PBoolean False -> Right "false"
      PArray lst -> fmap (\c -> "[" <> Text.intercalate ", " c <> "]") (mapM (go True) (V.toList lst))
      PHash hash -> fmap (\l -> "{" <> Text.intercalate ", " (map (\(k, v) -> show k <> "=>" <> v) l) <> "}") (mapM (traverse (go True)) (HM.toList hash))
      _ -> Left ("Can't display the ruby equivalent of" <+> pretty p)