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