1 {-# LANGUAGE NamedFieldPuns #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 {-# LANGUAGE TupleSections #-}
4 module Hcompta.CLI.Command.Balance where
6 import Control.Monad.IO.Class (liftIO)
7 import Control.Monad.Trans.Except (runExceptT)
8 import qualified Data.Either
9 import qualified Data.Foldable
10 import qualified Data.List
11 import qualified Data.Map
12 import qualified Data.Text.Lazy as TL
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)
23 import qualified Hcompta.CLI.Args as Args
24 import qualified Hcompta.CLI.Context as Context
25 import qualified Hcompta.CLI.Format.Ledger as CLI.Ledger
26 import qualified Hcompta.CLI.Write as Write
27 import qualified Hcompta.Calc.Balance as Balance
28 import qualified Hcompta.Format.Ledger.Journal
29 import qualified Hcompta.Format.Ledger.Read as Ledger.Read
30 import qualified Hcompta.Format.Ledger.Write as Ledger.Write
31 import qualified Hcompta.Lib.TreeMap as Lib.TreeMap
32 import qualified Hcompta.Lib.Leijen as W
33 import Hcompta.Lib.Leijen ((<>))
34 import qualified Hcompta.Model.Amount as Amount
35 import qualified Hcompta.Model.Transaction.Posting as Posting
36 -- import qualified Hcompta.Format.Ledger.Write
40 { ctx_input :: [FilePath]
51 bin <- Env.getProgName
54 , " "++bin++" balance [option..]"
56 , usageInfo "OPTIONS" options
59 options :: Args.Options Ctx
63 usage >>= IO.hPutStr IO.stderr
64 exitWith ExitSuccess))
66 , Option "i" ["input"]
68 return $ ctx{ctx_input=s:ctx_input ctx}) "FILE")
69 "read data from given file, can be use multiple times"
72 run :: Context.Context -> [String] -> IO ()
74 (ctx, _) <- Args.parse context usage options (nil, args)
75 CLI.Ledger.paths context $ ctx_input ctx
76 >>= do mapM $ \path -> do
80 Left ko -> return $ Left (path, ko)
81 Right ok -> return $ Right ok
82 >>= return . Data.Either.partitionEithers
85 (flip mapM_) kos $ \(_path, ko) ->
86 Write.fatal context $ ko
88 CLI.Ledger.equilibre context journals
91 (\b j -> Balance.journal_with_virtual
92 (Hcompta.Format.Ledger.Journal.to_Model j) b)
95 Write.debug context $ ppShow $ balance
96 Write.debug context $ ppShow $
97 Lib.TreeMap.flatten (const ()) (Balance.by_account balance)
98 let expanded = Balance.expand $ Balance.by_account balance
99 Write.debug context $ ppShow $ expanded
100 with_color <- Write.with_color context IO.stdout
101 Ledger.Write.put with_color IO.stdout $ do
102 let (max_amount_length, accounts) = write_accounts expanded
104 (W.bold $ W.dullblack $
105 W.text (TL.pack $ replicate max_amount_length '-') <>
106 (if max_amount_length <= 0 then W.empty else W.line)) <> do
107 write_amounts max_amount_length $
108 Data.Map.map Balance.amount $
109 (Balance.by_unit balance)
111 write_accounts :: Balance.Expanded -> (Int, W.Doc)
112 write_accounts accounts = do
113 let max_amount_length =
116 (\(len, plus) amounts ->
118 (max . Ledger.Write.amount_length)
119 len (Balance.inclusive amounts)
120 , if Data.Map.size (Balance.inclusive amounts) > 1
125 (max_amount_length,) $ do
126 Lib.TreeMap.foldl_with_Path_and_Node
127 (\doc account node amounts ->
128 if Data.Map.null (Balance.exclusive amounts) &&
129 Data.Map.size (Lib.TreeMap.nodes (Lib.TreeMap.node_descendants node)) == 1
132 doc <> Data.Map.foldl
137 W.fill (max_amount_length - Ledger.Write.amount_length amount) W.empty <> do
138 Ledger.Write.amount amount <> do
139 W.space <> W.space <> do
140 Ledger.Write.account Posting.Type_Regular account
142 (W.bold $ W.dullblack $ W.text "+" <> W.space) <> do
143 W.fill (max_amount_length - Ledger.Write.amount_length amount - 2) W.empty <> do
144 Ledger.Write.amount amount) <> do
146 ) W.empty (Balance.inclusive amounts)
150 write_amounts :: Int -> Amount.By_Unit -> W.Doc
151 write_amounts max_amount_length_ amounts = do
152 let max_amount_length =
154 (max . Ledger.Write.amount_length)
155 max_amount_length_ amounts
156 (if Data.Map.size amounts > 1
157 then W.space <> W.space
160 (W.line <> (W.bold $ W.dullblack $ W.text "+") <> W.space)
164 - Ledger.Write.amount_length amount
165 - (if Data.Map.size amounts > 1 then 2 else 0) in
166 W.fill len W.empty <> do
167 Ledger.Write.amount amount)
169 (if Data.Map.null amounts then W.empty else W.line)