hledger/HappsCommand.hs
2009-02-13 20:45:32 +00:00

133 lines
5.0 KiB
Haskell

{-|
A happs-based web UI for hledger.
-}
module WebCommand
where
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 qualified Text.StringTemplate as T
-- import Codec.Compression.GZip (compress)
-- import Data.ByteString.UTF8 (fromString)
import HAppS.Server
import System.Cmd (system)
import System.Info (os)
import System.Exit
import Ledger
import Options
import BalanceCommand
import RegisterCommand
import PrintCommand
-- | The application state when running the ui command.
data AppState = AppState {
aw :: Int -- ^ window width
,ah :: Int -- ^ window height
,amsg :: String -- ^ status message
,aopts :: [Opt] -- ^ command-line opts
,aargs :: [String] -- ^ command-line args
,aledger :: Ledger -- ^ parsed ledger
,abuf :: [String] -- ^ lines of the current buffered view
-- ^ never null, head is current location
} deriving (Show)
tcpport = 5000
web :: [Opt] -> [String] -> Ledger -> IO ()
web opts args l = do
putStrLn $ printf "starting web server on port %d" tcpport
tid <- forkIO $ simpleHTTP nullConf{port=tcpport} $ handlers opts args l
putStrLn "starting web browser"
browseUrl $ printf "http://localhost:%s/print" (show tcpport)
waitForTermination
putStrLn "shutting down..."
killThread tid
putStrLn "shutdown complete"
template = "<div align=center style=width:100%%>" ++
" <a href=print>ledger</a>" ++
" | <a href=register>register</a>" ++
" | <a href=balance>balance</a>" ++
"</div>" ++
"<pre>%s</pre>"
type Handler = ServerPart Response
handlers :: [Opt] -> [String] -> Ledger -> [Handler]
handlers opts args l =
[
dir "print" [withRequest $ \r -> respond $ printreport r]
, dir "register" [withRequest $ \r -> respond $ registerreport r]
, dir "balance" [withRequest $ \r -> respond $ balancereport r]
]
where
respond = ok . setContentType "text/html" . toResponse . (printf template :: String -> String)
printreport r = showEntries opts (pats r ++ args) l
registerreport r = showRegisterReport opts (pats r ++ args) l
balancereport r = showBalanceReport (opts++[SubTotal]) (pats r ++ args) l
pats r = as -- ++ if null ds then [] else ("--":ds)
where (as,ds) = (queryValues "a" r, queryValues "d" 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.
browseUrl :: String -> IO ExitCode
browseUrl 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 -a firefox", "open"]
| os=="mingw32" = ["firefox","safari","opera","iexplore"]
| otherwise = ["firefox","sensible-browser"]
-- 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);
withExpiresHeaders :: ServerPart Response -> ServerPart Response
withExpiresHeaders sp = require getCacheTime $ \t -> [liftM (setHeader "Expires" $ formatDateTime "%a, %d %b %Y %T GMT" t) sp]
getCacheTime :: IO (Maybe UTCTime)
getCacheTime = getCurrentTime >>= (return . Just . addMinutes 360)
addMinutes :: Int -> UTCTime -> UTCTime
addMinutes n = addUTCTime (fromIntegral n)
formatDateTime :: String -> UTCTime -> String
formatDateTime = formatTime defaultTimeLocale
setContentType :: String -> Response -> Response
setContentType = setHeader "Content-Type"
setFilename :: String -> Response -> Response
setFilename = setHeader "Content-Disposition" . \fname -> "attachment: filename=\"" ++ fname ++ "\""
-- gzipBinary :: Response -> Response
-- gzipBinary r@(Response {rsBody = b}) = setHeader "Content-Encoding" "gzip" $ r {rsBody = compress b}
-- acceptsZip :: Request -> Bool
-- acceptsZip req = isJust $ M.lookup (fromString "accept-encoding") (rqHeaders req)