loli 2009.6.26 → 2009.6.27
raw patch · 13 files changed
+278/−83 lines, 13 filesdep +bytestringdep +containersdep +templatePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: bytestring, containers, template
API changes (from Hackage documentation)
- Network.Loli: AppState :: Env -> Response -> AppState
- Network.Loli: Loli :: [RoutePath] -> [Middleware] -> [(String, String)] -> Loli
- Network.Loli: add_middleware :: Middleware -> Loli -> Loli
- Network.Loli: add_mime :: String -> String -> Loli -> Loli
- Network.Loli: add_route :: RoutePath -> Loli -> Loli
- Network.Loli: app :: Application -> AppUnit
- Network.Loli: captured :: AppUnitT [(String, String)]
- Network.Loli: data AppState
- Network.Loli: data Loli
- Network.Loli: delete :: String -> AppUnit -> Unit
- Network.Loli: env :: AppState -> Env
- Network.Loli: get :: String -> AppUnit -> Unit
- Network.Loli: get_env :: AppUnitT Env
- Network.Loli: get_response :: AppUnitT Response
- Network.Loli: html :: String -> AppUnit
- Network.Loli: insert_last :: a -> [a] -> [a]
- Network.Loli: io :: (MonadIO m) => IO a -> m a
- Network.Loli: loli :: Unit -> Application
- Network.Loli: lookup_mime :: [(String, String)] -> Middleware
- Network.Loli: middleware :: Middleware -> Unit
- Network.Loli: middlewares :: Loli -> [Middleware]
- Network.Loli: mime :: String -> String -> Unit
- Network.Loli: mimes :: Loli -> [(String, String)]
- Network.Loli: parse_params :: String -> String -> Maybe (String, [(String, String)])
- Network.Loli: post :: String -> AppUnit -> Unit
- Network.Loli: public :: Maybe String -> [String] -> Unit
- Network.Loli: put :: String -> AppUnit -> Unit
- Network.Loli: response :: AppState -> Response
- Network.Loli: route :: RequestMethod -> String -> AppUnit -> Unit
- Network.Loli: router :: [RoutePath] -> Middleware
- Network.Loli: routes :: Loli -> [RoutePath]
- Network.Loli: run_app :: AppUnit -> Application
- Network.Loli: set_response :: Response -> AppUnit
- Network.Loli: text :: String -> AppUnit
- Network.Loli: type AppUnit = AppUnitT ()
- Network.Loli: type AppUnitT a = StateT AppState IO a
- Network.Loli: type EnvFilter = Env -> Env
- Network.Loli: type Param = (String, String)
- Network.Loli: type ResponseFilter = Response -> Response
- Network.Loli: type RoutePath = (RequestMethod, String, AppUnit)
- Network.Loli: type Unit = UnitT ()
- Network.Loli: type UnitT a = State Loli a
- Network.Loli: update :: (MonadState a m, Functor m) => (a -> a) -> m ()
- Network.Loli: update_env :: EnvFilter -> AppUnit
- Network.Loli: update_response :: ResponseFilter -> AppUnit
+ Network.Loli.Config: loli_bindings :: String
+ Network.Loli.Config: loli_captures :: String
+ Network.Loli.Config: loli_views :: String
+ Network.Loli.Config: pre_installed_middlewares :: [Middleware]
+ Network.Loli.DSL: app :: Application -> AppUnit
+ Network.Loli.DSL: bind :: String -> String -> AppUnit -> AppUnit
+ Network.Loli.DSL: bindings :: AppUnitT [(String, String)]
+ Network.Loli.DSL: captures :: AppUnitT [(String, String)]
+ Network.Loli.DSL: context :: [(String, String)] -> AppUnit -> AppUnit
+ Network.Loli.DSL: delete :: String -> AppUnit -> Unit
+ Network.Loli.DSL: get :: String -> AppUnit -> Unit
+ Network.Loli.DSL: html :: String -> AppUnit
+ Network.Loli.DSL: io :: (MonadIO m) => IO a -> m a
+ Network.Loli.DSL: middleware :: Middleware -> Unit
+ Network.Loli.DSL: mime :: String -> String -> Unit
+ Network.Loli.DSL: post :: String -> AppUnit -> Unit
+ Network.Loli.DSL: public :: Maybe String -> [String] -> Unit
+ Network.Loli.DSL: put :: String -> AppUnit -> Unit
+ Network.Loli.DSL: text :: String -> AppUnit
+ Network.Loli.DSL: views :: String -> Unit
+ Network.Loli.Engine: Loli :: [RoutePath] -> [Middleware] -> [(String, String)] -> Loli
+ Network.Loli.Engine: add_middleware :: Middleware -> Loli -> Loli
+ Network.Loli.Engine: add_mime :: String -> String -> Loli -> Loli
+ Network.Loli.Engine: add_route :: RoutePath -> Loli -> Loli
+ Network.Loli.Engine: data Loli
+ Network.Loli.Engine: instance Default Loli
+ Network.Loli.Engine: loli :: Unit -> Application
+ Network.Loli.Engine: lookup_mime :: [(String, String)] -> Middleware
+ Network.Loli.Engine: middlewares :: Loli -> [Middleware]
+ Network.Loli.Engine: mimes :: Loli -> [(String, String)]
+ Network.Loli.Engine: parse_params :: String -> String -> Maybe (String, [(String, String)])
+ Network.Loli.Engine: route :: RequestMethod -> String -> AppUnit -> Unit
+ Network.Loli.Engine: router :: [RoutePath] -> Middleware
+ Network.Loli.Engine: routes :: Loli -> [RoutePath]
+ Network.Loli.Engine: run_app :: AppUnit -> Application
+ Network.Loli.Engine: type AppReader = Env
+ Network.Loli.Engine: type AppState = Response
+ Network.Loli.Engine: type AppUnit = AppUnitT ()
+ Network.Loli.Engine: type AppUnitT a = ReaderT AppReader (StateT AppState IO) a
+ Network.Loli.Engine: type EnvFilter = Env -> Env
+ Network.Loli.Engine: type Param = (String, String)
+ Network.Loli.Engine: type ResponseFilter = Response -> Response
+ Network.Loli.Engine: type RoutePath = (RequestMethod, String, AppUnit)
+ Network.Loli.Engine: type Unit = UnitT ()
+ Network.Loli.Engine: type UnitT a = State Loli a
+ Network.Loli.Engine: update :: (MonadState a m, Functor m) => (a -> a) -> m ()
+ Network.Loli.Template: template :: Template -> String -> AppUnit
+ Network.Loli.Template: type Context = [(String, String)]
+ Network.Loli.Template: type Template = String -> Context -> IO ByteString
+ Network.Loli.Template.TextTemplate: text_template :: String -> AppUnit
+ Network.Loli.Utils: add_namespace :: String -> String -> String -> Env -> Env
+ Network.Loli.Utils: insert_last :: a -> [a] -> [a]
+ Network.Loli.Utils: namespace :: String -> Env -> [(String, String)]
+ Network.Loli.Utils: set_namespace :: String -> [(String, String)] -> Env -> Env
Files
- Nemesis +15/−0
- changelog.md +9/−0
- loli.cabal +7/−4
- readme.md +86/−19
- src/Network/Loli.hs +1/−1
- src/Network/Loli/Config.hs +17/−2
- src/Network/Loli/DSL.hs +21/−15
- src/Network/Loli/Engine.hs +16/−40
- src/Network/Loli/Template.hs +23/−0
- src/Network/Loli/Template/TextTemplate.hs +20/−0
- src/Network/Loli/Utils.hs +29/−0
- src/Test.hs +28/−2
- views/hello.html +6/−0
Nemesis view
@@ -4,6 +4,8 @@ [ "**/*.hi" , "**/*.o" , "manifest"+ , "Test"+ , "myloli" ] desc "prepare cabal dist"@@ -23,3 +25,16 @@ sh "ghc --make -isrc src/Test.hs -o Test" sh "echo done.." sh "./Test"+ + desc "show sloc"+ task "stat" $ do+ sh "cloc -match-f=hs$ --quiet src --no3"+ ++ task "template" $ sh "ghci -isrc src/Network/Loli/Template.hs"+ + task "loli" $ do+ sh "ghc --make -isrc src/Test/myloli.hs -o myloli"+ sh "echo done.."+ sh "./myloli"+
changelog.md view
@@ -1,3 +1,12 @@+2009.6.27+---------++### Feature++* add template+* add local binding+* env and response are in reader and state monad respectively+ 2009.6.26 ---------
loli.cabal view
@@ -1,5 +1,5 @@ Name: loli-Version: 2009.6.26+Version: 2009.6.27 Build-type: Simple Synopsis: A minimum web dev DSL in Haskell Description:@@ -14,15 +14,18 @@ Cabal-version: >= 1.2 category: Web homepage: http://github.com/nfjinjing/loli-data-files: readme.md, changelog.md, Nemesis, known-issues.md, src/Test.hs+data-files: readme.md, changelog.md, Nemesis, known-issues.md, src/Test.hs, views/hello.html library ghc-options: -Wall- build-depends: base > 4 && <= 5, data-default, hack >= 2009.5.19, hack-contrib >= 2009.6.25, utf8-string, mps >= 2009.6.25, mtl+ build-depends: base > 4 && <= 5, data-default, hack >= 2009.5.19, hack-contrib >= 2009.6.25, utf8-string, mps >= 2009.6.25, mtl, containers, template, bytestring hs-source-dirs: src/ exposed-modules: Network.Loli- other-modules: Network.Loli.Config Network.Loli.DSL Network.Loli.Engine+ Network.Loli.Template+ Network.Loli.Template.TextTemplate+ Network.Loli.Utils+
readme.md view
@@ -1,29 +1,96 @@-### Example+# loli +A minimum web dev DSL++## Example++First app++ -- myloli.hs+ import Network.Loli import Hack.Handler.Happstack + main = run . loli $ get "/" (text "loli power")++Install and compile:++ cabal update+ cabal install loli+ cabal install hack-handler-happstack - main = run . loli $ do+ ghc --make myloli.hs+ ./myloli - -- simple- get "/hello" (text "hello world")- - -- io- get "/cabal" $ text =<< io (readFile "loli.cabal")+check: <http://localhost:3000> - -- route captures- get "/say/:user/:verb" $ do- text . show =<< captured - -- html output- get "/html" (html "<html><body><p>loli power!</p></body></html>")+## Routes - -- default- get "/" (text "at root")+### Verb - -- public serve, only allows /src- public (Just ".") ["/src"]- - -- treat .hs extension as text/plain- mime "hs" "text/plain"+ get "/" $ do+ -- something for a get request++ post "/" $ do+ -- for a post request+ + put "/" $ do+ -- put ..+ + delete "/" $ do+ -- ..+### Captures++ get "/say/:user/:something" $ do+ text . show =<< captures++ -- /say/jinjing/hello will output+ -- [("user","jinjing"),("something","hello")]+++## Static++ -- public serve, only allows /src+ public (Just ".") ["/src"]++## Views++ -- in `./views`, can be changed by+ views "template"++### Text Template++ import Network.Loli.Template.TextTemplate+ + -- template+ get "/hi/:user" $ text_template "hello.html"+ + -- in hello.html+ <html>+ <title>hello</title>+ <body>+ <p>hello $user</p>+ </body>+ </html>++### Local bindings++ get "/local-binding" $ do+ bind "user" "alice" (text_template "hello.html")++### Batched local bindings++ get "/batched-local-binding" $ do+ context [("user", "alice"), ("password", "foo")] $ + text . show =<< bindings+++## Mime types++ -- treat .hs extension as text/plain+ mime "hs" "text/plain"++## Note++If you see this, use the git version!
src/Network/Loli.hs view
@@ -5,5 +5,5 @@ ) where -import Network.Loli.Engine+import Network.Loli.Engine (loli) import Network.Loli.DSL
src/Network/Loli/Config.hs view
@@ -1,18 +1,33 @@ module Network.Loli.Config where import Hack+import Hack.Contrib.Middleware.Config import Hack.Contrib.Middleware.ContentLength import Hack.Contrib.Middleware.ContentType+import MPS.Light+import Prelude hiding ((.), (>), (^)) pre_installed_middlewares :: [Middleware] pre_installed_middlewares = [ content_length , content_type default_content_type+ , config set_view_root ] where+ set_view_root env =+ let hack_headers = env.hackHeaders+ view_root = (loli_views ++ "root", "views")+ in+ env {hackHeaders = hack_headers ++ [view_root]} default_content_type :: String default_content_type = "text/plain; charset=UTF-8" -loli_captures_prefix :: String-loli_captures_prefix = "loli_captures_"+loli_captures :: String+loli_captures = "loli_captures_"++loli_bindings :: String+loli_bindings = "loli_bindings_"++loli_views :: String+loli_views = "loli_views_"
src/Network/Loli/DSL.hs view
@@ -1,32 +1,36 @@ module Network.Loli.DSL where +import Control.Monad.Reader import Control.Monad.State import Data.ByteString.Lazy.UTF8 (fromString) import Hack import Hack.Contrib.Constants+import Hack.Contrib.Middleware.Config import Hack.Contrib.Middleware.Static import Hack.Contrib.Response import MPS-import Network.Loli.Engine import Network.Loli.Config+import Network.Loli.Engine+import Network.Loli.Utils import Prelude hiding ((.), (>), (^)) import qualified Control.Monad.State as State - app :: Application -> AppUnit-app f = do- get_env >>= (f > io) >>= set_response+app f = ask >>= (f > io) >>= State.put text :: String -> AppUnit text x = do- update_response $ set_content_type _TextPlain- update_response $ set_body (x.fromString)+ update $ set_content_type _TextPlain+ update $ set_body (x.fromString) html :: String -> AppUnit html x = do- update_response $ set_content_type _TextHtml- update_response $ set_body (x.fromString)+ update $ set_content_type _TextHtml+ update $ set_body (x.fromString) +views :: String -> Unit+views x = middleware $ config (set_namespace loli_views [("root", x)])+ get, put, delete, post :: String -> AppUnit -> Unit get = route GET put = route PUT@@ -45,10 +49,12 @@ 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))- +context :: [(String, String)] -> AppUnit -> AppUnit+context = set_namespace loli_bindings > local++bind :: String -> String -> AppUnit -> AppUnit+bind k v = context [(k, v)]++captures, bindings :: AppUnitT [(String, String)]+captures = ask ^ namespace loli_captures+bindings = ask ^ namespace loli_bindings
src/Network/Loli/Engine.hs view
@@ -2,6 +2,7 @@ module Network.Loli.Engine where +import Control.Monad.Reader hiding (join) import Control.Monad.State hiding (join) import Data.Default import Data.List (find)@@ -12,27 +13,23 @@ import Hack.Contrib.Utils hiding (get, put) import MPS import Network.Loli.Config-import Network.Loli.Config+import Network.Loli.Utils import Prelude hiding ((.), (/), (>), (^)) -type RoutePath = (RequestMethod, String, AppUnit)-type EnvFilter = Env -> Env++type RoutePath = (RequestMethod, String, AppUnit)+type EnvFilter = Env -> Env type ResponseFilter = Response -> Response-type Param = (String, String)-data AppState = AppState- {- env :: Env- , response :: Response- }+type Param = (String, String)+type AppState = Response+type AppReader = Env -instance Default AppState where- def = AppState def def+type AppUnitT a = ReaderT AppReader (StateT AppState IO) a+type AppUnit = AppUnitT () -type AppUnitT a = StateT AppState IO a-type AppUnit = AppUnitT () run_app :: AppUnit -> Application-run_app unit = \env -> execStateT unit def {env} ^ response+run_app unit = \env -> runReaderT unit env .flip execStateT def router :: [RoutePath] -> Middleware router h app' = \env'' ->@@ -52,14 +49,11 @@ 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}- + merge_captured params env' = + env'.set_namespace loli_captures params parse_params :: String -> String -> Maybe (String, [(String, String)])+parse_params "/" s = Just (s, []) parse_params t s = let template_tokens = t.split "/" url_tokens = s.split "/"@@ -92,10 +86,10 @@ } instance Default Loli where- def = Loli def def def+ def = Loli def [dummy_middleware] def type UnitT a = State Loli a-type Unit = UnitT ()+type Unit = UnitT () @@ -117,9 +111,6 @@ update :: (MonadState a m, Functor m) => (a -> a) -> m () update f = get ^ f >>= put -insert_last :: a -> [a] -> [a]-insert_last x xs = xs ++ [x]- add_route :: RoutePath -> Loli -> Loli add_route r s = let xs = s.routes in s {routes = xs.insert_last r} @@ -132,21 +123,6 @@ add_mime :: String -> String -> Loli -> Loli add_mime k v s = let xs = s.mimes in s {mimes = xs.insert_last (k, v)}--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--get_response :: AppUnitT Response-get_response = get ^ response--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/Network/Loli/Template.hs view
@@ -0,0 +1,23 @@+module Network.Loli.Template where++import Control.Monad.Reader+import Data.Maybe+import Hack.Contrib.Response+import MPS+import Network.Loli.Config+import Network.Loli.DSL+import Network.Loli.Engine+import Network.Loli.Utils+import Prelude hiding ((.), (>), (^), (/))+import qualified Data.ByteString.Lazy.Char8 as B+++type Context = [(String, String)]+type Template = String -> Context -> IO B.ByteString++template :: Template -> String -> AppUnit+template f x = do+ c <- captures+ b <- bindings+ root <- ask ^ namespace loli_views ^ lookup "root" ^ fromMaybe "."+ f (root / x) (c ++ b) .io >>= set_body > update
+ src/Network/Loli/Template/TextTemplate.hs view
@@ -0,0 +1,20 @@+module Network.Loli.Template.TextTemplate (text_template) where++import Control.Arrow ((***))+import Data.ByteString.Lazy.UTF8 (fromString)+import MPS+import Network.Loli.Engine+import Network.Loli.Template+import Prelude hiding ((.), (>), (^), (/))+import Text.Template hiding (Context, Template, template)+import qualified Data.ByteString.Lazy.Char8 as B+import qualified Data.Map as Map++create_context :: [(String, String)] -> Map.Map B.ByteString B.ByteString+create_context = map (fromString *** fromString) > to_h++backend :: Template+backend x c = readTemplate (x.u2b) ^ flip render (create_context c)++text_template :: String -> AppUnit+text_template = template backend
+ src/Network/Loli/Utils.hs view
@@ -0,0 +1,29 @@+module Network.Loli.Utils where++import Hack+import Hack.Contrib.Utils+import MPS.Light+import Prelude hiding ((.), (/), (>), (^))++namespace :: String -> Env -> [(String, String)]+namespace x env =+ env+ .custom+ .select (fst > starts_with x)+ .map_fst (drop (x.length))++set_namespace :: String -> [(String, String)] -> Env -> Env+set_namespace x xs env = + let adds = xs.map_fst (x ++)+ new_headers = adds.map fst+ new_hack_headers = + env.custom.reject (fst > belongs_to new_headers) ++ adds+ in+ env {hackHeaders = new_hack_headers}+++add_namespace :: String -> String -> String -> Env -> Env+add_namespace x k v = set_namespace x [(k,v)]++insert_last :: a -> [a] -> [a]+insert_last x xs = xs ++ [x]
src/Test.hs view
@@ -1,6 +1,12 @@ import Network.Loli import Hack.Handler.Happstack+import Hack.Contrib.Response+import Hack.Contrib.Utils (dummy_middleware)+import Network.Loli.Template.TextTemplate+import Network.Loli.Engine +-- default on port 3000+ main = run . loli $ do -- simple@@ -10,12 +16,31 @@ get "/cabal" $ text =<< io (readFile "loli.cabal") -- route captures- get "/say/:user/:verb" $ do- text . show =<< captured+ get "/say/:user/:something" $ do+ text . show =<< captures -- html output get "/html" (html "<html><body><p>loli power!</p></body></html>") + -- template+ get "/hi/:user" $ text_template "hello.html"++ -- manually tweak the reponse body+ get "/hi-html/:user" $ do+ update $ set_content_type "text/html"+ text_template "hello.html"++ -- add local binding+ get "/local-binding" $ do+ bind "user" "alice" (text_template "hello.html")+ + + -- batched local bindings+ get "/batched-local-binding" $ do+ context [("user", "alice"), ("password", "foo")] $ + text .show =<< bindings+ + -- default get "/" (text "at root") @@ -24,3 +49,4 @@ -- treat .hs extension as text/plain mime "hs" "text/plain"+
+ views/hello.html view
@@ -0,0 +1,6 @@+<html>+<title>hello</title>+<body>+ <p>hello $user</p>+</body>+</html>