{-# LANGUAGE TemplateHaskell, QuasiQuotes, OverloadedStrings #-} {- hledger-web's request handlers, and helpers. -} module Handlers where import Control.Applicative ((<$>), (<*>)) import Data.Aeson import Data.ByteString (ByteString) import Data.Either (lefts,rights) import Data.List import Data.Maybe import Data.Text(Text,pack,unpack) import Data.Time.Calendar import Data.Time.Clock import Data.Time.Format -- import Safe import System.FilePath (takeFileName, ()) import System.IO.Storage (putValue, getValue) import System.Locale (defaultTimeLocale) import Text.Hamlet hiding (hamletFile) import Text.Printf import Yesod.Form import Yesod.Json import Hledger.Cli import Hledger.Data hiding (today) import Hledger.Read (journalFromPathAndString) import Hledger.Read.JournalReader (someamount) import Hledger.Utils import App import Settings getFaviconR :: Handler () getFaviconR = sendFile "image/x-icon" $ Settings.staticdir "favicon.ico" getRobotsR :: Handler RepPlain getRobotsR = return $ RepPlain $ toContent ("User-agent: *" :: ByteString) getRootR :: Handler RepHtml getRootR = redirect RedirectTemporary defaultroute where defaultroute = RegisterR ---------------------------------------------------------------------- -- main views -- | The journal entries view, with accounts sidebar. getJournalR :: Handler RepHtml getJournalR = do vd@VD{opts=opts,m=m,am=am,j=j} <- getViewData let sidecontent = balanceReportAsHtml opts vd $ balanceReport2 opts am j title = "Journal entries" ++ if m /= MatchAny then ", filtered" else "" :: String maincontent = journalReportAsHtml opts vd $ journalReport opts nullfilterspec $ filterJournalTransactions2 m j defaultLayout $ do setTitle "hledger-web journal" addHamlet [$hamlet| ^{topbar vd} #{title} ^{searchform vd} "Journal"++filter Just (a,subs) -> "Transactions in "++a++andsubs++filter where andsubs = if subs then " (and subaccounts)" else "" where filter = if filtering then ", filtered" else "" maincontent = case inAccountMatcher qopts of Just m' -> registerReportAsHtml opts vd $ accountRegisterReport opts j m m' Nothing -> registerReportAsHtml opts vd $ journalRegisterReport opts j m defaultLayout $ do setTitle "hledger-web register" addHamlet [$hamlet| ^{topbar vd} #{title} ^{searchform vd} registerReportAsHtml opts vd $ accountRegisterReport opts j m m' Nothing -> registerReportAsHtml opts vd $ journalRegisterReport opts j m -- | A simple accounts view. This one is json-capable, returning the chart -- of accounts as json if the Accept header specifies json. getAccountsR :: Handler RepHtmlJson getAccountsR = do vd@VD{opts=opts,m=m,am=am,j=j} <- getViewData let j' = filterJournalPostings2 m j html = do setTitle "hledger-web accounts" addHamlet $ balanceReportAsHtml opts vd $ balanceReport2 opts am j' json = jsonMap [("accounts", toJSON $ journalAccountNames j')] defaultLayoutJson html json -- | A json-only version of "getAccountsR", does not require the special Accept header. getAccountsJsonR :: Handler RepJson getAccountsJsonR = do VD{m=m,j=j} <- getViewData let j' = filterJournalPostings2 m j jsonToRepJson $ jsonMap [("accounts", toJSON $ journalAccountNames j')] ---------------------------------------------------------------------- -- view helpers -- | Render a "BalanceReport" as HTML. balanceReportAsHtml :: [Opt] -> ViewData -> BalanceReport -> Hamlet AppRoute balanceReportAsHtml _ vd@VD{qopts=qopts,j=j} (items',total) = [$hamlet| [+/-] Add a transaction.. Journal entries   edit Accounts $forall i <- items ^{itemAsHtml vd i} #{mixedAmountAsHtml total} |] where l = journalToLedger nullfilterspec j inacctmatcher = inAccountMatcher qopts allaccts = isNothing inacctmatcher items = items' -- maybe items' (\m -> filter (matchesAccount m . \(a,_,_,_)->a) items') showacctmatcher itemAsHtml :: ViewData -> BalanceReportItem -> Hamlet AppRoute itemAsHtml _ (acct, adisplay, aindent, abal) = [$hamlet| #{adisplay} only #{mixedAmountAsHtml abal} (#{numpostings}) |] where hassubs = not $ null $ ledgerSubAccounts l $ ledgerAccount l acct numpostings = length $ apostings $ ledgerAccount l acct depthclass = "depth"++show aindent inacctclass = case inacctmatcher of Just m -> if m `matchesAccount` acct then "inacct" else "notinacct" Nothing -> "" :: String indent = preEscapedString $ concat $ replicate (2 * aindent) " " acctquery = (RegisterR, [("q", pack $ accountQuery acct)]) acctonlyquery = (RegisterR, [("q", pack $ accountOnlyQuery acct)]) accountQuery :: AccountName -> String accountQuery a = "inacct:" ++ quoteIfSpaced a -- (accountNameToAccountRegex a) accountOnlyQuery :: AccountName -> String accountOnlyQuery a = "inacctonly:" ++ quoteIfSpaced a -- (accountNameToAccountRegex a) -- accountUrl :: AppRoute -> AccountName -> (AppRoute,[(String,ByteString)]) accountUrl r a = (r, [("q",pack $ accountQuery a)]) -- | Render a "JournalReport" as HTML. journalReportAsHtml :: [Opt] -> ViewData -> JournalReport -> Hamlet AppRoute journalReportAsHtml _ vd items = [$hamlet| $forall i <- numbered items ^{itemAsHtml vd i} |] where itemAsHtml :: ViewData -> (Int, JournalReportItem) -> Hamlet AppRoute itemAsHtml _ (n, t) = [$hamlet|
#{txn}
 |]
     where
       evenodd = if even n then "even" else "odd" :: String
       txn = trimnl $ showTransaction t where trimnl = reverse . dropWhile (=='\n') . reverse

-- Render an "AccountRegisterReport" as html, for the journal/account register views.
registerReportAsHtml :: [Opt] -> ViewData -> AccountRegisterReport -> Hamlet AppRoute
registerReportAsHtml _ vd@VD{m=m,qopts=qopts} (balancelabel,items) = [$hamlet|
$if showlastcolumn