hledger/WebCommand.hs
2009-04-03 11:19:04 +00:00

128 lines
4.7 KiB
Haskell

{-|
A happs-based web UI for hledger.
-}
module WebCommand
where
import Control.Monad.Trans (liftIO)
import Data.ByteString.Lazy.UTF8 (toString)
import qualified Data.Map as M
import Data.Map ((!))
import Data.Time.Clock
import Data.Time.Format
import System.Locale
import Control.Concurrent
import qualified Data.ByteString.Lazy.Char8 as B
import Happstack.Data (defaultValue)
import Happstack.Server
import Happstack.Server.HTTP.FileServe (fileServe)
import Happstack.State.Control (waitForTermination)
import System.Cmd (system)
import System.Info (os)
import System.Exit
import Ledger
import Options
import BalanceCommand
import RegisterCommand
import PrintCommand
import HistogramCommand
tcpport = 5000
web :: [Opt] -> [String] -> Ledger -> IO ()
web opts args l =
if Debug `elem` opts
then do
putStrLn $ printf "starting web server on port %d in debug mode" tcpport
simpleHTTP nullConf{port=tcpport} handlers
else do
putStrLn $ printf "starting web server on port %d" tcpport
tid <- forkIO $ simpleHTTP nullConf{port=tcpport} handlers
putStrLn "starting web browser"
openBrowserOn $ printf "http://localhost:%d/balance" tcpport
waitForTermination
putStrLn "shutting down web server..."
killThread tid
putStrLn "shutdown complete"
where
handlers :: ServerPartT IO Response
handlers = msum
[methodSP GET $ withDataFn (look "a") $ \a -> templatise $ balancereport [a]
,methodSP GET $ templatise $ balancereport []
,dir "print" $ withDataFn (look "a") $ \a -> templatise $ printreport [a]
,dir "print" $ templatise $ printreport []
,dir "register" $ withDataFn (look "a") $ \a -> templatise $ registerreport [a]
,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
registerreport apats = showRegisterReport opts (apats ++ args) l
balancereport [] = showBalanceReport opts args l
balancereport apats = showBalanceReport opts (apats ++ args) l'
where l' = cacheLedger apats (rawledger l) -- re-filter by account pattern each time
histogramreport [] = showHistogram opts args l
histogramreport apats = showHistogram opts (apats ++ args) l'
where l' = cacheLedger apats (rawledger l) -- re-filter by account pattern each time
templatise :: String -> ServerPartT IO Response
templatise s = do
r <- askRq
return $ setHeader "Content-Type" "text/html" $ toResponse $ maintemplate r s
maintemplate :: Request -> String -> String
maintemplate r = printf (unlines
["<div style=float:right>"
,"<form action=%s>search:&nbsp;<input name=a value=%s></form>"
,"</div>"
,"<div align=center style=width:100%%>"
," <a href=balance>balance</a>"
,"|"
," <a href=register>register</a>"
,"|"
," <a href=print>print</a>"
,"|"
," <a href=histogram>histogram</a>"
,"</div>"
,"<pre>%s</pre>"
])
(dropWhile (=='/') $ rqUri r)
(fromMaybe "" $ queryValue "a" r)
queryValues :: String -> Request -> [String]
queryValues q r = map (B.unpack . inputValue . snd) $ filter ((==q).fst) $ rqInputs r
queryValue :: String -> Request -> Maybe String
queryValue q r = case filter ((==q).fst) $ rqInputs r of
[] -> Nothing
is -> Just $ B.unpack $ inputValue $ snd $ head is
-- | Attempt to open a web browser on the given url, all platforms.
openBrowserOn :: String -> IO ExitCode
openBrowserOn u = trybrowsers browsers u
where
trybrowsers (b:bs) u = do
e <- system $ printf "%s %s" b u
case e of
ExitSuccess -> return ExitSuccess
ExitFailure _ -> trybrowsers bs u
trybrowsers [] u = do
putStrLn $ printf "Sorry, I could not start a browser (tried: %s)" $ intercalate ", " browsers
putStrLn $ printf "Please open your browser and visit %s" u
return $ ExitFailure 127
browsers | os=="darwin" = ["open"]
| os=="mingw32" = ["firefox","safari","opera","iexplore"]
| otherwise = ["sensible-browser","firefox"]
-- jeffz: write a ffi binding for it using the Win32 package as a basis
-- start by adding System/Win32/Shell.hsc and follow the style of any
-- other module in that directory for types, headers, error handling and
-- what not.
-- ::ShellExecute(NULL, "open", "www.somepage.com", NULL, NULL, SW_SHOWNORMAL);
-- ::ShellExecute(NULL, "open", "firefox.exe", "www.somepage.com" NULL, SW_SHOWNORMAL);