{-#LANGUAGE FlexibleContexts #-} {-#LANGUAGE FlexibleInstances #-} {-#LANGUAGE OverloadedStrings #-} {-#LANGUAGE TupleSections #-} {-#LANGUAGE TypeSynonymInstances #-} {-#LANGUAGE MultiParamTypeClasses #-} {-#LANGUAGE ScopedTypeVariables #-} module Text.Ginger.Run.FuncUtils where import Prelude ( (.), ($), (==), (/=) , (>), (<), (>=), (<=) , (+), (-), (*), (/), div, (**), (^) , (||), (&&) , (++) , Show, show , undefined, otherwise , Maybe (..) , Bool (..) , Int, Integer, String , fromIntegral, floor, round , not , show , uncurry , seq , fst, snd , maybe , Either (..) , id ) import qualified Prelude import Data.Maybe (fromMaybe, isJust) import qualified Data.List as List import Text.Ginger.AST import Text.Ginger.Html import Text.Ginger.GVal import Text.Ginger.Run.Type import Text.Printf import Text.PrintfA import Data.Scientific (formatScientific) import Data.Text (Text) import Data.String (fromString) import qualified Data.Text as Text import qualified Data.ByteString.UTF8 as UTF8 import Control.Monad import Control.Monad.Identity import Control.Monad.Writer import Control.Monad.Reader import Control.Monad.State import Control.Applicative import qualified Data.HashMap.Strict as HashMap import Data.HashMap.Strict (HashMap) import Data.Scientific (Scientific) import Data.Scientific as Scientific import Data.Default (def) import Safe (readMay, lastDef, headMay) import Network.HTTP.Types (urlEncode) import Debug.Trace (trace) import Data.Maybe (isNothing) import Data.List (lookup, zipWith, unzip) unaryFunc :: forall m h p. (Monad m) => (GVal (Run p m h) -> GVal (Run p m h)) -> Function (Run p m h) unaryFunc f [] = do warn $ ArgumentsError Nothing "expected exactly one argument (zero given)" return def unaryFunc f ((_, x):[]) = return (f x) unaryFunc f ((_, x):_) = do warn $ ArgumentsError Nothing "expected exactly one argument (more given)" return (f x) binaryFunc :: forall m h p. (Monad m) => (GVal (Run p m h) -> GVal (Run p m h) -> GVal (Run p m h)) -> Function (Run p m h) binaryFunc f [] = do warn $ ArgumentsError Nothing "expected exactly two arguments (zero given)" return def binaryFunc f (_:[]) = do warn $ ArgumentsError Nothing "expected exactly two arguments (one given)" return def binaryFunc f ((_, x):(_, y):[]) = return (f x y) binaryFunc f ((_, x):(_, y):_) = do warn $ ArgumentsError Nothing "expected exactly two arguments (more given)" return (f x y) ignoreArgNames :: ([a] -> b) -> ([(c, a)] -> b) ignoreArgNames f args = f (Prelude.map snd args) variadicNumericFunc :: Monad m => Scientific -> ([Scientific] -> Scientific) -> [(Maybe Text, GVal (Run p m h))] -> Run p m h (GVal (Run p m h)) variadicNumericFunc zero f args = return . toGVal . f $ args' where args' :: [Scientific] args' = Prelude.map (fromMaybe zero . asNumber . snd) args unaryNumericFunc :: Monad m => Scientific -> (Scientific -> Scientific) -> [(Maybe Text, GVal (Run p m h))] -> Run p m h (GVal (Run p m h)) unaryNumericFunc zero f args = return . toGVal . f $ args' where args' :: Scientific args' = case args of [] -> 0 (arg:_) -> fromMaybe zero . asNumber . snd $ arg variadicStringFunc :: Monad m => ([Text] -> Text) -> [(Maybe Text, GVal (Run p m h))] -> Run p m h (GVal (Run p m h)) variadicStringFunc f args = return . toGVal . f $ args' where args' :: [Text] args' = Prelude.map (asText . snd) args -- | Match args according to a given arg spec, Python style. -- The return value is a triple of @(matched, args, kwargs, unmatchedNames)@, -- where @matches@ is a hash map of named captured arguments, args is a list of -- remaining unmatched positional arguments, kwargs is a list of remaining -- unmatched named arguments, and @unmatchedNames@ contains the argument names -- that haven't been matched. extractArgs :: [Text] -> [(Maybe Text, a)] -> (HashMap Text a, [a], HashMap Text a, [Text]) extractArgs argNames args = let (matchedPositional, argNames', args') = matchPositionalArgs argNames args (matchedKeyword, argNames'', args'') = matchKeywordArgs argNames' args' unmatchedPositional = [ a | (Nothing, a) <- args'' ] unmatchedKeyword = HashMap.fromList [ (k, v) | (Just k, v) <- args'' ] in ( HashMap.fromList (matchedPositional ++ matchedKeyword) , unmatchedPositional , unmatchedKeyword , argNames'' ) where matchPositionalArgs :: [Text] -> [(Maybe Text, a)] -> ([(Text, a)], [Text], [(Maybe Text, a)]) matchPositionalArgs [] args = ([], [], args) matchPositionalArgs names [] = ([], names, []) matchPositionalArgs names@(n:ns) allArgs@((anm, arg):args) | Just n == anm || isNothing anm = let (matched, ns', args') = matchPositionalArgs ns args in ((n, arg):matched, ns', args') | otherwise = ([], names, allArgs) matchKeywordArgs :: [Text] -> [(Maybe Text, a)] -> ([(Text, a)], [Text], [(Maybe Text, a)]) matchKeywordArgs [] args = ([], [], args) matchKeywordArgs names allArgs@((Nothing, arg):args) = let (matched, ns', args') = matchKeywordArgs names args in (matched, ns', (Nothing, arg):args') matchKeywordArgs names@(n:ns) args = case (lookup (Just n) args) of Nothing -> let (matched, ns', args') = matchKeywordArgs ns args in (matched, n:ns', args') Just v -> let args' = [ (k,v) | (k,v) <- args, k /= Just n ] (matched, ns', args'') = matchKeywordArgs ns args' in ((n,v):matched, ns', args'') -- | Parse argument list into type-safe argument structure. extractArgsT :: ([Maybe a] -> b) -> [Text] -> [(Maybe Text, a)] -> Either ([a], HashMap Text a, [Text]) b extractArgsT f argNames args = let (matchedMap, freeArgs, freeKwargs, unmatched) = extractArgs argNames args in if List.null freeArgs && HashMap.null freeKwargs then Right (f $ fmap (\name -> HashMap.lookup name matchedMap) argNames) else Left (freeArgs, freeKwargs, unmatched) -- | Parse argument list into flat list of matched arguments. extractArgsL :: [Text] -> [(Maybe Text, a)] -> Either ([a], HashMap Text a, [Text]) [Maybe a] extractArgsL = extractArgsT id extractArgsDefL :: [(Text, a)] -> [(Maybe Text, a)] -> Either ([a], HashMap Text a, [Text]) [a] extractArgsDefL argSpec args = let (names, defs) = unzip argSpec in injectDefaults defs <$> extractArgsL names args injectDefaults :: [a] -> [Maybe a] -> [a] injectDefaults = zipWith fromMaybe