loli 2009.6.25 → 2009.6.26
raw patch · 7 files changed
+137/−61 lines, 7 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Network.Loli: add_env_filter :: EnvFilter -> AppState -> AppState
- Network.Loli: add_response_filter :: ResponseFilter -> AppState -> AppState
- Network.Loli: application :: AppState -> Application
- Network.Loli: env_filters :: AppState -> [EnvFilter]
- Network.Loli: path :: AppState -> String
- Network.Loli: request :: EnvFilter -> AppUnit
- Network.Loli: response_filters :: AppState -> [ResponseFilter]
- Network.Loli: set_application :: Application -> AppState -> AppState
+ Network.Loli: captured :: AppUnitT [(String, String)]
+ Network.Loli: env :: AppState -> Env
+ Network.Loli: get_env :: AppUnitT Env
+ Network.Loli: get_response :: AppUnitT Response
+ Network.Loli: io :: (MonadIO m) => IO a -> m a
+ Network.Loli: parse_params :: String -> String -> Maybe (String, [(String, String)])
+ Network.Loli: set_response :: Response -> AppUnit
+ Network.Loli: type AppUnitT a = StateT AppState IO a
+ Network.Loli: type UnitT a = State Loli a
+ Network.Loli: update_env :: EnvFilter -> AppUnit
+ Network.Loli: update_response :: ResponseFilter -> AppUnit
- Network.Loli: AppState :: Application -> [EnvFilter] -> [ResponseFilter] -> String -> AppState
+ Network.Loli: AppState :: Env -> Response -> AppState
- Network.Loli: response :: ResponseFilter -> AppUnit
+ Network.Loli: response :: AppState -> Response
- Network.Loli: run_app :: String -> AppUnit -> Application
+ Network.Loli: run_app :: AppUnit -> Application
- Network.Loli: type AppUnit = State AppState ()
+ Network.Loli: type AppUnit = AppUnitT ()
- Network.Loli: type Unit = State Loli ()
+ Network.Loli: type Unit = UnitT ()
Files
- changelog.md +8/−1
- loli.cabal +4/−4
- readme.md +20/−4
- src/Network/Loli/Config.hs +3/−0
- src/Network/Loli/DSL.hs +26/−11
- src/Network/Loli/Engine.hs +58/−38
- src/Test.hs +18/−3
changelog.md view
@@ -1,4 +1,11 @@-2009.5.22+2009.6.26+---------++### Feature++* add route++2009.6.25 --------- Init
loli.cabal view
@@ -1,10 +1,10 @@ Name: loli-Version: 2009.6.25+Version: 2009.6.26 Build-type: Simple-Synopsis: loli+Synopsis: A minimum web dev DSL in Haskell Description:- - A minimum web dev DSL in Haskell+ + A simple and easy to use library for fast web prototyping in Haskell. License: BSD3 License-file: LICENSE
readme.md view
@@ -2,12 +2,28 @@ import Network.Loli import Hack.Handler.Happstack-+ + main = run . loli $ do - get "/hello" (text "hello world")- get "/" (html "<html><body><p>loli power!</p></body></html>")+ -- simple+ get "/hello" (text "hello world")+ + -- io+ get "/cabal" $ text =<< io (readFile "loli.cabal") - public (Just ".") ["/src"]+ -- route captures+ get "/say/:user/:verb" $ do+ text . show =<< captured + -- html output+ get "/html" (html "<html><body><p>loli power!</p></body></html>")++ -- default+ get "/" (text "at root")++ -- public serve, only allows /src+ public (Just ".") ["/src"]+ + -- treat .hs extension as text/plain mime "hs" "text/plain"
src/Network/Loli/Config.hs view
@@ -13,3 +13,6 @@ where default_content_type :: String default_content_type = "text/plain; charset=UTF-8"+ +loli_captures_prefix :: String+loli_captures_prefix = "loli_captures_"
src/Network/Loli/DSL.hs view
@@ -1,27 +1,31 @@ module Network.Loli.DSL where --import MPS-import Prelude hiding ((.), (>), (^))-import Network.Loli.Engine+import Control.Monad.State+import Data.ByteString.Lazy.UTF8 (fromString) import Hack import Hack.Contrib.Constants-import Hack.Contrib.Response-import Data.ByteString.Lazy.UTF8 (fromString) import Hack.Contrib.Middleware.Static+import Hack.Contrib.Response+import MPS+import Network.Loli.Engine+import Network.Loli.Config+import Prelude hiding ((.), (>), (^))+import qualified Control.Monad.State as State + app :: Application -> AppUnit-app = set_application > update+app f = do+ get_env >>= (f > io) >>= set_response text :: String -> AppUnit text x = do- response $ set_content_type _TextPlain- response $ set_body (x.fromString)+ update_response $ set_content_type _TextPlain+ update_response $ set_body (x.fromString) html :: String -> AppUnit html x = do- response $ set_content_type _TextHtml- response $ set_body (x.fromString)+ update_response $ set_content_type _TextHtml+ update_response $ set_body (x.fromString) get, put, delete, post :: String -> AppUnit -> Unit get = route GET@@ -37,3 +41,14 @@ public :: Maybe String -> [String] -> Unit public r xs = middleware $ static r xs++io :: (MonadIO m) => IO a -> m a+io = liftIO++captured :: AppUnitT [(String, String)]+captured = get_env ^ hackHeaders ^ filter_captured+ where+ filter_captured =+ select (fst > starts_with loli_captures_prefix)+ > map_fst (drop (loli_captures_prefix.length))+
src/Network/Loli/Engine.hs view
@@ -2,46 +2,37 @@ module Network.Loli.Engine where -import Control.Monad.State+import Control.Monad.State hiding (join) import Data.Default import Data.List (find)+import Data.Maybe import Hack-import Hack.Contrib.Middleware.Censor-import Hack.Contrib.Middleware.Config import Hack.Contrib.Middleware.NotFound import Hack.Contrib.Response import Hack.Contrib.Utils hiding (get, put) import MPS import Network.Loli.Config+import Network.Loli.Config import Prelude hiding ((.), (/), (>), (^)) - type RoutePath = (RequestMethod, String, AppUnit) type EnvFilter = Env -> Env type ResponseFilter = Response -> Response type Param = (String, String) data AppState = AppState {- application :: Application- , env_filters :: [EnvFilter]- , response_filters :: [ResponseFilter]- , path :: String+ env :: Env+ , response :: Response } instance Default AppState where- def = AppState def [id] [id] def+ def = AppState def def -type AppUnit = State AppState ()+type AppUnitT a = StateT AppState IO a+type AppUnit = AppUnitT () -run_app :: String -> AppUnit -> Application-run_app path unit = - let state = execState unit def {path}- before = state.env_filters.map config- after = state.response_filters.map (to_io_filter > censor)- in- state.application.use (before ++ after)- where- to_io_filter f = \x -> return (f x)+run_app :: AppUnit -> Application+run_app unit = \env -> execStateT unit def {env} ^ response router :: [RoutePath] -> Middleware router h app' = \env'' ->@@ -54,12 +45,44 @@ in case h.find (match_route env'') of Nothing -> app' env''- Just (_, location, app_state) -> - run_app location app_state (mod_env location)+ Just (_, template, app_state) -> do+ let (location, params) = parse_params template path .fromJust+ run_app app_state (mod_env location .merge_captured params) where- match_route env' (method, path, _) = - env'.request_method.is method && env'.path_info.starts_with path+ match_route env' (method, template, _) = + env'.request_method.is method + && env'.path_info.parse_params template .isJust+ merge_captured params env' =+ let loli_captures = params.map_fst (loli_captures_prefix ++)+ new_hack_headers = env'.custom ++ loli_captures+ in+ env' {hackHeaders = new_hack_headers}+ +parse_params :: String -> String -> Maybe (String, [(String, String)])+parse_params t s =+ let template_tokens = t.split "/"+ url_tokens = s.split "/"+ in+ if url_tokens.length < template_tokens.length+ then Nothing+ else + let rs = zipWith capture template_tokens url_tokens+ in+ if rs.all isJust+ then + let location = url_tokens.take (template_tokens.length).join "/"+ in+ Just $ (location, rs.map fromJust.filter isJust.map fromJust)+ else Nothing+ + where+ capture x y + | x.starts_with ":" = Just $ Just (x.tail, y)+ | x == y = Just Nothing+ | otherwise = Nothing+ + data Loli = Loli {@@ -71,7 +94,8 @@ instance Default Loli where def = Loli def def def -type Unit = State Loli ()+type UnitT a = State Loli a+type Unit = UnitT () @@ -90,9 +114,6 @@ in use [pre, mime_filter, stack, loli_app] -set_application :: Application -> AppState -> AppState-set_application application x = x { application }- update :: (MonadState a m, Functor m) => (a -> a) -> m () update f = get ^ f >>= put @@ -112,21 +133,20 @@ add_mime :: String -> String -> Loli -> Loli add_mime k v s = let xs = s.mimes in s {mimes = xs.insert_last (k, v)} -add_env_filter :: EnvFilter -> AppState -> AppState-add_env_filter x s = - let xs = s.env_filters in s {env_filters = xs.insert_last x}--add_response_filter :: ResponseFilter -> AppState -> AppState-add_response_filter x s = - let xs = s.response_filters in s {response_filters = xs.insert_last x}+update_response :: ResponseFilter -> AppUnit+update_response f = update $ \s -> let x = s.response.f in s {response = x} +set_response :: Response -> AppUnit+set_response r = update_response $ const r -request :: EnvFilter-> AppUnit-request x = add_env_filter x .update+get_response :: AppUnitT Response+get_response = get ^ response -response :: ResponseFilter -> AppUnit-response x = add_response_filter x .update+update_env :: EnvFilter -> AppUnit+update_env f = update $ \s -> let x = s.env.f in s {env = x} +get_env :: AppUnitT Env+get_env = get ^ env -- middleware lookup_mime :: [(String, String)] -> Middleware
src/Test.hs view
@@ -2,10 +2,25 @@ import Hack.Handler.Happstack main = run . loli $ do++ -- simple+ get "/hello" (text "hello world") - get "/hello" (text "hello world")- get "/" (html "<html><body><p>loli power!</p></body></html>")+ -- io+ get "/cabal" $ text =<< io (readFile "loli.cabal") + -- route captures+ get "/say/:user/:verb" $ do+ text . show =<< captured++ -- html output+ get "/html" (html "<html><body><p>loli power!</p></body></html>")++ -- default+ get "/" (text "at root")++ -- public serve, only allows /src public (Just ".") ["/src"]- + + -- treat .hs extension as text/plain mime "hs" "text/plain"