packages feed

threepenny-gui 0.4.0.2 → 0.4.1.0

raw patch · 20 files changed

+173/−115 lines, 20 filesdep +aesondep −jsondep ~bytestringdep ~text

Dependencies added: aeson

Dependencies removed: json

Dependency ranges changed: bytestring, text

Files

CHANGELOG.md view
@@ -1,8 +1,19 @@ ## Changelog for the `threepenny-gui` package -**0.4.0.1** -- Bugfix release.+**0.4.1.0** -- Maintenance release. +* Dependency on `text` package now from version 0.11 to 1.1.*.+* Dependency on `aeson` package replaces the former dependency on the `json` package.+* Unicode characters are now transmitted correctly to the browser. #75, #62.+* Change default port number to 8023. #64++**0.4.0.2** -- Bugfix release.+ * Fix CSS bug for `grid` function.++**0.4.0.1** -- Maintenance release.++* Adjust package dependencies.  **0.4.0.0** -- Snapshot release. 
samples/BarTab.hs view
@@ -11,7 +11,7 @@  -- | Main entry point. main :: IO ()-main = startGUI defaultConfig { tpPort = 10000 } setup+main = startGUI defaultConfig setup  setup :: Window -> UI () setup w = do
samples/Buttons.hs view
@@ -13,10 +13,7 @@ main :: IO () main = do     static <- getStaticDir-    startGUI defaultConfig-        { tpPort       = 10000-        , tpStatic     = Just static-        } setup+    startGUI defaultConfig { tpStatic = Just static } setup  setup :: Window -> UI () setup w = void $ do
samples/CRUD.hs view
@@ -24,7 +24,7 @@     Main ------------------------------------------------------------------------------} main :: IO ()-main = startGUI defaultConfig { tpPort = 10000 } setup+main = startGUI defaultConfig setup  setup :: Window -> UI () setup window = void $ mdo
samples/Chat.hs view
@@ -22,8 +22,7 @@     static   <- getStaticDir     messages <- Chan.newChan     startGUI defaultConfig-        { tpPort       = 10000-        , tpCustomHTML = Just "chat.html"+        { tpCustomHTML = Just "chat.html"         , tpStatic     = Just static         } $ setup messages 
samples/CurrencyConverter.hs view
@@ -10,7 +10,7 @@     Main ------------------------------------------------------------------------------} main :: IO ()-main = startGUI defaultConfig { tpPort = 10000 } setup+main = startGUI defaultConfig setup  setup :: Window -> UI () setup window = void $ do
samples/DragNDropExample.hs view
@@ -14,10 +14,7 @@ main :: IO () main = do     static <- getStaticDir-    startGUI defaultConfig-        { tpPort       = 10000-        , tpStatic     = Just static-        } setup+    startGUI defaultConfig { tpStatic = Just static } setup  setup :: Window -> UI () setup w = void $ do
samples/DrumMachine.hs view
@@ -30,7 +30,7 @@     Main ------------------------------------------------------------------------------} main :: IO ()-main = startGUI defaultConfig { tpPort = 10000 } setup+main = startGUI defaultConfig setup  setup :: Window -> UI () setup w = void $ do
samples/MissingDollars.hs view
@@ -12,10 +12,7 @@ main :: IO () main = do     static <- getStaticDir-    startGUI defaultConfig-        { tpPort       = 10000-        , tpStatic     = Just static-        } setup+    startGUI defaultConfig { tpStatic = Just static } setup   setup :: Window -> UI ()
samples/UseWords.hs view
@@ -17,10 +17,7 @@ main :: IO () main = do     static <- getStaticDir-    startGUI defaultConfig-        { tpPort       = 10000-        , tpStatic     = Just static-        } setup+    startGUI defaultConfig { tpStatic = Just static } setup   setup :: Window -> UI ()
src/Graphics/UI/Threepenny/Attributes.hs view
@@ -18,7 +18,7 @@     target, text_, type_, usemap, valign, version, vlink, vspace, width,     ) where -import Text.JSON+import qualified Data.Aeson       as JSON import Graphics.UI.Threepenny.Core  {-----------------------------------------------------------------------------@@ -26,20 +26,21 @@ ------------------------------------------------------------------------------} -- | The @checked@ status of an input element of type checkbox. checked :: Attr Element Bool-checked = fromProp "checked" (== JSBool True) JSBool+checked = fromProp "checked" (== JSON.Bool True) JSON.Bool  -- | The @enabled@ status of an input element enabled :: Attr Element Bool-enabled = fromProp "disabled" (== JSBool False) (JSBool . not)+enabled = fromProp "disabled" (== JSON.Bool False) (JSON.Bool . not)  -- | Index of the currently selected option of a @<select>@ element. -- -- The index starts at @0@. -- If no option is selected, then the selection is 'Nothing'. selection :: Attr Element (Maybe Int)-selection = fromProp "selectedIndex" from (showJSON . maybe (-1) id)+selection = fromProp "selectedIndex" from (JSON.toJSON . maybe (-1) id)     where-    from s = let Ok x = readJSON s in if x == -1 then Nothing else Just x+    from s = let JSON.Success x = JSON.fromJSON s in +        if x == -1 then Nothing else Just x   {-----------------------------------------------------------------------------
src/Graphics/UI/Threepenny/Core.hs view
@@ -71,7 +71,7 @@ import qualified Control.Monad.Trans.RWS.Lazy as Monad  import Network.URI-import Text.JSON+import qualified Data.Aeson as JSON  import           Reactive.Threepenny hiding (onChange) import qualified Reactive.Threepenny as Reactive@@ -355,7 +355,7 @@ audioStop el = runFunction $ ffi "prim_audio_stop(%1)" el  -- Turn a jQuery property @.prop()@ into an attribute.-fromProp :: String -> (JSValue -> a) -> (a -> JSValue) -> Attr Element a+fromProp :: String -> (JSON.Value -> a) -> (a -> JSON.Value) -> Attr Element a fromProp name from to = mkReadWriteAttr get set     where     set v el = runFunction $ ffi "$(%1).prop(%2,%3)" el name (to v)
src/Graphics/UI/Threepenny/Internal/Driver.hs view
@@ -72,7 +72,7 @@ import           Data.Maybe import           Data.Text                     (Text,pack,unpack) import qualified Data.Text                     as Text-import           Data.Text.Encoding+import           Data.Text.Encoding            as Text import           Data.Time import           Network.URI import qualified Network.WebSockets            as WS@@ -83,10 +83,14 @@ import           Snap.Core import qualified Snap.Http.Server              as Snap import           Snap.Util.FileServe+import           System.Environment            (getEnvironment) import           System.FilePath-import qualified Text.JSON as JSON-import           Text.JSON.Generic +import qualified Data.Aeson                    as JSON+import           Data.Aeson                    (Result(..))+import           Data.Aeson.Generic+import qualified Data.ByteString.Lazy.Char8    as LBS+import           Data.Data  import           Graphics.UI.Threepenny.Internal.Types     as Threepenny import           Graphics.UI.Threepenny.Internal.Resources@@ -109,10 +113,13 @@ --   worker action. serve :: Config -> (Session -> IO ()) -> IO () serve Config{..} worker = do+    env    <- getEnvironment+    let portEnv = Safe.readMay =<< Prelude.lookup "PORT" env+         server <- newServerState     _      <- forkIO $ custodian 30 (sSessions server)-    let config = Snap.setPort tpPort-               $ Snap.setErrorLog (Snap.ConfigIoLog tpLog)+    let config = Snap.setPort      (maybe defaultPort id (tpPort `mplus` portEnv))+               $ Snap.setErrorLog  (Snap.ConfigIoLog tpLog)                $ Snap.setAccessLog (Snap.ConfigIoLog tpLog)                $ Snap.defaultConfig     Snap.httpServe config . route $@@ -239,14 +246,11 @@ -- | Handle signals sent from the client. signal :: Session -> Snap () signal Session{..} = do-    input <- getInput "signal"-    case input of-      Just signalJson -> do-        let signal = JSON.decode signalJson-        case signal of-          Ok signal -> liftIO $ writeChan sSignals signal-          Error err -> error err-      Nothing -> error $ "Unable to parse " ++ show input+    input <- getParam "signal"+    let err = error $ "Unable to parse " ++ show input+    case JSON.decode . LBS.fromStrict =<< input of+        Just    signal -> liftIO $ writeChan sSignals signal+        Nothing        -> err  {-----------------------------------------------------------------------------     Implementation of two-way communication@@ -273,17 +277,19 @@     sendData <- forkIO . forever $ do         x <- readChan sInstructions         -- see note [Instruction strictness]-        WS.sendTextData connection . Text.pack . JSON.encode $ x+        WS.sendTextData connection . JSON.encode $ x      -- read data     let readData = do             input <- WS.receiveData connection             case input of-                "ping" -> WS.sendTextData connection . Text.pack $ "pong"+                "ping" -> WS.sendTextData connection . LBS.pack $ "pong"                 "quit" -> E.throw WS.ConnectionClosed-                input  -> case JSON.decode . Text.unpack $ input of-                    Ok signal -> writeChan sSignals signal-                    Error err -> E.throw $ Atto.ParseError [] err+                input  -> case JSON.decode input of+                    Just signal -> writeChan sSignals signal+                    Nothing     -> E.throw $ Atto.ParseError [] $+                        "Threepenny.Internal.Core: Couldn't parse 'Signal' "+                        ++ show input          forever readData `E.finally`         (do@@ -370,8 +376,8 @@     call window (CallJSFunction . toCode $ jsfunction) $ \signal ->         case signal of             FunctionResult v -> case marshalResult jsfunction window v of-                Ok    a -> return $ Just a-                Error _ -> return Nothing+                Success a -> return $ Just a+                Error   _ -> return Nothing             _ -> return Nothing  @@ -395,14 +401,14 @@     Snap utilities ------------------------------------------------------------------------------} -- Write JSON to output.-writeJson :: (MonadSnap m, JSON a) => a -> m ()+writeJson :: (MonadSnap m, JSON.ToJSON a) => a -> m () writeJson json = do     modifyResponse $ setContentType "application/json"-    (writeText . pack . (\x -> showJSValue x "") . showJSON) json+    writeLBS . JSON.encode $ json  -- Get a text input from snap. getInput :: (MonadSnap f) => ByteString -> f (Maybe String)-getInput = fmap (fmap (unpack . decodeUtf8)) . getParam+getInput = fmap (fmap (unpack . Text.decodeUtf8)) . getParam  -- Read an input from snap. readInput :: (MonadSnap f,Read a) => ByteString -> f (Maybe a)
src/Graphics/UI/Threepenny/Internal/FFI.hs view
@@ -9,17 +9,38 @@     FFI(..), ToJS(..),     JSFunction,     +    showJSON,+         toCode, marshalResult,     ) where -import           Data.Functor+import           Data.Aeson            as JSON+import qualified Data.Aeson.Types      as JSON+import qualified Data.Aeson.Encode import           Data.ByteString       (ByteString)-import qualified Data.ByteString.Char8 as BS-import           Text.JSON.Generic+import           Data.Data+import           Data.Functor+import           Data.Maybe+import           Data.String           (fromString)+import qualified Data.Text.Lazy+import qualified Data.Text.Lazy.Builder +import Safe (atMay)+ import Graphics.UI.Threepenny.Internal.Types  {-----------------------------------------------------------------------------+    Easy, if stupid conversion between String and JSON++    TODO: Use more efficient string types like ByteString, Text, etc.+------------------------------------------------------------------------------}+showJSON :: ToJSON a => a -> String+showJSON+    = Data.Text.Lazy.unpack+    . Data.Text.Lazy.Builder.toLazyText+    . Data.Aeson.Encode.fromValue . JSON.toJSON++{-----------------------------------------------------------------------------     JavaScript Code and Foreign Function Interface ------------------------------------------------------------------------------} -- | JavaScript code snippet.@@ -30,10 +51,11 @@ class ToJS a where     render :: a -> JSCode -instance ToJS String     where render   = JSCode . show+instance ToJS String     where render   = render . JSON.String . fromString instance ToJS Int        where render   = JSCode . show instance ToJS Bool       where render b = JSCode $ if b then "false" else "true"-instance ToJS JSValue    where render x = JSCode $ showJSValue x ""+instance ToJS JSON.Value where render   = JSCode . showJSON+-- TODO: ByteString instance may be wrong. Only needed for ElementId right now. instance ToJS ByteString where render   = JSCode . show instance ToJS ElementId  where     render (ElementId x) = apply "elidToElement(%1)" [render x]@@ -42,8 +64,9 @@  -- | A JavaScript function with a given output type @a@. data JSFunction a = JSFunction-    { code    :: JSCode                          -- code snippet-    , marshal :: Window -> JSValue -> Result a   -- convert to Haskell value+    { code    :: JSCode                                  -- ^ code snippet+    , marshal :: Window -> JSON.Value -> JSON.Parser a+      -- ^ conversion to Haskell value     }  -- | Render function to a textual representation using JavaScript syntax.@@ -52,11 +75,11 @@  -- | Convert function result to a Haskell value. marshalResult-    :: JSFunction a -- ^ Function that has been executed-    -> Window       -- ^ Browser context-    -> JSValue      -- ^ JSON representation of the return value -    -> Result a     -- ^ -marshalResult = marshal+    :: JSFunction a   -- ^ Function that has been executed+    -> Window         -- ^ Browser context+    -> JSON.Value     -- ^ JSON representation of the return value +    -> JSON.Result a  -- ^ Function result as parsed Haskell value+marshalResult fun w = JSON.parse (marshal fun w)  instance Functor JSFunction where     fmap f = fmapWindow (const f)@@ -65,7 +88,7 @@ fmapWindow f (JSFunction c m) = JSFunction c (\w v -> f w <$> m w v)  fromJSCode :: JSCode -> JSFunction ()-fromJSCode c = JSFunction { code = c, marshal = \_ _ -> Ok () }+fromJSCode c = JSFunction { code = c, marshal = \_ _ -> return () }  -- | Helper class for making 'ffi' a variable argument function. class FFI a where@@ -74,10 +97,10 @@ instance (ToJS a, FFI b) => FFI (a -> b) where     fancy f a = fancy $ f . (render a:) -instance FFI (JSFunction ())         where fancy f = fromJSCode $ f []-instance FFI (JSFunction String)     where fancy   = mkResult "%1.toString()"-instance FFI (JSFunction JSValue)    where fancy   = mkResult "%1"-instance FFI (JSFunction [ElementId]) where fancy  = mkResult "elementsToElids(%1)"+instance FFI (JSFunction ())          where fancy f = fromJSCode $ f []+instance FFI (JSFunction String)      where fancy   = mkResult "%1.toString()"+instance FFI (JSFunction JSON.Value)  where fancy   = mkResult "%1"+instance FFI (JSFunction [ElementId]) where fancy   = mkResult "elementsToElids(%1)"  -- FIXME: We need access to IO in order to turn a Coupon into an Element. {- @@ -85,10 +108,10 @@     fancy   = fmapWindow (\w elid -> Element elid w) . fancy -} -mkResult :: JSON a => String -> ([JSCode] -> JSCode) -> JSFunction a+mkResult :: FromJSON a => String -> ([JSCode] -> JSCode) -> JSFunction a mkResult client f = JSFunction     { code    = apply client [f []]-    , marshal = \w -> readJSON+    , marshal = \w -> parseJSON     }  -- | Simple JavaScript FFI with string substitution.@@ -121,7 +144,9 @@ apply :: String -> [JSCode] -> JSCode apply code args = JSCode $ go code     where-    argument i = unJSCode (args !! i)+    at xs i = maybe (error err) id $ atMay xs i+    err     = "Graphics.UI.Threepenny.FFI: Too few arguments in FFI call!"+    argument i = unJSCode (args `at` i)          go []         = []     go ('%':c:cs) = argument index ++ go cs
src/Graphics/UI/Threepenny/Internal/Include.hs view
@@ -6,12 +6,15 @@ import qualified Language.Haskell.TH as TH import Language.Haskell.TH.Quote -#ifdef CABAL+#if defined(CABAL) || defined(FPCOMPLETE)+root :: FilePath root = "src/" #else+root :: FilePath root = "../src/" -- running examples from ghci #endif +include :: QuasiQuoter include = QuasiQuoter         { quoteExp  = f             -- only used as an expression,         , quotePat  = undefined     -- hence all other use cases undefined@@ -21,6 +24,7 @@     where     f s = TH.LitE . TH.StringL <$> TH.runIO (readFileUTF8 $ root ++ s) +readFileUTF8 :: FilePath -> IO String readFileUTF8 path = do     h <- openFile path ReadMode     hSetEncoding h utf8
src/Graphics/UI/Threepenny/Internal/Types.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE OverloadedStrings #-}@@ -9,6 +10,7 @@ import Control.Applicative import Control.Concurrent import Control.DeepSeq+import Control.Monad import qualified Reactive.Threepenny    as E import           Data.ByteString.Char8  (ByteString) import qualified Data.ByteString.Char8  as BS@@ -17,7 +19,11 @@ import Data.Time  import Network.URI-import Text.JSON.Generic+import Data.Data+import           Data.Aeson             as JSON+import qualified Data.Aeson.Types       as JSON+import qualified Data.Aeson.Generic+ import System.IO (stderr) import System.IO.Unsafe @@ -39,11 +45,15 @@  instance NFData ElementId where     rnf (ElementId x) =+#if defined(CABAL) || defined(FPCOMPLETE) #if MIN_VERSION_bytestring(0, 10, 0)         rnf x #else         BS.length x `seq` () #endif+#else+        rnf x+#endif  type EventId  = String type Handlers = Map EventId (E.Handler EventData)@@ -51,11 +61,11 @@   -- Marshalling ElementId-instance JSON ElementId where-  showJSON (ElementId o) = showJSON o-  readJSON obj = do-    obj <- readJSON obj-    ElementId <$> valFromObj "Element" obj+instance ToJSON ElementId where+    toJSON (ElementId o)  = toJSON o+instance FromJSON ElementId where+    parseJSON (Object v)  = ElementId <$> v .: "Element"+    parseJSON _           = mzero   -- | Perform an action on the element.@@ -149,18 +159,29 @@  -- | Record for configuring the Threepenny GUI server. data Config = Config-  { tpPort       :: Int                 -- ^ Port number.-  , tpCustomHTML :: Maybe FilePath      -- ^ Custom HTML file to replace the default one.-  , tpStatic     :: Maybe FilePath      -- ^ Directory that is served under @/static@.-  , tpLog        :: ByteString -> IO () -- ^ Print a single log message.-  }+    { tpPort       :: Maybe Int           +        -- ^ Port number.+        -- @Nothing@ means that the port number is+        -- read from the environment variable @PORT@.+        -- Alternatively, port @8023@ is used if this variable is not set.+    , tpCustomHTML :: Maybe FilePath+        -- ^ Custom HTML file to replace the default one.+    , tpStatic     :: Maybe FilePath+        -- ^ Directory that is served under @/static@.+    , tpLog        :: ByteString -> IO ()+        -- ^ Print a single log message.+    } +defaultPort :: Int+defaultPort = 8023+ -- | Default configuration. ----- Port 10000, no custom HTML, no static directory, logging to stderr.+-- Port from environment variable or @8023@,+-- no custom HTML, no static directory, logging to stderr. defaultConfig :: Config defaultConfig = Config-    { tpPort       = 10000+    { tpPort       = Nothing     , tpCustomHTML = Nothing     , tpStatic     = Nothing     , tpLog        = \s -> BS.hPut stderr s >> BS.hPut stderr "\n"@@ -182,9 +203,8 @@   | Delete ElementId   deriving (Typeable,Data,Show) -instance JSON Instruction where-    readJSON _ = error "JSON.Instruction.readJSON: No method implemented."-    showJSON x = toJSON x +instance ToJSON Instruction where+    toJSON x = Data.Aeson.Generic.toJSON x   instance NFData Instruction where     rnf (Debug    x  ) = rnf x@@ -202,29 +222,28 @@   | Event ElementId EventId [Maybe String]   | Values [String]   | FunctionCallValues [Maybe String]-  | FunctionResult JSValue+  | FunctionResult JSON.Value   deriving (Typeable,Show) -instance JSON Signal where-  showJSON _ = error "JSON.Signal.showJSON: No method implemented."-  readJSON obj = do-    obj <- readJSON obj-    let quit     = Quit <$> valFromObj "Quit" obj+instance FromJSON Signal where+  parseJSON (Object v) = do+    let quit  = Quit <$> v .: "Quit"         event = do-          e         <- valFromObj "Event" obj-          elid      <- valFromObj "Element" e-          eventId   <- valFromObj "EventId" e-          arguments <- valFromObj "Params"  e+          e         <- v .: "Event"+          elid      <- e .: "Element"+          eventId   <- e .: "EventId"+          arguments <- e .: "Params"           args      <- mapM nullable arguments           return $ Event elid eventId args-        values = Values <$> valFromObj "Values" obj+        values = Values <$> v .: "Values"         fcallvalues = do-          FunctionCallValues <$> (valFromObj "FunctionCallValues" obj >>= mapM nullable)-        fresult = FunctionResult <$> valFromObj "FunctionResult" obj+          FunctionCallValues <$> (v .: "FunctionCallValues" >>= mapM nullable)+        fresult = FunctionResult <$> v .: "FunctionResult"     quit <|> event <|> values <|> fcallvalues <|> fresult+  parseJSON _        = mzero --- | Read a JSValue that may be null.-nullable :: JSON a => JSValue -> Result (Maybe a)-nullable JSNull = return Nothing-nullable v      = Just <$> readJSON v+-- | Read a JSON Value that may be null.+nullable :: FromJSON a => JSON.Value -> JSON.Parser (Maybe a)+nullable Null = return Nothing+nullable v    = Just <$> parseJSON v 
src/Graphics/UI/Threepenny/JQuery.hs view
@@ -2,15 +2,18 @@ module Graphics.UI.Threepenny.JQuery where  import Control.Arrow+import           Data.Aeson                 as JSON+import Data.String import Data.Char import Data.Default import Data.Maybe import Graphics.UI.Threepenny.Core+import Graphics.UI.Threepenny.Internal.FFI    (showJSON) import qualified Graphics.UI.Threepenny.Internal.Driver as Core-import qualified Graphics.UI.Threepenny.Internal.Types as Core-import Text.JSON+import qualified Graphics.UI.Threepenny.Internal.Types  as Core import Reactive.Threepenny + data Easing = Swing | Linear   deriving (Eq,Enum,Show) @@ -23,8 +26,10 @@     Core.withElement (toElement el) $ \elid window ->         callDeferredFunction window             "jquery_animate"-            [encode elid,encode (makeObj (map (second showJSON) props)),show duration,map toLower (show easing)]+            [showJSON elid,showJSON propsJSON,show duration,map toLower (show easing)]             (const complete)+    where+    propsJSON = JSON.object [fromString name .= val | (name,val) <- props]  -- | Fade in an element. fadeIn :: Element -> Int -> Easing -> IO () -> IO ()
src/Reactive/Threepenny/Monads.hs view
@@ -8,5 +8,5 @@ ------------------------------------------------------------------------------} runEvalP :: Values -> EvalP a -> IO (a, Values) runEvalP pulses m = do-    (a, s, w) <- runRWST m () pulses+    (a, s, _) <- runRWST m () pulses     return (a, s)
src/Reactive/Threepenny/Types.hs view
@@ -1,7 +1,7 @@ module Reactive.Threepenny.Types where  import Control.Monad.Trans.RWS.Lazy-import Data.Functor.Identity+import Data.Functor.Identity ()  import           Data.Hashable import qualified Data.Vault.Strict   as Vault.Strict
threepenny-gui.cabal view
@@ -1,5 +1,5 @@ Name:                threepenny-gui-Version:             0.4.0.2+Version:             0.4.1.0 Synopsis:            GUI framework that uses the web browser as a display. Description:     Threepenny-GUI is a GUI framework that uses the web browser as a display.@@ -92,6 +92,7 @@       cpp-options:  -DREBUG       ghc-options:  -O2      build-depends:     base                   >= 4     && < 5+                    ,aeson                  == 0.6.*                     ,attoparsec-enumerator  == 0.3.*                     ,bytestring             >= 0.9.2 && < 0.11                     ,containers             >= 0.4.2 && < 0.6@@ -99,7 +100,6 @@                     ,deepseq                == 1.3.*                     ,filepath               == 1.3.*                     ,hashable               >= 1.1.0  && < 1.3-                    ,json                   >= 0.4.4  && < 0.8                     ,MonadCatchIO-transformers == 0.3.*                     ,network                >= 2.3.0  && < 2.5                     ,safe                   == 0.3.*@@ -107,7 +107,7 @@                     ,snap-core              == 0.9.*                     ,stm                    >= 2.3    && < 2.5                     ,template-haskell       >= 2.7.0  && < 2.9-                    ,text                   == 0.11.*+                    ,text                   >= 0.11   && < 1.2                     ,time                   == 1.4.*                     ,transformers           == 0.3.*                     ,unordered-containers   == 0.2.*