packages feed

bird (empty) → 0.0.1

raw patch · 9 files changed

+332/−0 lines, 9 filesdep +basedep +bytestringdep +containerssetup-changed

Dependencies added: base, bytestring, containers, data-default, hack, haskell98, hyena, process

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2010, Matt Parker++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 Matt Parker nor the names of other+      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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bin/bird.hs view
@@ -0,0 +1,55 @@+module Main where+import System.Process+import System.Environment (getArgs)+import Directory++main = do+  args <- getArgs+  runArg $ head args++runArg a = +  case a of +    "build" -> runProcess "ghc" ["--make", "-O2", "Main.hs"] Nothing Nothing Nothing Nothing Nothing >> return ()+    "run"   -> runProcess "./Main" [] Nothing Nothing Nothing Nothing Nothing >> return ()+    appName -> createBirdApp appName  ++createBirdApp a = do+  createDirectory a+  writeFile (a ++ "/" ++ a ++ ".hs") (routeFile a)+  writeFile (a ++ "/" ++ "Main.hs") (mainFile a)++routeFile a = +  "module " ++ a ++ " where\n" +++  "import Bird\n\n" ++ +  "get, post, put, delete :: Request -> IO Reply\n" +++  "get _ = return notFound_\n" +++  "post _ = return notFound_\n" +++  "put _ = return notFound_\n" +++  "delete _ = return notFound_\n"++mainFile a = +  "import Hack\n" +++  "import Hack.Handler.Hyena\n" +++  "import Bird\n" +++  "import " ++ a ++ "\n" ++ "\n" ++++  "app :: Application\n" +++  "app = \\e -> route e\n" ++ "\n" ++++  "route :: Env -> IO Response\n" +++  "route e = response\n" +++  "  where \n" +++  "    r = envToRequest e\n" +++  "    response = do \n" +++  "      reply <- matchRequest r\n" +++  "      return $ replyToResponse reply\n" ++ "\n" ++++  "matchRequest r = \n" +++  "  case verb r of \n" +++  "    GET -> get r\n" +++  "    POST -> post r\n" +++  "    PUT -> put r\n" +++  "    DELETE -> delete r\n" +++  "    _ -> error \"not supported\"\n" ++ "\n" +++      +  "main = run app\n"
+ bird.cabal view
@@ -0,0 +1,23 @@+Name:                 bird+Version:              0.0.1+Build-type:           Simple+Synopsis:             A simple, sinatra-inspired web framework.+Description:          Bird is a hack-compatible framework for simple websites.+License:              BSD3+License-file:         LICENSE+Author:               Parker, Matt+Maintainer:           Parker, Matt <moonmaster9000@gmail.com>+Build-Depends:        base+Cabal-version:        >= 1.2+category:             Web+homepage:             http://github.com/moonmaster9000/bird+data-files:           readme.markdown++Executable bird+  main-Is: bird.hs+  hs-source-dirs: bin+  +library+  build-depends: haskell98, process, containers, bytestring, base >= 4.0 && < 5, hack >= 2009.10.30, hyena, data-default >= 0.2 +  exposed-modules: Bird, Bird.Request, Bird.Reply, Bird.Reply.Codes+  hs-source-dirs: src/
+ readme.markdown view
@@ -0,0 +1,56 @@+# Bird++A sinatra-ish web framework written in haskell, riding on top of Hack. ++## Why?++Sinatra has a beautiful, simple, elegant syntax, but it's essentially an attempt to bring pattern matching to a language never intended for +pattern matching. Why not attempt something similar in a language with not just beautiful pattern matching, but with all the declarative +bells and whistles: lazy evaluation, first-class functions, currying, polymorphism?++## Install++    $ cabal update && cabal install bird++Note: make sure $HOME/.cabal/bin is in your PATH.++## Create an app++    $ bird MyApp ++## Compile your app++    $ cd MyApp+    $ bird build++## Start your app (runs on port 3000)++    $ bird run++## Try it out+    +    $ curl http://localhost:3000+      404 Not Found++## Improvise!+    +    -- MyApp.hs+    module MyApp where+    import Bird+    import Data.String.Utils++    get, post, put, delete :: Request -> IO Reply+    get Request { path = ("howdy":xs) } +      = ok $ ("Howdy " ++) $ join ", " xs++    get _ = return notFound_+    post _ = return notFound_+    put _ = return notFound_+    delete _ = return notFound_++## Notes++This project is *still* in its infancy. Coming features:++* logging+* support for sending files
+ src/Bird.hs view
@@ -0,0 +1,13 @@+module Bird(+  module Hack, +  module Data.Default, +  module Bird.Reply,+  module Bird.Request, +  module Bird.Reply.Codes+) where++import Hack+import Data.Default+import Bird.Reply+import Bird.Request+import Bird.Reply.Codes
+ src/Bird/Reply.hs view
@@ -0,0 +1,25 @@+module Bird.Reply where++import qualified Data.Map as Hash+import Data.ByteString.Lazy.Char8 (pack)+import Hack+import Data.Default++data Reply = +  Reply {+    replyStatus   :: Int,+    replyHeaders  :: Hash.Map String String,+    replyBody     :: String,+    replyMime     :: String+  } deriving (Show)++instance Default Reply where+  def = Reply { replyMime = "text/html", replyBody = "", replyStatus = 200, replyHeaders = Hash.empty }++replyToResponse :: Reply -> Response+replyToResponse r = +  Response {+    status = replyStatus r,+    headers = [("Content-Type", replyMime r)] ++ (Hash.toList $ replyHeaders r),+    body = pack $ replyBody r+  }
+ src/Bird/Reply/Codes.hs view
@@ -0,0 +1,89 @@+module Bird.Reply.Codes where++import qualified Data.Map as Hash+import Bird.Reply+import Data.Default+++-- 200 OK default Reply record.+ok_ :: Reply+ok_ = def++-- 200 OK body convenience method.+ok :: String -> IO Reply+ok body = return $ ok_ { replyBody = body }+++--201 Created default Reply record.+created_ :: Reply+created_ = def { replyStatus = 201 }++--201 Created body convenience method+created :: String -> IO Reply+created body = return $ created_ { replyBody = body }+++--202 Accepted default Reply record. +accepted_ :: Reply+accepted_ = def { replyStatus = 202 }++--202 Accepted body convenience method.+accepted :: String -> IO Reply+accepted body = return $ accepted_ { replyBody = body }+++--301 Moved Permanently default Reply record (doesn't include a default redirection url). +movedPermanently_ :: Reply+movedPermanently_ = def { replyStatus = 301 }++--301 Moved Permanently url convenience method.+movedPermanently :: String -> IO Reply+movedPermanently url = return $ movedPermanently_ { replyHeaders = Hash.fromList [("Location", url)] }++--302 Found default Reply record (doesn't include a default redirection url).+found_ :: Reply+found_ = def { replyStatus = 302 }++--302 Found url convenience method+found :: String -> IO Reply+found url = return $ found_ { replyHeaders = Hash.fromList [("Location", url)] }++-- 401 Unauthorized Reply record+unauthorized_ :: Reply+unauthorized_ = def { replyStatus = 401, replyBody = "You are not authorized to access this resource."}++-- 401 Unauthorized Reply body convenience method+unauthorized :: String -> IO Reply+unauthorized body = return $ unauthorized_ { replyBody = body }++--403 Forbidden Reply record+forbidden_ :: Reply+forbidden_ = def { replyStatus = 403, replyBody = "You do not have permission to access this resource."}++--403 Forbidden body convenience method+forbidden :: String -> IO Reply+forbidden body = return $ forbidden_ { replyBody = body }++--404 Not Found record+notFound_ :: Reply+notFound_ = def { replyStatus = 404, replyBody = "404 Not Found" }++--404 Not Found body convenience method+notFound :: String -> IO Reply+notFound body = return $ notFound_ { replyBody = body }++--410 Gone Reply Record+gone_ :: Reply+gone_ = def { replyStatus = 410, replyBody = "410 Gone" }++--410 Gone Reply Record body convenience method.+gone :: String -> IO Reply+gone body = return $ gone_ { replyBody = body }++--500 Internal Server Error Reply record.+internalServerError_ :: Reply+internalServerError_ = def { replyStatus = 500, replyBody = "Oops... something went wrong." }++--500 Internal Server Error body convenience method.+internalServerError :: String -> IO Reply+internalServerError body = return $ internalServerError_ { replyBody = body }
+ src/Bird/Request.hs view
@@ -0,0 +1,39 @@+module Bird.Request(+  Request(..),+  envToRequest+) where++import Hack+import Data.Default+import Data.ByteString.Lazy.Char8 (pack)+import qualified Data.Map as Hash++data Request = +  Request { +    verb      :: RequestMethod,+    path      :: [String],+    params    :: Hash.Map String String,+    protocol  :: Hack_UrlScheme,+    hackEnvironment :: Env+  } deriving (Show)++instance Default Request where+  def = Request { verb = GET, path = [], params = Hash.empty, protocol = HTTP, hackEnvironment = def }++envToRequest :: Env -> Request+envToRequest e = +  Request {+    verb = requestMethod e,+    path = split '/' $ pathInfo e,+    params = Hash.empty,+    protocol = hackUrlScheme e,+    hackEnvironment = e+  }++split :: Char -> String -> [String]+split d s+  | findSep == [] = []+  | otherwise     = t : split d s''+    where+      (t, s'') = break (== d) findSep+      findSep = dropWhile (== d) s