web: code cleanup, better web ui supporting full patterns & period expressions

New dependency: http.
This commit is contained in:
Simon Michael 2009-05-24 21:16:58 +00:00
parent 4517cab686
commit 71e7f2b293
2 changed files with 67 additions and 51 deletions

View File

@ -20,6 +20,7 @@ import Happstack.State.Control (waitForTermination)
import System.Cmd (system) import System.Cmd (system)
import System.Info (os) import System.Info (os)
import System.Exit import System.Exit
import Network.HTTP (urlEncode, urlDecode, urlEncodeVars)
import Ledger import Ledger
import Options import Options
@ -27,72 +28,87 @@ import BalanceCommand
import RegisterCommand import RegisterCommand
import PrintCommand import PrintCommand
import HistogramCommand import HistogramCommand
import Utils (filterAndCacheLedgerWithOpts)
tcpport = 5000 tcpport = 5000
web :: [Opt] -> [String] -> Ledger -> IO () web :: [Opt] -> [String] -> Ledger -> IO ()
web opts args l = web opts args l = do
t <- getCurrentLocalTime -- how to get this per request ?
if Debug `elem` opts if Debug `elem` opts
then do then do
-- just run the server in the foreground
putStrLn $ printf "starting web server on port %d in debug mode" tcpport putStrLn $ printf "starting web server on port %d in debug mode" tcpport
simpleHTTP nullConf{port=tcpport} handlers simpleHTTP nullConf{port=tcpport} $ webHandlers opts args l t
else do else do
-- start the server (in background, so we can..) then start the web browser
putStrLn $ printf "starting web server on port %d" tcpport putStrLn $ printf "starting web server on port %d" tcpport
tid <- forkIO $ simpleHTTP nullConf{port=tcpport} handlers tid <- forkIO $ simpleHTTP nullConf{port=tcpport} $ webHandlers opts args l t
putStrLn "starting web browser" putStrLn "starting web browser"
openBrowserOn $ printf "http://localhost:%d/balance" tcpport openBrowserOn $ printf "http://localhost:%d/" tcpport
waitForTermination waitForTermination
putStrLn "shutting down web server..." putStrLn "shutting down web server..."
killThread tid killThread tid
putStrLn "shutdown complete" putStrLn "shutdown complete"
where webHandlers :: [Opt] -> [String] -> Ledger -> LocalTime -> ServerPartT IO Response
handlers :: ServerPartT IO Response webHandlers opts args l t = msum
handlers = msum [
[methodSP GET $ withDataFn (look "a") $ \a -> templatise $ balancereport [a] methodSP GET $ view showBalanceReport
,methodSP GET $ templatise $ balancereport [] ,dir "balance" $ view showBalanceReport
,dir "print" $ withDataFn (look "a") $ \a -> templatise $ printreport [a] ,dir "register" $ view showRegisterReport
,dir "print" $ templatise $ printreport [] ,dir "print" $ view showLedgerTransactions
,dir "register" $ withDataFn (look "a") $ \a -> templatise $ registerreport [a] ,dir "histogram" $ view showHistogram
,dir "register" $ templatise $ registerreport []
,dir "balance" $ withDataFn (look "a") $ \a -> templatise $ balancereport [a]
,dir "balance" $ templatise $ balancereport []
,dir "histogram" $ withDataFn (look "a") $ \a -> templatise $ histogramreport [a]
,dir "histogram" $ templatise $ histogramreport []
] ]
printreport apats = showLedgerTransactions opts (apats ++ args) l where
registerreport apats = showRegisterReport opts (apats ++ args) l view f = withDataFn rqdata $ render f
balancereport [] = showBalanceReport opts args l render f (a,p) = layout (a, p) $ f opts' args' l'
balancereport apats = showBalanceReport opts (apats ++ args) l' where
where l' = cacheLedger apats (rawledger l) -- re-filter by account pattern each time opts' = opts ++ [Period p]
histogramreport [] = showHistogram opts args l args' = args ++ (map urlDecode $ words a)
histogramreport apats = showHistogram opts (apats ++ args) l' -- re-filter the full ledger
where l' = cacheLedger apats (rawledger l) -- re-filter by account pattern each time l' = filterAndCacheLedgerWithOpts opts' args' t (rawledgertext l) (rawledger l)
templatise :: String -> ServerPartT IO Response rqdata = do
templatise s = do a <- look "a" `mplus` return "" -- filter patterns
p <- look "p" `mplus` return "" -- reporting period
return (a,p)
layout :: (String, String) -> String -> ServerPartT IO Response
layout (a,p) s = do
r <- askRq r <- askRq
return $ setHeader "Content-Type" "text/html" $ toResponse $ maintemplate r s return $ setHeader "Content-Type" "text/html" $ toResponse $ maintemplate (a,p) r s
maintemplate :: Request -> String -> String maintemplate :: (String, String) -> Request -> String -> String
maintemplate r = printf (unlines maintemplate (a,p) r = printf (unlines
["<div style=float:right>" ["<div style=\"float:right;text-align:right;\">"
,"<form action=%s>search:&nbsp;<input name=a value=%s></form>" ,"<form action=%s>"
,"&nbsp; filter by:&nbsp;<input name=a size=30 value=\"%s\">"
,"&nbsp; reporting period:&nbsp;<input name=p size=30 value=\"%s\">"
,resetlink
,"</form>"
,"</div>" ,"</div>"
,"<div align=center style=width:100%%>" ,"<div style=\"width:100%%; font-weight:bold;\">"
," <a href=balance>balance</a>" ," <a href=balance%s>balance</a>"
,"|" ,"|"
," <a href=register>register</a>" ," <a href=register%s>register</a>"
,"|" ,"|"
," <a href=print>print</a>" ," <a href=print%s>print</a>"
,"|" ,"|"
," <a href=histogram>histogram</a>" ," <a href=histogram%s>histogram</a>"
,"</div>" ,"</div>"
,"<pre>%s</pre>" ,"<pre>%s</pre>"
]) ]) u a p q q q q
(dropWhile (=='/') $ rqUri r) where
(fromMaybe "" $ queryValue "a" r) u = dropWhile (=='/') $ rqUri r
-- another way to get them
-- a = fromMaybe "" $ queryValue "a" r
-- p = fromMaybe "" $ queryValue "p" r
q' = intercalate "&" $ if null a then [] else [(("a="++).urlEncode) a] ++ if null p then [] else [(("p="++).urlEncode) p]
q = if null q' then "" else '?':q'
resetlink | null a && null p = ""
| otherwise = printf "&nbsp; <a href=%s>reset</a>" u
queryValues :: String -> Request -> [String] queryValues :: String -> Request -> [String]
queryValues q r = map (B.unpack . inputValue . snd) $ filter ((==q).fst) $ rqInputs r queryValues q r = map (B.unpack . inputValue . snd) $ filter ((==q).fst) $ rqInputs r

View File

@ -53,7 +53,7 @@ Executable hledger
Build-Depends: base, containers, haskell98, directory, parsec, Build-Depends: base, containers, haskell98, directory, parsec,
regex-compat, regexpr>=0.5.1, old-locale, time, regex-compat, regexpr>=0.5.1, old-locale, time,
HUnit, mtl, bytestring, filepath, process, testpack, HUnit, mtl, bytestring, filepath, process, testpack,
regex-pcre, csv, split, utf8-string regex-pcre, csv, split, utf8-string, http
Other-Modules: Other-Modules:
AddCommand AddCommand