{-|

A ledger-compatible @balance@ command, with additional support for
multi-column reports.

Here is a description/specification for the balance command.  See also
"Hledger.Reports" -> \"Balance reports\".


/Basic balance report/

With no report interval (@--monthly@ etc.), hledger's balance
command emulates ledger's, showing accounts indented according to
hierarchy, along with their total amount posted (including subaccounts).

Here's an example. With @examples/sample.journal@, which defines the following account tree:

@
 assets
   bank
     checking
     saving
   cash
 expenses
   food
   supplies
 income
   gifts
   salary
 liabilities
   debts
@

the basic @balance@ command gives this output:

@
 $ hledger -f sample.journal balance
                 $-1  assets
                  $1    bank:saving
                 $-2    cash
                  $2  expenses
                  $1    food
                  $1    supplies
                 $-2  income
                 $-1    gifts
                 $-1    salary
                  $1  liabilities:debts
--------------------
                   0
@

Subaccounts are displayed indented below their parent. Only the account leaf name (the final part) is shown.
(With @--flat@, account names are shown in full and unindented.)

Each account's \"balance\" is the sum of postings in that account and any subaccounts during the report period.
When the report period includes all transactions, this is equivalent to the account's current balance.

The overall total of the highest-level displayed accounts is shown below the line.
(The @--no-total/-N@ flag prevents this.)

/Eliding and omitting/

Accounts which have a zero balance, and no non-zero subaccount
balances, are normally omitted from the report.
(The @--empty/-E@ flag forces such accounts to be displayed.)
Eg, above @checking@ is omitted because it has a zero balance and no subaccounts.

Accounts which have a single subaccount also being displayed, with the same balance,
are normally elided into the subaccount's line.
(The @--no-elide@ flag prevents this.)
Eg, above @bank@ is elided to @bank:saving@ because it has only a
single displayed subaccount (@saving@) and their balance is the same
($1). Similarly, @liabilities@ is elided to @liabilities:debts@.

/Date limiting/

The default report period is that of the whole journal, including all
known transactions. The @--begin\/-b@, @--end\/-e@, @--period\/-p@
options or @date:@/@date2:@ patterns can be used to report only
on transactions before and/or after specified dates.

/Depth limiting/

The @--depth@ option can be used to limit the depth of the balance report.
Eg, to see just the top level accounts (still including their subaccount balances):

@
$ hledger -f sample.journal balance --depth 1
                 $-1  assets
                  $2  expenses
                 $-2  income
                  $1  liabilities
--------------------
                   0
@

/Account limiting/

With one or more account pattern arguments, the report is restricted
to accounts whose name matches one of the patterns, plus their parents
and subaccounts. Eg, adding the pattern @o@ to the first example gives:

@
 $ hledger -f sample.journal balance o
                  $1  expenses:food
                 $-2  income
                 $-1    gifts
                 $-1    salary
--------------------
                 $-1
@

* The @o@ pattern matched @food@ and @income@, so they are shown.

* @food@'s parent (@expenses@) is shown even though the pattern didn't
  match it, to clarify the hierarchy. The usual eliding rules cause it to be elided here.

* @income@'s subaccounts are also shown.

/Multi-column balance report/

hledger's balance command will show multiple columns when a reporting
interval is specified (eg with @--monthly@), one column for each sub-period.

There are three kinds of multi-column balance report, indicated by the heading:

* A \"period balance\" (or \"flow\") report (the default) shows the change of account
  balance in each period, which is equivalent to the sum of postings in each
  period. Here, checking's balance increased by 10 in Feb:

  > Change of balance (flow):
  >
  >                  Jan   Feb   Mar
  > assets:checking   20    10    -5

* A \"cumulative balance\" report (with @--cumulative@) shows the accumulated ending balance
  across periods, starting from zero at the report's start date.
  Here, 30 is the sum of checking postings during Jan and Feb:

  > Ending balance (cumulative):
  >
  >                  Jan   Feb   Mar
  > assets:checking   20    30    25

* A \"historical balance\" report (with @--historical/-H@) also shows ending balances,
  but it includes the starting balance from any postings before the report start date.
  Here, 130 is the balance from all checking postings at the end of Feb, including
  pre-Jan postings which created a starting balance of 100:

  > Ending balance (historical):
  >
  >                  Jan   Feb   Mar
  > assets:checking  120   130   125

/Eliding and omitting, 2/

Here's a (imperfect?) specification for the eliding/omitting behaviour:

* Each account is normally displayed on its own line.

* An account less deep than the report's max depth, with just one
interesting subaccount, and the same balance as the subaccount, is
non-interesting, and prefixed to the subaccount's line, unless
@--no-elide@ is in effect.

* An account with a zero inclusive balance and less than two interesting
subaccounts is not displayed at all, unless @--empty@ is in effect.

* Multi-column balance reports show full account names with no eliding
  (like @--flat@). Accounts (and periods) are omitted as described below.

/Which accounts to show in balance reports/

By default:

* single-column: accounts with non-zero balance in report period.
                 (With @--flat@: accounts with non-zero balance and postings.)

* periodic:      accounts with postings and non-zero period balance in any period

* cumulative:    accounts with non-zero cumulative balance in any period

* historical:    accounts with non-zero historical balance in any period

With @-E/--empty@:

* single-column: accounts with postings in report period

* periodic:      accounts with postings in report period

* cumulative:    accounts with postings in report period

* historical:    accounts with non-zero starting balance +
                 accounts with postings in report period

/Which periods (columns) to show in balance reports/

An empty period/column is one where no report account has any postings.
A zero period/column is one where no report account has a non-zero period balance.

Currently,

by default:

* single-column: N/A

* periodic:      all periods within the overall report period,
                 except for leading and trailing empty periods

* cumulative:    all periods within the overall report period,
                 except for leading and trailing empty periods

* historical:    all periods within the overall report period,
                 except for leading and trailing empty periods

With @-E/--empty@:

* single-column: N/A

* periodic:      all periods within the overall report period

* cumulative:    all periods within the overall report period

* historical:    all periods within the overall report period

/What to show in empty cells/

An empty periodic balance report cell is one which has no corresponding postings.
An empty cumulative/historical balance report cell is one which has no corresponding
or prior postings, ie the account doesn't exist yet.
Currently, empty cells show 0.

-}

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ExtendedDefaultRules #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE NamedFieldPuns #-}

module Hledger.Cli.Commands.Balance (
  balancemode
 ,balance
 ,balanceReportAsText
 ,balanceReportItemAsText
 ,multiBalanceReportAsText
 ,multiBalanceReportAsCsv
 ,multiBalanceReportAsHtml
 ,multiBalanceReportHtmlRows
 ,balanceReportAsTable
 ,balanceReportTableAsText
 ,tests_Balance
) where

import Data.List
import Data.Maybe
--import qualified Data.Map as Map
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import System.Console.CmdArgs.Explicit as C
import Lucid as L
import Text.Printf (printf)
import Text.Tabular as T
--import Text.Tabular.AsciiWide

import Hledger
import Hledger.Cli.CliOptions
import Hledger.Cli.Utils
import Hledger.Read.CsvReader (CSV, printCSV)


-- | Command line options for this command.
balancemode :: Mode RawOpts
balancemode = CommandDoc
-> [Flag RawOpts]
-> [(CommandDoc, [Flag RawOpts])]
-> [Flag RawOpts]
-> ([Arg RawOpts], Maybe (Arg RawOpts))
-> Mode RawOpts
hledgerCommandMode
  $(embedFileRelative "Hledger/Cli/Commands/Balance.txt")
  ([[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["change"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "change")
      "show balance change in each period (default)"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["cumulative"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "cumulative")
      "show balance change accumulated across periods (in multicolumn reports)"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["historical","H"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "historical")
      "show historical ending balance in each period (includes postings before report start date)\n "
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["tree"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "tree") "show accounts as a tree; amounts include subaccounts (default in simple reports)"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["flat"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "flat") "show accounts as a list; amounts exclude subaccounts except when account is depth-clipped (default in multicolumn reports)\n "
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["average","A"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "average") "show a row average column (in multicolumn reports)"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["row-total","T"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "row-total") "show a row total column (in multicolumn reports)"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["no-total","N"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "no-total") "omit the final total row"
   ,[CommandDoc]
-> Update RawOpts -> CommandDoc -> CommandDoc -> Flag RawOpts
forall a.
[CommandDoc] -> Update a -> CommandDoc -> CommandDoc -> Flag a
flagReq  ["drop"] (\s :: CommandDoc
s opts :: RawOpts
opts -> RawOpts -> Either CommandDoc RawOpts
forall a b. b -> Either a b
Right (RawOpts -> Either CommandDoc RawOpts)
-> RawOpts -> Either CommandDoc RawOpts
forall a b. (a -> b) -> a -> b
$ CommandDoc -> CommandDoc -> RawOpts -> RawOpts
setopt "drop" CommandDoc
s RawOpts
opts) "N" "omit N leading account name parts (in flat mode)"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["no-elide"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "no-elide") "don't squash boring parent accounts (in tree mode)"
   ,[CommandDoc]
-> Update RawOpts -> CommandDoc -> CommandDoc -> Flag RawOpts
forall a.
[CommandDoc] -> Update a -> CommandDoc -> CommandDoc -> Flag a
flagReq  ["format"] (\s :: CommandDoc
s opts :: RawOpts
opts -> RawOpts -> Either CommandDoc RawOpts
forall a b. b -> Either a b
Right (RawOpts -> Either CommandDoc RawOpts)
-> RawOpts -> Either CommandDoc RawOpts
forall a b. (a -> b) -> a -> b
$ CommandDoc -> CommandDoc -> RawOpts -> RawOpts
setopt "format" CommandDoc
s RawOpts
opts) "FORMATSTR" "use this custom line format (in simple reports)"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["pretty-tables"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "pretty-tables") "use unicode to display prettier tables"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["sort-amount","S"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "sort-amount") "sort by amount instead of account code/name (in flat mode). With multiple columns, sorts by the row total, or by row average if that is displayed."
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["budget"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "budget") "show performance compared to budget goals defined by periodic transactions"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["invert"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "invert") "display all amounts with reversed sign"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["transpose"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "transpose") "transpose rows and columns"
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone ["percent", "%"] (CommandDoc -> RawOpts -> RawOpts
setboolopt "percent") "express values in percentage of each column's total"
   ]
   [Flag RawOpts] -> [Flag RawOpts] -> [Flag RawOpts]
forall a. [a] -> [a] -> [a]
++ [Flag RawOpts]
outputflags
  )
  [(CommandDoc, [Flag RawOpts])
generalflagsgroup1]
  [Flag RawOpts]
hiddenflags
  ([], Arg RawOpts -> Maybe (Arg RawOpts)
forall a. a -> Maybe a
Just (Arg RawOpts -> Maybe (Arg RawOpts))
-> Arg RawOpts -> Maybe (Arg RawOpts)
forall a b. (a -> b) -> a -> b
$ CommandDoc -> Arg RawOpts
argsFlag "[QUERY]")

-- | The balance command, prints a balance report.
balance :: CliOpts -> Journal -> IO ()
balance :: CliOpts -> Journal -> IO ()
balance opts :: CliOpts
opts@CliOpts{rawopts_ :: CliOpts -> RawOpts
rawopts_=RawOpts
rawopts,reportopts_ :: CliOpts -> ReportOpts
reportopts_=ropts :: ReportOpts
ropts@ReportOpts{..}} j :: Journal
j = do
  Day
d <- IO Day
getCurrentDay
  case ReportOpts -> Either CommandDoc StringFormat
lineFormatFromOpts ReportOpts
ropts of
    Left err :: CommandDoc
err -> CommandDoc -> IO ()
forall a. CommandDoc -> a
error' (CommandDoc -> IO ()) -> CommandDoc -> IO ()
forall a b. (a -> b) -> a -> b
$ [CommandDoc] -> CommandDoc
unlines [CommandDoc
err]
    Right _ -> do
      let budget :: Bool
budget      = CommandDoc -> RawOpts -> Bool
boolopt "budget" RawOpts
rawopts
          multiperiod :: Bool
multiperiod = Interval
interval_ Interval -> Interval -> Bool
forall a. Eq a => a -> a -> Bool
/= Interval
NoInterval
          format :: CommandDoc
format      = CliOpts -> CommandDoc
outputFormatFromOpts CliOpts
opts

      if Bool
budget then do  -- single or multi period budget report
        DateSpan
reportspan <- Journal -> ReportOpts -> IO DateSpan
reportSpan Journal
j ReportOpts
ropts
        let budgetreport :: BudgetReport
budgetreport     = CommandDoc -> BudgetReport -> BudgetReport
forall a. Show a => CommandDoc -> a -> a
dbg1 "budgetreport"     (BudgetReport -> BudgetReport) -> BudgetReport -> BudgetReport
forall a b. (a -> b) -> a -> b
$ ReportOpts -> Bool -> DateSpan -> Day -> Journal -> BudgetReport
budgetReport ReportOpts
ropts Bool
assrt DateSpan
reportspan Day
d Journal
j
              where
                assrt :: Bool
assrt          = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ InputOpts -> Bool
ignore_assertions_ (InputOpts -> Bool) -> InputOpts -> Bool
forall a b. (a -> b) -> a -> b
$ CliOpts -> InputOpts
inputopts_ CliOpts
opts
            render :: BudgetReport -> CommandDoc
render = case CommandDoc
format of
              "csv"  -> CommandDoc -> BudgetReport -> CommandDoc
forall a b. a -> b -> a
const (CommandDoc -> BudgetReport -> CommandDoc)
-> CommandDoc -> BudgetReport -> CommandDoc
forall a b. (a -> b) -> a -> b
$ CommandDoc -> CommandDoc
forall a. CommandDoc -> a
error' "Sorry, CSV output is not yet implemented for this kind of report."  -- TODO
              "html" -> CommandDoc -> BudgetReport -> CommandDoc
forall a b. a -> b -> a
const (CommandDoc -> BudgetReport -> CommandDoc)
-> CommandDoc -> BudgetReport -> CommandDoc
forall a b. (a -> b) -> a -> b
$ CommandDoc -> CommandDoc
forall a. CommandDoc -> a
error' "Sorry, HTML output is not yet implemented for this kind of report."  -- TODO
              _      -> ReportOpts -> BudgetReport -> CommandDoc
budgetReportAsText ReportOpts
ropts
        CliOpts -> CommandDoc -> IO ()
writeOutput CliOpts
opts (CommandDoc -> IO ()) -> CommandDoc -> IO ()
forall a b. (a -> b) -> a -> b
$ BudgetReport -> CommandDoc
render BudgetReport
budgetreport

      else
        if Bool
multiperiod then do  -- multi period balance report
          let report :: MultiBalanceReport
report = ReportOpts -> Query -> Journal -> MultiBalanceReport
multiBalanceReport ReportOpts
ropts (Day -> ReportOpts -> Query
queryFromOpts Day
d ReportOpts
ropts) Journal
j
              render :: MultiBalanceReport -> CommandDoc
render = case CommandDoc
format of
                "csv"  -> (CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++ "\n") (CommandDoc -> CommandDoc)
-> (MultiBalanceReport -> CommandDoc)
-> MultiBalanceReport
-> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CSV -> CommandDoc
printCSV (CSV -> CommandDoc)
-> (MultiBalanceReport -> CSV) -> MultiBalanceReport -> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ReportOpts -> MultiBalanceReport -> CSV
multiBalanceReportAsCsv ReportOpts
ropts
                "html" ->  (CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++ "\n") (CommandDoc -> CommandDoc)
-> (MultiBalanceReport -> CommandDoc)
-> MultiBalanceReport
-> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> CommandDoc
TL.unpack (Text -> CommandDoc)
-> (MultiBalanceReport -> Text) -> MultiBalanceReport -> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Html () -> Text
forall a. Html a -> Text
L.renderText (Html () -> Text)
-> (MultiBalanceReport -> Html ()) -> MultiBalanceReport -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ReportOpts -> MultiBalanceReport -> Html ()
multiBalanceReportAsHtml ReportOpts
ropts
                _      -> ReportOpts -> MultiBalanceReport -> CommandDoc
multiBalanceReportAsText ReportOpts
ropts
          CliOpts -> CommandDoc -> IO ()
writeOutput CliOpts
opts (CommandDoc -> IO ()) -> CommandDoc -> IO ()
forall a b. (a -> b) -> a -> b
$ MultiBalanceReport -> CommandDoc
render MultiBalanceReport
report

        else do  -- single period simple balance report
          let report :: BalanceReport
report
                | BalanceType
balancetype_ BalanceType -> [BalanceType] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [BalanceType
HistoricalBalance, BalanceType
CumulativeChange]
                  = let ropts' :: ReportOpts
ropts' | ReportOpts -> Bool
flat_ ReportOpts
ropts = ReportOpts
ropts
                               | Bool
otherwise   = ReportOpts
ropts{accountlistmode_ :: AccountListMode
accountlistmode_=AccountListMode
ALTree}
                    in ReportOpts -> Query -> Journal -> BalanceReport
balanceReportFromMultiBalanceReport ReportOpts
ropts' (Day -> ReportOpts -> Query
queryFromOpts Day
d ReportOpts
ropts) Journal
j
                          -- for historical balances we must use balanceReportFromMultiBalanceReport (also forces --no-elide)
                | Bool
otherwise = ReportOpts -> Query -> Journal -> BalanceReport
balanceReport ReportOpts
ropts (Day -> ReportOpts -> Query
queryFromOpts Day
d ReportOpts
ropts) Journal
j -- simple Ledger-style balance report
              render :: ReportOpts -> BalanceReport -> CommandDoc
render = case CommandDoc
format of
                "csv"  -> \ropts :: ReportOpts
ropts r :: BalanceReport
r -> (CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++ "\n") (CommandDoc -> CommandDoc) -> CommandDoc -> CommandDoc
forall a b. (a -> b) -> a -> b
$ CSV -> CommandDoc
printCSV (CSV -> CommandDoc) -> CSV -> CommandDoc
forall a b. (a -> b) -> a -> b
$ ReportOpts -> BalanceReport -> CSV
balanceReportAsCsv ReportOpts
ropts BalanceReport
r
                "html" -> \_ _ -> CommandDoc -> CommandDoc
forall a. CommandDoc -> a
error' "Sorry, HTML output is not yet implemented for this kind of report."  -- TODO
                _      -> ReportOpts -> BalanceReport -> CommandDoc
balanceReportAsText
          CliOpts -> CommandDoc -> IO ()
writeOutput CliOpts
opts (CommandDoc -> IO ()) -> CommandDoc -> IO ()
forall a b. (a -> b) -> a -> b
$ ReportOpts -> BalanceReport -> CommandDoc
render ReportOpts
ropts BalanceReport
report

-- rendering single-column balance reports

-- | Render a single-column balance report as CSV.
balanceReportAsCsv :: ReportOpts -> BalanceReport -> CSV
balanceReportAsCsv :: ReportOpts -> BalanceReport -> CSV
balanceReportAsCsv opts :: ReportOpts
opts (items :: [BalanceReportItem]
items, total :: MixedAmount
total) =
  ["account","balance"] [CommandDoc] -> CSV -> CSV
forall a. a -> [a] -> [a]
:
  [[Text -> CommandDoc
T.unpack (ReportOpts -> Text -> Text
maybeAccountNameDrop ReportOpts
opts Text
a), MixedAmount -> CommandDoc
showMixedAmountOneLineWithoutPrice MixedAmount
b] | (a :: Text
a, _, _, b :: MixedAmount
b) <- [BalanceReportItem]
items]
  CSV -> CSV -> CSV
forall a. [a] -> [a] -> [a]
++
  if ReportOpts -> Bool
no_total_ ReportOpts
opts
  then []
  else [["total", MixedAmount -> CommandDoc
showMixedAmountOneLineWithoutPrice MixedAmount
total]]

-- | Render a single-column balance report as plain text.
balanceReportAsText :: ReportOpts -> BalanceReport -> String
balanceReportAsText :: ReportOpts -> BalanceReport -> CommandDoc
balanceReportAsText opts :: ReportOpts
opts ((items :: [BalanceReportItem]
items, total :: MixedAmount
total)) = [CommandDoc] -> CommandDoc
unlines ([CommandDoc] -> CommandDoc) -> [CommandDoc] -> CommandDoc
forall a b. (a -> b) -> a -> b
$ CSV -> [CommandDoc]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat CSV
lines [CommandDoc] -> [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a] -> [a]
++ [CommandDoc]
t
  where
      fmt :: Either CommandDoc StringFormat
fmt = ReportOpts -> Either CommandDoc StringFormat
lineFormatFromOpts ReportOpts
opts
      lines :: CSV
lines = case Either CommandDoc StringFormat
fmt of
                Right fmt :: StringFormat
fmt -> (BalanceReportItem -> [CommandDoc]) -> [BalanceReportItem] -> CSV
forall a b. (a -> b) -> [a] -> [b]
map (ReportOpts -> StringFormat -> BalanceReportItem -> [CommandDoc]
balanceReportItemAsText ReportOpts
opts StringFormat
fmt) [BalanceReportItem]
items
                Left err :: CommandDoc
err  -> [[CommandDoc
err]]
      t :: [CommandDoc]
t = if ReportOpts -> Bool
no_total_ ReportOpts
opts
           then []
           else
             case Either CommandDoc StringFormat
fmt of
               Right fmt :: StringFormat
fmt ->
                let
                  -- abuse renderBalanceReportItem to render the total with similar format
                  acctcolwidth :: Int
acctcolwidth = [Int] -> Int
forall a. Integral a => [a] -> a
maximum' [Text -> Int
T.length Text
fullname | (fullname :: Text
fullname, _, _, _) <- [BalanceReportItem]
items]
                  totallines :: [CommandDoc]
totallines = (CommandDoc -> CommandDoc) -> [CommandDoc] -> [CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map CommandDoc -> CommandDoc
rstrip ([CommandDoc] -> [CommandDoc]) -> [CommandDoc] -> [CommandDoc]
forall a b. (a -> b) -> a -> b
$ ReportOpts
-> StringFormat -> (Text, Int, MixedAmount) -> [CommandDoc]
renderBalanceReportItem ReportOpts
opts StringFormat
fmt (Int -> Text -> Text
T.replicate (Int
acctcolwidthInt -> Int -> Int
forall a. Num a => a -> a -> a
+1) " ", 0, MixedAmount
total)
                  -- with a custom format, extend the line to the full report width;
                  -- otherwise show the usual 20-char line for compatibility
                  overlinewidth :: Int
overlinewidth | Maybe CommandDoc -> Bool
forall a. Maybe a -> Bool
isJust (ReportOpts -> Maybe CommandDoc
format_ ReportOpts
opts) = [Int] -> Int
forall a. Integral a => [a] -> a
maximum' ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ (CommandDoc -> Int) -> [CommandDoc] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map CommandDoc -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([CommandDoc] -> [Int]) -> [CommandDoc] -> [Int]
forall a b. (a -> b) -> a -> b
$ CSV -> [CommandDoc]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat CSV
lines
                                | Bool
otherwise             = Int
defaultTotalFieldWidth
                  overline :: CommandDoc
overline   = Int -> Char -> CommandDoc
forall a. Int -> a -> [a]
replicate Int
overlinewidth '-'
                in CommandDoc
overline CommandDoc -> [CommandDoc] -> [CommandDoc]
forall a. a -> [a] -> [a]
: [CommandDoc]
totallines
               Left _ -> []

{-
:r
This implementation turned out to be a bit convoluted but implements the following algorithm for formatting:

- If there is a single amount, print it with the account name directly:
- Otherwise, only print the account name on the last line.

    a         USD 1   ; Account 'a' has a single amount
              EUR -1
    b         USD -1  ; Account 'b' has two amounts. The account name is printed on the last line.
-}
-- | Render one balance report line item as plain text suitable for console output (or
-- whatever string format is specified). Note, prices will not be rendered, and
-- differently-priced quantities of the same commodity will appear merged.
-- The output will be one or more lines depending on the format and number of commodities.
balanceReportItemAsText :: ReportOpts -> StringFormat -> BalanceReportItem -> [String]
balanceReportItemAsText :: ReportOpts -> StringFormat -> BalanceReportItem -> [CommandDoc]
balanceReportItemAsText opts :: ReportOpts
opts fmt :: StringFormat
fmt (_, accountName :: Text
accountName, depth :: Int
depth, amt :: MixedAmount
amt) =
  ReportOpts
-> StringFormat -> (Text, Int, MixedAmount) -> [CommandDoc]
renderBalanceReportItem ReportOpts
opts StringFormat
fmt (
    ReportOpts -> Text -> Text
maybeAccountNameDrop ReportOpts
opts Text
accountName,
    Int
depth,
    MixedAmount -> MixedAmount
normaliseMixedAmountSquashPricesForDisplay MixedAmount
amt
    )

-- | Render a balance report item using the given StringFormat, generating one or more lines of text.
renderBalanceReportItem :: ReportOpts -> StringFormat -> (AccountName, Int, MixedAmount) -> [String]
renderBalanceReportItem :: ReportOpts
-> StringFormat -> (Text, Int, MixedAmount) -> [CommandDoc]
renderBalanceReportItem opts :: ReportOpts
opts fmt :: StringFormat
fmt (acctname :: Text
acctname, depth :: Int
depth, total :: MixedAmount
total) =
  CommandDoc -> [CommandDoc]
lines (CommandDoc -> [CommandDoc]) -> CommandDoc -> [CommandDoc]
forall a b. (a -> b) -> a -> b
$
  case StringFormat
fmt of
    OneLine comps :: [StringFormatComponent]
comps       -> [CommandDoc] -> CommandDoc
concatOneLine      ([CommandDoc] -> CommandDoc) -> [CommandDoc] -> CommandDoc
forall a b. (a -> b) -> a -> b
$ [StringFormatComponent] -> [CommandDoc]
render1 [StringFormatComponent]
comps
    TopAligned comps :: [StringFormatComponent]
comps    -> [CommandDoc] -> CommandDoc
concatBottomPadded ([CommandDoc] -> CommandDoc) -> [CommandDoc] -> CommandDoc
forall a b. (a -> b) -> a -> b
$ [StringFormatComponent] -> [CommandDoc]
render [StringFormatComponent]
comps
    BottomAligned comps :: [StringFormatComponent]
comps -> [CommandDoc] -> CommandDoc
concatTopPadded    ([CommandDoc] -> CommandDoc) -> [CommandDoc] -> CommandDoc
forall a b. (a -> b) -> a -> b
$ [StringFormatComponent] -> [CommandDoc]
render [StringFormatComponent]
comps
  where
    render1 :: [StringFormatComponent] -> [CommandDoc]
render1 = (StringFormatComponent -> CommandDoc)
-> [StringFormatComponent] -> [CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map (ReportOpts
-> (Text, Int, MixedAmount) -> StringFormatComponent -> CommandDoc
renderComponent1 ReportOpts
opts (Text
acctname, Int
depth, MixedAmount
total))
    render :: [StringFormatComponent] -> [CommandDoc]
render  = (StringFormatComponent -> CommandDoc)
-> [StringFormatComponent] -> [CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map (ReportOpts
-> (Text, Int, MixedAmount) -> StringFormatComponent -> CommandDoc
renderComponent ReportOpts
opts (Text
acctname, Int
depth, MixedAmount
total))

defaultTotalFieldWidth :: Int
defaultTotalFieldWidth = 20

-- | Render one StringFormat component for a balance report item.
renderComponent :: ReportOpts -> (AccountName, Int, MixedAmount) -> StringFormatComponent -> String
renderComponent :: ReportOpts
-> (Text, Int, MixedAmount) -> StringFormatComponent -> CommandDoc
renderComponent _ _ (FormatLiteral s :: CommandDoc
s) = CommandDoc
s
renderComponent opts :: ReportOpts
opts (acctname :: Text
acctname, depth :: Int
depth, total :: MixedAmount
total) (FormatField ljust :: Bool
ljust min :: Maybe Int
min max :: Maybe Int
max field :: ReportItemField
field) = case ReportItemField
field of
  DepthSpacerField -> Bool -> Maybe Int -> Maybe Int -> CommandDoc -> CommandDoc
formatString Bool
ljust Maybe Int
forall a. Maybe a
Nothing Maybe Int
max (CommandDoc -> CommandDoc) -> CommandDoc -> CommandDoc
forall a b. (a -> b) -> a -> b
$ Int -> Char -> CommandDoc
forall a. Int -> a -> [a]
replicate Int
d ' '
                      where d :: Int
d = case Maybe Int
min of
                                 Just m :: Int
m  -> Int
depth Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
m
                                 Nothing -> Int
depth
  AccountField     -> Bool -> Maybe Int -> Maybe Int -> CommandDoc -> CommandDoc
formatString Bool
ljust Maybe Int
min Maybe Int
max (Text -> CommandDoc
T.unpack Text
acctname)
  TotalField       -> Maybe Int -> Maybe Int -> Bool -> Bool -> CommandDoc -> CommandDoc
fitStringMulti Maybe Int
min Maybe Int
max Bool
True Bool
False (CommandDoc -> CommandDoc) -> CommandDoc -> CommandDoc
forall a b. (a -> b) -> a -> b
$ MixedAmount -> CommandDoc
showamt MixedAmount
total
    where
      showamt :: MixedAmount -> CommandDoc
showamt | ReportOpts -> Bool
color_ ReportOpts
opts = MixedAmount -> CommandDoc
cshowMixedAmountWithoutPrice
              | Bool
otherwise   = MixedAmount -> CommandDoc
showMixedAmountWithoutPrice
  _                -> ""

-- | Render one StringFormat component for a balance report item.
-- This variant is for use with OneLine string formats; it squashes
-- any multi-line rendered values onto one line, comma-and-space separated,
-- while still complying with the width spec.
renderComponent1 :: ReportOpts -> (AccountName, Int, MixedAmount) -> StringFormatComponent -> String
renderComponent1 :: ReportOpts
-> (Text, Int, MixedAmount) -> StringFormatComponent -> CommandDoc
renderComponent1 _ _ (FormatLiteral s :: CommandDoc
s) = CommandDoc
s
renderComponent1 opts :: ReportOpts
opts (acctname :: Text
acctname, depth :: Int
depth, total :: MixedAmount
total) (FormatField ljust :: Bool
ljust min :: Maybe Int
min max :: Maybe Int
max field :: ReportItemField
field) = case ReportItemField
field of
  AccountField     -> Bool -> Maybe Int -> Maybe Int -> CommandDoc -> CommandDoc
formatString Bool
ljust Maybe Int
min Maybe Int
max ((CommandDoc -> [CommandDoc] -> CommandDoc
forall a. [a] -> [[a]] -> [a]
intercalate ", " ([CommandDoc] -> CommandDoc)
-> (CommandDoc -> [CommandDoc]) -> CommandDoc -> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CommandDoc -> [CommandDoc]
lines) (CommandDoc -> CommandDoc
indented (Text -> CommandDoc
T.unpack Text
acctname)))
                      where
                        -- better to indent the account name here rather than use a DepthField component
                        -- so that it complies with width spec. Uses a fixed indent step size.
                        indented :: CommandDoc -> CommandDoc
indented = ((Int -> Char -> CommandDoc
forall a. Int -> a -> [a]
replicate (Int
depthInt -> Int -> Int
forall a. Num a => a -> a -> a
*2) ' ')CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++)
  TotalField       -> Maybe Int -> Maybe Int -> Bool -> Bool -> CommandDoc -> CommandDoc
fitStringMulti Maybe Int
min Maybe Int
max Bool
True Bool
False (CommandDoc -> CommandDoc) -> CommandDoc -> CommandDoc
forall a b. (a -> b) -> a -> b
$ ((CommandDoc -> [CommandDoc] -> CommandDoc
forall a. [a] -> [[a]] -> [a]
intercalate ", " ([CommandDoc] -> CommandDoc)
-> (CommandDoc -> [CommandDoc]) -> CommandDoc -> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (CommandDoc -> CommandDoc) -> [CommandDoc] -> [CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map CommandDoc -> CommandDoc
strip ([CommandDoc] -> [CommandDoc])
-> (CommandDoc -> [CommandDoc]) -> CommandDoc -> [CommandDoc]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CommandDoc -> [CommandDoc]
lines) (MixedAmount -> CommandDoc
showamt MixedAmount
total))
    where
      showamt :: MixedAmount -> CommandDoc
showamt | ReportOpts -> Bool
color_ ReportOpts
opts = MixedAmount -> CommandDoc
cshowMixedAmountWithoutPrice
              | Bool
otherwise   = MixedAmount -> CommandDoc
showMixedAmountWithoutPrice
  _                -> ""

-- rendering multi-column balance reports

-- | Render a multi-column balance report as CSV.
-- The CSV will always include the initial headings row,
-- and will include the final totals row unless --no-total is set.
multiBalanceReportAsCsv :: ReportOpts -> MultiBalanceReport -> CSV
multiBalanceReportAsCsv :: ReportOpts -> MultiBalanceReport -> CSV
multiBalanceReportAsCsv opts :: ReportOpts
opts@ReportOpts{Bool
average_ :: Bool
average_ :: ReportOpts -> Bool
average_, Bool
row_total_ :: Bool
row_total_ :: ReportOpts -> Bool
row_total_} (MultiBalanceReport (colspans :: [DateSpan]
colspans, items :: [MultiBalanceReportRow]
items, (coltotals :: [MixedAmount]
coltotals,tot :: MixedAmount
tot,avg :: MixedAmount
avg))) =
  CSV -> CSV
forall a. [[a]] -> [[a]]
maybetranspose (CSV -> CSV) -> CSV -> CSV
forall a b. (a -> b) -> a -> b
$
  ("Account" CommandDoc -> [CommandDoc] -> [CommandDoc]
forall a. a -> [a] -> [a]
: (DateSpan -> CommandDoc) -> [DateSpan] -> [CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map DateSpan -> CommandDoc
showDateSpan [DateSpan]
colspans
   [CommandDoc] -> [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a] -> [a]
++ ["Total"   | Bool
row_total_]
   [CommandDoc] -> [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a] -> [a]
++ ["Average" | Bool
average_]
  ) [CommandDoc] -> CSV -> CSV
forall a. a -> [a] -> [a]
:
  [Text -> CommandDoc
T.unpack (ReportOpts -> Text -> Text
maybeAccountNameDrop ReportOpts
opts Text
a) CommandDoc -> [CommandDoc] -> [CommandDoc]
forall a. a -> [a] -> [a]
:
   (MixedAmount -> CommandDoc) -> [MixedAmount] -> [CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map MixedAmount -> CommandDoc
showMixedAmountOneLineWithoutPrice
   ([MixedAmount]
amts
    [MixedAmount] -> [MixedAmount] -> [MixedAmount]
forall a. [a] -> [a] -> [a]
++ [MixedAmount
rowtot | Bool
row_total_]
    [MixedAmount] -> [MixedAmount] -> [MixedAmount]
forall a. [a] -> [a] -> [a]
++ [MixedAmount
rowavg | Bool
average_])
  | (a :: Text
a, _, _, amts :: [MixedAmount]
amts, rowtot :: MixedAmount
rowtot, rowavg :: MixedAmount
rowavg) <- [MultiBalanceReportRow]
items]
  CSV -> CSV -> CSV
forall a. [a] -> [a] -> [a]
++
  if ReportOpts -> Bool
no_total_ ReportOpts
opts
  then []
  else ["Total:" CommandDoc -> [CommandDoc] -> [CommandDoc]
forall a. a -> [a] -> [a]
:
        (MixedAmount -> CommandDoc) -> [MixedAmount] -> [CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map MixedAmount -> CommandDoc
showMixedAmountOneLineWithoutPrice (
          [MixedAmount]
coltotals
          [MixedAmount] -> [MixedAmount] -> [MixedAmount]
forall a. [a] -> [a] -> [a]
++ [MixedAmount
tot | Bool
row_total_]
          [MixedAmount] -> [MixedAmount] -> [MixedAmount]
forall a. [a] -> [a] -> [a]
++ [MixedAmount
avg | Bool
average_]
          )]
  where
    maybetranspose :: [[a]] -> [[a]]
maybetranspose | ReportOpts -> Bool
transpose_ ReportOpts
opts = [[a]] -> [[a]]
forall a. [[a]] -> [[a]]
transpose
                   | Bool
otherwise = [[a]] -> [[a]]
forall a. a -> a
id

-- | Render a multi-column balance report as HTML.
multiBalanceReportAsHtml :: ReportOpts -> MultiBalanceReport -> Html ()
multiBalanceReportAsHtml :: ReportOpts -> MultiBalanceReport -> Html ()
multiBalanceReportAsHtml ropts :: ReportOpts
ropts mbr :: MultiBalanceReport
mbr =
  let
    (headingsrow :: Html ()
headingsrow,bodyrows :: [Html ()]
bodyrows,mtotalsrow :: Maybe (Html ())
mtotalsrow) = ReportOpts
-> MultiBalanceReport -> (Html (), [Html ()], Maybe (Html ()))
multiBalanceReportHtmlRows ReportOpts
ropts MultiBalanceReport
mbr
  in
    Html () -> Html ()
forall arg result. Term arg result => arg -> result
table_ (Html () -> Html ()) -> Html () -> Html ()
forall a b. (a -> b) -> a -> b
$ [Html ()] -> Html ()
forall a. Monoid a => [a] -> a
mconcat ([Html ()] -> Html ()) -> [Html ()] -> Html ()
forall a b. (a -> b) -> a -> b
$
         [Html ()
headingsrow]
      [Html ()] -> [Html ()] -> [Html ()]
forall a. [a] -> [a] -> [a]
++ [Html ()]
bodyrows
      [Html ()] -> [Html ()] -> [Html ()]
forall a. [a] -> [a] -> [a]
++ Maybe (Html ()) -> [Html ()]
forall a. Maybe a -> [a]
maybeToList Maybe (Html ())
mtotalsrow

-- | Render the HTML table rows for a MultiBalanceReport.
-- Returns the heading row, 0 or more body rows, and the totals row if enabled.
multiBalanceReportHtmlRows :: ReportOpts -> MultiBalanceReport -> (Html (), [Html ()], Maybe (Html ()))
multiBalanceReportHtmlRows :: ReportOpts
-> MultiBalanceReport -> (Html (), [Html ()], Maybe (Html ()))
multiBalanceReportHtmlRows ropts :: ReportOpts
ropts mbr :: MultiBalanceReport
mbr =
  let
    headingsrow :: [CommandDoc]
headingsrow:rest :: CSV
rest | ReportOpts -> Bool
transpose_ ReportOpts
ropts = CommandDoc -> CSV
forall a. CommandDoc -> a
error' "Sorry, --transpose is not supported with HTML output yet"
                     | Bool
otherwise = ReportOpts -> MultiBalanceReport -> CSV
multiBalanceReportAsCsv ReportOpts
ropts MultiBalanceReport
mbr
    (bodyrows :: CSV
bodyrows, mtotalsrow :: Maybe [CommandDoc]
mtotalsrow) | ReportOpts -> Bool
no_total_ ReportOpts
ropts = (CSV
rest,      Maybe [CommandDoc]
forall a. Maybe a
Nothing)
                           | Bool
otherwise       = (CSV -> CSV
forall a. [a] -> [a]
init CSV
rest, [CommandDoc] -> Maybe [CommandDoc]
forall a. a -> Maybe a
Just ([CommandDoc] -> Maybe [CommandDoc])
-> [CommandDoc] -> Maybe [CommandDoc]
forall a b. (a -> b) -> a -> b
$ CSV -> [CommandDoc]
forall a. [a] -> a
last CSV
rest)
  in
    (ReportOpts -> [CommandDoc] -> Html ()
multiBalanceReportHtmlHeadRow ReportOpts
ropts [CommandDoc]
headingsrow
    ,([CommandDoc] -> Html ()) -> CSV -> [Html ()]
forall a b. (a -> b) -> [a] -> [b]
map (ReportOpts -> [CommandDoc] -> Html ()
multiBalanceReportHtmlBodyRow ReportOpts
ropts) CSV
bodyrows
    ,ReportOpts -> [CommandDoc] -> Html ()
multiBalanceReportHtmlFootRow ReportOpts
ropts ([CommandDoc] -> Html ()) -> Maybe [CommandDoc] -> Maybe (Html ())
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe [CommandDoc]
mtotalsrow -- TODO pad totals row with zeros when there are
    )

-- | Render one MultiBalanceReport heading row as a HTML table row.
multiBalanceReportHtmlHeadRow :: ReportOpts -> [String] -> Html ()
multiBalanceReportHtmlHeadRow :: ReportOpts -> [CommandDoc] -> Html ()
multiBalanceReportHtmlHeadRow _ [] = Html ()
forall a. Monoid a => a
mempty  -- shouldn't happen
multiBalanceReportHtmlHeadRow ropts :: ReportOpts
ropts (acct :: CommandDoc
acct:rest :: [CommandDoc]
rest) =
  let
    defstyle :: Attribute
defstyle = Text -> Attribute
forall arg result. TermRaw arg result => arg -> result
style_ ""
    (amts :: [CommandDoc]
amts,tot :: [CommandDoc]
tot,avg :: [CommandDoc]
avg)
      | ReportOpts -> Bool
row_total_ ReportOpts
ropts Bool -> Bool -> Bool
&& ReportOpts -> Bool
average_ ReportOpts
ropts = ([CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init ([CommandDoc] -> [CommandDoc]) -> [CommandDoc] -> [CommandDoc]
forall a b. (a -> b) -> a -> b
$ [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest, [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last ([CommandDoc] -> CommandDoc) -> [CommandDoc] -> CommandDoc
forall a b. (a -> b) -> a -> b
$ [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest], [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last [CommandDoc]
rest])
      | ReportOpts -> Bool
row_total_ ReportOpts
ropts                   = ([CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest,        [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last [CommandDoc]
rest],        [])
      |                     ReportOpts -> Bool
average_ ReportOpts
ropts = ([CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest,        [],                 [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last [CommandDoc]
rest])
      | Bool
otherwise                          = ([CommandDoc]
rest,             [],                 [])
  in
    Html () -> Html ()
forall arg result. Term arg result => arg -> result
tr_ (Html () -> Html ()) -> Html () -> Html ()
forall a b. (a -> b) -> a -> b
$ [Html ()] -> Html ()
forall a. Monoid a => [a] -> a
mconcat ([Html ()] -> Html ()) -> [Html ()] -> Html ()
forall a b. (a -> b) -> a -> b
$
          [Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ "account"]              (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
acct)
       Html () -> [Html ()] -> [Html ()]
forall a. a -> [a] -> [a]
: [[Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ "", Attribute
defstyle]           (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
a) | CommandDoc
a <- [CommandDoc]
amts]
      [Html ()] -> [Html ()] -> [Html ()]
forall a. [a] -> [a] -> [a]
++ [[Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ "rowtotal", Attribute
defstyle]   (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
a) | CommandDoc
a <- [CommandDoc]
tot]
      [Html ()] -> [Html ()] -> [Html ()]
forall a. [a] -> [a] -> [a]
++ [[Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ "rowaverage", Attribute
defstyle] (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
a) | CommandDoc
a <- [CommandDoc]
avg]

-- | Render one MultiBalanceReport data row as a HTML table row.
multiBalanceReportHtmlBodyRow :: ReportOpts -> [String] -> Html ()
multiBalanceReportHtmlBodyRow :: ReportOpts -> [CommandDoc] -> Html ()
multiBalanceReportHtmlBodyRow _ [] = Html ()
forall a. Monoid a => a
mempty  -- shouldn't happen
multiBalanceReportHtmlBodyRow ropts :: ReportOpts
ropts (label :: CommandDoc
label:rest :: [CommandDoc]
rest) =
  let
    defstyle :: Attribute
defstyle = Text -> Attribute
forall arg result. TermRaw arg result => arg -> result
style_ "text-align:right"
    (amts :: [CommandDoc]
amts,tot :: [CommandDoc]
tot,avg :: [CommandDoc]
avg)
      | ReportOpts -> Bool
row_total_ ReportOpts
ropts Bool -> Bool -> Bool
&& ReportOpts -> Bool
average_ ReportOpts
ropts = ([CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init ([CommandDoc] -> [CommandDoc]) -> [CommandDoc] -> [CommandDoc]
forall a b. (a -> b) -> a -> b
$ [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest, [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last ([CommandDoc] -> CommandDoc) -> [CommandDoc] -> CommandDoc
forall a b. (a -> b) -> a -> b
$ [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest], [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last [CommandDoc]
rest])
      | ReportOpts -> Bool
row_total_ ReportOpts
ropts                   = ([CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest,        [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last [CommandDoc]
rest],        [])
      |                     ReportOpts -> Bool
average_ ReportOpts
ropts = ([CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest,        [],                 [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last [CommandDoc]
rest])
      | Bool
otherwise                          = ([CommandDoc]
rest,             [],                 [])
  in
    Html () -> Html ()
forall arg result. Term arg result => arg -> result
tr_ (Html () -> Html ()) -> Html () -> Html ()
forall a b. (a -> b) -> a -> b
$ [Html ()] -> Html ()
forall a. Monoid a => [a] -> a
mconcat ([Html ()] -> Html ()) -> [Html ()] -> Html ()
forall a b. (a -> b) -> a -> b
$
          [Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ "account", Text -> Attribute
forall arg result. TermRaw arg result => arg -> result
style_ "text-align:left"]  (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
label)
       Html () -> [Html ()] -> [Html ()]
forall a. a -> [a] -> [a]
: [[Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ "amount", Attribute
defstyle]            (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
a) | CommandDoc
a <- [CommandDoc]
amts]
      [Html ()] -> [Html ()] -> [Html ()]
forall a. [a] -> [a] -> [a]
++ [[Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ "amount rowtotal", Attribute
defstyle]   (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
a) | CommandDoc
a <- [CommandDoc]
tot]
      [Html ()] -> [Html ()] -> [Html ()]
forall a. [a] -> [a] -> [a]
++ [[Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ "amount rowaverage", Attribute
defstyle] (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
a) | CommandDoc
a <- [CommandDoc]
avg]

-- | Render one MultiBalanceReport totals row as a HTML table row.
multiBalanceReportHtmlFootRow :: ReportOpts -> [String] -> Html ()
multiBalanceReportHtmlFootRow :: ReportOpts -> [CommandDoc] -> Html ()
multiBalanceReportHtmlFootRow _ropts :: ReportOpts
_ropts [] = Html ()
forall a. Monoid a => a
mempty
-- TODO pad totals row with zeros when subreport is empty
--  multiBalanceReportHtmlFootRow ropts $
--     ""
--   : repeat nullmixedamt zeros
--  ++ (if row_total_ ropts then [nullmixedamt] else [])
--  ++ (if average_ ropts   then [nullmixedamt]   else [])
multiBalanceReportHtmlFootRow ropts :: ReportOpts
ropts (acct :: CommandDoc
acct:rest :: [CommandDoc]
rest) =
  let
    defstyle :: Attribute
defstyle = Text -> Attribute
forall arg result. TermRaw arg result => arg -> result
style_ "text-align:right"
    (amts :: [CommandDoc]
amts,tot :: [CommandDoc]
tot,avg :: [CommandDoc]
avg)
      | ReportOpts -> Bool
row_total_ ReportOpts
ropts Bool -> Bool -> Bool
&& ReportOpts -> Bool
average_ ReportOpts
ropts = ([CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init ([CommandDoc] -> [CommandDoc]) -> [CommandDoc] -> [CommandDoc]
forall a b. (a -> b) -> a -> b
$ [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest, [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last ([CommandDoc] -> CommandDoc) -> [CommandDoc] -> CommandDoc
forall a b. (a -> b) -> a -> b
$ [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest], [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last [CommandDoc]
rest])
      | ReportOpts -> Bool
row_total_ ReportOpts
ropts                   = ([CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest,        [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last [CommandDoc]
rest],        [])
      |                     ReportOpts -> Bool
average_ ReportOpts
ropts = ([CommandDoc] -> [CommandDoc]
forall a. [a] -> [a]
init [CommandDoc]
rest,        [],                 [[CommandDoc] -> CommandDoc
forall a. [a] -> a
last [CommandDoc]
rest])
      | Bool
otherwise                          = ([CommandDoc]
rest,             [],                 [])
  in
    Html () -> Html ()
forall arg result. Term arg result => arg -> result
tr_ (Html () -> Html ()) -> Html () -> Html ()
forall a b. (a -> b) -> a -> b
$ [Html ()] -> Html ()
forall a. Monoid a => [a] -> a
mconcat ([Html ()] -> Html ()) -> [Html ()] -> Html ()
forall a b. (a -> b) -> a -> b
$
          [Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
th_ [Text -> Attribute
forall arg result. TermRaw arg result => arg -> result
style_ "text-align:left"]             (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
acct)
       Html () -> [Html ()] -> [Html ()]
forall a. a -> [a] -> [a]
: [[Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
th_ [Text -> Attribute
class_ "amount coltotal", Attribute
defstyle]   (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
a) | CommandDoc
a <- [CommandDoc]
amts]
      [Html ()] -> [Html ()] -> [Html ()]
forall a. [a] -> [a] -> [a]
++ [[Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
th_ [Text -> Attribute
class_ "amount coltotal", Attribute
defstyle]   (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
a) | CommandDoc
a <- [CommandDoc]
tot]
      [Html ()] -> [Html ()] -> [Html ()]
forall a. [a] -> [a] -> [a]
++ [[Attribute] -> Html () -> Html ()
forall arg result. Term arg result => arg -> result
th_ [Text -> Attribute
class_ "amount colaverage", Attribute
defstyle] (CommandDoc -> Html ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
toHtml CommandDoc
a) | CommandDoc
a <- [CommandDoc]
avg]

--thRow :: [String] -> Html ()
--thRow = tr_ . mconcat . map (th_ . toHtml)

-- | Render a multi-column balance report as plain text suitable for console output.
multiBalanceReportAsText :: ReportOpts -> MultiBalanceReport -> String
multiBalanceReportAsText :: ReportOpts -> MultiBalanceReport -> CommandDoc
multiBalanceReportAsText ropts :: ReportOpts
ropts@ReportOpts{..} r :: MultiBalanceReport
r =
    CommandDoc
title CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++ "\n\n" CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++ (ReportOpts -> Table CommandDoc CommandDoc MixedAmount -> CommandDoc
balanceReportTableAsText ReportOpts
ropts (Table CommandDoc CommandDoc MixedAmount -> CommandDoc)
-> Table CommandDoc CommandDoc MixedAmount -> CommandDoc
forall a b. (a -> b) -> a -> b
$ ReportOpts
-> MultiBalanceReport -> Table CommandDoc CommandDoc MixedAmount
balanceReportAsTable ReportOpts
ropts MultiBalanceReport
r)
  where
    multiperiod :: Bool
multiperiod = Interval
interval_ Interval -> Interval -> Bool
forall a. Eq a => a -> a -> Bool
/= Interval
NoInterval
    title :: CommandDoc
title = CommandDoc -> CommandDoc -> CommandDoc -> CommandDoc -> CommandDoc
forall r. PrintfType r => CommandDoc -> r
printf "%s in %s%s:"
      (case BalanceType
balancetype_ of
        PeriodChange       -> "Balance changes"
        CumulativeChange   -> "Ending balances (cumulative)"
        HistoricalBalance  -> "Ending balances (historical)")
      (DateSpan -> CommandDoc
showDateSpan (DateSpan -> CommandDoc) -> DateSpan -> CommandDoc
forall a b. (a -> b) -> a -> b
$ MultiBalanceReport -> DateSpan
multiBalanceReportSpan MultiBalanceReport
r)
      (case Maybe ValuationType
value_ of
        Just (AtCost _mc :: Maybe Text
_mc)   -> ", valued at cost"
        Just (AtEnd _mc :: Maybe Text
_mc)    -> ", valued at period ends"
        Just (AtNow _mc :: Maybe Text
_mc)    -> ", current value"
        -- XXX duplicates the above
        Just (AtDefault _mc :: Maybe Text
_mc) | Bool
multiperiod -> ", valued at period ends"
        Just (AtDefault _mc :: Maybe Text
_mc) -> ", current value"
        Just (AtDate d :: Day
d _mc :: Maybe Text
_mc) -> ", valued at "CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++Day -> CommandDoc
showDate Day
d
        Nothing             -> "")

-- | Build a 'Table' from a multi-column balance report.
balanceReportAsTable :: ReportOpts -> MultiBalanceReport -> Table String String MixedAmount
balanceReportAsTable :: ReportOpts
-> MultiBalanceReport -> Table CommandDoc CommandDoc MixedAmount
balanceReportAsTable opts :: ReportOpts
opts@ReportOpts{Bool
average_ :: Bool
average_ :: ReportOpts -> Bool
average_, Bool
row_total_ :: Bool
row_total_ :: ReportOpts -> Bool
row_total_, BalanceType
balancetype_ :: BalanceType
balancetype_ :: ReportOpts -> BalanceType
balancetype_} (MultiBalanceReport (colspans :: [DateSpan]
colspans, items :: [MultiBalanceReportRow]
items, (coltotals :: [MixedAmount]
coltotals,tot :: MixedAmount
tot,avg :: MixedAmount
avg))) =
   Table CommandDoc CommandDoc MixedAmount
-> Table CommandDoc CommandDoc MixedAmount
forall rh a. Table rh rh a -> Table rh rh a
maybetranspose (Table CommandDoc CommandDoc MixedAmount
 -> Table CommandDoc CommandDoc MixedAmount)
-> Table CommandDoc CommandDoc MixedAmount
-> Table CommandDoc CommandDoc MixedAmount
forall a b. (a -> b) -> a -> b
$
   Table CommandDoc CommandDoc MixedAmount
-> Table CommandDoc CommandDoc MixedAmount
forall ch.
Table CommandDoc ch MixedAmount -> Table CommandDoc ch MixedAmount
addtotalrow (Table CommandDoc CommandDoc MixedAmount
 -> Table CommandDoc CommandDoc MixedAmount)
-> Table CommandDoc CommandDoc MixedAmount
-> Table CommandDoc CommandDoc MixedAmount
forall a b. (a -> b) -> a -> b
$
   Header CommandDoc
-> Header CommandDoc
-> [[MixedAmount]]
-> Table CommandDoc CommandDoc MixedAmount
forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Table
     (Properties -> [Header CommandDoc] -> Header CommandDoc
forall h. Properties -> [Header h] -> Header h
T.Group Properties
NoLine ([Header CommandDoc] -> Header CommandDoc)
-> [Header CommandDoc] -> Header CommandDoc
forall a b. (a -> b) -> a -> b
$ (CommandDoc -> Header CommandDoc)
-> [CommandDoc] -> [Header CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map CommandDoc -> Header CommandDoc
forall h. h -> Header h
Header [CommandDoc]
accts)
     (Properties -> [Header CommandDoc] -> Header CommandDoc
forall h. Properties -> [Header h] -> Header h
T.Group Properties
NoLine ([Header CommandDoc] -> Header CommandDoc)
-> [Header CommandDoc] -> Header CommandDoc
forall a b. (a -> b) -> a -> b
$ (CommandDoc -> Header CommandDoc)
-> [CommandDoc] -> [Header CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map CommandDoc -> Header CommandDoc
forall h. h -> Header h
Header [CommandDoc]
colheadings)
     ((MultiBalanceReportRow -> [MixedAmount])
-> [MultiBalanceReportRow] -> [[MixedAmount]]
forall a b. (a -> b) -> [a] -> [b]
map MultiBalanceReportRow -> [MixedAmount]
forall a b c a. (a, b, c, [a], a, a) -> [a]
rowvals [MultiBalanceReportRow]
items)
  where
    totalscolumn :: Bool
totalscolumn = Bool
row_total_ Bool -> Bool -> Bool
&& Bool -> Bool
not (BalanceType
balancetype_ BalanceType -> [BalanceType] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [BalanceType
CumulativeChange, BalanceType
HistoricalBalance])
    mkDate :: DateSpan -> CommandDoc
mkDate = case BalanceType
balancetype_ of
       PeriodChange -> DateSpan -> CommandDoc
showDateSpanMonthAbbrev
       _            -> CommandDoc -> (Day -> CommandDoc) -> Maybe Day -> CommandDoc
forall b a. b -> (a -> b) -> Maybe a -> b
maybe "" (Day -> CommandDoc
showDate (Day -> CommandDoc) -> (Day -> Day) -> Day -> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Day -> Day
prevday) (Maybe Day -> CommandDoc)
-> (DateSpan -> Maybe Day) -> DateSpan -> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DateSpan -> Maybe Day
spanEnd
    colheadings :: [CommandDoc]
colheadings = (DateSpan -> CommandDoc) -> [DateSpan] -> [CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map DateSpan -> CommandDoc
mkDate [DateSpan]
colspans
                  [CommandDoc] -> [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a] -> [a]
++ ["  Total" | Bool
totalscolumn]
                  [CommandDoc] -> [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a] -> [a]
++ ["Average" | Bool
average_]
    accts :: [CommandDoc]
accts = (MultiBalanceReportRow -> CommandDoc)
-> [MultiBalanceReportRow] -> [CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map MultiBalanceReportRow -> CommandDoc
forall d e f. (Text, Text, Int, d, e, f) -> CommandDoc
renderacct [MultiBalanceReportRow]
items
    renderacct :: (Text, Text, Int, d, e, f) -> CommandDoc
renderacct (a :: Text
a,a' :: Text
a',i :: Int
i,_,_,_)
      | ReportOpts -> Bool
tree_ ReportOpts
opts = Int -> Char -> CommandDoc
forall a. Int -> a -> [a]
replicate ((Int
iInt -> Int -> Int
forall a. Num a => a -> a -> a
-1)Int -> Int -> Int
forall a. Num a => a -> a -> a
*2) ' ' CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++ Text -> CommandDoc
T.unpack Text
a'
      | Bool
otherwise  = Text -> CommandDoc
T.unpack (Text -> CommandDoc) -> Text -> CommandDoc
forall a b. (a -> b) -> a -> b
$ ReportOpts -> Text -> Text
maybeAccountNameDrop ReportOpts
opts Text
a
    rowvals :: (a, b, c, [a], a, a) -> [a]
rowvals (_,_,_,as :: [a]
as,rowtot :: a
rowtot,rowavg :: a
rowavg) = [a]
as
                             [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a
rowtot | Bool
totalscolumn]
                             [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a
rowavg | Bool
average_]
    addtotalrow :: Table CommandDoc ch MixedAmount -> Table CommandDoc ch MixedAmount
addtotalrow | ReportOpts -> Bool
no_total_ ReportOpts
opts = Table CommandDoc ch MixedAmount -> Table CommandDoc ch MixedAmount
forall a. a -> a
id
                | Bool
otherwise      = (Table CommandDoc ch MixedAmount
-> SemiTable CommandDoc MixedAmount
-> Table CommandDoc ch MixedAmount
forall rh ch a. Table rh ch a -> SemiTable rh a -> Table rh ch a
+----+ (CommandDoc -> [MixedAmount] -> SemiTable CommandDoc MixedAmount
forall rh a. rh -> [a] -> SemiTable rh a
row "" ([MixedAmount] -> SemiTable CommandDoc MixedAmount)
-> [MixedAmount] -> SemiTable CommandDoc MixedAmount
forall a b. (a -> b) -> a -> b
$
                                    [MixedAmount]
coltotals
                                    [MixedAmount] -> [MixedAmount] -> [MixedAmount]
forall a. [a] -> [a] -> [a]
++ [MixedAmount
tot | Bool
totalscolumn Bool -> Bool -> Bool
&& Bool -> Bool
not ([MixedAmount] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [MixedAmount]
coltotals)]
                                    [MixedAmount] -> [MixedAmount] -> [MixedAmount]
forall a. [a] -> [a] -> [a]
++ [MixedAmount
avg | Bool
average_   Bool -> Bool -> Bool
&& Bool -> Bool
not ([MixedAmount] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [MixedAmount]
coltotals)]
                                    ))
    maybetranspose :: Table rh rh a -> Table rh rh a
maybetranspose | ReportOpts -> Bool
transpose_ ReportOpts
opts = \(Table rh :: Header rh
rh ch :: Header rh
ch vals :: [[a]]
vals) -> Header rh -> Header rh -> [[a]] -> Table rh rh a
forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Table Header rh
ch Header rh
rh ([[a]] -> [[a]]
forall a. [[a]] -> [[a]]
transpose [[a]]
vals)
                   | Bool
otherwise       = Table rh rh a -> Table rh rh a
forall a. a -> a
id

-- | Given a table representing a multi-column balance report (for example,
-- made using 'balanceReportAsTable'), render it in a format suitable for
-- console output.
balanceReportTableAsText :: ReportOpts -> Table String String MixedAmount -> String
balanceReportTableAsText :: ReportOpts -> Table CommandDoc CommandDoc MixedAmount -> CommandDoc
balanceReportTableAsText ropts :: ReportOpts
ropts = ReportOpts
-> (MixedAmount -> CommandDoc)
-> Table CommandDoc CommandDoc MixedAmount
-> CommandDoc
forall a.
ReportOpts
-> (a -> CommandDoc) -> Table CommandDoc CommandDoc a -> CommandDoc
tableAsText ReportOpts
ropts MixedAmount -> CommandDoc
showamt
  where
    showamt :: MixedAmount -> CommandDoc
showamt | ReportOpts -> Bool
color_ ReportOpts
ropts = MixedAmount -> CommandDoc
cshowMixedAmountOneLineWithoutPrice
            | Bool
otherwise    =  MixedAmount -> CommandDoc
showMixedAmountOneLineWithoutPrice


tests_Balance :: TestTree
tests_Balance = CommandDoc -> [TestTree] -> TestTree
tests "Balance" [

   CommandDoc -> [TestTree] -> TestTree
tests "balanceReportAsText" [
    CommandDoc -> IO () -> TestTree
test "unicode in balance layout" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$ do
      Journal
j <- Text -> IO Journal
readJournal' "2009/01/01 * медвежья шкура\n  расходы:покупки  100\n  актив:наличные\n"
      let opts :: ReportOpts
opts = ReportOpts
defreportopts
      ReportOpts -> BalanceReport -> CommandDoc
balanceReportAsText ReportOpts
opts (ReportOpts -> Query -> Journal -> BalanceReport
balanceReport ReportOpts
opts (Day -> ReportOpts -> Query
queryFromOpts (CommandDoc -> Day
parsedate "2008/11/26") ReportOpts
opts) Journal
j)
        CommandDoc -> CommandDoc -> IO ()
forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
@?=
        [CommandDoc] -> CommandDoc
unlines
        ["                -100  актив:наличные"
        ,"                 100  расходы:покупки"
        ,"--------------------"
        ,"                   0"
        ]
    ]

  ]