web: switch to hack/loli/hsp, allow web data entry, detect file changes
This commit is contained in:
parent
aa4fab9468
commit
2cdc21959e
362
Commands/Web.hs
362
Commands/Web.hs
@ -1,38 +1,65 @@
|
|||||||
|
{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-}
|
||||||
|
{-# OPTIONS_GHC -F -pgmFtrhsx #-}
|
||||||
{-|
|
{-|
|
||||||
A server-side-html web UI using happstack.
|
A web-based UI.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Commands.Web
|
module Commands.Web
|
||||||
where
|
where
|
||||||
|
import Control.Applicative.Error (Failing(Success,Failure))
|
||||||
import Control.Concurrent
|
import Control.Concurrent
|
||||||
import Happstack.Server
|
import Control.Monad.Reader (ask)
|
||||||
|
import Data.IORef (newIORef, atomicModifyIORef)
|
||||||
|
import HSP hiding (Request)
|
||||||
|
import HSP.HTML (renderAsHTML)
|
||||||
|
import qualified HSX.XMLGenerator (XML)
|
||||||
|
import Hack.Contrib.Constants (_TextHtmlUTF8)
|
||||||
|
import Hack.Contrib.Response (set_content_type)
|
||||||
|
import Hack.Handler.Happstack (run)
|
||||||
import Happstack.State.Control (waitForTermination)
|
import Happstack.State.Control (waitForTermination)
|
||||||
import Network.HTTP (urlEncode, urlDecode)
|
import Network.HTTP (urlEncode, urlDecode)
|
||||||
import Text.XHtml hiding (dir)
|
import Network.Loli (loli, io, get, post, html, text, public)
|
||||||
|
--import Network.Loli.Middleware.IOConfig (ioconfig)
|
||||||
import Ledger
|
import Network.Loli.Type (AppUnit)
|
||||||
|
import Network.Loli.Utils (update)
|
||||||
import Options hiding (value)
|
import Options hiding (value)
|
||||||
|
import System.Directory (getModificationTime)
|
||||||
|
import System.IO.Storage (withStore, putValue, getValue, getDefaultValue)
|
||||||
|
import System.Time (ClockTime, getClockTime, diffClockTimes, TimeDiff(TimeDiff))
|
||||||
|
import Text.XHtml hiding (dir, text, param, label)
|
||||||
|
import Text.XHtml.Strict ((<<),(+++),(!))
|
||||||
|
import qualified HSP (Request(..))
|
||||||
|
import qualified Hack (Env, http, Response)
|
||||||
|
import qualified Hack.Contrib.Request (inputs, params, path)
|
||||||
|
import qualified Hack.Contrib.Response (redirect)
|
||||||
|
import qualified Text.XHtml.Strict as H
|
||||||
|
|
||||||
|
import Commands.Add (addTransaction)
|
||||||
import Commands.Balance
|
import Commands.Balance
|
||||||
import Commands.Register
|
|
||||||
import Commands.Print
|
|
||||||
import Commands.Histogram
|
import Commands.Histogram
|
||||||
import Utils (filterAndCacheLedgerWithOpts, openBrowserOn)
|
import Commands.Print
|
||||||
|
import Commands.Register
|
||||||
|
import Ledger
|
||||||
|
import Utils (filterAndCacheLedgerWithOpts, openBrowserOn, readLedgerWithOpts)
|
||||||
|
|
||||||
|
-- import Debug.Trace
|
||||||
|
-- strace :: Show a => a -> a
|
||||||
|
-- strace a = trace (show a) a
|
||||||
|
|
||||||
tcpport = 5000
|
tcpport = 3000 :: Int
|
||||||
|
homeurl = printf "http://localhost:%d/" tcpport
|
||||||
|
|
||||||
web :: [Opt] -> [String] -> Ledger -> IO ()
|
web :: [Opt] -> [String] -> Ledger -> IO ()
|
||||||
web opts args l = do
|
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
|
-- 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 opts args l t
|
server opts args l
|
||||||
else do
|
else do
|
||||||
-- start the server (in background, so we can..) then start the web browser
|
-- start the server (in background, so we can..) then start the web browser
|
||||||
printf "starting web interface at %s\n" homeurl
|
printf "starting web interface at %s\n" homeurl
|
||||||
tid <- forkIO $ simpleHTTP nullConf{port=tcpport} $ handlers opts args l t
|
tid <- forkIO $ server opts args l
|
||||||
putStrLn "starting web browser"
|
putStrLn "starting web browser"
|
||||||
openBrowserOn homeurl
|
openBrowserOn homeurl
|
||||||
waitForTermination
|
waitForTermination
|
||||||
@ -40,77 +67,260 @@ web opts args l = do
|
|||||||
killThread tid
|
killThread tid
|
||||||
putStrLn "shutdown complete"
|
putStrLn "shutdown complete"
|
||||||
|
|
||||||
homeurl = printf "http://localhost:%d/" tcpport
|
getenv = ask
|
||||||
|
response = update
|
||||||
|
redirect u c = response $ Hack.Contrib.Response.redirect u c
|
||||||
|
|
||||||
handlers :: [Opt] -> [String] -> Ledger -> LocalTime -> ServerPartT IO Response
|
reqparam :: Hack.Env -> String -> [String]
|
||||||
handlers opts args l t = msum
|
reqparam env p = map snd $ filter ((==p).fst) $ Hack.Contrib.Request.params env
|
||||||
[
|
|
||||||
methodSP GET $ view showBalanceReport
|
|
||||||
,dir "balance" $ view showBalanceReport
|
|
||||||
,dir "register" $ view showRegisterReport
|
|
||||||
,dir "print" $ view showLedgerTransactions
|
|
||||||
,dir "histogram" $ view showHistogram
|
|
||||||
]
|
|
||||||
where
|
|
||||||
view f = withDataFn rqdata $ render f
|
|
||||||
render f (a,p) = renderPage (a,p) $ f opts' args' l'
|
|
||||||
where
|
|
||||||
opts' = opts ++ [Period p]
|
|
||||||
args' = args ++ (map urlDecode $ words a)
|
|
||||||
-- re-filter the full ledger with the new opts
|
|
||||||
l' = filterAndCacheLedgerWithOpts opts' args' t (rawledgertext l) (rawledger l)
|
|
||||||
rqdata = do
|
|
||||||
a <- look "a" `mplus` return "" -- filter patterns
|
|
||||||
p <- look "p" `mplus` return "" -- reporting period
|
|
||||||
return (a,p)
|
|
||||||
renderPage :: (String, String) -> String -> ServerPartT IO Response
|
|
||||||
renderPage (a,p) s = do
|
|
||||||
r <- askRq
|
|
||||||
return $ setHeader "Content-Type" "text/html" $ toResponse $ renderHtml $ hledgerview r a p s
|
|
||||||
|
|
||||||
hledgerview :: Request -> String -> String -> String -> Html
|
ledgerFileModifiedTime :: Ledger -> IO ClockTime
|
||||||
hledgerview r a p' s = body << topbar r a p' +++ pre << s
|
ledgerFileModifiedTime l
|
||||||
|
| null path = getClockTime
|
||||||
|
| otherwise = getModificationTime path `Prelude.catch` \e -> getClockTime
|
||||||
|
where path = filepath $ rawledger l
|
||||||
|
|
||||||
topbar :: Request -> String -> String -> Html
|
ledgerFileReadTime :: Ledger -> ClockTime
|
||||||
topbar r a p' = concatHtml
|
ledgerFileReadTime l = filereadtime $ rawledger l
|
||||||
[thediv ! [thestyle "float:right; text-align:right;"] << searchform r a p'
|
|
||||||
,thediv ! [thestyle "width:100%; font-weight:bold;"] << navlinks r a p']
|
|
||||||
|
|
||||||
searchform :: Request -> String -> String -> Html
|
reload :: Ledger -> IO Ledger
|
||||||
searchform r a p' =
|
reload l = do
|
||||||
form ! [action u] << concatHtml
|
l' <- readLedgerWithOpts [] [] (filepath $ rawledger l)
|
||||||
[spaceHtml +++ stringToHtml "filter by:" +++ spaceHtml
|
putValue "hledger" "ledger" l'
|
||||||
,textfield "a" ! [size s, value a]
|
return l'
|
||||||
,spaceHtml
|
|
||||||
,spaceHtml +++ stringToHtml "reporting period:" +++ spaceHtml
|
reloadIfChanged :: [Opt] -> [String] -> Ledger -> IO Ledger
|
||||||
,textfield "p" ! [size s, value p']
|
reloadIfChanged opts args l = do
|
||||||
,submit "submit" "filter" ! [thestyle "display:none;"]
|
tmod <- ledgerFileModifiedTime l
|
||||||
,resetlink]
|
let tread = ledgerFileReadTime l
|
||||||
|
newer = diffClockTimes tmod tread > (TimeDiff 0 0 0 0 0 0 0)
|
||||||
|
-- when (Debug `elem` opts) $ printf "checking file, last modified %s, last read %s, %s\n" (show tmod) (show tread) (show newer)
|
||||||
|
if newer
|
||||||
|
then do
|
||||||
|
when (Verbose `elem` opts) $ printf "%s has changed, reloading\n" (filepath $ rawledger l)
|
||||||
|
reload l
|
||||||
|
else return l
|
||||||
|
|
||||||
|
-- refilter :: [Opt] -> [String] -> Ledger -> LocalTime -> IO Ledger
|
||||||
|
-- refilter opts args l t = return $ filterAndCacheLedgerWithOpts opts args t (rawledgertext l) (rawledger l)
|
||||||
|
|
||||||
|
server :: [Opt] -> [String] -> Ledger -> IO ()
|
||||||
|
server opts args l =
|
||||||
|
-- server initialisation
|
||||||
|
withStore "hledger" $ do -- IO ()
|
||||||
|
putValue "hledger" "ledger" l
|
||||||
|
run $ -- (Env -> IO Response) -> IO ()
|
||||||
|
\env -> do -- IO Response
|
||||||
|
-- general request handler
|
||||||
|
printf $ "request\n"
|
||||||
|
tl <- getCurrentLocalTime
|
||||||
|
let a = intercalate "+" $ reqparam env "a"
|
||||||
|
p = intercalate "+" $ reqparam env "p"
|
||||||
|
opts' = opts ++ [Period p]
|
||||||
|
args' = args ++ (map urlDecode $ words a)
|
||||||
|
l' <- fromJust `fmap` getValue "hledger" "ledger"
|
||||||
|
l'' <- reloadIfChanged opts' args' l'
|
||||||
|
-- declare path-specific request handlers
|
||||||
|
let command :: [String] -> ([Opt] -> [String] -> Ledger -> String) -> AppUnit
|
||||||
|
command msgs f = string msgs $ f opts' args' l''
|
||||||
|
(loli $ -- State Loli () -> (Env -> IO Response)
|
||||||
|
do
|
||||||
|
get "/balance" $ command [] showBalanceReport -- String -> ReaderT Env (StateT Response IO) () -> State Loli ()
|
||||||
|
get "/register" $ command [] showRegisterReport
|
||||||
|
get "/histogram" $ command [] showHistogram
|
||||||
|
get "/ledger" $ ledgerpage [] l'' $ showLedgerTransactions opts' args'
|
||||||
|
post "/ledger" $ handleAddform l''
|
||||||
|
get "/env" $ getenv >>= (text . show)
|
||||||
|
get "/params" $ getenv >>= (text . show . Hack.Contrib.Request.params)
|
||||||
|
get "/inputs" $ getenv >>= (text . show . Hack.Contrib.Request.inputs)
|
||||||
|
public (Just "Commands/Web") ["/static"]
|
||||||
|
get "/" $ redirect (homeurl++"balance") Nothing
|
||||||
|
) env
|
||||||
|
|
||||||
|
ledgerpage :: [String] -> Ledger -> (Ledger -> String) -> AppUnit
|
||||||
|
ledgerpage msgs l f = do
|
||||||
|
env <- getenv
|
||||||
|
l' <- io $ reloadIfChanged [] [] l
|
||||||
|
hsp msgs $ const <div><% addform env %><pre><% f l' %></pre></div>
|
||||||
|
|
||||||
|
-- | A loli directive to serve a string in pre tags within the hledger web
|
||||||
|
-- layout.
|
||||||
|
string :: [String] -> String -> AppUnit
|
||||||
|
string msgs s = hsp msgs $ const <pre><% s %></pre>
|
||||||
|
|
||||||
|
-- | A loli directive to serve a hsp template wrapped in the hledger web
|
||||||
|
-- layout. The hack environment is passed in to every hsp template as an
|
||||||
|
-- argument, since I don't see how to get it within the hsp monad.
|
||||||
|
-- A list of messages is also passed, eg for form errors.
|
||||||
|
hsp :: [String] -> (Hack.Env -> HSP XML) -> AppUnit
|
||||||
|
hsp msgs f = do
|
||||||
|
env <- getenv
|
||||||
|
let contenthsp = f env
|
||||||
|
pagehsp = hledgerpage env msgs title contenthsp
|
||||||
|
html =<< (io $ do
|
||||||
|
hspenv <- hackEnvToHspEnv env
|
||||||
|
(_,xml) <- runHSP html4Strict pagehsp hspenv
|
||||||
|
return $ addDoctype $ applyFixups $ renderAsHTML xml)
|
||||||
|
response $ set_content_type _TextHtmlUTF8
|
||||||
where
|
where
|
||||||
-- another way to get them
|
title = ""
|
||||||
-- a = fromMaybe "" $ queryValue "a" r
|
addDoctype = ("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n" ++)
|
||||||
-- p = fromMaybe "" $ queryValue "p" r
|
applyFixups = gsubRegexPR "\\[NBSP\\]" " "
|
||||||
u = rqUri r
|
hackEnvToHspEnv :: Hack.Env -> IO HSPEnv
|
||||||
s = "20"
|
hackEnvToHspEnv env = do
|
||||||
resetlink | null a && null p' = noHtml
|
x <- newIORef 0
|
||||||
| otherwise = spaceHtml +++ anchor ! [href u] << stringToHtml "reset"
|
let req = HSP.Request (reqparam env) (Hack.http env)
|
||||||
|
num = NumberGen (atomicModifyIORef x (\a -> (a+1,a)))
|
||||||
|
return $ HSPEnv req num
|
||||||
|
|
||||||
navlinks :: Request -> String -> String -> Html
|
-- htmlToHsp :: Html -> HSP XML
|
||||||
navlinks _ a p' =
|
-- htmlToHsp h = return $ cdata $ showHtml h
|
||||||
concatHtml $ intersperse sep $ map linkto ["balance", "register", "print", "histogram"]
|
|
||||||
|
-- views
|
||||||
|
|
||||||
|
hledgerpage :: Hack.Env -> [String] -> String -> HSP XML -> HSP XML
|
||||||
|
hledgerpage env msgs title content =
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/style.css" media="all" />
|
||||||
|
<title><% title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<% navbar env %>
|
||||||
|
<span id="messages"><% intercalate ", " msgs %></span>
|
||||||
|
<div id="content"><% content %></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
navbar :: Hack.Env -> HSP XML
|
||||||
|
navbar env =
|
||||||
|
<div id="navbar">
|
||||||
|
<div style="float:right; text-align:right;"><% searchform env %></div>
|
||||||
|
<% navlinks env %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
getParamOrNull p = fromMaybe "" `fmap` getParam p
|
||||||
|
|
||||||
|
navlinks :: Hack.Env -> HSP XML
|
||||||
|
navlinks env = do
|
||||||
|
a <- getParamOrNull "a"
|
||||||
|
p <- getParamOrNull "p"
|
||||||
|
let addparams=(++(printf "?a=%s&p=%s" (urlEncode a) (urlEncode p)))
|
||||||
|
link s = <a href=(addparams s) class="navlink"><% s %></a>
|
||||||
|
<div id="navlinks">
|
||||||
|
<% link "balance" %> |
|
||||||
|
<% link "register" %> |
|
||||||
|
<% link "histogram" %> |
|
||||||
|
<% link "ledger" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
searchform :: Hack.Env -> HSP XML
|
||||||
|
searchform env = do
|
||||||
|
a <- getParamOrNull "a"
|
||||||
|
p <- getParamOrNull "p"
|
||||||
|
let resetlink | null a && null p = <span></span>
|
||||||
|
| otherwise = <span>[NBSP]<a href=u>reset</a></span>
|
||||||
|
where u = dropWhile (=='/') $ Hack.Contrib.Request.path env
|
||||||
|
<form action="" id="searchform">
|
||||||
|
[NBSP]filter by:[NBSP]<input name="a" size="20" value=a
|
||||||
|
/>[NBSP][NBSP]reporting period:[NBSP]<input name="p" size="20" value=p />
|
||||||
|
<input type="submit" name="submit" value="filter" style="display:none" />
|
||||||
|
<% resetlink %>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
addform :: Hack.Env -> HSP XML
|
||||||
|
addform env = do
|
||||||
|
let inputs = Hack.Contrib.Request.inputs env
|
||||||
|
date = fromMaybe "" $ lookup "date" inputs
|
||||||
|
desc = fromMaybe "" $ lookup "desc" inputs
|
||||||
|
<form action="" id="addform" method="POST">
|
||||||
|
<table border="0">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Date: <input size="10" name="date" value=date />[NBSP]
|
||||||
|
Description: <input size="40" name="desc" value=desc />[NBSP]
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% transactionfields 1 env %>
|
||||||
|
<% transactionfields 2 env %>
|
||||||
|
<tr align="right"><td><input type="submit" value="add" /></td></tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
transactionfields :: Int -> Hack.Env -> HSP XML
|
||||||
|
transactionfields n env = do
|
||||||
|
let inputs = Hack.Contrib.Request.inputs env
|
||||||
|
acct = fromMaybe "" $ lookup acctvar inputs
|
||||||
|
amt = fromMaybe "" $ lookup amtvar inputs
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
[NBSP][NBSP]
|
||||||
|
Account: <input size="40" name=acctvar value=acct />[NBSP]
|
||||||
|
Amount: <input size="10" name=amtvar value=amt />[NBSP]
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
where
|
where
|
||||||
sep = stringToHtml " | "
|
numbered = (++ show n)
|
||||||
linkto s = anchor ! [href (s++q)] << s
|
acctvar = numbered "acct"
|
||||||
q' = intercalate "&" $
|
amtvar = numbered "amt"
|
||||||
(if null a then [] else [(("a="++).urlEncode) a]) ++
|
|
||||||
(if null p' then [] else [(("p="++).urlEncode) p'])
|
|
||||||
q = if null q' then "" else '?':q'
|
|
||||||
|
|
||||||
-- queryValues :: String -> Request -> [String]
|
handleAddform :: Ledger -> AppUnit
|
||||||
-- queryValues q r = map (B.unpack . inputValue . snd) $ filter ((==q).fst) $ rqInputs r
|
handleAddform l = do
|
||||||
|
env <- getenv
|
||||||
|
handle $ validate env
|
||||||
|
where
|
||||||
|
validate :: Hack.Env -> Failing LedgerTransaction
|
||||||
|
validate env =
|
||||||
|
let inputs = Hack.Contrib.Request.inputs env
|
||||||
|
date = fromMaybe "" $ lookup "date" inputs
|
||||||
|
desc = fromMaybe "" $ lookup "desc" inputs
|
||||||
|
acct1 = fromMaybe "" $ lookup "acct1" inputs
|
||||||
|
amt1 = fromMaybe "" $ lookup "amt1" inputs
|
||||||
|
acct2 = fromMaybe "" $ lookup "acct2" inputs
|
||||||
|
amt2 = fromMaybe "" $ lookup "amt2" inputs
|
||||||
|
validateDate "" = ["missing date"]
|
||||||
|
validateDate s = []
|
||||||
|
validateDesc "" = ["missing description"]
|
||||||
|
validateDesc s = []
|
||||||
|
validateAcct1 "" = ["missing account 1"]
|
||||||
|
validateAcct1 s = []
|
||||||
|
validateAmt1 "" = ["missing amount 1"]
|
||||||
|
validateAmt1 s = []
|
||||||
|
validateAcct2 "" = ["missing account 2"]
|
||||||
|
validateAcct2 s = []
|
||||||
|
validateAmt2 "" = ["missing amount 2"]
|
||||||
|
validateAmt2 s = []
|
||||||
|
t = LedgerTransaction {
|
||||||
|
ltdate = parsedate date
|
||||||
|
,lteffectivedate=Nothing
|
||||||
|
,ltstatus=False
|
||||||
|
,ltcode=""
|
||||||
|
,ltdescription=desc
|
||||||
|
,ltcomment=""
|
||||||
|
,ltpostings=[
|
||||||
|
Posting False acct1 (Mixed [dollars $ read amt1]) "" RegularPosting
|
||||||
|
,Posting False acct2 (Mixed [dollars $ read amt2]) "" RegularPosting
|
||||||
|
]
|
||||||
|
,ltpreceding_comment_lines=""
|
||||||
|
}
|
||||||
|
errs = concat [
|
||||||
|
validateDate date
|
||||||
|
,validateDesc desc
|
||||||
|
,validateAcct1 acct1
|
||||||
|
,validateAmt1 amt1
|
||||||
|
,validateAcct2 acct2
|
||||||
|
,validateAmt2 amt2
|
||||||
|
]
|
||||||
|
errs' | null errs = either (:[]) (const []) (balanceLedgerTransaction t)
|
||||||
|
| otherwise = errs
|
||||||
|
in
|
||||||
|
case null errs' of
|
||||||
|
False -> Failure errs'
|
||||||
|
True -> Success t
|
||||||
|
|
||||||
-- queryValue :: String -> Request -> Maybe String
|
handle :: Failing LedgerTransaction -> AppUnit
|
||||||
-- queryValue q r = case filter ((==q).fst) $ rqInputs r of
|
handle (Failure errs) = hsp errs addform
|
||||||
-- [] -> Nothing
|
handle (Success t) = io (addTransaction l t >> reload l) >> (ledgerpage [msg] l (showLedgerTransactions [] [])) -- redirect (homeurl++"print") Nothing -- hsp [msg] addform
|
||||||
-- is -> Just $ B.unpack $ inputValue $ snd $ head is
|
where msg = printf "\nAdded transaction:\n%s" (show t)
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
{-# LANGUAGE DeriveDataTypeable #-}
|
||||||
{-|
|
{-|
|
||||||
|
|
||||||
Most data types are defined here to avoid import cycles. See the
|
Most data types are defined here to avoid import cycles. See the
|
||||||
@ -27,6 +28,7 @@ where
|
|||||||
import Ledger.Utils
|
import Ledger.Utils
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import System.Time (ClockTime)
|
import System.Time (ClockTime)
|
||||||
|
import Data.Typeable (Typeable)
|
||||||
|
|
||||||
|
|
||||||
type SmartDate = (String,String,String)
|
type SmartDate = (String,String,String)
|
||||||
@ -148,5 +150,5 @@ data Ledger = Ledger {
|
|||||||
rawledger :: RawLedger,
|
rawledger :: RawLedger,
|
||||||
accountnametree :: Tree AccountName,
|
accountnametree :: Tree AccountName,
|
||||||
accountmap :: Map.Map AccountName Account
|
accountmap :: Map.Map AccountName Account
|
||||||
}
|
} deriving Typeable
|
||||||
|
|
||||||
|
|||||||
@ -130,12 +130,21 @@ executable hledger
|
|||||||
cpp-options: -DHAPPS
|
cpp-options: -DHAPPS
|
||||||
other-modules:Commands.Web
|
other-modules:Commands.Web
|
||||||
build-depends:
|
build-depends:
|
||||||
happstack >= 0.2 && < 0.3
|
hsp
|
||||||
,happstack-data >= 0.2 && < 0.3
|
,hsx
|
||||||
,happstack-server >= 0.2 && < 0.3
|
|
||||||
,happstack-state >= 0.2 && < 0.3
|
|
||||||
,xhtml >= 3000.2 && < 3000.3
|
,xhtml >= 3000.2 && < 3000.3
|
||||||
|
,loli
|
||||||
|
,io-storage
|
||||||
|
,hack-contrib
|
||||||
|
,hack
|
||||||
|
,hack-handler-happstack
|
||||||
|
,happstack >= 0.3 && < 0.4
|
||||||
|
,happstack-data >= 0.3 && < 0.4
|
||||||
|
,happstack-server >= 0.3 && < 0.4
|
||||||
|
,happstack-state >= 0.3 && < 0.4
|
||||||
,HTTP >= 4000.0 && < 4000.1
|
,HTTP >= 4000.0 && < 4000.1
|
||||||
|
,applicative-extras
|
||||||
|
|
||||||
|
|
||||||
-- source-repository head
|
-- source-repository head
|
||||||
-- type: darcs
|
-- type: darcs
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user