template (empty) → 0.1
raw patch · 4 files changed
+313/−0 lines, 4 filesdep +basedep +mtlbuild-type:Customsetup-changed
Dependencies added: base, mtl
Files
- LICENSE +31/−0
- Setup.lhs +3/−0
- Text/Template.hs +263/−0
- template.cabal +16/−0
+ LICENSE view
@@ -0,0 +1,31 @@+Copyright Johan Tibell 2007++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Johan Tibell nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ Text/Template.hs view
@@ -0,0 +1,263 @@+-- | A simple string substitution library that supports \"$\"-based+-- substitution. Substitution uses the following rules:+--+-- * \"$$\" is an escape; it is replaced with a single \"$\".+--+-- * \"$identifier\" names a substitution placeholder matching a+-- mapping key of \"identifier\". \"identifier\" must spell a+-- Haskell identifier. The first non-identifier character after the+-- \"$\" character terminates this placeholder specification.+--+-- * \"${identifier}\" is equivalent to \"$identifier\". It is+-- required when valid identifier characters follow the placeholder+-- but are not part of the placeholder, such as+-- \"${noun}ification\".+--+-- Any other apperance of \"$\" in the string will result in an+-- 'Prelude.error' being raised.+--+-- Here is an example of a simple substitution:+--+-- > import qualified Data.ByteString.Lazy.Char8 as B+-- > import Text.Template+-- >+-- > context = Map.fromList . map packPair+-- > where packPair (x, y) = (B.pack x, B.pack y)+-- >+-- > helloTemplate = B.pack "Hello, $name! Want some ${fruit}s?"+-- > helloContext = context [("name", "Johan"), ("fruit", "banana")]+-- >+-- > main = B.putStrLn $ substitute helloTemplate helloContext+--+-- If you render the same template multiple times it's faster to first+-- convert it to a more efficient representation using 'template' and+-- then rendering it using 'render'. In fact, all that 'substitute' does+-- is to combine these two steps.++module Text.Template+ (+ -- * The @Template@ type.+ Template, -- abstract++ -- * The @Context@ type.+ Context,+ + -- * Basic interface+ template, -- :: ByteString -> Template+ render, -- :: Template -> Context -> ByteString+ substitute, -- :: ByteString -> Context -> ByteString+ showTemplate, -- :: Template -> ByteString++ -- * I\/O with 'Template's++ -- ** Files+ readTemplate, -- :: FilePath -> IO Template+ renderToFile, -- :: FilePath -> Template -> Context -> IO ()++ -- ** I\/O with Handles+ hRender -- :: Handle -> Template -> Context -> IO ()+ ) where++import Data.ByteString.Lazy.Char8 (ByteString)+import qualified Data.ByteString.Lazy.Char8 as B+import Data.Int+import Control.Monad.State+import qualified Control.Monad.State as State+import Data.Char+import Data.Map (Map)+import qualified Data.Map as Map+import Prelude hiding (takeWhile)+import System.IO++-- -----------------------------------------------------------------------------++-- | A repesentation of a 'Data.ByteString.Lazy.Char8.ByteString'+-- template, supporting efficient rendering.+newtype Template = Template [Frag]++instance Eq Template where+ t1 == t2 = showTemplate t1 == showTemplate t2++instance Show Template where+ show = B.unpack . showTemplate++-- | Shows the template string.+showTemplate :: Template -> ByteString+showTemplate (Template fs) = B.concat $ map showFrag fs++data Frag = Lit !ByteString | Var !ByteString !Bool++instance Show Frag where+ show = B.unpack . showFrag++showFrag :: Frag -> ByteString+showFrag (Var s b) | b = B.concat [B.pack "${", s, B.pack "}"]+ | otherwise = B.concat [B.pack "$", s]+showFrag (Lit s) = B.concatMap escape s+ where escape c = case c of+ '$' -> B.pack "$$"+ c' -> B.singleton c'++-- | A mapping with keys that match the placeholders in the template.+type Context = Map ByteString ByteString++-- -----------------------------------------------------------------------------+-- Basic interface++-- | Creates a template from a template string.+template :: ByteString -> Template+template = runParser pTemplate++pTemplate :: Parser Template+pTemplate = pFrags >>= return . Template++pFrags :: Parser [Frag]+pFrags = do+ c <- peek+ case c of+ Nothing -> return []+ Just '$' -> do c' <- peekSnd+ case c' of+ Just '$' -> do Just '$' <- char+ Just '$' <- char+ continue (return $ Lit $ B.pack "$")+ _ -> continue pVar+ _ -> continue pLit+ where+ continue x = liftM2 (:) x pFrags++pLit :: Parser Frag+pLit = do+ s <- takeWhile (/= '$')+ return $ Lit s++pVar :: Parser Frag+pVar = do+ Just '$' <- char+ c <- peek+ case c of+ Just '{' -> do Just '{' <- char+ v <- pIdentifier+ c' <- peek+ case c' of+ Just '}' -> do Just '}' <- char+ return $ Var v True+ _ -> liftM parseError pos+ _ -> do v <- pIdentifier+ return $ Var v False++pIdentifier :: Parser ByteString+pIdentifier = do+ c <- peek+ case c of+ Just c' -> if isAlphaNum c'+ then takeWhile isIdentifier+ else liftM parseError pos+ Nothing -> liftM parseError pos+ where+ isIdentifier c = or [isAlphaNum c, c `elem` "_'"]++parseError :: (Int64, Int64) -> a+parseError (row, col) = error $ "Invalid placeholder in string: line " +++ show row ++ ", col " ++ show col++-- | Performs the template substitution, returning a new+-- 'Data.ByteString.Lazy.Char8.ByteString'.+--+-- If a key is not found in the context an 'Prelude.error' is raised.+render :: Template -> Context -> ByteString+render (Template frags) ctx = B.concat $ map (renderFrag ctx) frags++renderFrag :: Context -> Frag -> ByteString+renderFrag _ (Lit s) = s+renderFrag ctx (Var x _) =+ case Map.lookup x ctx of+ Just s -> s+ Nothing -> error $ "Key not found: " ++ (show $ B.unpack x)++-- | Performs the template substitution, returning a new+-- 'Data.ByteString.Lazy.Char8.ByteString'. Note that+--+-- > substitute tmpl ctx == render (template tmpl) ctx+--+-- If a key is not found in the context an 'Prelude.error' is raised.+substitute :: ByteString -> Context -> ByteString+substitute tmpl = render (template tmpl)++-- -----------------------------------------------------------------------------+-- Files++-- | Reads a template from a file lazily. Use 'text mode' on Windows to+-- interpret newlines+readTemplate :: FilePath -> IO Template+readTemplate f = (return . template) =<< B.readFile f++-- | Renders and writes a template to a file. This is more efficient+-- than first 'render'ing the template to a+-- 'Data.ByteString.Lazy.Char8.ByteString' and then writing it to a file+-- using 'Data.ByteString.Lazy.Char8.writeFile'.+renderToFile :: FilePath -> Template -> Context -> IO ()+renderToFile f tmpl = B.writeFile f . render tmpl++-- -----------------------------------------------------------------------------+-- I/O with Handles++-- | Renders and writes a template to a 'System.IO.Handle'. This is more+-- efficient than first 'render'ing the template to a+-- 'Data.ByteString.Lazy.Char8.ByteString' and then writing it to a+-- 'System.IO.Handle' using 'Data.ByteString.Lazy.Char8.hPutStr'.+hRender :: Handle -> Template -> Context -> IO ()+hRender h (Template frags) ctx = mapM_ (B.hPut h . renderFrag ctx) frags++-- -----------------------------------------------------------------------------+-- ByteString parser++type Parser = State (ByteString, Int64, Int64)+ +char :: Parser (Maybe Char)+char = do+ (s, row, col) <- get+ if B.null s+ then return Nothing+ else do c <- return $! B.head s+ case c of+ '\n' -> put (B.tail s, row + 1 :: Int64, 1 :: Int64)+ _ -> put (B.tail s, row, col + 1 :: Int64)+ return $ Just c++peek :: Parser (Maybe Char)+peek = do+ s <- get+ c <- char+ put s+ return c++peekSnd :: Parser (Maybe Char)+peekSnd = do+ s <- get+ char+ c <- char+ put s+ return c++takeWhile :: (Char -> Bool) -> Parser ByteString+takeWhile p = do+ (s, row, col) <- get+ case B.span p s of+ (x, s') -> do + let newlines = B.elemIndices '\n' x+ n = B.length x+ row' = row + fromIntegral (length newlines)+ col' = case newlines of+ [] -> col + n+ _ -> n - last newlines+ put (s', row', col')+ return x++pos :: Parser (Int64, Int64)+pos = do+ (_, row, col) <- get+ return (row, col)++runParser :: Parser a -> ByteString -> a+runParser p s = evalState p (s, 1 :: Int64, 1 :: Int64)
+ template.cabal view
@@ -0,0 +1,16 @@+Name: template+Version: 0.1+Description: Simple string substitution library that supports+ \"$\"-based substitution. Meant to be used when+ Text.Printf or string concatenation would lead to+ code that is hard to read but when a full blown+ templating system might be overkill.+Synopsis: Simple string substitution+Category: Text+License: BSD3+License-file: LICENSE+Author: Johan Tibell+Maintainer: johan.tibell@gmail.com+Build-Depends: base, mtl+Exposed-modules: Text.Template+ghc-options: -Wall -O2 -funbox-strict-fields