1 {-# LANGUAGE NamedFieldPuns #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 module Hcompta.CLI.Command.Balance where
5 import Control.Monad.IO.Class (liftIO)
6 import Control.Monad.Trans.Except (runExceptT)
7 import qualified Data.Either
8 import qualified Data.Foldable
9 import qualified Data.List
10 import qualified Data.Map
11 import qualified Data.Text.Lazy as TL
12 import qualified Data.Text as Text
13 import System.Console.GetOpt
18 import System.Environment as Env (getProgName)
19 import System.Exit (exitWith, ExitCode(..))
20 import qualified System.IO as IO
21 import Text.Show.Pretty (ppShow) -- TODO: may be not necessary
23 import qualified Hcompta.Calc.Balance as Balance
24 import qualified Hcompta.CLI.Args as Args
25 import qualified Hcompta.CLI.Context as Context
26 import Hcompta.CLI.Context (Context)
27 import qualified Hcompta.CLI.Format.Ledger as CLI.Ledger
28 import qualified Hcompta.CLI.Lib.Shakespeare.Leijen as I18N
29 import qualified Hcompta.CLI.Lib.Leijen.Table as Table
30 import qualified Hcompta.CLI.Write as Write
31 import qualified Hcompta.Format.Ledger as Ledger
32 import qualified Hcompta.Format.Ledger.Journal as Ledger.Journal
33 import qualified Hcompta.Format.Ledger.Read as Ledger.Read
34 import qualified Hcompta.Format.Ledger.Write as Ledger.Write
35 import qualified Hcompta.Lib.TreeMap as Lib.TreeMap
36 import qualified Hcompta.Lib.Leijen as W
37 import Hcompta.Lib.Leijen ((<>), toDoc, ToDoc(..))
38 import qualified Hcompta.Model.Amount as Amount
39 import Hcompta.Model.Amount (Amount, Unit)
43 { ctx_input :: [FilePath]
44 , ctx_redundant :: Bool
51 , ctx_redundant = False
56 bin <- Env.getProgName
59 , " "++bin++" balance [option..]"
61 , usageInfo "OPTIONS" options
64 options :: Args.Options Ctx
67 (NoArg (\_context _ctx -> do
68 usage >>= IO.hPutStr IO.stderr
69 exitWith ExitSuccess))
71 , Option "i" ["input"]
72 (ReqArg (\s _context ctx -> do
73 return $ ctx{ctx_input=s:ctx_input ctx}) "FILE")
74 "read data from given file, can be use multiple times"
75 , Option "" ["redundant"]
76 (OptArg (\arg context ctx -> do
77 redundant <- case arg of
78 Nothing -> return $ True
79 Just "yes" -> return $ True
80 Just "no" -> return $ False
81 Just _ -> Write.fatal context $
82 W.text "--redundant option expects \"yes\", or \"no\" as value"
83 return $ ctx{ctx_redundant=redundant})
85 "also print accounts with zero amount or the same amounts than its ascending account"
88 run :: Context.Context -> [String] -> IO ()
90 (ctx, _) <- Args.parse context usage options (nil, args)
92 CLI.Ledger.paths context $ ctx_input ctx
93 >>= do mapM $ \path -> do
97 Left ko -> return $ Left (path, ko)
98 Right ok -> return $ Right ok
99 >>= return . Data.Either.partitionEithers
102 (flip mapM_) kos $ \(_path, ko) -> do
103 Write.debug context $ ppShow $ ko
104 Write.fatal context $ toDoc context ko
109 (flip (Data.Foldable.foldr
110 (flip (Data.Foldable.foldr
111 (flip (Data.Foldable.foldr Balance.postings)
112 . Ledger.transaction_postings))))
113 . Ledger.journal_transactions))
116 let expanded = Balance.expanded $ Balance.balance_by_account balance
117 let by_accounts_columns = write_by_accounts context ctx expanded
118 style_color <- Write.with_color context IO.stdout
119 Ledger.Write.put Ledger.Write.Style
120 { Ledger.Write.style_align = True
121 , Ledger.Write.style_color
123 toDoc () by_accounts_columns <> do
124 case by_accounts_columns of
125 [col_balance, _col_account] ->
126 (W.bold $ W.dullblack $ do
127 W.text (TL.pack $ replicate
128 (foldr ((+) . (2 +) . Table.column_width)
129 (length by_accounts_columns - 1)
130 by_accounts_columns) '=') <> W.line) <> do
131 toDoc () $ write_by_amounts (Table.column_width col_balance) $
133 Balance.unit_sum_amount
134 (Balance.balance_by_unit balance)
135 _ -> error "Oops, should not happen: Hcompta.CLI.Command.Balance"
139 -> Balance.Expanded Amount Unit
141 write_by_accounts context ctx =
142 let posting_type = Ledger.Posting_Type_Regular in
143 let title = TL.toStrict . W.displayT . W.renderCompact False .
144 I18N.renderMessage Context.App (Context.langs context) in
146 [ Table.column (title Write.I18N_Balance) Table.Align_Right
147 , Table.column (title Write.I18N_Account) Table.Align_Left
149 Lib.TreeMap.foldr_with_Path_and_Node
150 (\account node amounts rows -> do
151 let descendants = Lib.TreeMap.nodes
152 (Lib.TreeMap.node_descendants node)
157 (not . Amount.is_zero)
158 (Balance.exclusive amounts)) > 0
161 ( maybe False (not . Amount.are_zero . Balance.inclusive)
162 . Lib.TreeMap.node_value )
171 { Table.cell_content = Ledger.Write.amount amount
172 , Table.cell_width = Ledger.Write.amount_length amount
175 { Table.cell_content = Ledger.Write.account posting_type account
176 , Table.cell_width = Ledger.Write.account_length posting_type account
181 (Balance.inclusive amounts)
189 write_by_amounts min_col_width =
192 let col = Table.column Text.empty Table.Align_Right col_content in
193 col{Table.column_width = max min_col_width $ Table.column_width col})
199 { Table.cell_content = Ledger.Write.amount amount
200 , Table.cell_width = Ledger.Write.amount_length amount