{-|

The @aregister@ command lists a single account's transactions,
like the account register in hledger-ui and hledger-web,
and unlike the register command which lists postings across multiple accounts.

-}

{-# LANGUAGE CPP #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}

module Hledger.Cli.Commands.Aregister (
  aregistermode
 ,aregister
 -- ,showPostingWithBalanceForVty
 ,tests_Aregister
) where

import Data.Aeson (toJSON)
import Data.Aeson.Text (encodeToLazyText)
import Data.List
import Data.Maybe
#if !(MIN_VERSION_base(4,11,0))
import Data.Semigroup ((<>))
#endif
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Data.Time (addDays)
import Safe (headDef)
import System.Console.CmdArgs.Explicit
import Hledger.Read.CsvReader (CSV, CsvRecord, printCSV)

import Hledger
import Hledger.Cli.CliOptions
import Hledger.Cli.Utils

aregistermode :: Mode RawOpts
aregistermode = CommandDoc
-> [Flag RawOpts]
-> [(CommandDoc, [Flag RawOpts])]
-> [Flag RawOpts]
-> ([Arg RawOpts], Maybe (Arg RawOpts))
-> Mode RawOpts
hledgerCommandMode
  $(embedFileRelative "Hledger/Cli/Commands/Aregister.txt")
  ([
   [CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone [CommandDoc
"txn-dates"] (CommandDoc -> RawOpts -> RawOpts
setboolopt CommandDoc
"txn-dates") 
     CommandDoc
"filter strictly by transaction date, not posting date. Warning: this can show a wrong running balance."
   ,[CommandDoc] -> (RawOpts -> RawOpts) -> CommandDoc -> Flag RawOpts
forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone [CommandDoc
"no-elide"] (CommandDoc -> RawOpts -> RawOpts
setboolopt CommandDoc
"no-elide") CommandDoc
"don't show only 2 commodities per amount"
  --  flagNone ["cumulative"] (setboolopt "change")
  --    "show running total from report start date (default)"
  -- ,flagNone ["historical","H"] (setboolopt "historical")
  --    "show historical running total/balance (includes postings before report start date)\n "
  -- ,flagNone ["average","A"] (setboolopt "average")
  --    "show running average of posting amounts instead of total (implies --empty)"
  -- ,flagNone ["related","r"] (setboolopt "related") "show postings' siblings instead"
  -- ,flagNone ["invert"] (setboolopt "invert") "display all amounts with reversed sign"
  ,[CommandDoc]
-> Update RawOpts -> CommandDoc -> CommandDoc -> Flag RawOpts
forall a.
[CommandDoc] -> Update a -> CommandDoc -> CommandDoc -> Flag a
flagReq  [CommandDoc
"width",CommandDoc
"w"] (\CommandDoc
s 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 CommandDoc
"width" CommandDoc
s RawOpts
opts) CommandDoc
"N"
     (CommandDoc
"set output width (default: " CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++
#ifdef mingw32_HOST_OS
      show defaultWidth
#else
      CommandDoc
"terminal width"
#endif
      CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++ CommandDoc
" or $COLUMNS). -wN,M sets description width as well."
     )
  ,[CommandDoc] -> Flag RawOpts
outputFormatFlag [CommandDoc
"txt",CommandDoc
"csv",CommandDoc
"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 CommandDoc
"ACCTPAT [QUERY]")

-- based on Hledger.UI.RegisterScreen:

-- | Print an account register report for a specified account.
aregister :: CliOpts -> Journal -> IO ()
aregister :: CliOpts -> Journal -> IO ()
aregister opts :: CliOpts
opts@CliOpts{rawopts_ :: CliOpts -> RawOpts
rawopts_=RawOpts
rawopts,reportspec_ :: CliOpts -> ReportSpec
reportspec_=ReportSpec
rspec} Journal
j = do
  Day
d <- IO Day
getCurrentDay
  -- the first argument specifies the account, any remaining arguments are a filter query
  (CommandDoc
apat,[Text]
querystring) <- case CommandDoc -> RawOpts -> [CommandDoc]
listofstringopt CommandDoc
"args" RawOpts
rawopts of
      []     -> CommandDoc -> IO (CommandDoc, [Text])
forall (m :: * -> *) a. MonadFail m => CommandDoc -> m a
fail CommandDoc
"aregister needs an account, please provide an account name or pattern"
      (CommandDoc
a:[CommandDoc]
as) -> (CommandDoc, [Text]) -> IO (CommandDoc, [Text])
forall (m :: * -> *) a. Monad m => a -> m a
return (CommandDoc
a, (CommandDoc -> Text) -> [CommandDoc] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map CommandDoc -> Text
T.pack [CommandDoc]
as)
  Query
argsquery <- (CommandDoc -> IO Query)
-> ((Query, [QueryOpt]) -> IO Query)
-> Either CommandDoc (Query, [QueryOpt])
-> IO Query
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either CommandDoc -> IO Query
forall (m :: * -> *) a. MonadFail m => CommandDoc -> m a
fail (Query -> IO Query
forall (m :: * -> *) a. Monad m => a -> m a
return (Query -> IO Query)
-> ((Query, [QueryOpt]) -> Query)
-> (Query, [QueryOpt])
-> IO Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Query, [QueryOpt]) -> Query
forall a b. (a, b) -> a
fst) (Either CommandDoc (Query, [QueryOpt]) -> IO Query)
-> Either CommandDoc (Query, [QueryOpt]) -> IO Query
forall a b. (a -> b) -> a -> b
$ Day -> [Text] -> Either CommandDoc (Query, [QueryOpt])
parseQueryList Day
d [Text]
querystring
  let
    acct :: Text
acct = Text -> [Text] -> Text
forall a. a -> [a] -> a
headDef (CommandDoc -> Text
forall a. CommandDoc -> a
error' (CommandDoc -> Text) -> CommandDoc -> Text
forall a b. (a -> b) -> a -> b
$ CommandDoc -> CommandDoc
forall a. Show a => a -> CommandDoc
show CommandDoc
apatCommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++CommandDoc
" did not match any account")   -- PARTIAL:
           ([Text] -> Text) -> ([Text] -> [Text]) -> [Text] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> [Text]
filterAccts ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ Journal -> [Text]
journalAccountNames Journal
j
    filterAccts :: [Text] -> [Text]
filterAccts = case CommandDoc -> Either CommandDoc Regexp
toRegexCI CommandDoc
apat of
        Right Regexp
re -> (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Regexp -> CommandDoc -> Bool
regexMatch Regexp
re (CommandDoc -> Bool) -> (Text -> CommandDoc) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> CommandDoc
T.unpack)
        Left  CommandDoc
_  -> [Text] -> [Text] -> [Text]
forall a b. a -> b -> a
const []
    -- gather report options
    inclusive :: Bool
inclusive = Bool
True  -- tree_ ropts
    thisacctq :: Query
thisacctq = Regexp -> Query
Acct (Regexp -> Query) -> Regexp -> Query
forall a b. (a -> b) -> a -> b
$ (if Bool
inclusive then Text -> Regexp
accountNameToAccountRegex else Text -> Regexp
accountNameToAccountOnlyRegex) Text
acct
    rspec' :: ReportSpec
rspec' = ReportSpec
rspec{ rsQuery :: Query
rsQuery=Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
And [ReportOpts -> Query
queryFromFlags ReportOpts
ropts, Query
argsquery]
                  , rsOpts :: ReportOpts
rsOpts=ReportOpts
ropts'
                  }
    ropts' :: ReportOpts
ropts' = ReportOpts
ropts
      { -- remove a depth limit for reportq, as in RegisterScreen, I forget why XXX
        depth_ :: Maybe Int
depth_=Maybe Int
forall a. Maybe a
Nothing
        -- always show historical balance
      , balancetype_ :: BalanceType
balancetype_= BalanceType
HistoricalBalance
      }
    ropts :: ReportOpts
ropts = ReportSpec -> ReportOpts
rsOpts ReportSpec
rspec
    reportq :: Query
reportq = [Query] -> Query
And [ReportSpec -> Query
rsQuery ReportSpec
rspec', Bool -> Query
excludeforecastq (Maybe DateSpan -> Bool
forall a. Maybe a -> Bool
isJust (Maybe DateSpan -> Bool) -> Maybe DateSpan -> Bool
forall a b. (a -> b) -> a -> b
$ ReportOpts -> Maybe DateSpan
forecast_ ReportOpts
ropts')]
      where
        -- As in RegisterScreen, why ? XXX
        -- Except in forecast mode, exclude future/forecast transactions.
        excludeforecastq :: Bool -> Query
excludeforecastq Bool
True = Query
Any
        excludeforecastq Bool
False =  -- not:date:tomorrow- not:tag:generated-transaction
          [Query] -> Query
And [
             Query -> Query
Not (DateSpan -> Query
Date (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ Maybe Day -> Maybe Day -> DateSpan
DateSpan (Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Day -> Day
addDays Integer
1 Day
d) Maybe Day
forall a. Maybe a
Nothing)
            ,Query -> Query
Not Query
generatedTransactionTag
          ]
    -- run the report
    -- TODO: need to also pass the queries so we can choose which date to render - move them into the report ?
    (CommandDoc
balancelabel,[AccountTransactionsReportItem]
items) = ReportSpec
-> Journal
-> Query
-> Query
-> (CommandDoc, [AccountTransactionsReportItem])
accountTransactionsReport ReportSpec
rspec' Journal
j Query
reportq Query
thisacctq
    items' :: [AccountTransactionsReportItem]
items' = (if ReportOpts -> Bool
empty_ ReportOpts
ropts then [AccountTransactionsReportItem] -> [AccountTransactionsReportItem]
forall a. a -> a
id else (AccountTransactionsReportItem -> Bool)
-> [AccountTransactionsReportItem]
-> [AccountTransactionsReportItem]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool)
-> (AccountTransactionsReportItem -> Bool)
-> AccountTransactionsReportItem
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MixedAmount -> Bool
mixedAmountLooksZero (MixedAmount -> Bool)
-> (AccountTransactionsReportItem -> MixedAmount)
-> AccountTransactionsReportItem
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AccountTransactionsReportItem -> MixedAmount
forall a b c d e f. (a, b, c, d, e, f) -> e
fifth6)) ([AccountTransactionsReportItem]
 -> [AccountTransactionsReportItem])
-> [AccountTransactionsReportItem]
-> [AccountTransactionsReportItem]
forall a b. (a -> b) -> a -> b
$
             [AccountTransactionsReportItem] -> [AccountTransactionsReportItem]
forall a. [a] -> [a]
reverse [AccountTransactionsReportItem]
items
    -- select renderer
    render :: (CommandDoc, [AccountTransactionsReportItem]) -> CommandDoc
render | CommandDoc
fmtCommandDoc -> CommandDoc -> Bool
forall a. Eq a => a -> a -> Bool
==CommandDoc
"json" = (CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++CommandDoc
"\n") (CommandDoc -> CommandDoc)
-> ((CommandDoc, [AccountTransactionsReportItem]) -> CommandDoc)
-> (CommandDoc, [AccountTransactionsReportItem])
-> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> CommandDoc
T.unpack (Text -> CommandDoc)
-> ((CommandDoc, [AccountTransactionsReportItem]) -> Text)
-> (CommandDoc, [AccountTransactionsReportItem])
-> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
TL.toStrict (Text -> Text)
-> ((CommandDoc, [AccountTransactionsReportItem]) -> Text)
-> (CommandDoc, [AccountTransactionsReportItem])
-> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Text
forall a. ToJSON a => a -> Text
encodeToLazyText (Value -> Text)
-> ((CommandDoc, [AccountTransactionsReportItem]) -> Value)
-> (CommandDoc, [AccountTransactionsReportItem])
-> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (CommandDoc, [AccountTransactionsReportItem]) -> Value
forall a. ToJSON a => a -> Value
toJSON
           | CommandDoc
fmtCommandDoc -> CommandDoc -> Bool
forall a. Eq a => a -> a -> Bool
==CommandDoc
"csv"  = (CommandDoc -> CommandDoc -> CommandDoc
forall a. [a] -> [a] -> [a]
++CommandDoc
"\n") (CommandDoc -> CommandDoc)
-> ((CommandDoc, [AccountTransactionsReportItem]) -> CommandDoc)
-> (CommandDoc, [AccountTransactionsReportItem])
-> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CSV -> CommandDoc
printCSV (CSV -> CommandDoc)
-> ((CommandDoc, [AccountTransactionsReportItem]) -> CSV)
-> (CommandDoc, [AccountTransactionsReportItem])
-> CommandDoc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Query
-> Query -> (CommandDoc, [AccountTransactionsReportItem]) -> CSV
accountTransactionsReportAsCsv Query
reportq Query
thisacctq
           | CommandDoc
fmtCommandDoc -> CommandDoc -> Bool
forall a. Eq a => a -> a -> Bool
==CommandDoc
"txt"  = CliOpts
-> Query
-> Query
-> (CommandDoc, [AccountTransactionsReportItem])
-> CommandDoc
accountTransactionsReportAsText CliOpts
opts Query
reportq Query
thisacctq
           | Bool
otherwise   = CommandDoc
-> (CommandDoc, [AccountTransactionsReportItem]) -> CommandDoc
forall a b. a -> b -> a
const (CommandDoc
 -> (CommandDoc, [AccountTransactionsReportItem]) -> CommandDoc)
-> CommandDoc
-> (CommandDoc, [AccountTransactionsReportItem])
-> 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  -- PARTIAL:
      where
        fmt :: CommandDoc
fmt = CliOpts -> CommandDoc
outputFormatFromOpts CliOpts
opts

  CliOpts -> CommandDoc -> IO ()
writeOutput CliOpts
opts (CommandDoc -> IO ()) -> CommandDoc -> IO ()
forall a b. (a -> b) -> a -> b
$ (CommandDoc, [AccountTransactionsReportItem]) -> CommandDoc
render (CommandDoc
balancelabel,[AccountTransactionsReportItem]
items')

accountTransactionsReportAsCsv :: Query -> Query -> AccountTransactionsReport -> CSV
accountTransactionsReportAsCsv :: Query
-> Query -> (CommandDoc, [AccountTransactionsReportItem]) -> CSV
accountTransactionsReportAsCsv Query
reportq Query
thisacctq (CommandDoc
_,[AccountTransactionsReportItem]
is) =
  [CommandDoc
"txnidx",CommandDoc
"date",CommandDoc
"code",CommandDoc
"description",CommandDoc
"otheraccounts",CommandDoc
"change",CommandDoc
"balance"]
  [CommandDoc] -> CSV -> CSV
forall a. a -> [a] -> [a]
: (AccountTransactionsReportItem -> [CommandDoc])
-> [AccountTransactionsReportItem] -> CSV
forall a b. (a -> b) -> [a] -> [b]
map (Query -> Query -> AccountTransactionsReportItem -> [CommandDoc]
accountTransactionsReportItemAsCsvRecord Query
reportq Query
thisacctq) [AccountTransactionsReportItem]
is

accountTransactionsReportItemAsCsvRecord :: Query -> Query -> AccountTransactionsReportItem -> CsvRecord
accountTransactionsReportItemAsCsvRecord :: Query -> Query -> AccountTransactionsReportItem -> [CommandDoc]
accountTransactionsReportItemAsCsvRecord
  Query
reportq Query
thisacctq
  (t :: Transaction
t@Transaction{Integer
tindex :: Transaction -> Integer
tindex :: Integer
tindex,Text
tcode :: Transaction -> Text
tcode :: Text
tcode,Text
tdescription :: Transaction -> Text
tdescription :: Text
tdescription}, Transaction
_, Bool
_issplit, CommandDoc
otheracctsstr, MixedAmount
change, MixedAmount
balance)
  = [CommandDoc
idx,CommandDoc
date,CommandDoc
code,CommandDoc
desc,CommandDoc
otheracctsstr,CommandDoc
amt,CommandDoc
bal]
  where
    idx :: CommandDoc
idx  = Integer -> CommandDoc
forall a. Show a => a -> CommandDoc
show Integer
tindex
    date :: CommandDoc
date = Day -> CommandDoc
showDate (Day -> CommandDoc) -> Day -> CommandDoc
forall a b. (a -> b) -> a -> b
$ Query -> Query -> Transaction -> Day
transactionRegisterDate Query
reportq Query
thisacctq Transaction
t
    code :: CommandDoc
code = Text -> CommandDoc
T.unpack Text
tcode
    desc :: CommandDoc
desc = Text -> CommandDoc
T.unpack Text
tdescription
    amt :: CommandDoc
amt  = Bool -> MixedAmount -> CommandDoc
showMixedAmountOneLineWithoutPrice Bool
False MixedAmount
change
    bal :: CommandDoc
bal  = Bool -> MixedAmount -> CommandDoc
showMixedAmountOneLineWithoutPrice Bool
False MixedAmount
balance

-- | Render a register report as plain text suitable for console output.
accountTransactionsReportAsText :: CliOpts -> Query -> Query -> AccountTransactionsReport -> String
accountTransactionsReportAsText :: CliOpts
-> Query
-> Query
-> (CommandDoc, [AccountTransactionsReportItem])
-> CommandDoc
accountTransactionsReportAsText
  copts :: CliOpts
copts@CliOpts{reportspec_ :: CliOpts -> ReportSpec
reportspec_=ReportSpec{rsOpts :: ReportSpec -> ReportOpts
rsOpts=ReportOpts{Bool
no_elide_ :: ReportOpts -> Bool
no_elide_ :: Bool
no_elide_}}} Query
reportq Query
thisacctq (CommandDoc
_balancelabel,[AccountTransactionsReportItem]
items)
  = [CommandDoc] -> CommandDoc
unlines ([CommandDoc] -> CommandDoc) -> [CommandDoc] -> CommandDoc
forall a b. (a -> b) -> a -> b
$ CommandDoc
title CommandDoc -> [CommandDoc] -> [CommandDoc]
forall a. a -> [a] -> [a]
:
    (AccountTransactionsReportItem -> CommandDoc)
-> [AccountTransactionsReportItem] -> [CommandDoc]
forall a b. (a -> b) -> [a] -> [b]
map (CliOpts
-> Query
-> Query
-> Int
-> Int
-> AccountTransactionsReportItem
-> CommandDoc
accountTransactionsReportItemAsText CliOpts
copts Query
reportq Query
thisacctq Int
amtwidth Int
balwidth) [AccountTransactionsReportItem]
items
  where
    amtwidth :: Int
amtwidth = [Int] -> Int
forall a. Ord a => [a] -> a
maximumStrict ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ Int
12 Int -> [Int] -> [Int]
forall a. a -> [a] -> [a]
: (AccountTransactionsReportItem -> Int)
-> [AccountTransactionsReportItem] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ((CommandDoc, Int) -> Int
forall a b. (a, b) -> b
snd ((CommandDoc, Int) -> Int)
-> (AccountTransactionsReportItem -> (CommandDoc, Int))
-> AccountTransactionsReportItem
-> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MixedAmount -> (CommandDoc, Int)
showamt (MixedAmount -> (CommandDoc, Int))
-> (AccountTransactionsReportItem -> MixedAmount)
-> AccountTransactionsReportItem
-> (CommandDoc, Int)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AccountTransactionsReportItem -> MixedAmount
forall a b c d e f. (a, b, c, d, e, f) -> e
itemamt) [AccountTransactionsReportItem]
items
    balwidth :: Int
balwidth = [Int] -> Int
forall a. Ord a => [a] -> a
maximumStrict ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ Int
12 Int -> [Int] -> [Int]
forall a. a -> [a] -> [a]
: (AccountTransactionsReportItem -> Int)
-> [AccountTransactionsReportItem] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ((CommandDoc, Int) -> Int
forall a b. (a, b) -> b
snd ((CommandDoc, Int) -> Int)
-> (AccountTransactionsReportItem -> (CommandDoc, Int))
-> AccountTransactionsReportItem
-> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MixedAmount -> (CommandDoc, Int)
showamt (MixedAmount -> (CommandDoc, Int))
-> (AccountTransactionsReportItem -> MixedAmount)
-> AccountTransactionsReportItem
-> (CommandDoc, Int)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AccountTransactionsReportItem -> MixedAmount
forall a b c d e f. (a, b, c, d, e, f) -> f
itembal) [AccountTransactionsReportItem]
items
    showamt :: MixedAmount -> (CommandDoc, Int)
showamt = (Amount -> CommandDoc)
-> Maybe Int
-> Maybe Int
-> Bool
-> MixedAmount
-> (CommandDoc, Int)
showMixedOneLine Amount -> CommandDoc
showAmountWithoutPrice (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
12) Maybe Int
mmax Bool
False  -- color_
      where mmax :: Maybe Int
mmax = if Bool
no_elide_ then Maybe Int
forall a. Maybe a
Nothing else Int -> Maybe Int
forall a. a -> Maybe a
Just Int
32
    itemamt :: (a, b, c, d, e, f) -> e
itemamt (a
_,b
_,c
_,d
_,e
a,f
_) = e
a
    itembal :: (a, b, c, d, e, f) -> f
itembal (a
_,b
_,c
_,d
_,e
_,f
a) = f
a
    -- show a title indicating which account was picked, which can be confusing otherwise
    title :: CommandDoc
title = Text -> CommandDoc
T.unpack (Text -> CommandDoc) -> Text -> CommandDoc
forall a b. (a -> b) -> a -> b
$ Text -> (Text -> Text) -> Maybe Text -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" ((Text
"Transactions in "Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>)(Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
.(Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>Text
" and subaccounts:")) Maybe Text
macct
      where
        -- XXX temporary hack ? recover the account name from the query
        macct :: Maybe Text
macct = case (Query -> Bool) -> Query -> Query
filterQuery Query -> Bool
queryIsAcct Query
thisacctq of
                  Acct Regexp
r -> Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text)
-> (CommandDoc -> Text) -> CommandDoc -> Maybe Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Text -> Text
T.drop Int
1 (Text -> Text) -> (CommandDoc -> Text) -> CommandDoc -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Text -> Text
T.dropEnd Int
5 (Text -> Text) -> (CommandDoc -> Text) -> CommandDoc -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CommandDoc -> Text
T.pack (CommandDoc -> Maybe Text) -> CommandDoc -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Regexp -> CommandDoc
reString Regexp
r  -- Acct "^JS:expenses(:|$)"
                  Query
_      -> Maybe Text
forall a. Maybe a
Nothing  -- shouldn't happen

-- | Render one account register report line item as plain text. Layout is like so:
-- @
-- <---------------- width (specified, terminal width, or 80) -------------------->
-- date (10)  description           other accounts       change (12)   balance (12)
-- DDDDDDDDDD dddddddddddddddddddd  aaaaaaaaaaaaaaaaaaa  AAAAAAAAAAAA  AAAAAAAAAAAA
-- @
-- If description's width is specified, account will use the remaining space.
-- Otherwise, description and account divide up the space equally.
--
-- Returns a string which can be multi-line, eg if the running balance
-- has multiple commodities.
--
accountTransactionsReportItemAsText :: CliOpts -> Query -> Query -> Int -> Int -> AccountTransactionsReportItem -> String
accountTransactionsReportItemAsText :: CliOpts
-> Query
-> Query
-> Int
-> Int
-> AccountTransactionsReportItem
-> CommandDoc
accountTransactionsReportItemAsText
  copts :: CliOpts
copts@CliOpts{reportspec_ :: CliOpts -> ReportSpec
reportspec_=ReportSpec{rsOpts :: ReportSpec -> ReportOpts
rsOpts=ReportOpts{Bool
color_ :: ReportOpts -> Bool
color_ :: Bool
color_}}}
  Query
reportq Query
thisacctq Int
preferredamtwidth Int
preferredbalwidth
  (t :: Transaction
t@Transaction{Text
tdescription :: Text
tdescription :: Transaction -> Text
tdescription}, Transaction
_, Bool
_issplit, CommandDoc
otheracctsstr, MixedAmount
change, MixedAmount
balance)
    -- Transaction -- the transaction, unmodified
    -- Transaction -- the transaction, as seen from the current account
    -- Bool        -- is this a split (more than one posting to other accounts) ?
    -- String      -- a display string describing the other account(s), if any
    -- MixedAmount -- the amount posted to the current account(s) (or total amount posted)
    -- MixedAmount -- the register's running total or the current account(s)'s historical balance, after this transaction

  = CommandDoc -> [CommandDoc] -> CommandDoc
forall a. [a] -> [[a]] -> [a]
intercalate CommandDoc
"\n" ([CommandDoc] -> CommandDoc) -> [CommandDoc] -> CommandDoc
forall a b. (a -> b) -> a -> b
$
    [CommandDoc] -> CommandDoc
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [Maybe Int -> Maybe Int -> Bool -> Bool -> CommandDoc -> CommandDoc
fitString (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
datewidth) (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
datewidth) Bool
True Bool
True CommandDoc
date
           ,CommandDoc
" "
           ,Maybe Int -> Maybe Int -> Bool -> Bool -> CommandDoc -> CommandDoc
fitString (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
descwidth) (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
descwidth) Bool
True Bool
True CommandDoc
desc
           ,CommandDoc
"  "
           ,Maybe Int -> Maybe Int -> Bool -> Bool -> CommandDoc -> CommandDoc
fitString (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
acctwidth) (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
acctwidth) Bool
True Bool
True CommandDoc
accts
           ,CommandDoc
"  "
           ,CommandDoc
amtfirstline
           ,CommandDoc
"  "
           ,CommandDoc
balfirstline
           ]
    CommandDoc -> [CommandDoc] -> [CommandDoc]
forall a. a -> [a] -> [a]
:
    [[CommandDoc] -> CommandDoc
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [CommandDoc
spacer
            ,CommandDoc
a
            ,CommandDoc
"  "
            ,CommandDoc
b
            ]
     | (CommandDoc
a,CommandDoc
b) <- [CommandDoc] -> [CommandDoc] -> [(CommandDoc, CommandDoc)]
forall a b. [a] -> [b] -> [(a, b)]
zip [CommandDoc]
amtrest [CommandDoc]
balrest
     ]
    where
      -- calculate widths
      (Int
totalwidth,Maybe Int
mdescwidth) = CliOpts -> (Int, Maybe Int)
registerWidthsFromOpts CliOpts
copts
      (Int
datewidth, CommandDoc
date) = (Int
10, Day -> CommandDoc
showDate (Day -> CommandDoc) -> Day -> CommandDoc
forall a b. (a -> b) -> a -> b
$ Query -> Query -> Transaction -> Day
transactionRegisterDate Query
reportq Query
thisacctq Transaction
t)
      (Int
amtwidth, Int
balwidth)
        | Int
shortfall Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0 = (Int
preferredamtwidth, Int
preferredbalwidth)
        | Bool
otherwise      = (Int
adjustedamtwidth, Int
adjustedbalwidth)
        where
          mincolwidth :: Int
mincolwidth = Int
2 -- columns always show at least an ellipsis
          maxamtswidth :: Int
maxamtswidth = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (Int
totalwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
- (Int
datewidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
mincolwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
mincolwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2))
          shortfall :: Int
shortfall = (Int
preferredamtwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
preferredbalwidth) Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
maxamtswidth
          amtwidthproportion :: Double
amtwidthproportion = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
preferredamtwidth Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int
preferredamtwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
preferredbalwidth)
          adjustedamtwidth :: Int
adjustedamtwidth = Double -> Int
forall a b. (RealFrac a, Integral b) => a -> b
round (Double -> Int) -> Double -> Int
forall a b. (a -> b) -> a -> b
$ Double
amtwidthproportion Double -> Double -> Double
forall a. Num a => a -> a -> a
* Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
maxamtswidth
          adjustedbalwidth :: Int
adjustedbalwidth = Int
maxamtswidth Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
adjustedamtwidth

      remaining :: Int
remaining = Int
totalwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
- (Int
datewidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
amtwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
balwidth)
      (Int
descwidth, Int
acctwidth) = (Int
w, Int
remaining Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
w)
        where
          w :: Int
w = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe ((Int
remaining Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
2) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
2) Maybe Int
mdescwidth

      -- gather content
      desc :: CommandDoc
desc = Text -> CommandDoc
T.unpack Text
tdescription
      accts :: CommandDoc
accts = -- T.unpack $ elideAccountName acctwidth $ T.pack
              CommandDoc
otheracctsstr
      amt :: CommandDoc
amt = (CommandDoc, Int) -> CommandDoc
forall a b. (a, b) -> a
fst ((CommandDoc, Int) -> CommandDoc)
-> (CommandDoc, Int) -> CommandDoc
forall a b. (a -> b) -> a -> b
$ (Amount -> CommandDoc)
-> Maybe Int
-> Maybe Int
-> Bool
-> MixedAmount
-> (CommandDoc, Int)
showMixed Amount -> CommandDoc
showAmountWithoutPrice (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
amtwidth) (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
balwidth) Bool
color_ MixedAmount
change
      bal :: CommandDoc
bal = (CommandDoc, Int) -> CommandDoc
forall a b. (a, b) -> a
fst ((CommandDoc, Int) -> CommandDoc)
-> (CommandDoc, Int) -> CommandDoc
forall a b. (a -> b) -> a -> b
$ (Amount -> CommandDoc)
-> Maybe Int
-> Maybe Int
-> Bool
-> MixedAmount
-> (CommandDoc, Int)
showMixed Amount -> CommandDoc
showAmountWithoutPrice (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
balwidth) (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
balwidth) Bool
color_ MixedAmount
balance
      -- alternate behaviour, show null amounts as 0 instead of blank
      -- amt = if null amt' then "0" else amt'
      -- bal = if null bal' then "0" else bal'
      ([CommandDoc]
amtlines, [CommandDoc]
ballines) = (CommandDoc -> [CommandDoc]
lines CommandDoc
amt, CommandDoc -> [CommandDoc]
lines CommandDoc
bal)
      (Int
amtlen, Int
ballen) = ([CommandDoc] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [CommandDoc]
amtlines, [CommandDoc] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [CommandDoc]
ballines)
      numlines :: Int
numlines = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
amtlen Int
ballen)
      (CommandDoc
amtfirstline:[CommandDoc]
amtrest) = Int -> [CommandDoc] -> [CommandDoc]
forall a. Int -> [a] -> [a]
take Int
numlines ([CommandDoc] -> [CommandDoc]) -> [CommandDoc] -> [CommandDoc]
forall a b. (a -> b) -> a -> b
$ [CommandDoc]
amtlines [CommandDoc] -> [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a] -> [a]
++ CommandDoc -> [CommandDoc]
forall a. a -> [a]
repeat (Int -> Char -> CommandDoc
forall a. Int -> a -> [a]
replicate Int
amtwidth Char
' ') -- posting amount is top-aligned
      (CommandDoc
balfirstline:[CommandDoc]
balrest) = Int -> [CommandDoc] -> [CommandDoc]
forall a. Int -> [a] -> [a]
take Int
numlines ([CommandDoc] -> [CommandDoc]) -> [CommandDoc] -> [CommandDoc]
forall a b. (a -> b) -> a -> b
$ Int -> CommandDoc -> [CommandDoc]
forall a. Int -> a -> [a]
replicate (Int
numlines Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
ballen) (Int -> Char -> CommandDoc
forall a. Int -> a -> [a]
replicate Int
balwidth Char
' ') [CommandDoc] -> [CommandDoc] -> [CommandDoc]
forall a. [a] -> [a] -> [a]
++ [CommandDoc]
ballines -- balance amount is bottom-aligned
      spacer :: CommandDoc
spacer = Int -> Char -> CommandDoc
forall a. Int -> a -> [a]
replicate (Int
totalwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
- (Int
amtwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
balwidth)) Char
' '

-- tests

tests_Aregister :: TestTree
tests_Aregister = CommandDoc -> [TestTree] -> TestTree
tests CommandDoc
"Aregister" [

 ]