diff --git a/.gitignore b/.gitignore index e5067852..64faee7b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ client_session_key.aes cabal-dev/ yesod/foobar/ .virthualenv +/vendor/ +/.shelly/ diff --git a/.travis.yml b/.travis.yml index a2a948b3..bac77556 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,13 @@ language: haskell install: + - cabal update - cabal install mega-sdist hspec cabal-meta cabal-src - - cabal-meta install --force-reinstalls + - git clone https://github.com/snoyberg/tagstream-conduit.git + - cd tagstream-conduit + - cabal-src-install --src-only + - cd .. + - cabal-meta install --force-reinstalls --enable-tests -script: mega-sdist --test +script: + - echo Done diff --git a/demo/appcache/AppCache.hs b/demo/appcache/AppCache.hs new file mode 100644 index 00000000..5283d17a --- /dev/null +++ b/demo/appcache/AppCache.hs @@ -0,0 +1,60 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} +module AppCache where + +import Control.Monad (when) +import Control.Monad.Trans.Writer +import Data.Hashable (hashWithSalt) +import Data.List (intercalate) +import qualified Data.Set as Set +import Data.Text (Text) +import Data.Text (pack) +import Language.Haskell.TH.Syntax +import Yesod.Core +import Yesod.Routes.TH + +newtype AppCache = AppCache { unAppCache :: Text } + +appCache :: [ResourceTree String] -> Q Exp +appCache trees = do + piecesSet <- execWriterT $ mapM_ (goTree id) trees + let body = unlines $ map toPath $ Set.toList piecesSet + hash = hashWithSalt 0 body + total = concat + [ "CACHE MANIFEST\n# Version: " + , show hash + , "\n\nCACHE:\n" + , body + ] + [|return (AppCache (pack total))|] + where + toPath [] = "/" + toPath x = concatMap ('/':) x + +goTree :: Monad m + => ([String] -> [String]) + -> ResourceTree String + -> WriterT (Set.Set [String]) m () +goTree front (ResourceLeaf res) = do + pieces' <- goPieces (resourceName res) $ resourcePieces res + when ("CACHE" `elem` resourceAttrs res) $ + tell $ Set.singleton $ front pieces' +goTree front (ResourceParent name pieces trees) = do + pieces' <- goPieces name pieces + mapM_ (goTree $ front . (pieces' ++)) trees + +goPieces :: Monad m => String -> [(CheckOverlap, Piece String)] -> m [String] +goPieces name = + mapM (goPiece . snd) + where + goPiece (Static s) = return s + goPiece (Dynamic _) = fail $ concat + [ "AppCache only applies to fully-static paths, but " + , name + , " has dynamic pieces." + ] + +instance ToContent AppCache where + toContent = toContent . unAppCache +instance ToTypedContent AppCache where + toTypedContent = TypedContent "text/cache-manifest" . toContent diff --git a/demo/appcache/Main.hs b/demo/appcache/Main.hs new file mode 100644 index 00000000..d48c6830 --- /dev/null +++ b/demo/appcache/Main.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} +import AppCache +import Routes +import Yesod.Core + +instance Yesod App + +mkYesodDispatch "App" resourcesApp + +getHomeR :: Handler String +getHomeR = return "Hello" + +getSomethingR :: Handler String +getSomethingR = return "Hello" + +getAppCacheR :: Handler AppCache +getAppCacheR = $(appCache resourcesApp) + +main :: IO () +main = warp 3000 App diff --git a/demo/appcache/Routes.hs b/demo/appcache/Routes.hs new file mode 100644 index 00000000..78f2826b --- /dev/null +++ b/demo/appcache/Routes.hs @@ -0,0 +1,15 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} +module Routes where + +import Yesod.Core + +data App = App + +mkYesodData "App" [parseRoutes| +/ HomeR GET +/some/thing SomethingR GET !CACHE +/appcache AppCacheR GET +|] diff --git a/demo/lite/lite.hs b/demo/lite/lite.hs new file mode 100644 index 00000000..4c9e2fa6 --- /dev/null +++ b/demo/lite/lite.hs @@ -0,0 +1,21 @@ +{-# LANGUAGE OverloadedStrings #-} +import Yesod.Core +import Data.Aeson +import Data.Monoid ((<>)) +import Data.Text (Text, pack) + +people :: [(Text, Int)] +people = [("Alice", 25), ("Bob", 43), ("Charlie", 37)] + +main = warp 3000 $ liteApp $ do + onStatic "people" $ dispatchTo getPeople + onStatic "person" $ withDynamic $ dispatchTo . getPerson + +getPeople = return $ toJSON $ map fst people + +getPerson name = + case lookup name people of + Nothing -> notFound + Just age -> selectRep $ do + provideRep $ return $ object ["name" .= name, "age" .= age] + provideRep $ return $ name <> " is " <> pack (show age) <> " years old" diff --git a/demo/streaming-db/streaming-db.hs b/demo/streaming-db/streaming-db.hs new file mode 100644 index 00000000..bcc0b2e9 --- /dev/null +++ b/demo/streaming-db/streaming-db.hs @@ -0,0 +1,67 @@ +{-# LANGUAGE EmptyDataDecls #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} +import Control.Monad.Logger (runNoLoggingT) +import Data.Conduit (awaitForever, runResourceT, ($=)) +import Data.Text (Text) +import Database.Persist.Sqlite (ConnectionPool, SqlPersist, + SqliteConf (..), runMigration, + runSqlPool) +import Database.Persist.Store (createPoolConfig) +import Yesod.Core +import Yesod.Persist + +share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist| +Person + name Text +|] + +data App = App + { appConfig :: SqliteConf + , appPool :: ConnectionPool + } + +mkYesod "App" [parseRoutes| +/ HomeR GET +|] + +instance Yesod App +instance YesodPersist App where + type YesodPersistBackend App = SqlPersist + runDB = defaultRunDB appConfig appPool +instance YesodPersistRunner App where + getDBRunner = defaultGetDBRunner appPool + +getHomeR :: Handler TypedContent +getHomeR = do + runDB $ do + runMigration migrateAll + deleteWhere ([] :: [Filter Person]) + insert_ $ Person "Charlie" + insert_ $ Person "Alice" + insert_ $ Person "Bob" + respondSourceDB typePlain $ selectSource [] [Asc PersonName] $= awaitForever toBuilder + where + toBuilder (Entity _ (Person name)) = do + sendChunkText name + sendChunkText "\n" + sendFlush + +main :: IO () +main = do + let config = SqliteConf ":memory:" 1 + pool <- createPoolConfig config + runNoLoggingT $ runResourceT $ flip runSqlPool pool $ do + runMigration migrateAll + deleteWhere ([] :: [Filter Person]) + insert_ $ Person "Charlie" + insert_ $ Person "Alice" + insert_ $ Person "Bob" + warp 3000 App + { appConfig = config + , appPool = pool + } diff --git a/demo/streaming/streaming.hs b/demo/streaming/streaming.hs new file mode 100644 index 00000000..481759ba --- /dev/null +++ b/demo/streaming/streaming.hs @@ -0,0 +1,48 @@ +{-# LANGUAGE OverloadedStrings, TemplateHaskell, QuasiQuotes, TypeFamilies #-} +import Yesod.Core +import Data.Conduit +import qualified Data.Conduit.Binary as CB +import Control.Concurrent.Lifted (threadDelay) +import Data.Monoid ((<>)) +import qualified Data.Text as T +import Control.Monad (forM_) + +data App = App + +mkYesod "App" [parseRoutes| +/ HomeR GET +|] + +instance Yesod App + +fibs :: [Int] +fibs = 1 : 1 : zipWith (+) fibs (tail fibs) + +getHomeR :: Handler TypedContent +getHomeR = do + value <- lookupGetParam "x" + case value of + Just "file" -> respondSource typePlain $ do + sendChunkText "Going to read a file\n\n" + CB.sourceFile "streaming.hs" $= awaitForever sendChunkBS + sendChunkText "Finished reading the file\n" + Just "fibs" -> respondSource typePlain $ do + forM_ fibs $ \fib -> do + $logError $ "Got fib: " <> T.pack (show fib) + sendChunkText $ "Next fib is: " <> T.pack (show fib) <> "\n" + yield Flush + sendFlush + threadDelay 1000000 + _ -> fmap toTypedContent $ defaultLayout $ do + setTitle "Streaming" + [whamlet| +

Notice how in the code above we perform selection before starting the stream. +

Anyway, choose one of the options below. +