packages feed

kibro 0.0 → 0.1

raw patch · 14 files changed

+101/−391 lines, 14 filesdep +data-defaultPVP ok

version bump matches the API change (PVP)

Dependencies added: data-default

API changes (from Hackage documentation)

+ Kibro: con :: KibroSt c -> c
+ Kibro: data KibroT m c a
+ Kibro: deleteSess :: (IConnection c) => String -> Kibro c ()
+ Kibro: getParams :: (IConnection c) => Kibro c [String]
+ Kibro: gets :: (MonadState s m) => (s -> a) -> m a
+ Kibro: instance (Monad m) => Functor (KibroT m c)
+ Kibro: instance (Monad m, IConnection c) => Applicative (KibroT m c)
+ Kibro: io :: (MonadIO m) => IO a -> m a
+ Kibro: stylesheet :: String -> Html
+ Kibro: type Page c = Kibro c CGIResult
+ Kibro: type PageList c = [(String, Page c)]
+ Kibro.DB: commit :: (IConnection c) => Kibro c ()
+ Kibro.DB: exec :: (IConnection c) => String -> [SqlValue] -> Kibro c Integer
+ Kibro.DB: query :: (IConnection c) => String -> [SqlValue] -> Kibro c [[SqlValue]]
+ Kibro.DB.Sqlite3: data Connection :: *
- Kibro: getInputDef :: (Functor f, MonadCGI f) => String -> String -> f String
+ Kibro: getInputDef :: (MonadCGI m) => String -> String -> m String
- Kibro: getSessDef :: (Functor (KibroT IO c), IConnection c) => String -> String -> KibroT IO c String
+ Kibro: getSessDef :: (IConnection c) => String -> String -> KibroT IO c String
- Kibro: readInputDef :: (Functor f, Read a, MonadCGI f) => String -> a -> f a
+ Kibro: readInputDef :: (MonadCGI m, Read a) => String -> a -> m a
- Kibro: readSessDef :: (Functor (KibroT IO c), IConnection c, Read a) => String -> a -> KibroT IO c a
+ Kibro: readSessDef :: (IConnection c, Read a) => String -> a -> KibroT IO c a

Files

− README
@@ -1,1 +0,0 @@-Kibro is a tool set for creating and working with FastCGI and Lighttpd.
− executable/Build.hs
@@ -1,21 +0,0 @@-module Build where--import Directory-import Control.Applicative-import Data.List-import System.Process-import System.FilePath-import System.Exit--import Utils--build :: [String] -> IO ()-build _ = inKibro $ \dir name -> do-            doing $ "Building " ++ name-            doBuild dir name-            return ()--doBuild :: String -> String -> IO ExitCode-doBuild dir name = do doing cmd; runCommand cmd >>= waitForProcess-      where cmd = "ghc --make src" ./. "Main.hs -o public" ./. fcgi ++ " -threaded"-            fcgi = name ++ ".fcgi"
− executable/Data/Lighttpd.hs
@@ -1,62 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-module Data.Lighttpd-    ((.=>.)-    ,(.=.)-    ,(./.)-    ,showSettings)-where--import System.FilePath-import Data.List-import Utils----------------------------------------------------------------------------------- Lighttpd configuration---- | Class for generalising the properties.-class LightyProperty a where toProp :: a -> Prop---- | Necessary instances.-instance LightyProperty [[Char]] where toProp = List-instance LightyProperty [Char] where toProp = String-instance LightyProperty Int where toProp = Number-instance LightyProperty [Prop] where toProp = Props-instance LightyProperty [[Prop]] where toProp = Props . map Props---- | A property.-data Prop = String String-          | Number Int-          | List [String]-          | Assign String Prop-          | Props [Prop]--- | A property "setting".-data Set = Set String Prop--instance Show Prop where-    show (String s) = show s-    show (Number n) = show n-    show (List xs)  = "(" ++ commas xs ++ ")" where-    show (Assign p v) = show p ++ " => " ++ show v-    show (Props ps) = "(" ++ commas ps ++ ")"--instance Show Set where show (Set s p) = s ++ " = " ++ show p---- | Print a list of settings in the lighttpd config format.-showSettings :: [Set] -> String-showSettings = unlines . map show--commas :: Show a => [a] -> String-commas = concat . intersperse "," . map show----------------------------------------------------------------------------------- Combinators---- | "Set" a property.-(.=.) :: LightyProperty a => String -> a -> Set-p .=. v = Set p (toProp v)-infixr 0 .=.---- | "Assign" a value to something.-(.=>.) :: LightyProperty a => String -> a -> Prop-(.=>.) a b = Assign a (toProp b)-infixr 0 .=>.
− executable/Help.hs
@@ -1,10 +0,0 @@-module Help where--import Utils----------------------------------------------------------------------------------- Help command--showHelp :: IO ()-showHelp = todo "help"-
executable/Main.hs view
@@ -18,6 +18,8 @@ import Start import Restart import Stop+import Configure+import Clean  import Utils @@ -37,4 +39,6 @@            ,("start",start)            ,("restart",restart)            ,("stop",stop)-           ,("refresh",refresh)]+           ,("refresh",refresh)+           ,("configure",configure)+           ,("clean",clean)]
− executable/New.hs
@@ -1,107 +0,0 @@---------------------------------------------------------------------------------- New project command--module New where--import System-import System.IO-import System.Directory-import Text.Printf-import Data.Maybe-import Data.List-import System.FilePath-import Data.Validate-import Control.Applicative--import Utils-import Help-import Data.Lighttpd--new :: [String] -> IO ()-new []     = do todo "interactive mode"; showHelp-new [name] = name .->. validName $ do-               doing $ "Creating project " ++ name-               dir <- getCurrentDirectory-               exists <- any (==name) <$> getDirectoryContents dir-               exists .->. (test "project must not exist" (==False)) $ do-                 makeDirStructure dir name---- | Valid project name.-validName = pattern "project name" "[a-z][a-z0-9]+"---------------------------- Directory structure--makeDirStructure :: String -> String -> IO ()-makeDirStructure dir name = do-  doing $ "Creating directory structure"-  mapM_ create (dirs name)-  mapM_ ($ name) [writeLighttpd,writeMain]-  doing $ "Writing " ++ conf; touch $ conf-      where conf = name ./. (name ++ ".kibro")--touch p = openFile p WriteMode >>= hClose--create f = do doing $ "Creating " ++ f-              createDirectoryIfMissing True f--dirs = (.//. ("app" .//. ["lighttpd","fastcgi","memcached"]-              ++-              ["db","public","src"]))-    where sup .//. sub = map (sup ./.) sub---------------------------- Lighttpd config--writeLighttpd :: String -> IO ()-writeLighttpd name = do-  doing $ "Writing " ++ path-  root <- (./. name) <$> getCurrentDirectory-  h <- openFile path WriteMode-  hPutStrLn h (lighttpdconf root name)-  hClose h-  where path = name ./. "app" ./. "lighttpd" ./. "lighttpd.conf"---------------------------- Default Main.hs--writeMain :: String -> IO ()-writeMain name = do-  doing $ "Writing " ++ path-  h <- openFile path WriteMode-  hPutStrLn h mainSrc-  hClose h-  where path = name ./. "src" ./. "Main.hs"----------------------------------------------------------------------------------- Lighttpd template--lighttpdconf :: String -> String -> String-lighttpdconf root name = showSettings-    ["server.modules"           .=. ["mod_rewrite","mod_redirect","mod_fastcgi"]-    ,"server.document-root"     .=. root ./. "public"-    ,"server.port"              .=. (3000 :: Int)-    ,"server.pid-file"          .=. root ./. "app" ./. "lighttpd" ./. "lighttpd.pid"-    ,"server.errorlog"          .=. root ./. "app" ./. "lighttpd" ./. "error.log"-    ,"server.dir-listing"       .=. "enable"-    ,"dir-listing.encoding"     .=. "utf-8"-    ,"mimetype.assign"          .=. ["" .=>. "text/plain"]-    ,"server.error-handler-404" .=. name ++ ".fcgi"-    ,"index-file.names"         .=. [name ++ ".fcgi"]-    ,"fastcgi.server"           .=.-     [(root ./. "public" ./. (name ++ ".fcgi")) .=>.-      [["socket" .=>. root ./. "app" ./. "fastcgi" ./. (name ++ ".sock")]]]-    ]----------------------------------------------------------------------------------- Main.hs template--mainSrc = "module Main where\n\-          \import Kibro\n\-          \import Kibro.DB.Sqlite3\n\-          \\n\-          \main = kibro (db \"\") pages\n\-          \\n\-          \pages = [(\".\", example)]\n\-          \\n\-          \example = output \"Change me! :-)\""
− executable/Restart.hs
@@ -1,19 +0,0 @@-module Restart where--import Start-import Stop-import Build--import Utils--restart :: [String] -> IO ()-restart _ = do stop []-               start []--refresh :: [String] -> IO ()-refresh _ = inKibro $ \dir name -> do-              doing "Rebuilding"-              build []-              doing "Restarting fastcgi"-              stopFastCGI dir name-              startFastCGI dir name
− executable/Start.hs
@@ -1,54 +0,0 @@-module Start where--import Directory-import Control.Applicative-import Data.List-import System.Process-import System.FilePath-import System.Exit-import Control.Monad--import Build-import Utils--start :: [String] -> IO ()-start _ = inKibro $ \dir name -> do-  let fcgi = "public" ./. (name++".fcgi")-  d <- doesFileExist fcgi-  if d-     then doStart dir name-     else do doing "Not built. Building"-             e <- doBuild dir name-             case e of-               ExitSuccess -> doStart dir name-               _           -> appError "cannot start"--doStart :: String -> String -> IO ()-doStart dir name = do-  pid <- doesFileExist $ "app" ./. "lighttpd" ./. "lighttpd.pid"-  when (not pid) startLighty-  pid <- doesFileExist $ "app" ./. "fastcgi" ./. "fastcgi.pid"-  when (not pid) (startFastCGI dir name)--startLighty = do-  doing "Starting lighttpd"-  r <- runCommand lighttpd >>= waitForProcess-  case r of-    ExitSuccess -> return ()-    _ -> appError "unable to start lighttpd! \-                  \perhaps the port is already \-                  \in use. Do: kibro configure --port <myport>"-    where lighttpd = "lighttpd -f app/lighttpd/lighttpd.conf"--startFastCGI dir name = do-  doing "Starting fastcgi process"-  doing fastcgi-  r <- runCommand fastcgi >>= waitForProcess-  case r of-    ExitSuccess -> return ()-    _ -> appError "unable to start the fastcgi process! is spawn-fcgi installed?"-    where fastcgi = "spawn-fcgi -f " ++ fcgi ++ " -s " ++ sock ++ " -P " ++ pid-          fcgi = "public" ./. name ++ ".fcgi"-          sock = dir ./. "app" ./. "fastcgi" ./. name ++ ".sock"-          pid = "app" ./. "fastcgi" ./. "fastcgi.pid"-
− executable/Stop.hs
@@ -1,39 +0,0 @@-module Stop where--import Directory-import System.Process-import Control.Applicative--import Utils--stop :: [String] -> IO ()-stop _ = inKibro $ \dir name -> do stopFastCGI dir name-                                   stopLighty dir name---- TODO: abstract this--stopLighty dir name = do-  exists <- doesFileExist pid-  if exists-     then do doing "Stopping lighttpd"-             id <- readFile pid-             -- TODO: make this compatible with windows-             runCommand $ "kill " ++ id-             return ()-     else return ()-      where pid = dir ./. "app" ./. "lighttpd" ./. "lighttpd.pid"  --stopFastCGI dir name = do-  exists <- doesFileExist pid-  if exists-     then do doing "Stopping fastcgi"-             id <- readFile pid-             -- TODO: make this compatible with windows-             runCommand $ "kill " ++ id-             deleteFile pid-             return ()-     else return ()-      where pid = dir ./. "app" ./. "fastcgi" ./. "fastcgi.pid"---
− executable/Utils.hs
@@ -1,53 +0,0 @@-module Utils where--import Data.Validate-import System.FilePath-import Control.Applicative-import System.Process-import System.FilePath-import System.Exit-import Directory-import Data.List----------------------------------------------------------------------------------- Output helpers--todo :: String -> IO ()-todo = putStrLn . ("TODO: "++)--doing :: String -> IO ()-doing = putStrLn . (++" ...")--appError :: String -> IO ()-appError err = error err----------------------------------------------------------------------------------- Input validation---- | With a valid value, do an action, otherwise throw an error.-(.->.) :: Str e => a -> Test e a b -> IO () -> IO ()-(val .->. test) thunk =-    case validate test val of-      Just errs -> appError $ "invalid: " ++ (concat $ map strToString errs)-      Nothing   -> thunk---- | Make a platform-independant path separator-(./.) :: String -> String -> FilePath-a ./. b = a ++ [pathSeparator] ++ b-infixl 9 ./.--inKibro :: (String -> String -> IO ()) -> IO ()-inKibro m = do-  dir <- getCurrentDirectory-  let name = dirName dir-  inKibro <- any (any (==".kibro") . tails) <$> getDirectoryContents dir-  if inKibro-     then m dir name-     else appError "not in a Kibro directory"--dirName :: String -> String-dirName = reverse . takeWhile (/=pathSeparator) . reverse---- TODO: use something cross platform.. no idea why I can't find one like--- this already. "~_~-deleteFile = runCommand . ("rm " ++)
kibro.cabal view
@@ -1,5 +1,5 @@ name:          kibro-version:       0.0+version:       0.1 synopsis:      Web development framework. description:   Web development framework. category:      Web@@ -12,8 +12,8 @@ cabal-Version: >= 1.2  library-   build-depends: base,HDBC,mtl,regex-compat,random,safe,xhtml,containers,fastcgi,cgi,HDBC-sqlite3-   exposed-modules: Kibro,Kibro.DB.Sqlite3+   build-depends: base,HDBC,mtl,regex-compat,random,safe,xhtml,containers,fastcgi,cgi,HDBC-sqlite3,data-default+   exposed-modules: Kibro,Kibro.DB,Kibro.DB.Sqlite3    hs-source-dirs: library/    GHC-Prof-options: -auto-all 
library/Kibro.hs view
@@ -2,10 +2,13 @@ module Kibro      (       module Network.CGI-    , module Database.HDBC-    , module Text.XHtml.Strict     , kibro-    , Kibro+    , Kibro, KibroT+    , Page+    , PageList+    , gets+    , con+    , getParams     , getSess     , readSess     , putSess@@ -14,8 +17,11 @@     , getInputDef     , readSessDef     , readInputDef+    , deleteSess     , outputHtml     , (<<$)+    , io+    , stylesheet     )     where @@ -33,6 +39,8 @@ import Safe import Text.XHtml.Strict import Control.Applicative+import Control.Arrow+import Data.Function  ------------------------------------------------------------------------------ -- Main Kibro entry point@@ -42,27 +50,36 @@ kibro getDb ps = do   ss  <- newMVar M.empty   ids <- newMVar [0..]-  db  <- getDb-  runFastCGIConcurrent' forkIO 1000 (kibroMain ss ids ps db)+  runFastCGIConcurrent' forkOS 1000 (handleErrors (kibroMain getDb ss ids ps)) -kibroMain :: IConnection c => SessionsVar -> IdsVar -> PageList c -> c -> CGI CGIResult-kibroMain ssvar ids ps db = do-  (id,s) <- getSession ids ssvar-  path <- liftM (fromMaybe "") $ getVar "REQUEST_URI"-  let p = maybe notFound snd (find (see path) ps)-  (sess,res) <- runKibro p (KibroSt db s)-  liftIO $ modifyMVar_ ssvar (\s -> return $ M.insert id sess s)-  return res-      where see path = match . flip matchRegex path . mkRegex . fst-            match = maybe False (const True)+-- TODO: use applicative on mvar+kibroMain :: IConnection c => (IO c) -> SessionsVar -> IdsVar -> PageList c -> CGI CGIResult+kibroMain getDb ssvar ids ps = do+  -- Connect to the database for this session+  db <- io $ getDb+  -- Get or create a new session+  (id,session) <- getSession ids ssvar+  -- Get the page to run+  (params,page) <- pageMatch ps <$> fromMaybe "" <$> getVar "REQUEST_URI"+  -- Run a page, returning a new session and result+  (session',result) <- runKibro page (KibroSt db session params)+  -- Update the session+  io $ modifyMVar_ ssvar (return . M.insert id session')+  --  ...+  return result +pageMatch :: IConnection c => PageList c -> String -> ([String],Page c)+pageMatch ps path = extract $ look $ map (first match) ps where+    match regex = matchRegex (mkRegex regex) path+    look = find (isJust . fst)+    extract = maybe ([],notFound) (first fromJust)+ runKibro :: IConnection c => Kibro c CGIResult -> KibroSt c -> CGI (Session,CGIResult) runKibro p st = evalStateT (unKibro (getSess p)) st where     getSess a = do r <- a                    ss <- gets session                    return (ss,r) --- TODO: make proper 404 notFound :: IConnection c => Page c notFound = getVar "REQUEST_URI" >>= outputNotFound . fromMaybe "" @@ -105,6 +122,11 @@                  let news = M.insert k v (session st)                  put st { session = news } +deleteSess :: (IConnection c) => String -> Kibro c ()+deleteSess k = do st <- get+                  let news = M.delete k (session st)+                  put st { session = news }+ writeSess :: (IConnection c, Show a) => String -> a -> Kibro c () writeSess k v = putSess k (show v)   @@ -118,15 +140,20 @@ -- | A state containing the current session and a database connection. data KibroSt c = KibroSt     { con     :: c-    , session :: Session }+    , session :: Session+    , params  :: [String] }  type Session = M.Map String String type Sessions = M.Map SessID Session type SessID = (Integer,Int)  newtype KibroT m c a = Kibro { unKibro :: (StateT (KibroSt c) (CGIT m) a) }-    deriving (Monad, MonadIO, MonadState (KibroSt c))+    deriving (Monad, MonadIO, MonadState (KibroSt c), Functor) +instance (Monad m, IConnection c) => Applicative (KibroT m c) where+    pure = return+    (<*>) = ap+ instance IConnection c => MonadCGI (KibroT IO c) where     cgiAddHeader n v = Kibro $ lift $ cgiAddHeader n v     cgiGet x = Kibro $ lift $ cgiGet x@@ -137,12 +164,17 @@ -- Kibro utilities -- +getParams :: IConnection c => Kibro c [String] +getParams = gets params+ outputHtml :: (HTML a, IConnection c) => a -> Kibro c CGIResult-outputHtml = output . showHtml+outputHtml = output . html . showHtmlFragment where+    html c = "<html>" ++ c ++ "</html>" -defGet n = (fromMaybe n <$>)+defGet n = liftM (fromMaybe n)  -- Simply utilities+ readSessDef  s v = defGet v $ readSess s readInputDef s v = defGet v $ readInput s getSessDef   s v = defGet v $ getSess s@@ -156,8 +188,11 @@ a <<$ b = a << b infixr 0 <<$ +stylesheet url = thelink ! [rel "stylesheet",thetype "text/css",href url] << ""+ ------------------------------------------------------------------------------ -- Utilities for this file -- +io :: MonadIO m => IO a -> m a io = liftIO
+ library/Kibro/DB.hs view
@@ -0,0 +1,35 @@+module Kibro.DB+    (query,exec,Kibro.DB.commit+    ,module Database.HDBC)+    where++import Kibro hiding (handleErrors)+import Database.HDBC hiding (commit)+import qualified Database.HDBC as HDBC+import Control.Exception++runSql :: IConnection c +       => (c -> String -> [SqlValue] -> IO a) -- ^ SQL function.+       -> c                                   -- ^ SQL connection.+       -> String                              -- ^ Query.+       -> [SqlValue]                          -- ^ Parameters.+       -> Kibro c a                           -- ^ Result.+runSql f dbh s vs = io $ handleSql throwSqlError $ f dbh s vs where+    throwSqlError (SqlError _ _ e) =+        throwIO $ AssertionFailed $+                    "SQL error: " ++ show s ++ " " ++ show vs ++ e++query :: IConnection c => String -> [SqlValue] -> Kibro c [[SqlValue]]+query sql values = do+  dbh <- gets con+  runSql quickQuery' dbh sql values++exec :: IConnection c => String -> [SqlValue] -> Kibro c Integer+exec sql values = do+  dbh <- gets con+  runSql run dbh sql values++commit :: IConnection c => Kibro c ()+commit = do+  dbh <- gets con+  io $ HDBC.commit dbh
library/Kibro/DB/Sqlite3.hs view
@@ -1,4 +1,6 @@-module Kibro.DB.Sqlite3 where+module Kibro.DB.Sqlite3 +    (db,Connection)+    where  import Database.HDBC.Sqlite3