ginger 0.8.4.0 → 0.8.4.1
raw patch · 6 files changed
+214/−97 lines, 6 filesdep +optparse-applicativedep ~aesondep ~aeson-prettydep ~bytestringPVP: minor bump suggested
API additions: PVP suggests at least a minor version bump
Dependencies added: optparse-applicative
Dependency ranges changed: aeson, aeson-pretty, bytestring, data-default, mtl, process, tasty, tasty-hunit, tasty-quickcheck, text, time, transformers, utf8-string, vector, yaml
API changes (from Hackage documentation)
+ Text.Ginger.Run.Builtins: gfnLength :: Monad m => Function (Run p m h)
Files
- cli/GingerCLI.hs +85/−66
- cli/Options.hs +69/−0
- ginger.cabal +33/−26
- src/Text/Ginger/Run.hs +1/−1
- src/Text/Ginger/Run/Builtins.hs +12/−0
- test/Text/Ginger/SimulationTests.hs +14/−4
cli/GingerCLI.hs view
@@ -19,6 +19,7 @@ import System.IO import System.IO.Error import qualified Data.ByteString as BS+import qualified Data.ByteString.UTF8 as UTF8 import qualified Data.ByteString.Lazy as LBS import Control.Monad.Trans.Class ( lift ) import Control.Monad.Trans.Maybe@@ -26,7 +27,85 @@ import Data.Default ( def ) import System.Process as Process import Text.Printf (printf)+import Options (parseOptions, Options (..), TemplateSource (..), DataSource (..))+import System.Exit +main :: IO ()+main = do+ args <- getArgs+ options <- parseOptions args+ case options of+ RunOptions tpl dat ->+ run tpl dat++loadData :: DataSource -> IO (Maybe (HashMap Text JSON.Value))+loadData (DataFromFile fn) = decodeFile fn+loadData DataFromStdin = decodeStdin+loadData (DataLiteral str) = decodeString str++loadTemplate :: TemplateSource -> IO (Template SourcePos)+loadTemplate tplSrc = do+ let resolve = loadFileMay+ (tpl, src) <- case tplSrc of+ TemplateFromFile fn -> (,) <$> parseGingerFile resolve fn <*> return Nothing+ TemplateFromStdin -> getContents >>= \s -> (,) <$> parseGinger resolve Nothing s <*> return (Just s)++ case tpl of+ Left err -> do+ tplSource <-+ case src of+ Just s ->+ return (Just s)+ Nothing -> do+ let s = sourceName <$> peSourcePosition err+ case s of+ Nothing -> return Nothing+ Just sn -> Just <$> loadFile sn+ printParserError tplSource err+ exitFailure+ Right t -> do+ return t+++run :: TemplateSource -> DataSource -> IO ()+run tplSrc dataSrc = do+ scope <- loadData dataSrc+ let scopeLookup key = toGVal (scope >>= HashMap.lookup key)+ let contextLookup :: Text -> Run p IO Html (GVal (Run p IO Html))+ contextLookup key =+ case key of+ "print" -> return printF+ "system" -> return systemF+ _ -> return $ scopeLookup key+ let context =+ makeContextHtmlExM+ contextLookup+ (putStr . Text.unpack . htmlSource)+ (hPutStrLn stderr . show)++ tpl <- loadTemplate tplSrc+ runGingerT context tpl >>= either (hPutStrLn stderr . show) showOutput+ where+ showOutput value+ | isNull value = return ()+ | otherwise = putStrLn . show $ value+++printParserError :: Maybe String -> ParserError -> IO ()+printParserError srcMay = putStrLn . formatParserError srcMay++displayParserError :: String -> ParserError -> IO ()+displayParserError src pe = do+ case peSourcePosition pe of+ Just pos -> do+ let ln = Prelude.take 1 . Prelude.drop (sourceLine pos - 1) . Prelude.lines $ src+ case ln of+ [] -> return ()+ x:_ -> do+ putStrLn x+ putStrLn $ Prelude.replicate (sourceColumn pos - 1) ' ' ++ "^"+ _ -> return ()+ loadFile fn = openFile fn ReadMode >>= hGetContents loadFileMay fn =@@ -40,6 +119,12 @@ decodeFile :: (JSON.FromJSON v) => FilePath -> IO (Maybe v) decodeFile fn = YAML.decode <$> (openFile fn ReadMode >>= BS.hGetContents) +decodeString :: (JSON.FromJSON v) => String -> IO (Maybe v)+decodeString = return . YAML.decode . UTF8.fromString++decodeStdin :: (JSON.FromJSON v) => IO (Maybe v)+decodeStdin = YAML.decode <$> BS.getContents+ printF :: GVal (Run p IO Html) printF = fromFunction $ go where@@ -66,69 +151,3 @@ strToGVal :: String -> GVal m strToGVal = toGVal . Text.pack -main = do- args <- getArgs- let (srcFn, scopeFn) = case args of- [] -> (Nothing, Nothing)- a:[] -> (Just a, Nothing)- a:b:[] -> (Just a, Just b)-- scope <- case scopeFn of- Nothing -> return Nothing- Just fn -> (decodeFile fn :: IO (Maybe (HashMap Text JSON.Value)))-- let scopeLookup key = toGVal (scope >>= HashMap.lookup key)- resolve = loadFileMay- let contextLookup :: Text -> Run p IO Html (GVal (Run p IO Html))- contextLookup key =- case key of- "print" -> return printF- "system" -> return systemF- _ -> return $ scopeLookup key-- (tpl, src) <- case srcFn of- Just fn -> (,) <$> parseGingerFile resolve fn <*> return Nothing- Nothing -> getContents >>= \s -> (,) <$> parseGinger resolve Nothing s <*> return (Just s)-- -- TODO: do some sort of arg parsing thing so that we can turn- -- template dumping on or off.- -- print tpl-- case tpl of- Left err -> do- tplSource <-- case src of- Just s ->- return (Just s)- Nothing -> do- let s = sourceName <$> peSourcePosition err- case s of- Nothing -> return Nothing- Just sn -> Just <$> loadFile sn- printParserError tplSource err- Right t -> do- let context =- makeContextHtmlExM- contextLookup- (putStr . Text.unpack . htmlSource)- (hPutStrLn stderr . show)- runGingerT context t >>= either (hPutStrLn stderr . show) showOutput- where- showOutput value- | isNull value = return ()- | otherwise = putStrLn . show $ value--printParserError :: Maybe String -> ParserError -> IO ()-printParserError srcMay = putStrLn . formatParserError srcMay--displayParserError :: String -> ParserError -> IO ()-displayParserError src pe = do- case peSourcePosition pe of- Just pos -> do- let ln = Prelude.take 1 . Prelude.drop (sourceLine pos - 1) . Prelude.lines $ src- case ln of- [] -> return ()- x:_ -> do- putStrLn x- putStrLn $ Prelude.replicate (sourceColumn pos - 1) ' ' ++ "^"- _ -> return ()
+ cli/Options.hs view
@@ -0,0 +1,69 @@+module Options+where++import Options.Applicative+++data TemplateSource+ = TemplateFromFile FilePath+ | TemplateFromStdin++data DataSource+ = DataFromFile FilePath+ | DataLiteral String+ | DataFromStdin++data Options+ = RunOptions TemplateSource DataSource++parseOptions :: [String] -> IO Options+parseOptions args =+ execParser $ info (options <**> helper)+ ( fullDesc+ <> header "ginger - A command-line interface for the Ginger template language"+ )++options :: Parser Options+options = runOptions++runOptions :: Parser Options+runOptions =+ RunOptions <$> templateSource <*> dataSource++templateSource :: Parser TemplateSource+templateSource =+ convert <$> option str+ ( long "template"+ <> short 't'+ <> metavar "TEMPLATE"+ <> help "Load ginger template from this file"+ <> value "-"+ )+ where+ convert "-" = TemplateFromStdin+ convert f = TemplateFromFile f++dataSource :: Parser DataSource+dataSource =+ dataFromFile <|> dataLiteral++dataFromFile :: Parser DataSource+dataFromFile =+ convert <$> option str+ ( long "data-file"+ <> metavar "DATAFILE"+ <> help "Load JSON or YAML data from this file (`-' to read from stdin)"+ )+ where+ convert "-" = DataFromStdin+ convert f = DataFromFile f++dataLiteral :: Parser DataSource+dataLiteral =+ DataLiteral <$> option str+ ( long "data"+ <> short 'd'+ <> metavar "DATA"+ <> help "Use specified (JSON or YAML) DATA"+ <> value "{}"+ )
ginger.cabal view
@@ -2,11 +2,11 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: ginger-version: 0.8.4.0+version: 0.8.4.1 synopsis: An implementation of the Jinja2 template language in Haskell description: Ginger is Jinja, minus the most blatant pythonisms. Wants to be feature complete, but isn't quite there yet.-homepage: https://bitbucket.org/tdammers/ginger+homepage: https://ginger.tobiasdammers.nl/ license: MIT license-file: LICENSE author: Tobias Dammers@@ -20,6 +20,10 @@ cabal-version: >=1.10 bug-reports: https://github.com/tdammers/ginger/issues +source-repository head+ type: git+ location: https://github.com/tdammers/ginger+ library exposed-modules: Text.Ginger , Text.Ginger.AST@@ -36,9 +40,9 @@ -- other-modules: -- other-extensions: build-depends: base >=4.8 && <5- , aeson- , aeson-pretty- , bytestring+ , aeson >=1.4.2.0 && <1.5+ , aeson-pretty >=0.8.7 && <0.9+ , bytestring >=0.10.8.2 && <0.11 , data-default >= 0.5 , filepath >= 1.3 , http-types >= 0.8 && (< 0.11 || >= 0.12)@@ -47,12 +51,12 @@ , regex-tdfa >=1.2.3 && <=1.3 , safe >= 0.3 , scientific >= 0.3- , text+ , text >=1.2.3.1 && <1.3 , time >= 0.1.6.0 , transformers >= 0.3 , unordered-containers >= 0.2.5- , utf8-string- , vector+ , utf8-string >=1.0.1.1 && <1.1+ , vector >=0.12.0.2 && <0.13 if !impl(ghc >= 8.0) build-depends: semigroups == 0.18.* hs-source-dirs: src@@ -60,18 +64,21 @@ executable ginger main-is: GingerCLI.hs+ other-modules: Options hs-source-dirs: cli default-language: Haskell2010 build-depends: base >= 4.8 && <5- , bytestring- , data-default >= 0.5 , ginger- , aeson- , process- , text- , transformers+ , aeson >=1.4.2.0 && <1.5+ , bytestring >=0.10.8.2 && <0.11+ , data-default >= 0.5+ , optparse-applicative >=0.14.3.0 && <0.15+ , process >=1.6.5.0 && <1.7+ , text >=1.2.3.1 && <1.3+ , transformers >=0.5.6.2 && <0.6 , unordered-containers >= 0.2.5- , yaml+ , utf8-string >=1.0.1.1 && <1.1+ , yaml >=0.11.0.0 && <0.12 test-suite tests type: exitcode-stdio-1.0@@ -82,15 +89,15 @@ default-language: Haskell2010 build-depends: base >=4.8 && <5 , ginger- , aeson- , bytestring- , data-default- , mtl- , tasty- , tasty-hunit- , tasty-quickcheck- , text- , time- , transformers+ , aeson >=1.4.2.0 && <1.5+ , bytestring >=0.10.8.2 && <0.11+ , data-default >=0.5+ , mtl >=2.2.2 && <2.3+ , tasty >=1.2.1 && <1.3+ , tasty-hunit >=0.10.0.1 && <0.11+ , tasty-quickcheck >=0.10 && <0.11+ , text >=1.2.3.1 && <1.3+ , time >= 0.1.6.0+ , transformers >=0.5.6.2 && <0.6 , unordered-containers >= 0.2.5- , utf8-string+ , utf8-string >=1.0.1.1 && <1.1
src/Text/Ginger/Run.hs view
@@ -166,7 +166,7 @@ , ("is_lt", fromFunction gfnLess) , ("iterable", fromFunction . unaryFunc $ toGVal . (\x -> isList x || isDict x)) , ("json", fromFunction gfnJSON)- , ("length", fromFunction . unaryFunc $ toGVal . length)+ , ("length", fromFunction gfnLength) , ("le", fromFunction gfnLessEquals) , ("less", fromFunction gfnLess) , ("lessthan", fromFunction gfnLess)
src/Text/Ginger/Run/Builtins.hs view
@@ -272,6 +272,18 @@ gfnCenter ((_, s):(_, w):(_, pad):_) = return . toGVal $ center (asText s) (fromMaybe 80 $ Prelude.truncate <$> asNumber w) (asText pad) +gfnLength :: Monad m => Function (Run p m h)+gfnLength [] =+ return def+gfnLength ((_,x):_) =+ case (asDictItems x, asList x) of+ (Nothing, Nothing) ->+ return . toGVal . Text.length . asText $ x+ (Just items, _) ->+ return . toGVal . Prelude.length $ items+ (_, Just items) ->+ return . toGVal . Prelude.length $ items+ gfnSlice :: Monad m => Function (Run p m h) gfnSlice args = let argValues =
test/Text/Ginger/SimulationTests.hs view
@@ -745,10 +745,20 @@ -- TODO -- \"iterable\" -- TODO- , testCase "\"length\"" $ do- mkTestHtml [] []- "{{ [1,2,3]|length }}"- "3"+ , testGroup "\"length\""+ [ testCase "list" $ do+ mkTestHtml [] []+ "{{ [1,2,3]|length }}"+ "3"+ , testCase "dict" $ do+ mkTestHtml [] []+ "{{ {'foo':'bar', 'baz':'quux'}|length }}"+ "2"+ , testCase "string" $ do+ mkTestHtml [] []+ "{{ 'foo'|length }}"+ "3"+ ] -- \"modulo\" -- \"num\" -- TODO