    {-|

    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 Data.Time (fromGregorian)
    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 "
       ]
       [Flag RawOpts] -> [Flag RawOpts] -> [Flag RawOpts]
    forall a. [a] -> [a] -> [a]
    ++ Bool -> [Flag RawOpts]
    flattreeflags Bool
    True [Flag RawOpts] -> [Flag RawOpts] -> [Flag RawOpts]
    forall a. [a] -> [a] -> [a]
    ++
       [[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 ["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] -> (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 ["percent", "%"] (CommandDoc -> RawOpts -> RawOpts
    setboolopt "percent") "express values in percentage of each column's total"
       ,[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 ["budget"] (CommandDoc -> RawOpts -> RawOpts
    setboolopt "budget") "show performance compared to budget goals defined by periodic transactions"
       ,[CommandDoc] -> Flag RawOpts
    outputFormatFlag ["txt","html","csv","json"]
       ,Flag RawOpts
    outputFileFlag
       ]
      )
      [(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]  -- PARTIAL:
        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
              fmt :: CommandDoc
    fmt         = 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
    dbg4 "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
    fmt of
                  "txt"  -> ReportOpts -> BudgetReport -> CommandDoc
    budgetReportAsText ReportOpts
    ropts
                  "json" -> (CommandDoc -> CommandDoc -> CommandDoc
    forall a. [a] -> [a] -> [a]
    ++"\n") (CommandDoc -> CommandDoc)
    -> (BudgetReport -> CommandDoc) -> BudgetReport -> CommandDoc
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Text -> CommandDoc
    TL.unpack (Text -> CommandDoc)
    -> (BudgetReport -> Text) -> BudgetReport -> CommandDoc
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . BudgetReport -> Text
    forall a. ToJSON a => a -> Text
    toJsonText
                  _      -> 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' (CommandDoc -> CommandDoc) -> CommandDoc -> CommandDoc
    forall a b. (a -> b) -> a -> b
    $ CommandDoc -> CommandDoc
    unsupportedOutputFormatError CommandDoc
    fmt
            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 = Day -> ReportOpts -> Journal -> MultiBalanceReport
    multiBalanceReport Day
    d ReportOpts
    ropts Journal
    j
                  render :: MultiBalanceReport -> CommandDoc
    render = case CommandDoc
    fmt of
                    "txt"  -> ReportOpts -> MultiBalanceReport -> CommandDoc
    multiBalanceReportAsText ReportOpts
    ropts
                    "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
                    "json" -> (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
    . MultiBalanceReport -> Text
    forall a. ToJSON a => a -> Text
    toJsonText
                    _      -> CommandDoc -> MultiBalanceReport -> CommandDoc
    forall a b. a -> b -> a
    const (CommandDoc -> MultiBalanceReport -> CommandDoc)
    -> CommandDoc -> MultiBalanceReport -> CommandDoc
    forall a b. (a -> b) -> a -> b
    $ CommandDoc -> CommandDoc
    forall a. CommandDoc -> a
    error' (CommandDoc -> CommandDoc) -> CommandDoc -> CommandDoc
    forall a b. (a -> b) -> a -> b
    $ CommandDoc -> CommandDoc
    unsupportedOutputFormatError CommandDoc
    fmt
              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 = 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
    fmt of
                    "txt"  -> ReportOpts -> BalanceReport -> CommandDoc
    balanceReportAsText
                    "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
                    "json" -> (BalanceReport -> CommandDoc)
    -> ReportOpts -> BalanceReport -> CommandDoc
    forall a b. a -> b -> a
    const ((BalanceReport -> CommandDoc)
     -> ReportOpts -> BalanceReport -> CommandDoc)
    -> (BalanceReport -> CommandDoc)
    -> ReportOpts
    -> BalanceReport
    -> CommandDoc
    forall a b. (a -> b) -> a -> b
    $ (CommandDoc -> CommandDoc -> CommandDoc
    forall a. [a] -> [a] -> [a]
    ++"\n") (CommandDoc -> CommandDoc)
    -> (BalanceReport -> CommandDoc) -> BalanceReport -> CommandDoc
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Text -> CommandDoc
    TL.unpack (Text -> CommandDoc)
    -> (BalanceReport -> Text) -> BalanceReport -> CommandDoc
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . BalanceReport -> Text
    forall a. ToJSON a => a -> Text
    toJsonText
                    _      -> (BalanceReport -> CommandDoc)
    -> ReportOpts -> BalanceReport -> CommandDoc
    forall a b. a -> b -> a
    const ((BalanceReport -> CommandDoc)
     -> ReportOpts -> BalanceReport -> CommandDoc)
    -> (BalanceReport -> CommandDoc)
    -> ReportOpts
    -> BalanceReport
    -> CommandDoc
    forall a b. (a -> b) -> a -> b
    $ CommandDoc -> BalanceReport -> CommandDoc
    forall a. CommandDoc -> a
    error' (CommandDoc -> BalanceReport -> CommandDoc)
    -> CommandDoc -> BalanceReport -> CommandDoc
    forall a b. (a -> b) -> a -> b
    $ CommandDoc -> CommandDoc
    unsupportedOutputFormatError CommandDoc
    fmt
              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 Text
    a, Bool -> MixedAmount -> CommandDoc
    showMixedAmountOneLineWithoutPrice Bool
    False 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", Bool -> MixedAmount -> CommandDoc
    showMixedAmountOneLineWithoutPrice Bool
    False 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 (
        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
    $ Bool -> MixedAmount -> CommandDoc
    showMixedAmountWithoutPrice (ReportOpts -> Bool
    color_ ReportOpts
    opts) MixedAmount
    total
      _                -> ""

    -- | 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 = Bool -> MixedAmount -> CommandDoc
    showMixedAmountWithoutPrice (ReportOpts -> Bool
    color_ ReportOpts
    opts)
      _                -> ""

    -- 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_}
        (PeriodicReport colspans :: [DateSpan]
    colspans items :: [PeriodicReportRow DisplayName MixedAmount]
    items (PeriodicReportRow _ 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 (DisplayName -> Text
    displayFull DisplayName
    a) CommandDoc -> [CommandDoc] -> [CommandDoc]
    forall a. a -> [a] -> [a]
    :
       (MixedAmount -> CommandDoc) -> [MixedAmount] -> [CommandDoc]
    forall a b. (a -> b) -> [a] -> [b]
    map (Bool -> MixedAmount -> CommandDoc
    showMixedAmountOneLineWithoutPrice Bool
    False)
       ([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_])
      | PeriodicReportRow a :: DisplayName
    a amts :: [MixedAmount]
    amts rowtot :: MixedAmount
    rowtot rowavg :: MixedAmount
    rowavg <- [PeriodicReportRow DisplayName MixedAmount]
    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 (Bool -> MixedAmount -> CommandDoc
    showMixedAmountOneLineWithoutPrice Bool
    False) (
              [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"  -- PARTIAL:
                         | 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
    forall a b. PeriodicReport a b -> DateSpan
    periodicReportSpan MultiBalanceReport
    r)
          (case Maybe ValuationType
    value_ of
            Just (AtCost _mc :: Maybe Text
    _mc)   -> ", valued at cost"
            Just (AtThen _mc :: Maybe Text
    _mc)   -> CommandDoc -> CommandDoc
    forall a. CommandDoc -> a
    error' CommandDoc
    unsupportedValueThenError  -- TODO -- ", valued at period ends"  -- handled like AtEnd for now  -- PARTIAL:
            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_}
        (PeriodicReport spans :: [DateSpan]
    spans items :: [PeriodicReportRow DisplayName MixedAmount]
    items (PeriodicReportRow _ 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)
         ((PeriodicReportRow DisplayName MixedAmount -> [MixedAmount])
    -> [PeriodicReportRow DisplayName MixedAmount] -> [[MixedAmount]]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName MixedAmount -> [MixedAmount]
    forall a a. PeriodicReportRow a a -> [a]
    rowvals [PeriodicReportRow DisplayName MixedAmount]
    items)
      where
        totalscolumn :: Bool
    totalscolumn = Bool
    row_total_ Bool -> Bool -> Bool
    && BalanceType
    balancetype_ BalanceType -> [BalanceType] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `notElem` [BalanceType
    CumulativeChange, BalanceType
    HistoricalBalance]
        colheadings :: [CommandDoc]
    colheadings = (DateSpan -> CommandDoc) -> [DateSpan] -> [CommandDoc]
    forall a b. (a -> b) -> [a] -> [b]
    map (BalanceType -> [DateSpan] -> DateSpan -> CommandDoc
    reportPeriodName BalanceType
    balancetype_ [DateSpan]
    spans) [DateSpan]
    spans
                      [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 = (PeriodicReportRow DisplayName MixedAmount -> CommandDoc)
    -> [PeriodicReportRow DisplayName MixedAmount] -> [CommandDoc]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName MixedAmount -> CommandDoc
    forall a. PeriodicReportRow DisplayName a -> CommandDoc
    renderacct [PeriodicReportRow DisplayName MixedAmount]
    items
        renderacct :: PeriodicReportRow DisplayName a -> CommandDoc
    renderacct row :: PeriodicReportRow DisplayName a
    row =
            Int -> Char -> CommandDoc
    forall a. Int -> a -> [a]
    replicate ((PeriodicReportRow DisplayName a -> Int
    forall a. PeriodicReportRow DisplayName a -> Int
    prrDepth PeriodicReportRow DisplayName a
    row Int -> 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 (PeriodicReportRow DisplayName a -> Text
    forall a. PeriodicReportRow DisplayName a -> Text
    prrDisplayName PeriodicReportRow DisplayName a
    row)
        rowvals :: PeriodicReportRow a a -> [a]
    rowvals (PeriodicReportRow _ 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. Amounts with more than two commodities will be elided
    -- unless --no-elide is used.
    balanceReportTableAsText :: ReportOpts -> Table String String MixedAmount -> String
    balanceReportTableAsText :: ReportOpts -> Table CommandDoc CommandDoc MixedAmount -> CommandDoc
    balanceReportTableAsText ropts :: ReportOpts
    ropts@ReportOpts{..} = 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
          | Bool
    no_elide_ = Bool -> MixedAmount -> CommandDoc
    showMixedAmountOneLineWithoutPrice Bool
    color_
          | Bool
    otherwise = Bool -> MixedAmount -> CommandDoc
    showMixedAmountElided Bool
    color_

    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 (Integer -> Int -> Int -> Day
    fromGregorian 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"
            ]
        ]

      ]