happstack (empty) → 0.2.1
raw patch · 38 files changed
+1414/−0 lines, 38 filesdep +HStringTemplatedep +HUnitdep +basesetup-changedbinary-added
Dependencies added: HStringTemplate, HUnit, base, bytestring, directory, filepath, happstack-data, happstack-ixset, happstack-server, happstack-state, happstack-util, haskell-src-exts, hslogger, hsp, hsx, mtl, old-time, syb, utf8-string
Files
- CHANGELOG +34/−0
- COPYING +30/−0
- CREDITS +33/−0
- RELEASE_NOTES +216/−0
- Setup.hs +7/−0
- commands/happstack.hs +102/−0
- happstack.cabal +101/−0
- src/Happstack/Server/HSP/HTML.hs +37/−0
- src/Happstack/Server/HStringTemplate.hs +39/−0
- src/Happstack/State/ClockTime.hs +18/−0
- templates/project/Setup.hs +13/−0
- templates/project/bin/build.bat +1/−0
- templates/project/bin/build.sh +2/−0
- templates/project/bin/run-interactive.bat +1/−0
- templates/project/bin/run-interactive.sh +2/−0
- templates/project/bin/run.bat +1/−0
- templates/project/bin/run.sh +2/−0
- templates/project/guestbook.cabal +48/−0
- templates/project/public/theme/images/blockquote.gif binary
- templates/project/public/theme/images/date.gif binary
- templates/project/public/theme/images/entrymeta.gif binary
- templates/project/public/theme/images/grunge.gif binary
- templates/project/public/theme/images/header_loop.gif binary
- templates/project/public/theme/images/logo.gif binary
- templates/project/public/theme/images/menu_hili.gif binary
- templates/project/public/theme/images/menu_hover.gif binary
- templates/project/public/theme/images/peel.gif binary
- templates/project/public/theme/images/ql_rss.gif binary
- templates/project/public/theme/readme.txt +1/−0
- templates/project/public/theme/style.css +349/−0
- templates/project/src/AppControl.hs +43/−0
- templates/project/src/AppLogger.hs +25/−0
- templates/project/src/AppState.hs +59/−0
- templates/project/src/AppView.hs +118/−0
- templates/project/src/Main.hs +95/−0
- templates/project/templates/readme.st +6/−0
- tests/Happstack/Tests.hs +12/−0
- tests/Test.hs +19/−0
@@ -0,0 +1,34 @@+Happstack 0.2+ * Module namespace refactoring from HAppS to Happstack+ HAppS.Server -> Happstack.Server+ HAppS.State -> Happstack.State+ HAppS.Util -> Happstack.Util+ HAppS.IxSet -> Happstack.IxSet+ HAppS.Data -> Happstack.Data+ HAppS.Contrib -> Happstack.Contrib+ * Creation of additional package 'happstack'+ * Code generation tool added for creating "quick start" project folders+ * Removed dependency of happstack-server on happstack-state+ * Removed dependency of happstack-server on happstack-ixset+ * Module Cron module moved from happstack-server to happstack-util+ * Moved Atom, HList, HListBase from happstack-data to happstack-contrib+ * Monadic machinery for ServerPartT and WebT trimmed / cleaned / optimized (Daniel Rogers)+ * New experimental gzip filter in happstack-server (similar to apache mod_deflate)+ * Builtin support for HSP, HStringTemplate (see example template / release notes)+ * happstack-server once again builds on Windows+ * Nearly all the exported functions have been documented!+ * Cookie fix from happstack-helpers was integrated into the main code+ +Happstack 0.1+ * Cabal packaging name change:+ HAppS-Server -> happstack-server+ HAppS-State -> happstack-state+ HAppS-Util -> happstack-util+ HAppS-IxSet -> happstack-ixset+ HAppS-Data -> happstack-data+ * Creation of additional package 'happstack-contrib' (HAppS.Contrib)+ * Moved Facebook module from happstack-server to happstack-contrib+ * Fixed various warnings+ * Removed dead modules and many dead code blocks+ * Turned name resolution off for peers in HTTP.Listen (fixes getNameInfo crash)+
@@ -0,0 +1,30 @@+Copyright 2009 Happstack.com+Copyright 2005 HAppS LLC+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.++Neither the name of the HAppS.org; nor the names of its contributors+may be used to endorse or promote products derived from this software+without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"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 OWNER OR+CONTRIBUTORS 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.
@@ -0,0 +1,33 @@+-- This file exists to honor the various contributions by individuals to+-- the Happstack Project. Among these there will undoubtedly be ones I have+-- missed. Please contact the mailing list at happs@googlegroups.com if updates+-- need to be made. Much thanks to all our contributors!+-- +-- Sincerely,+-- Matthew Elder <matt@mattelder.org>++Alex Jacobson+ - HAppS founder+ - HAppS Evangelist+Andrea Vezzosi+ - Worked on original HAppS Project+Creighton Hogg+ - Code Cleanup+ - Maintainer of HAppS/Happstack Tutorial+Doug Beardsley+ - Creator of various HAppS tutorials+Matthew Elder <matt@mattelder.org>+ - Happstack Founder+ - HAppS/Happstack Evangelist+ - Code Generation Tool (Project Template)+Jeremy Shaw+ - Creator of many example applications+ - Hyena integration+ - HSP Integration+ - Made Project Template Pretty+Lemmih (replace with real name?)+ - Worked on original HAppS Project+ - Multimaster Support+Thomas Hartman+ - Creator of HAppS Tutorial+
@@ -0,0 +1,216 @@+Happstack 0.2 Release Notes+================================================================================++ Project / Code generation tool+ -------------------------------+ There is now a code generation tool for Happstack. This is inspired by the+ 'rails' command.+ + 1. First install happstack (cabal install happstack)+ + 2. Make sure the cabal bin dir is in your path+ - in linux this is+ ~/.cabal/bin+ - in windows this is+ C:\Documents and Settings\<username>\Application Data\cabal\bin+ where <username> is your windows username.+ + 3. Run this command:+ happstack new project <dir>+ where <dir> is the folder you want to create the project in.+ + 4. It will create several folders:+ - bin (scripts for your project go here)+ - dist (where deployment files will go)+ - public (static files for the http server)+ - src (project source code goes here)+ - templates (HStringTemplate files go here)+ + 5. There are three scripts to get your project kick-started+ - bin/build (build a static compiled binary at dist/app)+ - bin/run (run the project dynamically)+ - bin/run-interactive (run the project interactively in ghci)+ Each script has a .sh version if you are on *nix and a .bat version if+ you are on windows.+ + 6. By default, your project will bind to port 8000 on every ip address your+ machine has.+ + 7. There are five source files in the src folder by default+ - AppControl.hs (routing and controller code goes here)+ - AppState.hs (MACID state objects are defined here)+ - AppView.hs (presentation / view code goes here)+ - AppLogger.hs (controls logging for the hslogger system)+ - Main.hs (entry point to your app, is has the routines needed to+ run your application)+ + 8. Enjoy!+ + The WebT/ServerPartT change+ -------------------------------+ WebT and ServerPartT have been substantially refactored. They are now built+ from simpler monads. ServerPartT is now the only monad you have to deal+ with, most of the time. I.e. you almost never have to look at WebT again.+ You just have do blocks in ServerPartT. Inside ServerPartT you can get your+ request (askRq), modify your request (localRq), exit immediately+ (finishWith), exit failure (mzero), and alter your response filters+ (composeFilter, setFilter, getFilter) It also lifts ErrorT, ReaderT, WriterT+ and StateT, so you can embed one of those as well. As a bonus, nearly all+ the functions you expected to take a WebT or to operate only within a WebT+ can now work inside a ServerPartT directly. The monad stack handles all the+ data threading.+ + That being said, here are a list of the non-backwards compatible changes.+ ServerPartT, WebT, unServerPartT and unWebT now have different semantics. To+ get existing code to compile you can change your methods like so:++ ServerPartT -> withRequest+ WebT -> mkWebT+ unServerPartT -> runServerPartT+ unWebT -> ununWebT or runWebT depending on what you're going for.++ By just making the above changes, your existing code should continue to work+ as before. However, at this point, any old code you have can be greatly+ simplified. For example, you will never have to use anyRequest or+ withRequest to change from ServerPartT to WebT. You never have to interact+ with WebT directly. You can do everything in a ServerPartT do block. The+ examples provided in the documentation (especially basicAuth') just barely+ touch on the power of ServerPartT now. Notice that basicAuth' mixes reading+ requests and altering responses all without exposing WebT, constructing a+ ServerPartT, or really doing any data threading at all.++ As a quick example:++ This:+ uriRest :: Monad m => (String -> ServerPartT m a) -> ServerPartT m a+ uriRest handle = withRequest $ \rq ->+ unServerPartT (handle (rqURL rq)) rq++ Becomes this:+ uriRest :: (ServerMonad m, Monad m) => (String -> m a) -> m a+ uriRest handle = askRq >>= handle . rqURL++ Every single function that uses withRequest, anyRequest, unServerPartT,+ unWebT or ununWebT can be rewritten to take advantage of these simple monad+ behaviors. Additionally, a lot of functions were added to make it easier to+ work entirely inside a do block.+ + getHeaderM, addHeaderM, getData, getDataFn, escape', finishWith, getFilter,+ setFilter, composeFilter, applyFilter, setResponseCode, guardRq, methodM,+ methodOnly, nullDir, requireM++ The following were deprecated since they duplicate functionality+ + noHandle -> Use mzero+ multi -> Use msum+ method -> you should be able to use methodSP though you'll need to fix+ your typing+ modifyResponse -> use composeFilter++ Also, this release sees the return of simpleHTTP', though not as you knew it+ before. simpleHTTP must take a ServerPartT IO a. simpleHTTP' lets you use+ Any ServerPartT m a, as long as you provide a function for unwrapping your+ interior monad into an IO monad. This is particularly important since+ ServerPartT has lifting instances for WriterT, StateT, ErrorT, and ReaderT,+ thus embedding your favorite monad transformer into ServerPartT is easier+ than ever.++ Documentation+ -------------------------------+ This release also adds substantial documentation for the package+ Happstack.Server.SimpleHTTP which convers most of the new structures added,+ as well as many of your old favorites. Be sure to check it out.++ Removing list types from SimpleHTTP+ -------------------------------+ Having most of the methods in SimpleHTTP take array types was cumbersome,+ since often you only had one part and had to wrap it up. All methods that+ used to take a list of ServerPartTs now only take a single ServerPartT. To+ convert your list of ServerPartT into a single ServerPartT, use msum.++ For example:+ simpleHTTP nullConf [spart1, spart2]+ Becomes:+ simpleHTTP nullConf $ msum [spart1, spart2]+ + The functions affected by this change are:+ simpleHTTP, dir, path, anyPath, withData, withDataFn, require, requireM,+ xslt, debugFilter, applyRequest, basicAuth, and errorHandlerSP++ Gzip/Deflate encoding+ ------------------------------+ There is now a ServerPartT for gzip/deflate encoding, in+ Happstack.Server.Parts.compressedResponseFilter++ Guidlines for porting existing HAppS and Happstack <= 0.1 code to 0.2+ -------------------------------+ The first change you should do is change the following functions+ as described above.+ ServerPartT -> withRequest+ WebT -> mkWebT+ unServerPartT -> runServerPartT+ unWebT -> ununWebT+ + Next, work on prepending $ msum in front of every list of ServerPartTs, or+ deleting lists that are no longer necessary.++ After this point, you should be able to get your app compiled and running.+ To further clean up your code and take advantage of the new functionality in+ ServerPartT, the general procedure is as follows:++ - consider studying the revised examples in happstack-server/Examples. They+ all use the new idioms.+ + - delete your type signatures that reference WebT or ServerPartT, as after+ the first step they will all have changed dramatically++ - delete all uses of withRequest and anyRequest (they aren't needed anymore)++ - change the idiom 'method m $' to 'methodM m >>'++ - change 'withRequest \rq -> do ...' to 'do { rq <- askRq; ... }'++ - change withData \myDat $ \dat -> do ... to+ do dat <- getData >>= maybe mzero return+ ... + + - change withDataFn fn \myDat $ \dat -> do ... to+ do dat <- getDataFn fn >>= maybe mzero return+ ...++ - change all references of noHandle to mzero+ + - you can probably benefit from some do blocks at this point++ - if you're passing around the request explictly, stop. You can just+ "rq <- askRq" whenever you need it.++ - consider embedding your favorite monad transformer into your ServerPartTs.+ To make this work, you'll want to read the documentation for simpleHTTP',+ mapServerPartT, and WebT+ + - consider reading the newly minted documentation for+ Happstack.Server.SimpleHTTP. Hopefully you'll learn something.++ Windows Bin Path Caveats+ ------------------------------+ By default when you install say ghc 6.8.3 or 6.10.1, windows won't setup the paths.+ You need to perform these steps after running the installer for ghc:+ 1. Right Click on "My Computer"+ 2. Left Click on "Properties"+ 3. Advanced Tab+ 4. Environmental Variables+ 5. Add or Edit existing 'PATH' variable+ If you have ghc 6.8.3 for instance:+ 1. Prepend "C:\ghc\ghc-6.8.3;C:\ghc\ghc-6.8.3\bin;" to the Variable value+ 2. Click OK+ 3. Restart and cmd windows you have open+ 6. Now all the commands should work i.e. ghci, ghc-pkg, perl etc.+ 7. You will need happy to compile happstack+ Follow these steps:+ 1. Install happy via cabal install or the manual method+ 2. Prepend "C:\Documents and Settings\user\Application Data\cabal\bin"+ to your Environment Path variable where user is your username.+ 3. Now any binaries installed by cabal packages will also be in your+ command path.+
@@ -0,0 +1,7 @@+module Main (main) where++import Distribution.Simple++main :: IO ()+main = defaultMain+
@@ -0,0 +1,102 @@+-- happstack command+module Main where++import Paths_happstack (getDataDir)+import System.Environment (getArgs)+import System.Directory (createDirectoryIfMissing, doesFileExist, copyFile)+import System.FilePath ((</>))+import Control.Monad (mapM_)++data Command+ = NewProjectCmd FilePath+ | HelpCmd++main :: IO ()+main = do+ args <- getArgs+ case (processArgs args) of+ NewProjectCmd dir -> do+ newProject dir+ HelpCmd -> do+ putStrLn "Usage: happstack <command>"+ putStrLn "Possible commands:"+ putStrLn " new project <dir>: create a new happstack project"+ +processArgs :: [String] -> Command+processArgs ["new", "project", dir] = NewProjectCmd dir+processArgs ["help"] = HelpCmd+processArgs _ = HelpCmd+ +newProject :: FilePath -> IO ()+newProject destDir = do+ dataDir <- getDataDir+ + -- create destDir if needed+ createDirectoryIfMissing True destDir+ + -- create folders+ mapM_ (createDirectoryIfMissing False) (destinationFolders destDir)+ + -- copy files+ mapM_ cp $ zip (sourceFiles dataDir) (destinationFiles destDir)+ +-- destination folders that need to be created+destinationFolders destDir =+ map+ (destDir </>) + [ "bin"+ , "dist"+ , "public"+ , "public/theme"+ , "public/theme/images"+ , "src"+ , "templates"+ ]+ +-- source files for project skeleton (needs to know dataDir location)+sourceFiles dataDir =+ map (dataDir </>) projectFiles++-- destination files for project skeleton+destinationFiles destDir =+ map ((destDir </>) . removePrefix) projectFiles+ where removePrefix = drop $ length "templates/project/" ++-- all files in the data dir+projectFiles = + [ "templates/project/bin/build.bat"+ , "templates/project/bin/build.sh"+ , "templates/project/bin/run-interactive.bat"+ , "templates/project/bin/run-interactive.sh"+ , "templates/project/bin/run.bat"+ , "templates/project/bin/run.sh"+ , "templates/project/guestbook.cabal"+ , "templates/project/public/theme/images/blockquote.gif"+ , "templates/project/public/theme/images/date.gif"+ , "templates/project/public/theme/images/entrymeta.gif"+ , "templates/project/public/theme/images/grunge.gif"+ , "templates/project/public/theme/images/header_loop.gif"+ , "templates/project/public/theme/images/logo.gif"+ , "templates/project/public/theme/images/menu_hili.gif"+ , "templates/project/public/theme/images/menu_hover.gif"+ , "templates/project/public/theme/images/peel.gif"+ , "templates/project/public/theme/images/ql_rss.gif"+ , "templates/project/public/theme/readme.txt"+ , "templates/project/public/theme/style.css"+ , "templates/project/Setup.hs"+ , "templates/project/src/AppControl.hs"+ , "templates/project/src/AppLogger.hs"+ , "templates/project/src/AppState.hs"+ , "templates/project/src/AppView.hs"+ , "templates/project/src/Main.hs"+ , "templates/project/templates/readme.st"+ ]++-- does not work for folders+cp :: (FilePath, FilePath) -> IO ()+cp (src, dest) = do+ exists <- doesFileExist dest+ if exists+ then putStrLn $ "File exists already, skipping: " ++ dest+ else copyFile src dest+
@@ -0,0 +1,101 @@+Name: happstack+Version: 0.2.1+Synopsis: The haskell application server stack + code generation+Description: The haskell application server stack+License: BSD3+License-file: COPYING+Author: Happstack team, HAppS LLC+Maintainer: Happstack team <happs@googlegroups.com>+homepage: http://happstack.com+Category: Web, Distributed Computing+Build-Type: Simple+Cabal-Version: >= 1.4++data-files: CHANGELOG+ CREDITS+ RELEASE_NOTES+ templates/project/bin/build.bat+ templates/project/bin/build.sh+ templates/project/bin/run-interactive.bat+ templates/project/bin/run-interactive.sh+ templates/project/bin/run.bat+ templates/project/bin/run.sh+ templates/project/guestbook.cabal+ templates/project/public/theme/images/blockquote.gif+ templates/project/public/theme/images/date.gif+ templates/project/public/theme/images/entrymeta.gif+ templates/project/public/theme/images/grunge.gif+ templates/project/public/theme/images/header_loop.gif+ templates/project/public/theme/images/logo.gif+ templates/project/public/theme/images/menu_hili.gif+ templates/project/public/theme/images/menu_hover.gif+ templates/project/public/theme/images/peel.gif+ templates/project/public/theme/images/ql_rss.gif+ templates/project/public/theme/readme.txt+ templates/project/public/theme/style.css+ templates/project/Setup.hs+ templates/project/src/AppControl.hs+ templates/project/src/AppLogger.hs+ templates/project/src/AppState.hs+ templates/project/src/AppView.hs+ templates/project/src/Main.hs+ templates/project/templates/readme.st++Flag base4+ Description: Choose the even newer, even smaller, split-up base package.++Flag tests+ Description: Build the testsuite, and include the tests in the library+ Default: False++Library+ exposed-modules: Happstack.Server.HSP.HTML+ Happstack.Server.HStringTemplate+ Happstack.State.ClockTime+ if flag(tests)+ Exposed-modules: Happstack.Tests+ other-modules: Paths_happstack++ build-depends: base >= 3,+ bytestring,+ happstack-data >= 0.2.1 && < 0.3,+ happstack-ixset >= 0.2.1 && < 0.3,+ happstack-server >= 0.2.1 && < 0.3,+ happstack-state >= 0.2.1 && < 0.3,+ happstack-util >= 0.2.1 && < 0.3,+ hslogger,+ hsp >= 0.4.5 && < 0.5,+ HStringTemplate >= 0.4.3 && < 0.5,+ mtl,+ old-time,+ utf8-string+ + if flag(base4)+ Build-Depends: base >= 4 && < 5, syb+ else+ build-depends: haskell-src-exts == 0.3.9,+ hsx == 0.4.5++ hs-source-dirs: src+ if flag(tests)+ hs-source-dirs: tests++ ghc-options: -Wall++Executable happstack+ build-depends: directory >= 1,+ filepath >= 1+ + hs-source-dirs: commands+ main-is: happstack.hs+++Executable happstack-tests+ Main-Is: Test.hs+ GHC-Options: -threaded+ Build-depends: HUnit+ hs-source-dirs: tests, src+ if flag(tests)+ Buildable: True+ else+ Buildable: False
@@ -0,0 +1,37 @@+{-# LANGUAGE FlexibleInstances, FlexibleContexts #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Happstack.Server.HSP.HTML+ ( webHSP+ ) where++import Control.Monad.Trans (MonadIO(), liftIO)+import qualified Data.ByteString.Char8 as P+import qualified Data.ByteString.Lazy.UTF8 as L+import Happstack.Server+ ( ToMessage(toMessage, toContentType, toResponse)+ , Response()+ )+import HSP+ ( HSP()+ , XML()+ , XMLMetaData(XMLMetaData, contentType)+ , evalHSP+ , html4Strict+ , renderAsHTML+ )++instance ToMessage XML where+ toContentType _ = P.pack "text/html;charset=utf-8"+ toMessage xml = toMessage (html4Strict, xml)++instance ToMessage (Maybe XMLMetaData, XML) where+ toContentType (Just md,_) = P.pack (contentType md)+ toContentType _ = P.pack "text/html;charset=utf-8"+ toMessage (Just (XMLMetaData (showDt, dt) _ pr), xml) = + L.fromString ((if showDt then (dt ++) else id) (pr xml))+ toMessage (Nothing, xml) =+ L.fromString (renderAsHTML xml)++webHSP :: (MonadIO m) => HSP XML -> m Response+webHSP hsp = return . toResponse =<< liftIO (evalHSP Nothing hsp)+
@@ -0,0 +1,39 @@+{-# LANGUAGE ScopedTypeVariables, FlexibleInstances, PatternSignatures #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Happstack.Server.HStringTemplate (webST) where++import Happstack.Server+ ( ToMessage(toMessage, toContentType, toResponse)+ , Response+ )+import Control.Monad.Trans (MonadIO, liftIO)+import Text.StringTemplate+ ( STGroup+ , StringTemplate+ , directoryGroupLazy+ , getStringTemplate+ , render+ , setManyAttrib+ )+import qualified Data.ByteString.Char8 as B (pack)+import qualified Data.ByteString.Lazy.Char8 as L (pack)+import System.Log.Logger (Priority(..), logM)+log' :: Priority -> String -> IO ()+log' = logM "Happstack.Server.HStringTemplate"++-- FIXME: Caveat, assumes text/html, can this be handled at the filter level?+instance ToMessage (StringTemplate String) where+ toContentType _ = B.pack "text/plain;charset=utf-8"+ toMessage = L.pack . render++-- renders a name template with attrs+webST :: (MonadIO m) => String -> [(String, String)] -> m Response+webST name attrs = do+ grp :: STGroup String <- liftIO $ directoryGroupLazy "templates"+ case getStringTemplate name grp of+ Just tmp -> do+ -- FIXME: I would rather use show, but StringTemplate String has no Show instance+ liftIO $ log' INFO ("webST executing: " ++ name)+ return . toResponse $ setManyAttrib attrs tmp+ Nothing -> fail $ "Template does not exist: " ++ name+
@@ -0,0 +1,18 @@+{-# LANGUAGE TemplateHaskell, TypeFamilies, DeriveDataTypeable,+ FlexibleInstances, MultiParamTypeClasses, FlexibleContexts,+ UndecidableInstances, StandaloneDeriving, TypeSynonymInstances+ #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- |instances of Typeable, Data, Serialize, Version, and Default for ClockTime+module Happstack.State.ClockTime (ClockTime(..)) where++import Data.Generics (Data, Typeable)+import Happstack.Data (deriveNewData)+import Happstack.State (Version, deriveSerialize)+import System.Time (ClockTime(..))++deriving instance Typeable ClockTime+deriving instance Data ClockTime+instance Version ClockTime+$(deriveSerialize ''ClockTime)+$(deriveNewData [''ClockTime])
@@ -0,0 +1,13 @@+#!/usr/bin/env runghc++module Main where++import Distribution.Simple+import Distribution.Simple.Program++trhsxProgram = simpleProgram "trhsx"++main :: IO ()+main = defaultMainWithHooks simpleUserHooks {+ hookedPrograms = [trhsxProgram]+ }
@@ -0,0 +1,1 @@+ghc -isrc src\Main.hs --make -o dist\app
@@ -0,0 +1,2 @@+#!/bin/sh+ghc -isrc src/Main.hs --make -o dist/app
@@ -0,0 +1,1 @@+ghci -isrc src\Main.hs
@@ -0,0 +1,2 @@+#!/bin/sh+ghci -isrc src/Main.hs
@@ -0,0 +1,1 @@+runghc -isrc src\Main.hs
@@ -0,0 +1,2 @@+#!/bin/sh+runghc -isrc src/Main.hs
@@ -0,0 +1,48 @@+Name: guestbook+Version: 1.0+Author: Jeremy Shaw <jeremy@seereason.com>, Matthew Elder <matt@mattelder.org>+Copyright: 2009 Jeremy Shaw+Stability: Experimental+Category: Web+Synopsis: An example guestbook application which uses happstack and HSP+Description:+ This small example guestbook makes an excellent starting point for+ your own happstack applications.+Cabal-version: >= 1.4.0.0+build-type: Custom+Data-files:+ public/theme/style.css+ public/theme/images/date.gif+ public/theme/images/ql_rss.gif+ public/theme/images/header_loop.gif+ public/theme/images/Thumbs.db+ public/theme/images/blockquote.gif+ public/theme/images/menu_hili.gif+ public/theme/images/grunge.gif+ public/theme/images/entrymeta.gif+ public/theme/images/logo.gif+ public/theme/images/peel.gif+ public/theme/images/menu_hover.gif+ public/theme/readme.txt++Flag base4+ Description: Choose the even newer, even smaller, split-up base package.++Library+ Exposed-Modules:+ AppControl,+ AppLogger,+ AppState+ hs-source-dirs: src++ GHC-Options: -threaded -Wall -Wwarn -O2 -fno-warn-name-shadowing -fno-warn-missing-signatures -fwarn-tabs -fno-warn-unused-binds -fno-warn-orphans -fwarn-unused-imports+ Build-depends: happstack-state, happstack-server, hslogger, mtl, old-time, old-locale, hsx, hsp, happstack, utf8-string, happstack-data+ if flag(base4)+ Build-Depends: base >= 4 && < 5, syb++Executable guestbook-server+ Main-Is: Main.hs+ hs-source-dirs: src+ GHC-options: -threaded -Wall -Wwarn -O2 -fno-warn-name-shadowing -fno-warn-missing-signatures -fwarn-tabs -fno-warn-unused-binds -fno-warn-orphans -fwarn-unused-imports+ Build-depends: happstack-util+ Build-Tools: trhsx
binary file changed (absent → 103 bytes)
binary file changed (absent → 941 bytes)
binary file changed (absent → 182 bytes)
binary file changed (absent → 3669 bytes)
binary file changed (absent → 207 bytes)
binary file changed (absent → 3102 bytes)
binary file changed (absent → 313 bytes)
binary file changed (absent → 205 bytes)
binary file changed (absent → 4961 bytes)
binary file changed (absent → 296 bytes)
@@ -0,0 +1,1 @@+Theme by Dezinerfolio.com
@@ -0,0 +1,349 @@+ +/* +Theme Name: Dezinerfolio (s)impress +Theme URI: dezinerfolio.com +Description: Simple and Clean theme +Version: 2.0 +Author: Navdeep Raj +*/ + +*{ + margin:0; + padding:0; +} + +body{ + font-family: arial, helvetica; + font-size:14px; + background: #fff; +} + +#header{ + height:98px; + clear:both; + background:#FFF url(images/header_loop.gif) repeat-x 0 0; +} + +.mainlogo{ + float:left; + padding:0px 50px; +} + +.mainlogo a{ + background:url(images/logo.gif) no-repeat 0 0; + width:144px; + height:98px; + display:block; + overflow:hidden; + text-indent:-9999px; +} + +.topnavi li{ + float:left; + list-style:none; + overflow:hidden; +} + +.topnavi .page_item{ + background:url(images/header_loop.gif) repeat-x left top; + border-left:1px solid #e4e4e4; + border-right:1px solid #e4e4e4; + display:block; +} + +.topnavi li a{ + padding:60px 20px 0 20px; + height:27px; + display:block; + float:left; + text-decoration:none; + font-size:12px; + color:#585858; +} + +.topnavi li a:hover{ + background:url(images/menu_hover.gif) repeat-x 0 0; +} + +.topnavi .current_page_item{ + background:url(images/menu_hili.gif) repeat-x left top; + border:1px solid #3e8709; + overflow:hidden; + display:block; +} + +.topnavi .current_page_item a:hover{ + background:url(images/menu_hili.gif) repeat-x left top; +} + +.topnavi .current_page_item a{ + color:#FFF; + font-weight:bold; +} + +.grunge{ + background:url(images/grunge.gif); + width:105px; + height:98px; + display:block; + float:left; +} + +.side1{ + float:left; + width:200px; + padding:12px +} + +.side2{ + float:left; + width:200px; + padding:12px; +} + +.sbar_section{ + padding:10px 0; +} + +.side1 li, .side2 li{ + list-style:none; +} + +.side1 li a, .side2 li a{ + padding:8px 5px; + display:block; + border-bottom:1px solid #CDCDCD; + text-decoration:none; + color:#555; +} + +.side1 li a:hover, .side2 li a:hover{ + border-bottom:1px solid #888; + color:#222; + background:#f1f1f1; +} + +.side1{ + border-right:1px solid #ddd; +} + +.side1 h2, .side2 h2{ + color:#333; + font-size:16px; + padding:5px 0; +} + +.sbar_section #s{ + padding:4px; + border:1px solid #CDCDCD; + text-align:center; + font-family:Georgia, "Times New Roman", Times, serif; + color:#555; + width:185px; + background:#f1f1f1; +} + +.sbar_section #s:hover, .sbar_section #s:focus{ + border:1px solid #888; + background:#FFF; +} + +.peel{ + background:url(images/peel.gif); + width:102px; + height:129px; + overflow:hidden; + display:block; + position:absolute; + z-index:9999px; + right:0; + top:0; +} + +.wrap{ + padding-left:265px; + color:#333; + padding-right:25px; +} + +.wrap h1 a, .wrap h1{ + color:#59ba12; + font-size:20px; + font-family:Arial, Helvetica, sans-serif; + font-weight:bold; + text-decoration:none; +} + + +.wrap p{ + padding:15px 0px; + line-height:19px; +} + +.wrap img{ + padding:10px; +} + +.wrap ul{ + padding-left:20px; +} +.wrap li{ + padding:5px; +} + +.wrap ul p{ + padding:0; +} + +blockquote { + background:transparent url(images/blockquote.gif) no-repeat scroll left top; + line-height:18px; +} + + + + +.wrap h2 a, .wrap h2{ + color:#333333; + font-size:18px; + font-family:Arial, Helvetica, sans-serif; + font-weight:normal; +} + +.entry-meta{ + background:red; + display:block; + font-size:11px; + background:url(images/entrymeta.gif) repeat-x left bottom; + padding:12px; + border:1px solid #cfcfcf; + line-height:18px; +} + +.entry-meta a{ + text-decoration:none; + color:#4a9d0d; +} + +.entry-meta a:hover{ + color:#0066FF; +} + + + +.date{ + float:left; + background:red; + background:url(images/date.gif) no-repeat; + width:45px; + height:48px; + margin-top:-8px; + padding-top:2px; + font-size:16px; + color:#FFFFFF; + font-weight:bold; + padding-left:11px; + z-index:9999999999999px; +} + +.date div{ + color:#999999; + font-size:11px; + padding-top:8px; +} + +.posttop{ + border-bottom:2px solid #52af10; + padding-bottom:25px; + margin-bottom:8px; +} + +.posttop .feedback a{ + color:#666; + text-decoration:none; + float:right; + padding-top:5px; +} + +.footer{ + padding:15px; + background:#222; + clear:both; + text-align:center; + color:#999; + margin-top:25px; +} + +.footer a{ + color:#59ba12; + text-decoration:none; +} + +.commentlist li{ + list-style:none; +} + +#commentform p{ + padding:5px; +} + +#comment, #author, #email, #url{ + width:250px; + padding:3px; + font-family:Arial, Helvetica, sans-serif; + font-size:12px; + color:#333; + border:1px solid #AAA; +} + + +#comment:hover, #author:hover, #email:hover, #url:hover, #comment:focus, #author:focus, #email:focus, #url:focus{ + border:1px solid #333; +} + +#commentform label{ + color:#666666; +} + +#commentform #submit{ + padding:5px 15px; + border:none; + width:260px; + text-align:center; + background:#FFF url(images/entrymeta.gif) repeat-x left bottom; + color:#666; + border:1px solid #999; + cursor:pointer; +} + +.wrap ul.commentlist{ + padding:20px 0px; +} + +.commentlist li{ + padding:10px; + border-bottom:1px solid #AAA; +} + +.commentmetadata +{ + color:#999; + } + +.commentlist .commentmetadata a{ + color:#999; +} + +.commentlist li.alt{ + background:#F4F4F4; +} + +.storycontent a, #commentform a, .commentlist li a{ + color:#009900; + text-decoration:none; +} + +.accesskey +{ + text-decoration: underline; + }
@@ -0,0 +1,43 @@+{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-}+module AppControl (appHandler) where++import AppState+import AppView+import Control.Applicative((<$>))+import Control.Monad(msum)+import Control.Monad.Trans(liftIO, MonadIO)+import Data.ByteString.Lazy.UTF8 (toString)+import Happstack.Data (defaultValue)+import Happstack.Server+import Happstack.State (update,query)+import System.Time (getClockTime)++appHandler :: ServerPartT IO Response+appHandler = msum+ [ methodM GET >> seeOther "/entries" (toResponse ()) -- matches /+ , dir "entries" $ msum[postEntry, getEntries] -- RESTful /entries+ , dir "README" getREADME -- StringTemplate example+ , fileServe ["index.html"] "public" -- static files+ ]++getEntries = methodM GET >> do+ gb <- query ReadGuestBook+ renderFromBody "Happstack Guestbook Example" gb++getREADME = methodM GET >> do+ now <- liftIO getClockTime+ renderREADME now++postEntry = methodM POST >> do -- only accept a post method+ Just entry <- getData -- get the data+ now <- liftIO getClockTime+ update $ AddGuestBookEntry entry{date=now}+ seeOther "/entries" (toResponse ())++-- this tells happstack how to turn post data into a datatype using 'withData'+instance FromData GuestBookEntry where+ fromData = do+ author <- toString <$> lookBS "author"+ message <- toString <$> lookBS "message"+ return $ GuestBookEntry (if (null author) then "Anonymous" else author) message defaultValue+
@@ -0,0 +1,25 @@+module AppLogger (setupLogger) where++import System.Log.Logger+ ( Priority(..)+ , setLevel+ , setHandlers+ , getLogger+ , getRootLogger+ , saveGlobalLogger+ )+import System.Log.Handler.Simple (fileHandler, streamHandler)+import System.IO (stdout)++setupLogger = do+ logFileHandler <- fileHandler ("app.log") DEBUG+ stdoutHandler <- streamHandler stdout DEBUG++ -- Log Everything to app.log+ server <- getRootLogger+ saveGlobalLogger $ setLevel DEBUG $ setHandlers [logFileHandler] server++ -- Log Happstack.Server messages of at least INFO priority to stdout+ server <- getLogger "Happstack.Server"+ saveGlobalLogger $ setLevel INFO $ setHandlers [stdoutHandler] server+
@@ -0,0 +1,59 @@+{-# LANGUAGE TemplateHaskell, TypeFamilies, DeriveDataTypeable,+ FlexibleInstances, MultiParamTypeClasses, FlexibleContexts,+ UndecidableInstances+ #-}+module AppState where+import Happstack.Data+import Happstack.State+import Control.Monad.Reader (ask)+import Control.Monad.State (get, put)+import Happstack.State.ClockTime++-- GuestBookEntry: simple guest book entry+$(deriveAll [''Show, ''Eq, ''Ord]+ [d|+ data GuestBookEntry = GuestBookEntry+ { author :: String+ , message :: String+ , date :: ClockTime+ }+ + newtype GuestBook = GuestBook { guestBookEntries :: [GuestBookEntry] }+ |]+ )++$(deriveSerialize ''GuestBookEntry)+instance Version GuestBookEntry++$(deriveSerialize ''GuestBook)+instance Version GuestBook++-- AppState: define our own component, 'AppState' for data persistence+$(deriveAll [''Show, ''Eq, ''Ord]+ [d|+ data AppState = AppState+ { guestBook :: GuestBook+ }+ |])+$(deriveSerialize ''AppState)+instance Version AppState++instance Component AppState where+ type Dependencies AppState = End+ initialValue = AppState (GuestBook []) -- empty list of GuestBookEntry's++-- readGuestBook: get the guestBook from AppState+readGuestBook :: Query AppState GuestBook+readGuestBook = do+ state <- ask+ return $ guestBook state+ +addGuestBookEntry :: GuestBookEntry -> Update AppState ()+addGuestBookEntry e = do+ st <- get+ let (GuestBook gb) = guestBook st+ put st{guestBook=GuestBook (e:gb)}+ +-- create types for event serialization+$(mkMethods ''AppState ['readGuestBook, 'addGuestBookEntry])+
@@ -0,0 +1,118 @@+{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-}+{-# OPTIONS_GHC -F -pgmFtrhsx #-}+module AppView where++import AppState+import HSP+import System.Locale (defaultTimeLocale)+import System.Time (formatCalendarTime, toUTCTime)+import Control.Monad.Trans (MonadIO)+import Happstack.Server.HStringTemplate (webST)+import Happstack.Server.HSP.HTML (webHSP)++-- Convenience Functions+dateStr ct =+ formatCalendarTime+ defaultTimeLocale+ "%a, %B %d, %Y at %H:%M:%S (UTC)"+ (toUTCTime ct)++-- Main Implementation+instance (XMLGenerator m) => (EmbedAsChild m (GuestBookEntry, Bool)) where+ asChild ((GuestBookEntry author message date), alt) =+ <%+ <li class=(if alt then "alt" else "")>+ <strong><% author %></strong> said:<br /><br />+ <% map p (lines message) %>+ <br />+ <small class="commentmetadata"><% dateStr date %></small> + </li>+ %>+ where p str = <p><% str %></p>++instance (XMLGenerator m) => (EmbedAsChild m GuestBook) where+ asChild (GuestBook entries) = + <% + <div>+ <h2 id="comments" class="h2comment">Words of Wisdom</h2>+ <div class="clear" />+ <ul class="commentlist">+ <% zip entries (cycle [False,True]) %>+ </ul>+ </div>+ %>++renderFromBody title body = webHSP $ pageFromBody title body+pageFromBody :: (EmbedAsChild (HSPT' IO) xml) => String -> xml -> HSP XML+pageFromBody title body =+ withMetaData html4Strict $+ <html>+ <head>+ <title><% title %></title>+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />+ <link rel="stylesheet" type="text/css" href="/theme/style.css" media="screen" />+ </head>+ <body>+ <div id="header">+ <div class="grunge"></div>+ <div class="peel"></div>+ <div class="topnavi">+ <ul>+ <li class="current_page_item"><a href="/" title="Guestbook">Guestbook</a></li>+ </ul>+ </div>+ </div>++ <div class="side1">+ <div class="sbar_section">+ <h2>Links</h2>+ <ul>+ <li class="cat-item cat-item-1"><a href="http://happstack.com/" title="happstack" accesskey="H"><span class="accesskey">H</span>appstack</a></li>+ <li class="cat-item cat-item-2"><a href="http://happstack.com/tutorials.html" title="happstack" accesskey="T"><span class="accesskey">T</span>utorials</a></li>+ </ul>+ </div>+ </div>++ <div class="wrap">+ <div class="innercont_main">++ <div class="post">+ <div class="posttop">+ <div class="date">14<div>Feb</div></div>+ <h1 class="posttitle">Happstack Guestbook</h1>+ <div class="storycontent">+ <p>+ Hey congrats! You're using+ <a href="http://happstack.com">Happstack</a> 0.2.+ This is a guestbook example which you can freely change to your+ whims and fancies.+ </p>+ <p>+ This page is written using Haskell Server Pages (HSP). For an example+ of a page using HStringTemplate, look at the+ <a href="/README">dynamic README</a>.+ </p>+ <p>Leave a message for the next visitor here...</p>+ <form action="/entries" method="post" enctype="multipart/form-data:charset=UTF-8" accept-charset="UTF-8">+ <p><label for="author">A<span class="accesskey">u</span>thor</label><br /><input type="text" name="author" id="author" tabindex="1" accesskey="U" /></p>+ <p><label for="message"><span class="accesskey">M</span>essage</label><br /><textarea cols="80" rows="10" name="message" id="message" tabindex="2" accesskey="M"></textarea></p>+ <p><input type="submit" tabindex="3" accesskey="L" value="Leave GuestBook Entry" /></p>+ </form>+ </div>+ </div>+ </div>++ <% body %>+ </div>+ </div>+ + <div class="footer">+ <div class="finalfooter">Theme : <a href="http://www.dezinerfolio.com/2007/10/10/just-another-wodpress-theme" title="sIMPRESS v2 theme">sIMPRESS v2</a> by <a href="http://dezinerfolio.com" title="Dezinerfolio">Dezinerfolio</a></div>+ </div>++ </body>+ </html>++renderREADME now = do+ webST "readme" [("time", dateStr now)]+
@@ -0,0 +1,95 @@+module Main where++import Control.Concurrent (MVar(..), forkIO, killThread)+import Happstack.Util.Cron (cron)+import Happstack.State (waitForTermination)+import Happstack.Server+ ( Conf(port)+ , simpleHTTP+ , nullConf+ , validator+ , wdgHTMLValidator+ )+import Happstack.State+ ( Component+ , Proxy(..)+ , Methods+ , TxControl+ , Saver(Queue, FileSaver)+ , runTxSystem+ , shutdownSystem+ , createCheckpoint+ )+import System.Environment (getArgs, getProgName)+import System.Log.Logger (Priority(..), logM)+import System.Exit (exitFailure)+import System.Console.GetOpt +import AppLogger (setupLogger)+import AppState (AppState(..))+import AppControl (appHandler)++stateProxy :: Proxy AppState+stateProxy = Proxy++main = do+ -- progname effects where state is stored and what the logfile is named+ let progName = "guestbook"+ + args <- getArgs+ setupLogger++ appConf <- case parseConfig args of+ (Left e) -> do logM progName ERROR (unlines e)+ exitFailure+ (Right f) -> return (f $ defaultConf progName)+ + -- start the state system+ control <- startSystemState' (store appConf) stateProxy+ + -- start the http server+ httpTid <- forkIO $ simpleHTTP (httpConf appConf) appHandler++ -- checkpoint the state once a day+ cronTid <- forkIO $ cron (60*60*24) (createCheckpoint control)+ + -- wait for termination signal+ waitForTermination+ + -- cleanup+ killThread httpTid+ killThread cronTid+ createCheckpoint control+ shutdownSystem control +++data AppConf+ = AppConf { httpConf :: Conf+ , store :: FilePath+ , static :: FilePath + }++defaultConf :: String -> AppConf+defaultConf progName+ = AppConf { httpConf = nullConf+ , store = "_local/" ++ progName ++ "_state"+ , static = "public"+ }++opts :: [OptDescr (AppConf -> AppConf)]+opts = [ Option [] ["http-port"] (ReqArg (\h c -> c { httpConf = (httpConf c) {port = read h} }) "port") "port to bind http server"+ , Option [] ["no-validate"] (NoArg (\ c -> c { httpConf = (httpConf c) { validator = Nothing } })) "Turn off HTML validation"+ , Option [] ["validate"] (NoArg (\ c -> c { httpConf = (httpConf c) { validator = Just wdgHTMLValidator } })) "Turn on HTML validation"+ , Option [] ["store"] (ReqArg (\h c -> c {store = h}) "PATH") "The directory used for database storage."+ , Option [] ["static"] (ReqArg (\h c -> c {static = h}) "PATH") "The directory searched for static files" + ]++parseConfig :: [String] -> Either [String] (AppConf -> AppConf)+parseConfig args+ = case getOpt Permute opts args of+ (flags,_,[]) -> Right $ \appConf -> foldr ($) appConf flags+ (_,_,errs) -> Left errs++startSystemState' :: (Component st, Methods st) => String -> Proxy st -> IO (MVar TxControl)+startSystemState' path proxy =+ runTxSystem (Queue (FileSaver path)) proxy+
@@ -0,0 +1,6 @@+This is a dynamic README file which uses HStringTemplate.++The time is $time$.++Thanks for reading!+
@@ -0,0 +1,12 @@+module Happstack.Tests where++import qualified Happstack.Util.Tests as Util+import qualified Happstack.Data.Tests as Data+import qualified Happstack.Data.IxSet.Tests as IxSet+import qualified Happstack.State.Tests as State+import qualified Happstack.Server.Tests as Server+import qualified Happstack.Server.Tests as Contrib+import Test.HUnit++allTests :: Test+allTests = ("happstack" ~: [ Util.allTests, Data.allTests, IxSet.allTests, State.allTests, Server.allTests, Contrib.allTests ])
@@ -0,0 +1,19 @@+module Main where++import Happstack.Tests (allTests)+import Test.HUnit (errors, failures, putTextToShowS,runTestText, runTestTT)+import System.Exit (exitFailure)+import System.IO (hIsTerminalDevice, stdout)++-- |A simple driver for running the local test suite.+main :: IO ()+main =+ do c <- do istty <- hIsTerminalDevice stdout+ if istty+ then runTestTT allTests+ else do (c,st) <- runTestText putTextToShowS allTests+ putStrLn (st "")+ return c+ case (failures c) + (errors c) of+ 0 -> return ()+ n -> exitFailure