1 {-# LANGUAGE BangPatterns #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE FlexibleInstances #-}
4 {-# LANGUAGE MultiParamTypeClasses #-}
5 {-# LANGUAGE NamedFieldPuns #-}
6 {-# LANGUAGE OverloadedStrings #-}
7 {-# LANGUAGE ScopedTypeVariables #-}
8 {-# LANGUAGE TupleSections #-}
9 {-# LANGUAGE TypeFamilies #-}
10 module Hcompta.CLI.Command.Tags where
12 import Control.Monad (liftM, forM_)
13 import Control.Monad.IO.Class (liftIO)
14 import Control.Monad.Trans.Except (runExceptT)
15 import qualified Data.Either
16 import qualified Data.Foldable
17 import Data.Functor.Compose (Compose(..))
18 import Data.Monoid ((<>))
19 import qualified Data.Map.Strict as Data.Map
20 import Data.Map.Strict (Map)
21 -- import Data.Text (Text)
22 import System.Console.GetOpt
26 import System.Environment as Env (getProgName)
27 import System.Exit (exitSuccess)
28 import qualified System.IO as IO
30 import Hcompta.Account (Account)
31 import qualified Hcompta.CLI.Args as Args
32 import qualified Hcompta.CLI.Context as Context
33 import Hcompta.CLI.Context (Context)
34 import qualified Hcompta.CLI.Format.Ledger as CLI.Ledger
35 import qualified Hcompta.CLI.Write as Write
36 import qualified Hcompta.Filter as Filter
37 import qualified Hcompta.Filter.Read as Filter.Read
38 import qualified Hcompta.Format.Ledger as Ledger
39 import qualified Hcompta.Format.Ledger.Journal as Ledger.Journal
40 import qualified Hcompta.Format.Ledger.Read as Ledger.Read
41 import qualified Hcompta.Lib.Leijen as W
42 import Hcompta.Lib.Consable (Consable(..))
43 import qualified Hcompta.Tag as Tag
47 { ctx_input :: [FilePath]
48 , ctx_filter_transaction :: Filter.Simplified
50 (Filter.Filter_Transaction
52 , ctx_filter_tag :: Filter.Simplified
61 , ctx_filter_transaction = mempty
62 , ctx_filter_tag = mempty
68 bin <- Env.getProgName
69 let pad = replicate (length bin) ' '
72 , " "++bin++" tags [-i JOURNAL_FILE]"
73 , " "++pad++" [-t TRANSACTION_FILTER]"
74 , " "++pad++" [JOURNAL_FILE] [...]"
76 , usageInfo "OPTIONS" options
79 options :: Args.Options Ctx
82 (NoArg (\_context _ctx -> do
83 usage >>= IO.hPutStr IO.stderr
86 , Option "i" ["input"]
87 (ReqArg (\s _context ctx -> do
88 return $ ctx{ctx_input=s:ctx_input ctx}) "FILE")
89 "read data from given file, multiple uses merge the data as would a concatenation do"
90 , Option "t" ["transaction-filter"]
91 (ReqArg (\s context ctx -> do
92 ctx_filter_transaction <-
93 liftM ((ctx_filter_transaction ctx <>) . Filter.simplify) $
94 liftIO $ Filter.Read.read Filter.Read.filter_transaction s
96 Left ko -> Write.fatal context $ ko
98 Write.debug context $ "filter: transaction: " ++ show ok
100 return $ ctx{ctx_filter_transaction}) "FILTER")
101 "filter at transaction level, multiple uses are merged with a logical AND"
102 , Option "T" ["tag-filter"]
103 (ReqArg (\s context ctx -> do
105 liftM ((ctx_filter_tag ctx <>) . Filter.simplify) $
106 liftIO $ Filter.Read.read Filter.Read.filter_tag s
108 Left ko -> Write.fatal context $ ko
110 Write.debug context $ "filter: tag: " ++ show ok
112 return $ ctx{ctx_filter_tag}) "FILTER")
113 "filter at transaction level, multiple uses are merged with a logical AND"
115 (OptArg (\arg context ctx -> do
116 ctx_tree <- case arg of
117 Nothing -> return $ True
118 Just "yes" -> return $ True
119 Just "no" -> return $ False
120 Just _ -> Write.fatal context $
121 W.text "--tree option expects \"yes\", or \"no\" as value"
122 return $ ctx{ctx_tree})
124 "print tags as a tree"
127 run :: Context.Context -> [String] -> IO ()
128 run context args = do
129 (ctx, inputs) <- Args.parse context usage options (nil, args)
131 liftM Data.Either.partitionEithers $ do
132 CLI.Ledger.paths context $ ctx_input ctx ++ inputs
135 liftIO $ runExceptT $ Ledger.Read.file
137 ( ctx_filter_transaction ctx
142 Left ko -> return $ Left (path, ko)
143 Right ok -> return $ Right ok
144 case read_journals of
145 (errs@(_:_), _journals) ->
146 forM_ errs $ \(_path, err) -> do
147 Write.fatal context $ err
149 let files = ledger_tags ctx journals
150 style_color <- Write.with_color context IO.stdout
151 W.displayIO IO.stdout $ do
152 W.renderPretty style_color 1.0 maxBound $ do
153 doc_tags context ctx files
157 -> [ Ledger.Journal (Tags Ledger.Transaction) ]
158 -> Tags Ledger.Transaction
161 (flip $ Ledger.Journal.fold
162 (\Ledger.Journal{Ledger.journal_transactions=ts} ->
169 -> Tags Ledger.Transaction
171 doc_tags _context ctx =
172 (case ctx_tree ctx of
174 Data.Map.foldlWithKey
176 doc <> foldMap (\s -> W.dullyellow (W.toDoc () s) <> (W.bold $ W.dullblack ":")) p <>
177 " (" <> (W.toDoc () (Data.Map.size vs)) <> ")" <>
179 Data.Map.foldlWithKey
181 doc' <> W.line <> W.dullred (W.toDoc () v) <>
182 " (" <> (W.toDoc () vn) <> ")"
189 Data.Map.foldlWithKey
192 Data.Map.foldlWithKey
195 foldMap (\s -> W.dullyellow (W.toDoc () s) <> (W.bold $ W.dullblack ":")) p <>
196 W.dullred (W.toDoc () v) <> W.line
203 -- * Requirements' interface
205 -- ** Class 'Posting'
207 class Posting p where
208 posting_account :: p -> Account
210 instance Posting Ledger.Posting where
211 posting_account = Ledger.posting_account
213 -- ** Class 'Transaction'
216 ( Posting (Transaction_Posting t)
217 , Foldable (Transaction_Postings t)
219 => Transaction t where
220 type Transaction_Posting t
221 type Transaction_Postings t :: * -> *
222 -- transaction_postings :: t -> Transaction_Postings t (Transaction_Posting t)
223 transaction_tags :: t -> Map Tag.Path [Tag.Value]
225 instance Transaction Ledger.Transaction where
226 type Transaction_Posting Ledger.Transaction = Ledger.Posting
227 type Transaction_Postings Ledger.Transaction = Compose [] (Compose (Map Account) [])
228 transaction_tags = Ledger.transaction_tags
232 data Transaction t => Tags t
234 { tags :: Map Tag.Path (Map Tag.Value Integer)
238 instance Transaction t => Monoid (Tags t) where
242 { tags = Data.Map.unionWith
243 (Data.Map.unionWith (+))
247 instance Transaction t => Consable (Filter.Simplified Filter.Filter_Tag) Tags t where
249 case Filter.simplified f of
251 Right True -> ts{ tags = merge (transaction_tags t) (tags ts) }
254 (Data.Map.mapMaybeWithKey
256 if Filter.test fT (p, vs)
257 then Just $ filter (\v -> Filter.test fT (p, [v])) vs
259 (transaction_tags t))
264 :: Map Tag.Path [Tag.Value]
265 -> Map Tag.Path (Map Tag.Value Integer)
266 -> Map Tag.Path (Map Tag.Value Integer)
268 Data.Map.mergeWithKey
270 Data.Map.unionWith (+) x2 $
271 Data.Map.fromListWith (+) $ (, 1) <$> x1)
272 ((Data.Map.fromListWith (+) . ((, 1) <$>)) <$>)
275 ( Filter.Transaction t
280 (Filter.Filter_Transaction t))
281 , Filter.Simplified Filter.Filter_Tag
284 mcons (ft, fT) t !ts =