diff --git a/CodeGenQ.hs b/CodeGen.hs similarity index 63% rename from CodeGenQ.hs rename to CodeGen.hs index ddd5d740..75f1e609 100644 --- a/CodeGenQ.hs +++ b/CodeGen.hs @@ -1,29 +1,24 @@ {-# LANGUAGE TemplateHaskell #-} --- | A code generation quasi-quoter. Everything is taken as literal text, with ~var~ variable interpolation, and ~~ is completely ignored. -module CodeGenQ (codegen) where +-- | A code generation template haskell. Everything is taken as literal text, +-- with ~var~ variable interpolation. +module CodeGen (codegen) where -import Language.Haskell.TH.Quote import Language.Haskell.TH.Syntax import Text.ParserCombinators.Parsec - -codegen :: QuasiQuoter -codegen = QuasiQuoter codegen' $ error "codegen cannot be a pattern" +import qualified System.IO.UTF8 as U data Token = VarToken String | LitToken String | EmptyToken -codegen' :: String -> Q Exp -codegen' s' = do - let s = killFirstBlank s' +codegen :: FilePath -> Q Exp +codegen fp = do + s' <- qRunIO $ U.readFile $ "scaffold/" ++ fp ++ ".cg" + let s = init s' case parse (many parseToken) s s of Left e -> error $ show e Right tokens' -> do let tokens'' = map toExp tokens' concat' <- [|concat|] return $ concat' `AppE` ListE tokens'' - where - killFirstBlank ('\n':x) = x - killFirstBlank ('\r':'\n':x) = x - killFirstBlank x = x toExp :: Token -> Exp toExp (LitToken s) = LitE $ StringL s diff --git a/scaffold.hs b/scaffold.hs index 86ce7db4..8b72e5e6 100644 --- a/scaffold.hs +++ b/scaffold.hs @@ -1,5 +1,5 @@ -{-# LANGUAGE QuasiQuotes, TemplateHaskell #-} -import CodeGenQ +{-# LANGUAGE TemplateHaskell #-} +import CodeGen import System.IO import System.Directory import qualified Data.ByteString.Char8 as S @@ -7,63 +7,35 @@ import Language.Haskell.TH.Syntax main :: IO () main = do - putStr [$codegen|Welcome to the Yesod scaffolder. -I'm going to be creating a skeleton Yesod project for you. - -What is your name? We're going to put this in the cabal and LICENSE files. - -Your name: |] + putStr $(codegen "welcome") hFlush stdout name <- getLine - putStr [$codegen| -Welcome ~name~. -What do you want to call your project? We'll use this for the cabal name. - -Project name: |] + putStr $(codegen "project-name") hFlush stdout project <- getLine - putStr [$codegen| -Now where would you like me to place your generated files? I'm smart enough -to create the directories, don't worry about that. If you leave this answer -blank, we'll place the files in ~project~. - -Directory name: |] + putStr $(codegen "dir-name") hFlush stdout dirRaw <- getLine let dir = if null dirRaw then project else dirRaw - putStr [$codegen| -Great, we'll be creating ~project~ today, and placing it in ~dir~. -What's going to be the name of your site argument datatype? This name must -start with a capital letter. - -Site argument: |] + putStr $(codegen "site-arg") hFlush stdout sitearg <- getLine - putStr [$codegen| -That's it! I'm creating your files now... -|] - - putStr [$codegen| -Yesod uses Persistent for its (you guessed it) persistence layer. -This tool will build in either SQLite or PostgreSQL support for you. If you -want to use a different backend, you'll have to make changes manually. -If you're not sure, stick with SQLite: it has no dependencies. - -So, what'll it be? s for sqlite, p for postgresql: |] + putStr $(codegen "database") hFlush stdout backendS <- getLine - let pconn1 = [$codegen|user=~project~ password=~project~ host=localhost port=5432 dbname=~project~_debug|] - let pconn2 = [$codegen|user=~project~ password=~project~ host=localhost port=5432 dbname=~project~_production|] + let pconn1 = $(codegen "pconn1") + let pconn2 = $(codegen "pconn2") let (lower, upper, connstr1, connstr2) = case backendS of "s" -> ("sqlite", "Sqlite", "debug.db3", "production.db3") "p" -> ("postgresql", "Postgresql", pconn1, pconn2) _ -> error $ "Invalid backend: " ++ backendS + putStrLn "That's it! I'm creating your files now..." let writeFile' fp s = do putStrLn $ "Generating " ++ fp @@ -75,433 +47,24 @@ So, what'll it be? s for sqlite, p for postgresql: |] mkDir "cassius" mkDir "julius" - writeFile' "simple-server.hs" [$codegen| -import Controller -import Network.Wai.Handler.SimpleServer (run) - -main :: IO () -main = putStrLn "Loaded" >> with~sitearg~ (run 3000) -|] - - writeFile' "fastcgi.hs" [$codegen| -import Controller -import Network.Wai.Handler.FastCGI (run) - -main :: IO () -main = with~sitearg~ run -|] - - writeFile' (project ++ ".cabal") [$codegen| -name: ~project~ -version: 0.0.0 -license: BSD3 -license-file: LICENSE -author: ~name~ -maintainer: ~name~ -synopsis: The greatest Yesod web application ever. -description: I'm sure you can say something clever here if you try. -category: Web -stability: Experimental -cabal-version: >= 1.6 -build-type: Simple -homepage: http://www.yesodweb.com/~project~ - -Flag production - Description: Build the production executable. - Default: False - -executable simple-server - if flag(production) - Buildable: False - main-is: simple-server.hs - build-depends: base >= 4 && < 5, - yesod >= 0.5 && < 0.6, - wai-extra, - directory, - bytestring, - persistent, - persistent-~lower~, - template-haskell, - hamlet - ghc-options: -Wall - extensions: TemplateHaskell, QuasiQuotes, TypeFamilies - -executable fastcgi - if flag(production) - Buildable: True - else - Buildable: False - cpp-options: -DPRODUCTION - main-is: fastcgi.hs - build-depends: wai-handler-fastcgi - ghc-options: -Wall - extensions: TemplateHaskell, QuasiQuotes, TypeFamilies -|] - - writeFile' "LICENSE" [$codegen| -The following license covers this documentation, and the source code, except -where otherwise indicated. - -Copyright 2010, ~name~. 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. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "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 HOLDERS 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. -|] - - writeFile' (sitearg ++ ".hs") [$codegen| -{-# LANGUAGE QuasiQuotes, TemplateHaskell, TypeFamilies #-} -module ~sitearg~ - ( ~sitearg~ (..) - , ~sitearg~Route (..) - , resources~sitearg~ - , Handler - , maybeAuth - , requireAuth - , module Yesod - , module Settings - , module Model - , StaticRoute (..) - , AuthRoute (..) - ) where - -import Yesod -import Yesod.Mail -import Yesod.Helpers.Static -import Yesod.Helpers.Auth -import qualified Settings -import System.Directory -import qualified Data.ByteString.Lazy as L -import Yesod.WebRoutes -import Database.Persist.GenericSql -import Settings (hamletFile, cassiusFile, juliusFile) -import Model -import Control.Monad (join) -import Data.Maybe (isJust) - -data ~sitearg~ = ~sitearg~ - { getStatic :: Static - , connPool :: Settings.ConnectionPool - } - -type Handler = GHandler ~sitearg~ ~sitearg~ - -mkYesodData "~sitearg~" [$parseRoutes| -/static StaticR Static getStatic -/auth AuthR Auth getAuth - -/favicon.ico FaviconR GET -/robots.txt RobotsR GET - -/ RootR GET -|~~] - -instance Yesod ~sitearg~ where - approot _ = Settings.approot - defaultLayout widget = do - mmsg <- getMessage - pc <- widgetToPageContent $ do - widget - addStyle $(Settings.cassiusFile "default-layout") - hamletToRepHtml $(Settings.hamletFile "default-layout") - urlRenderOverride a (StaticR s) = - Just $ uncurry (joinPath a Settings.staticroot) $ format s - where - format = formatPathSegments ss - ss :: Site StaticRoute (String -> Maybe (GHandler Static ~sitearg~ ChooseRep)) - ss = getSubSite - urlRenderOverride _ _ = Nothing - authRoute _ = Just $ AuthR LoginR - addStaticContent ext' _ content = do - let fn = base64md5 content ++ '.' : ext' - let statictmp = Settings.staticdir ++ "/tmp/" - liftIO $ createDirectoryIfMissing True statictmp - liftIO $ L.writeFile (statictmp ++ fn) content - return $ Just $ Right (StaticR $ StaticRoute ["tmp", fn] [], []) - -instance YesodPersist ~sitearg~ where - type YesodDB ~sitearg~ = SqlPersist - runDB db = fmap connPool getYesod >>= Settings.runConnectionPool db - -instance YesodAuth ~sitearg~ where - type AuthEntity ~sitearg~ = User - type AuthEmailEntity ~sitearg~ = Email - - defaultDest _ = RootR - - getAuthId creds _extra = runDB $ do - x <- getBy $ UniqueUser $ credsIdent creds - case x of - Just (uid, _) -> return $ Just uid - Nothing -> do - fmap Just $ insert $ User (credsIdent creds) Nothing - - openIdEnabled _ = True - - emailSettings _ = Just EmailSettings - { addUnverified = \email verkey -> - runDB $ insert $ Email email Nothing (Just verkey) - , sendVerifyEmail = sendVerifyEmail' - , getVerifyKey = runDB . fmap (join . fmap emailVerkey) . get - , setVerifyKey = \eid key -> runDB $ update eid [EmailVerkey $ Just key] - , verifyAccount = \eid -> runDB $ do - me <- get eid - case me of - Nothing -> return Nothing - Just e -> do - let email = emailEmail e - case emailUser e of - Just uid -> return $ Just uid - Nothing -> do - uid <- insert $ User email Nothing - update eid [EmailUser $ Just uid] - return $ Just uid - , getPassword = runDB . fmap (join . fmap userPassword) . get - , setPassword = \uid pass -> runDB $ update uid [UserPassword $ Just pass] - , getEmailCreds = \email -> runDB $ do - me <- getBy $ UniqueEmail email - case me of - Nothing -> return Nothing - Just (eid, e) -> return $ Just EmailCreds - { emailCredsId = eid - , emailCredsAuthId = emailUser e - , emailCredsStatus = isJust $ emailUser e - , emailCredsVerkey = emailVerkey e - } - , getEmail = runDB . fmap (fmap emailEmail) . get - } - -sendVerifyEmail' :: String -> String -> String -> GHandler Auth m () -sendVerifyEmail' email _ verurl = - liftIO $ renderSendMail Mail - { mailHeaders = - [ ("From", "noreply") - , ("To", email) - , ("Subject", "Verify your email address") - ] - , mailPlain = verurl - , mailParts = return Part - { partType = "text/html; charset=utf-8" - , partEncoding = None - , partDisposition = Inline - , partContent = renderHamlet id [$hamlet| -%p Please confirm your email address by clicking on the link below. -%p - %a!href=$verurl$ $verurl$ -%p Thank you -|~~] - } - } -|] - - writeFile' "Controller.hs" [$codegen| -{-# LANGUAGE TemplateHaskell #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} -module Controller - ( with~sitearg~ - ) where - -import ~sitearg~ -import Settings -import Yesod.Helpers.Static -import Yesod.Helpers.Auth -import Database.Persist.GenericSql - -import Handler.Root - -mkYesodDispatch "~sitearg~" resources~sitearg~ - -getFaviconR :: Handler () -getFaviconR = sendFile "image/x-icon" "favicon.ico" - -getRobotsR :: Handler RepPlain -getRobotsR = return $ RepPlain $ toContent "User-agent: *" - -with~sitearg~ :: (Application -> IO a) -> IO a -with~sitearg~ f = Settings.withConnectionPool $ \p -> do - flip runConnectionPool p $ runMigration $ do - migrate (undefined :: User) - migrate (undefined :: Email) - let h = ~sitearg~ s p - toWaiApp h >>= f - where - s = fileLookupDir Settings.staticdir typeByExt -|] - - writeFile' "Handler/Root.hs" [$codegen| -{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} -module Handler.Root where - -import ~sitearg~ - -getRootR :: Handler RepHtml -getRootR = do - mu <- maybeAuth - defaultLayout $ do - h2id <- newIdent - setTitle "~project~ homepage" - addBody $(hamletFile "homepage") - addStyle $(cassiusFile "homepage") - addJavascript $(juliusFile "homepage") -|] - - writeFile' "Model.hs" [$codegen| -{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving #-} -module Model where - -import Yesod - -mkPersist [$persist| -User - ident String - password String null update - UniqueUser ident -Email - email String - user UserId null update - verkey String null update - UniqueEmail email -|~~] -|] - - writeFile' "Settings.hs" [$codegen| -{-# LANGUAGE CPP #-} -module Settings - ( hamletFile - , cassiusFile - , juliusFile - , connStr - , ConnectionPool - , withConnectionPool - , runConnectionPool - , approot - , staticroot - , staticdir - ) where - -import qualified Text.Hamlet as H -import qualified Text.Cassius as H -import qualified Text.Julius as H -import Language.Haskell.TH.Syntax -import Database.Persist.~upper~ -import Yesod (MonadCatchIO) - -hamletFile :: FilePath -> Q Exp -#ifdef PRODUCTION -hamletFile x = H.hamletFile $ "hamlet/" ++ x ++ ".hamlet" -#else -hamletFile x = H.hamletFileDebug $ "hamlet/" ++ x ++ ".hamlet" -#endif - -cassiusFile :: FilePath -> Q Exp -#ifdef PRODUCTION -cassiusFile x = H.cassiusFile $ "cassius/" ++ x ++ ".cassius" -#else -cassiusFile x = H.cassiusFileDebug $ "cassius/" ++ x ++ ".cassius" -#endif - -juliusFile :: FilePath -> Q Exp -#ifdef PRODUCTION -juliusFile x = H.juliusFile $ "julius/" ++ x ++ ".julius" -#else -juliusFile x = H.juliusFileDebug $ "julius/" ++ x ++ ".julius" -#endif - -connStr :: String -#ifdef PRODUCTION -connStr = "~connstr2~" -#else -connStr = "~connstr1~" -#endif - -connectionCount :: Int -connectionCount = 10 - -withConnectionPool :: MonadCatchIO m => (ConnectionPool -> m a) -> m a -withConnectionPool = with~upper~Pool connStr connectionCount - -runConnectionPool :: MonadCatchIO m => SqlPersist m a -> ConnectionPool -> m a -runConnectionPool = runSqlPool - -approot :: String -#ifdef PRODUCTION -approot = "http://localhost:3000" -#else -approot = "http://localhost:3000" -#endif - -staticroot :: String -staticroot = approot ++ "/static" - -staticdir :: FilePath -staticdir = "static" -|] - - writeFile' "cassius/default-layout.cassius" [$codegen| -body - font-family: sans-serif -|] - - writeFile' "hamlet/default-layout.hamlet" [$codegen| -!!! -%html - %head - %title $pageTitle.pc$ - ^pageHead.pc^ - %body - $maybe mmsg msg - #message $msg$ - ^pageBody.pc^ -|] - - writeFile' "hamlet/homepage.hamlet" [$codegen| -%h1 Hello -%h2#$h2id$ You do not have Javascript enabled. -$maybe mu u - %p - You are logged in as $userIdent.snd.u$. $ - %a!href=@AuthR.LogoutR@ Logout - \. -$nothing - %p - You are not logged in. $ - %a!href=@AuthR.LoginR@ Login now - \. -|] - - writeFile' "cassius/homepage.cassius" [$codegen| -body - font-family: sans-serif -h1 - text-align: center -h2#$h2id$ - color: #990 -|] - - writeFile' "julius/homepage.julius" [$codegen| -window.onload = function(){ - document.getElementById("%h2id%").innerHTML = "Added from JavaScript."; -} -|] + writeFile' "simple-server.hs" $(codegen "simple-server_hs") + writeFile' "fastcgi.hs" $(codegen "fastcgi_hs") + writeFile' (project ++ ".cabal") $(codegen "cabal") + writeFile' "LICENSE" $(codegen "LICENSE") + writeFile' (sitearg ++ ".hs") $(codegen "sitearg_hs") + writeFile' "Controller.hs" $(codegen "Controller_hs") + writeFile' "Handler/Root.hs" $(codegen "Root_hs") + writeFile' "Model.hs" $(codegen "Model_hs") + writeFile' "Settings.hs" $(codegen "Settings_hs") + writeFile' "cassius/default-layout.cassius" + $(codegen "default-layout_cassius") + writeFile' "hamlet/default-layout.hamlet" + $(codegen "default-layout_hamlet") + writeFile' "hamlet/homepage.hamlet" $(codegen "homepage_hamlet") + writeFile' "cassius/homepage.cassius" $(codegen "homepage_cassius") + writeFile' "julius/homepage.julius" $(codegen "homepage_julius") S.writeFile (dir ++ "/favicon.ico") - $(runIO (S.readFile "favicon.ico") >>= \bs -> do + $(runIO (S.readFile "scaffold/favicon_ico.cg") >>= \bs -> do pack <- [|S.pack|] return $ pack `AppE` LitE (StringL $ S.unpack bs)) diff --git a/scaffold/Controller_hs.cg b/scaffold/Controller_hs.cg new file mode 100644 index 00000000..b0ef7f7d --- /dev/null +++ b/scaffold/Controller_hs.cg @@ -0,0 +1,32 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} +module Controller + ( with~sitearg~ + ) where + +import ~sitearg~ +import Settings +import Yesod.Helpers.Static +import Yesod.Helpers.Auth +import Database.Persist.GenericSql + +import Handler.Root + +mkYesodDispatch "~sitearg~" resources~sitearg~ + +getFaviconR :: Handler () +getFaviconR = sendFile "image/x-icon" "favicon.ico" + +getRobotsR :: Handler RepPlain +getRobotsR = return $ RepPlain $ toContent "User-agent: *" + +with~sitearg~ :: (Application -> IO a) -> IO a +with~sitearg~ f = Settings.withConnectionPool $ \p -> do + flip runConnectionPool p $ runMigration $ do + migrate (undefined :: User) + migrate (undefined :: Email) + let h = ~sitearg~ s p + toWaiApp h >>= f + where + s = fileLookupDir Settings.staticdir typeByExt + diff --git a/scaffold/LICENSE.cg b/scaffold/LICENSE.cg new file mode 100644 index 00000000..049c97b2 --- /dev/null +++ b/scaffold/LICENSE.cg @@ -0,0 +1,26 @@ +The following license covers this documentation, and the source code, except +where otherwise indicated. + +Copyright 2010, ~name~. 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. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "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 HOLDERS 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. + diff --git a/scaffold/Model_hs.cg b/scaffold/Model_hs.cg new file mode 100644 index 00000000..e7c25078 --- /dev/null +++ b/scaffold/Model_hs.cg @@ -0,0 +1,17 @@ +{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving #-} +module Model where + +import Yesod + +mkPersist [$persist| +User + ident String + password String null update + UniqueUser ident +Email + email String + user UserId null update + verkey String null update + UniqueEmail email +|~~] + diff --git a/scaffold/Root_hs.cg b/scaffold/Root_hs.cg new file mode 100644 index 00000000..88b16022 --- /dev/null +++ b/scaffold/Root_hs.cg @@ -0,0 +1,15 @@ +{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} +module Handler.Root where + +import ~sitearg~ + +getRootR :: Handler RepHtml +getRootR = do + mu <- maybeAuth + defaultLayout $ do + h2id <- newIdent + setTitle "~project~ homepage" + addBody $(hamletFile "homepage") + addStyle $(cassiusFile "homepage") + addJavascript $(juliusFile "homepage") + diff --git a/scaffold/Settings_hs.cg b/scaffold/Settings_hs.cg new file mode 100644 index 00000000..24aab568 --- /dev/null +++ b/scaffold/Settings_hs.cg @@ -0,0 +1,71 @@ +{-# LANGUAGE CPP #-} +module Settings + ( hamletFile + , cassiusFile + , juliusFile + , connStr + , ConnectionPool + , withConnectionPool + , runConnectionPool + , approot + , staticroot + , staticdir + ) where + +import qualified Text.Hamlet as H +import qualified Text.Cassius as H +import qualified Text.Julius as H +import Language.Haskell.TH.Syntax +import Database.Persist.~upper~ +import Yesod (MonadCatchIO) + +hamletFile :: FilePath -> Q Exp +#ifdef PRODUCTION +hamletFile x = H.hamletFile $ "hamlet/" ++ x ++ ".hamlet" +#else +hamletFile x = H.hamletFileDebug $ "hamlet/" ++ x ++ ".hamlet" +#endif + +cassiusFile :: FilePath -> Q Exp +#ifdef PRODUCTION +cassiusFile x = H.cassiusFile $ "cassius/" ++ x ++ ".cassius" +#else +cassiusFile x = H.cassiusFileDebug $ "cassius/" ++ x ++ ".cassius" +#endif + +juliusFile :: FilePath -> Q Exp +#ifdef PRODUCTION +juliusFile x = H.juliusFile $ "julius/" ++ x ++ ".julius" +#else +juliusFile x = H.juliusFileDebug $ "julius/" ++ x ++ ".julius" +#endif + +connStr :: String +#ifdef PRODUCTION +connStr = "~connstr2~" +#else +connStr = "~connstr1~" +#endif + +connectionCount :: Int +connectionCount = 10 + +withConnectionPool :: MonadCatchIO m => (ConnectionPool -> m a) -> m a +withConnectionPool = with~upper~Pool connStr connectionCount + +runConnectionPool :: MonadCatchIO m => SqlPersist m a -> ConnectionPool -> m a +runConnectionPool = runSqlPool + +approot :: String +#ifdef PRODUCTION +approot = "http://localhost:3000" +#else +approot = "http://localhost:3000" +#endif + +staticroot :: String +staticroot = approot ++ "/static" + +staticdir :: FilePath +staticdir = "static" + diff --git a/scaffold/cabal.cg b/scaffold/cabal.cg new file mode 100644 index 00000000..b9d0d890 --- /dev/null +++ b/scaffold/cabal.cg @@ -0,0 +1,45 @@ +name: ~project~ +version: 0.0.0 +license: BSD3 +license-file: LICENSE +author: ~name~ +maintainer: ~name~ +synopsis: The greatest Yesod web application ever. +description: I'm sure you can say something clever here if you try. +category: Web +stability: Experimental +cabal-version: >= 1.6 +build-type: Simple +homepage: http://www.yesodweb.com/~project~ + +Flag production + Description: Build the production executable. + Default: False + +executable simple-server + if flag(production) + Buildable: False + main-is: simple-server.hs + build-depends: base >= 4 && < 5, + yesod >= 0.5 && < 0.6, + wai-extra, + directory, + bytestring, + persistent, + persistent-~lower~, + template-haskell, + hamlet + ghc-options: -Wall + extensions: TemplateHaskell, QuasiQuotes, TypeFamilies + +executable fastcgi + if flag(production) + Buildable: True + else + Buildable: False + cpp-options: -DPRODUCTION + main-is: fastcgi.hs + build-depends: wai-handler-fastcgi + ghc-options: -Wall + extensions: TemplateHaskell, QuasiQuotes, TypeFamilies + diff --git a/scaffold/database.cg b/scaffold/database.cg new file mode 100644 index 00000000..25c13784 --- /dev/null +++ b/scaffold/database.cg @@ -0,0 +1,6 @@ +Yesod uses Persistent for its (you guessed it) persistence layer. +This tool will build in either SQLite or PostgreSQL support for you. If you +want to use a different backend, you'll have to make changes manually. +If you're not sure, stick with SQLite: it has no dependencies. + +So, what'll it be? s for sqlite, p for postgresql: diff --git a/scaffold/default-layout_cassius.cg b/scaffold/default-layout_cassius.cg new file mode 100644 index 00000000..77177469 --- /dev/null +++ b/scaffold/default-layout_cassius.cg @@ -0,0 +1,3 @@ +body + font-family: sans-serif + diff --git a/scaffold/default-layout_hamlet.cg b/scaffold/default-layout_hamlet.cg new file mode 100644 index 00000000..3bcfae41 --- /dev/null +++ b/scaffold/default-layout_hamlet.cg @@ -0,0 +1,10 @@ +!!! +%html + %head + %title $pageTitle.pc$ + ^pageHead.pc^ + %body + $maybe mmsg msg + #message $msg$ + ^pageBody.pc^ + diff --git a/scaffold/dir-name.cg b/scaffold/dir-name.cg new file mode 100644 index 00000000..dc74c147 --- /dev/null +++ b/scaffold/dir-name.cg @@ -0,0 +1,5 @@ +Now where would you like me to place your generated files? I'm smart enough +to create the directories, don't worry about that. If you leave this answer +blank, we'll place the files in ~project~. + +Directory name: diff --git a/scaffold/fastcgi_hs.cg b/scaffold/fastcgi_hs.cg new file mode 100644 index 00000000..d946d7c7 --- /dev/null +++ b/scaffold/fastcgi_hs.cg @@ -0,0 +1,6 @@ +import Controller +import Network.Wai.Handler.FastCGI (run) + +main :: IO () +main = with~sitearg~ run + diff --git a/favicon.ico b/scaffold/favicon_ico.cg similarity index 100% rename from favicon.ico rename to scaffold/favicon_ico.cg diff --git a/scaffold/homepage_cassius.cg b/scaffold/homepage_cassius.cg new file mode 100644 index 00000000..c2873e00 --- /dev/null +++ b/scaffold/homepage_cassius.cg @@ -0,0 +1,5 @@ +h1 + text-align: center +h2#$h2id$ + color: #990 + diff --git a/scaffold/homepage_hamlet.cg b/scaffold/homepage_hamlet.cg new file mode 100644 index 00000000..55bf9683 --- /dev/null +++ b/scaffold/homepage_hamlet.cg @@ -0,0 +1,13 @@ +%h1 Hello +%h2#$h2id$ You do not have Javascript enabled. +$maybe mu u + %p + You are logged in as $userIdent.snd.u$. $ + %a!href=@AuthR.LogoutR@ Logout + \. +$nothing + %p + You are not logged in. $ + %a!href=@AuthR.LoginR@ Login now + \. + diff --git a/scaffold/homepage_julius.cg b/scaffold/homepage_julius.cg new file mode 100644 index 00000000..281c89aa --- /dev/null +++ b/scaffold/homepage_julius.cg @@ -0,0 +1,4 @@ +window.onload = function(){ + document.getElementById("%h2id%").innerHTML = "Added from JavaScript."; +} + diff --git a/scaffold/pconn1.cg b/scaffold/pconn1.cg new file mode 100644 index 00000000..2fbf5964 --- /dev/null +++ b/scaffold/pconn1.cg @@ -0,0 +1 @@ +user=~project~ password=~project~ host=localhost port=5432 dbname=~project~_debug diff --git a/scaffold/pconn2.cg b/scaffold/pconn2.cg new file mode 100644 index 00000000..5dbfefe0 --- /dev/null +++ b/scaffold/pconn2.cg @@ -0,0 +1 @@ +user=~project~ password=~project~ host=localhost port=5432 dbname=~project~_production diff --git a/scaffold/project-name.cg b/scaffold/project-name.cg new file mode 100644 index 00000000..a9742993 --- /dev/null +++ b/scaffold/project-name.cg @@ -0,0 +1,4 @@ +Welcome ~name~. +What do you want to call your project? We'll use this for the cabal name. + +Project name: diff --git a/scaffold/simple-server_hs.cg b/scaffold/simple-server_hs.cg new file mode 100644 index 00000000..9a630481 --- /dev/null +++ b/scaffold/simple-server_hs.cg @@ -0,0 +1,6 @@ +import Controller +import Network.Wai.Handler.SimpleServer (run) + +main :: IO () +main = putStrLn "Loaded" >> with~sitearg~ (run 3000) + diff --git a/scaffold/site-arg.cg b/scaffold/site-arg.cg new file mode 100644 index 00000000..28e7e31a --- /dev/null +++ b/scaffold/site-arg.cg @@ -0,0 +1,5 @@ +Great, we'll be creating ~project~ today, and placing it in ~dir~. +What's going to be the name of your site argument datatype? This name must +start with a capital letter. + +Site argument: diff --git a/scaffold/sitearg_hs.cg b/scaffold/sitearg_hs.cg new file mode 100644 index 00000000..3c7530a5 --- /dev/null +++ b/scaffold/sitearg_hs.cg @@ -0,0 +1,143 @@ +{-# LANGUAGE QuasiQuotes, TemplateHaskell, TypeFamilies #-} +module ~sitearg~ + ( ~sitearg~ (..) + , ~sitearg~Route (..) + , resources~sitearg~ + , Handler + , maybeAuth + , requireAuth + , module Yesod + , module Settings + , module Model + , StaticRoute (..) + , AuthRoute (..) + ) where + +import Yesod +import Yesod.Mail +import Yesod.Helpers.Static +import Yesod.Helpers.Auth +import qualified Settings +import System.Directory +import qualified Data.ByteString.Lazy as L +import Yesod.WebRoutes +import Database.Persist.GenericSql +import Settings (hamletFile, cassiusFile, juliusFile) +import Model +import Control.Monad (join) +import Data.Maybe (isJust) + +data ~sitearg~ = ~sitearg~ + { getStatic :: Static + , connPool :: Settings.ConnectionPool + } + +type Handler = GHandler ~sitearg~ ~sitearg~ + +mkYesodData "~sitearg~" [$parseRoutes| +/static StaticR Static getStatic +/auth AuthR Auth getAuth + +/favicon.ico FaviconR GET +/robots.txt RobotsR GET + +/ RootR GET +|~~] + +instance Yesod ~sitearg~ where + approot _ = Settings.approot + defaultLayout widget = do + mmsg <- getMessage + pc <- widgetToPageContent $ do + widget + addStyle $(Settings.cassiusFile "default-layout") + hamletToRepHtml $(Settings.hamletFile "default-layout") + urlRenderOverride a (StaticR s) = + Just $ uncurry (joinPath a Settings.staticroot) $ format s + where + format = formatPathSegments ss + ss :: Site StaticRoute (String -> Maybe (GHandler Static ~sitearg~ ChooseRep)) + ss = getSubSite + urlRenderOverride _ _ = Nothing + authRoute _ = Just $ AuthR LoginR + addStaticContent ext' _ content = do + let fn = base64md5 content ++ '.' : ext' + let statictmp = Settings.staticdir ++ "/tmp/" + liftIO $ createDirectoryIfMissing True statictmp + liftIO $ L.writeFile (statictmp ++ fn) content + return $ Just $ Right (StaticR $ StaticRoute ["tmp", fn] [], []) + +instance YesodPersist ~sitearg~ where + type YesodDB ~sitearg~ = SqlPersist + runDB db = fmap connPool getYesod >>= Settings.runConnectionPool db + +instance YesodAuth ~sitearg~ where + type AuthEntity ~sitearg~ = User + type AuthEmailEntity ~sitearg~ = Email + + defaultDest _ = RootR + + getAuthId creds _extra = runDB $ do + x <- getBy $ UniqueUser $ credsIdent creds + case x of + Just (uid, _) -> return $ Just uid + Nothing -> do + fmap Just $ insert $ User (credsIdent creds) Nothing + + openIdEnabled _ = True + + emailSettings _ = Just EmailSettings + { addUnverified = \email verkey -> + runDB $ insert $ Email email Nothing (Just verkey) + , sendVerifyEmail = sendVerifyEmail' + , getVerifyKey = runDB . fmap (join . fmap emailVerkey) . get + , setVerifyKey = \eid key -> runDB $ update eid [EmailVerkey $ Just key] + , verifyAccount = \eid -> runDB $ do + me <- get eid + case me of + Nothing -> return Nothing + Just e -> do + let email = emailEmail e + case emailUser e of + Just uid -> return $ Just uid + Nothing -> do + uid <- insert $ User email Nothing + update eid [EmailUser $ Just uid] + return $ Just uid + , getPassword = runDB . fmap (join . fmap userPassword) . get + , setPassword = \uid pass -> runDB $ update uid [UserPassword $ Just pass] + , getEmailCreds = \email -> runDB $ do + me <- getBy $ UniqueEmail email + case me of + Nothing -> return Nothing + Just (eid, e) -> return $ Just EmailCreds + { emailCredsId = eid + , emailCredsAuthId = emailUser e + , emailCredsStatus = isJust $ emailUser e + , emailCredsVerkey = emailVerkey e + } + , getEmail = runDB . fmap (fmap emailEmail) . get + } + +sendVerifyEmail' :: String -> String -> String -> GHandler Auth m () +sendVerifyEmail' email _ verurl = + liftIO $ renderSendMail Mail + { mailHeaders = + [ ("From", "noreply") + , ("To", email) + , ("Subject", "Verify your email address") + ] + , mailPlain = verurl + , mailParts = return Part + { partType = "text/html; charset=utf-8" + , partEncoding = None + , partDisposition = Inline + , partContent = renderHamlet id [$hamlet| +%p Please confirm your email address by clicking on the link below. +%p + %a!href=$verurl$ $verurl$ +%p Thank you +|~~] + } + } + diff --git a/scaffold/welcome.cg b/scaffold/welcome.cg new file mode 100644 index 00000000..ac3742a7 --- /dev/null +++ b/scaffold/welcome.cg @@ -0,0 +1,6 @@ +Welcome to the Yesod scaffolder. +I'm going to be creating a skeleton Yesod project for you. + +What is your name? We're going to put this in the cabal and LICENSE files. + +Your name: diff --git a/yesod.cabal b/yesod.cabal index cc909a60..8d194cb7 100644 --- a/yesod.cabal +++ b/yesod.cabal @@ -14,7 +14,7 @@ stability: Stable cabal-version: >= 1.6 build-type: Simple homepage: http://docs.yesodweb.com/yesod/ -extra-source-files: favicon.ico +extra-source-files: scaffold/*.cg flag buildtests description: Build the executable to run unit tests @@ -79,7 +79,7 @@ executable yesod build-depends: parsec >= 2.1 && < 4 ghc-options: -Wall main-is: scaffold.hs - other-modules: CodeGenQ + other-modules: CodeGen extensions: TemplateHaskell executable runtests