Snap is a web framework for the Haskell programming language. Nice!
$ cabal update $ cabal install snap
$ mkdir myproject; cd myproject $ snap init
$ cabal install $ myproject -p 8000
It comes with a built-in fast HTTP server with an optional high-concurrency backend using the libev event loop library.
<artists> <div> <a href="${artisturl}"><name/></a> - born on <birthdate/> </div> <ul> <albums> <li><year/> - <albumtitle/></li> </albums> </ul> </artists>
#snapframeworksnap-core: Core types and functionality + utilitiessnap: Convenience functions and snaplet functionalitysnap-server: An iteratee-based HTTP server library, which runs Snap web handlersheist: Awesome HTML-based templating enginexmlhtml: An XML/HTML parsing and rendering library made for Heist
Snap monadSnap monadThe monad that web handlers run in. It gives you:
IO monadxmlhtmlxmlhtml is a Node<div class="bear-info"> <h1>The grizzly bear</h1> The grizzly bear is a very <em>fearsome</em> beast. Avoid at all costs. </div>
<person> Hi I'm <name/>, these are my favorites: <ul> <flavors> <li><flavor/></li> </flavors> </ul> </person>
Hi I'm Andy, these are my favorites: <ul> <li>Vanilla</li> <li>Strawberry</li> <li>Pistachio</li> </ul>
The flavors node ...
<flavors>
<li><flavor></li>
</flavors>
.. gets substituted by three li nodes.
<li>Vanilla</li>
<li>Strawberry</li>
<li>Pistachio</li>
reverseSplice :: Splice AppHandler reverseSplice = do node <- getParamNode let text = nodeText node return [TextNode $ reverse text]
<div> <reverse>Hello</reverse> </div>
<div> olleH </div>
The apply splice is bound to the apply tag. It loads a template in the node's place.
... <div class="navbar"> <apply template="navigation"/> </div> ...
navigation.tpl:
<ul> <li><a href="/">Home</a></li> <li><a href="/art">Art</a></li> <li><a href="/family">My beautiful family</a></li> <li><a href="/rugcollection">Rug collection</a></li> </ul>
index.tpl
<html> <head> <title>My home page! Welcome</title> </head> <body> <div id="main"> <maincontent/> </div> <div id="sidebar"> <sidebar/> </div> </body> </html>
mainpage.tpl<apply template="index"> <bind tag="maincontent"> <h1>Welcome to my cool site! I'm 12!</h1> <p> Hi, I like sloths, they're cool </p> </bind> <bind tag="sidebar"> <ul> <li><a href="/cartoons">Cartoons</a></li> <li><a href="/toys">My toys</a></li> <li><a href="/superpowers">My superpowers</a></li> </ul> </bind> </apply>
$ cabal install snap -fhint
$ mkdir sample-project; cd sample-project $ snap init $ cabal install -fdevelopment
Installing Snap with -fhint and installing your project with -fdevelopment enables dynamic recompilation.
routes :: [(ByteString, Handler App App ())] routes = [ ("/", index) , ("/echo/:word", echo)] app :: SnapletInit App App app = makeSnaplet "app" "my cool app." Nothing $ do h <- nestSnaplet "heist" heist $ heistInit "templates" addRoutes routes return $ App h
index :: Handler App App () index = ifTop $ writeBS "hey i'm in your browser" echo :: Handler App App () echo = do (Just word) <- getParam "word" writeBS $ "Here's a word: " `mappend` word
data Post = Post { postID :: Maybe Integer , title :: Text , body :: Text , slug :: Text , timePosted :: UTCTime } deriving (Eq, Show, Read)
data Player = Player { playerID :: Maybe Integer , name :: Text , race :: Race , rating :: Integer } deriving (Eq, Show, Read) data Race = Terran | Protoss | Zerg deriving (Eq, Show, Read, Enum, Ord)
data Game = Game { gameID :: Maybe Integer , player1 :: Player , player2 :: Player , winner :: Player , timePlayed :: UTCTime , videoUrl :: ByteString } deriving (Eq, Show, Read)
Db.hsallPosts :: Connection -> IO [Post] playerByName :: Connection -> Text -> IO (Maybe Player) allPlayers :: Connection -> IO [Player] putPlayer :: Connection -> Player -> IO () games :: Connection -> IO [Game] game :: Connection -> Integer -> IO (Maybe Game) gamesByPlayer :: Connection -> Player -> IO [Game]
base.tpl
<div id="navigation">
<a href="/">Home</a>
<a href="/players">Players</a>
<a href="/games">Games</a>
<a href="/about">About</a>
</div>
<div id="blurb">
This is a blog about StarCraft 2
</div>
<div id="left-column">
<main/>
</div>
<div id="right-column">
<sidebar/>
</div>
Let's make a splice for displaying post info. It should take something like this ...
<posts> <h1><posttitle/></h1> <p><postbody/></p> <span>Posted on: <timeposted/></span> </posts>
<h1>Thorzain wins Dreamhack</h1> <p>The Swedish Terran Thorzain has won the Dreamhack tournament!</p> <span>Posted on: 2012-05-25 11:11:11 UTC</span> <h1>Oz switches to team Fnatic</h1> <p>The Korean Protoss Oz is now on the team Fnatic.</p> <span>Posted on: 2012-05-24 11:11:11 UTC</span> <h1>MVP wins his fourth GSL!</h1> <p>He's so dreamy.</p> <span>Posted on: 2012-05-21 11:11:11 UTC</span>
The splice will interact with the database!
mkPostSplice :: Post -> Splice AppHandler mkPostSplice p = runChildrenWithText [("posttitle", DB.title p) ,("postbody", DB.body p) ,("timeposted", showAsText $ DB.timePosted p) ]
This function takes a Post and returns a splice which will substitute
relevant tags with data from the Post.
mkPostSplice function gets this Post
Post { postID = Nothing
, title = "Thing happens"
, body = "Wow, a thing happened, this is amazing"
, slug = "thing-happens"
, timePosted = someTime
}
and we bind the resulting splice to the tag examplepost, Heist will transform each occurence of this
<examplepost> <h1><posttitle></h1> <p><postbody></p> </examplepost>
into this:
<h1>Thing happens</h1> <p>Wow, a thing happened, this is amazing</p>
postsSplice :: Splice AppHandler postsSplice = do conn <- lift $ gets _conn posts <- liftIO $ DB.allPosts conn mapSplices mkPostSplice posts
<posts> <h1><posttitle/></h1> <p><postbody/></p> <span>Posted on: <timeposted/></span> </posts>
<h1>Thorzain wins Dreamhack</h1> <p>The Swedish Terran Thorzain has won the Dreamhack tournament!</p> <span>Posted on: 2012-05-25 11:11:11 UTC</span> <h1>Oz switches to team Fnatic</h1> <p>The Korean Protoss Oz is now on the team Fnatic.</p> <span>Posted on: 2012-05-24 11:11:11 UTC</span> <h1>MVP wins his fourth GSL!</h1> <p>He's so dreamy.</p> <span>Posted on: 2012-05-21 11:11:11 UTC</span>
posts spliceapp :: SnapletInit App App app = makeSnaplet "app" "our sc2 site" Nothing $ do h <- nestSnaplet "heist" heist $ heistInit "templates" conn <- liftIO $ connectSqlite3 "data.db" addSplices $ map (second liftHeist) [ ("posts", postsSplice) ] addRoutes routes wrapHandlers (<|> the404) return $ App h conn
Because we bind it when our app is initialized, it's usable from anywhere within our app!
We don't have to do anything special in the handler. We just render index.tpl! That's because our splice was bound at initialization.
index :: Handler App App () index = ifTop $ render "index" routes :: [(ByteString, Handler App App ())] routes = [ ("/", index) , ("", serveDirectory "static") ]
index.tpl<apply template="base"> <bind tag="main"> <posts> <h1><posttitle/></h1> <p> <postbody/> <br> <span class="date">Posted on: <timeposted/></span> </p> </posts> </bind> <bind tag="sidebar"> </bind> </apply>
mkPlayerSplice :: Player -> Splice AppHandler mkPlayerSplice p = runChildrenWithText [("name", DB.name p) ,("race", showAsText $ DB.race p) ,("r", T.take 1 . showAsText $ DB.race p) ,("rating", showAsText $ DB.rating p) ]
playersSplice :: Splice AppHandler playersSplice = do conn <- lift $ gets _conn players <- liftIO $ DB.allPlayers conn mapSplices mkPlayerSplice players
players.tpl<apply template="base"> <bind tag="main"> ... <tbody> <players> <tr> <td><a href="/players/${name}"><name/></a></td> <td><race/></td> <td><rating/></td> </tr> </players> </tbody> ... </bind> <bind tag="sidebar"> </bind> </apply>
players spliceapp :: SnapletInit App App app = makeSnaplet "app" "our sc2 site" Nothing $ do h <- nestSnaplet "heist" heist $ heistInit "templates" conn <- liftIO $ connectSqlite3 "data.db" addSplices $ map (second liftHeist) [ ("posts", postsSplice) , ("players", playersSplice) ] addRoutes routes wrapHandlers (<|> the404) return $ App h conn
index :: Handler App App () index = ifTop $ render "index" players :: Handler App App () players = render "players" routes :: [(ByteString, Handler App App ())] routes = [ ("/", index) , ("/players", players) , ("", serveDirectory "static") ]
mkGameSplice :: Game -> Splice AppHandler mkGameSplice game = runChildrenWith [("id", textSplice $ maybe "invalid" showAsText $ DB.gameID game) ,("player1", mkPlayerSplice $ DB.player1 game) ,("player2", mkPlayerSplice $ DB.player2 game) ,("winner", mkPlayerSplice $ DB.winner game) ,("timeplayed", textSplice $ showAsText $ DB.timePlayed game) ,("videourl", textSplice $ bs2text $ DB.videoUrl game) ]
gamesSplice :: Splice AppHandler gamesSplice = do conn <- lift $ gets _conn games <- liftIO $ DB.games conn mapSplices mkGameSplice games
games.tpl<apply template="base"> <bind tag="main"> <ul> <games> <li> <a href="/games/${id}"> <player1><name/>(<r/>)</player1> vs. <player2><name/>(<r/>)</player2> </a> <br> <span class="date"><timeplayed/></span> </li> </games> </ul> </bind> <bind tag="sidebar"> </bind> </apply>
app :: SnapletInit App App app = makeSnaplet "app" "our sc2 blog" Nothing $ do h <- nestSnaplet "heist" heist $ heistInit "templates" conn <- liftIO $ connectSqlite3 "data.db" addSplices $ map (second liftHeist) [ ("posts", postsSplice) , ("players", playersSplice) , ("games", gamesSplice) ] addRoutes routes wrapHandlers (<|> the404) return $ App h conn
index :: Handler App App () index = ifTop $ render "index" players :: Handler App App () players = render "players" games :: Handler App App () games = render "games" routes :: [(ByteString, Handler App App ())] routes = [ ("/", index) , ("/players", players) , ("/games", games) , ("", serveDirectory "static") ]
player :: Handler App App () player = do (Just name) <- getParam "name" conn <- gets _conn mplayer <- liftIO $ DB.playerByName conn (bs2text name) case mplayer of Nothing -> the404 (Just player) -> do games <- liftIO $ DB.gamesByPlayer conn player let playerSplice = mkPlayerSplice player let gamesSplice = mapSplices mkGameSplice games heistLocal (bindSplice "player" playerSplice .bindSplice "games" gamesSplice ) $ render "player"
player.tpl<apply template="base"> <bind tag="main"> <player> <h1><name/></h1> <p>He plays <race/> and his rating is <rating/>.</p> <ul> <games> <li> <a href="/games/${id}"> <player1><name/>(<r/>)</player1> vs. <player2><name/>(<r/>)</player2> </a> <br> <span class="date"><timeplayed/></span> </li> </games> </ul> </player> </bind> <bind tag="sidebar"> </bind> </apply>
Snap.Snaplet.Authdigestive-functors package)snaplet-hdbcAlso thanks a million to
Roman Gonzalez, Jesse Heaslip & Tavis Rudd
#