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.DeepSeq (NFData(..))
13 import Control.Monad (Monad(..), forM_, liftM, mapM)
14 import Control.Monad.IO.Class (liftIO)
15 import Control.Monad.Trans.Except (runExceptT)
17 import Data.Either (Either(..), partitionEithers)
18 import Data.Foldable (Foldable(..))
19 import Data.Functor ((<$>))
20 import Data.Functor.Compose (Compose(..))
21 import Data.List ((++))
22 import qualified Data.List (filter)
23 import Data.Map.Strict (Map)
24 import qualified Data.Map.Strict as Data.Map
25 import Data.Maybe (Maybe(..))
26 import Data.Monoid (Monoid(..), (<>))
27 import Data.String (String)
28 import Prelude (($), (.), FilePath, Integer, IO, Num(..), flip, id, unlines)
29 import Text.Show (Show(..))
30 import System.Console.GetOpt
34 import System.Environment as Env (getProgName)
35 import System.Exit (exitSuccess)
36 import qualified System.IO as IO
38 import qualified Hcompta.CLI.Args as Args
39 import qualified Hcompta.CLI.Context as C
40 import qualified Hcompta.CLI.Env as CLI.Env
41 import Hcompta.CLI.Format.Ledger ()
42 import qualified Hcompta.CLI.Lang as Lang
43 import qualified Hcompta.CLI.Write as Write
44 import qualified Hcompta.Filter as Filter
45 import qualified Hcompta.Filter.Read as Filter.Read
46 import qualified Hcompta.Format.Ledger as Ledger
47 import qualified Hcompta.Format.Ledger.Journal as Ledger.Journal
48 import qualified Hcompta.Format.Ledger.Read as Ledger.Read
49 import Hcompta.Lib.Consable (Consable(..))
50 import qualified Hcompta.Lib.Leijen as W
51 import qualified Hcompta.Tag as Tag
52 import Hcompta.Transaction (Transaction_Tags(..))
56 { ctx_input :: [FilePath]
57 , ctx_filter_transaction :: Filter.Simplified
59 (Filter.Filter_Transaction
60 (Ledger.Chart_With Ledger.Transaction)))
61 , ctx_filter_tag :: Filter.Simplified
70 , ctx_filter_transaction = mempty
71 , ctx_filter_tag = mempty
75 usage :: C.Context -> IO String
77 bin <- Env.getProgName
79 [ C.translate c Lang.Section_Description
80 , " "++C.translate c Lang.Help_Command_Tags
82 , C.translate c Lang.Section_Syntax
83 , " "++bin++" tags ["++C.translate c Lang.Type_Option++"] [...]"++
84 " ["++C.translate c Lang.Type_File_Journal++"] [...]"
86 , usageInfo (C.translate c Lang.Section_Options) (options c)
89 options :: C.Context -> Args.Options Ctx
93 usage c >>= IO.hPutStr IO.stderr
95 C.translate c Lang.Help_Option_Help
96 , Option "i" ["input"]
98 return $ ctx{ctx_input=s:ctx_input ctx}) $
99 C.translate c Lang.Type_File_Journal) $
100 C.translate c Lang.Help_Option_Input
101 , Option "t" ["transaction-filter"]
102 (ReqArg (\s ctx -> do
103 ctx_filter_transaction <-
104 liftM ((ctx_filter_transaction ctx <>) . Filter.simplify) $
105 liftIO $ Filter.Read.read Filter.Read.filter_transaction s
107 Left ko -> Write.fatal c $ ko
109 Write.debug c $ "filter: transaction: " ++ show ok
111 return $ ctx{ctx_filter_transaction}) $
112 C.translate c Lang.Type_Filter_Transaction) $
113 C.translate c Lang.Help_Option_Filter_Transaction
114 , Option "T" ["tag-filter"]
115 (ReqArg (\s ctx -> do
117 liftM ((ctx_filter_tag ctx <>) . Filter.simplify) $
118 liftIO $ Filter.Read.read Filter.Read.filter_tag s
120 Left ko -> Write.fatal c $ ko
122 Write.debug c $ "filter: tag: " ++ show ok
124 return $ ctx{ctx_filter_tag}) $
125 C.translate c Lang.Type_Filter_Tag) $
126 C.translate c Lang.Help_Option_Filter_Tag
128 (OptArg (\arg ctx -> do
129 ctx_tree <- case arg of
130 Nothing -> return $ True
131 Just "yes" -> return $ True
132 Just "no" -> return $ False
133 Just _ -> Write.fatal c Lang.Error_Option_Tags_Tree
134 return $ ctx{ctx_tree})
136 C.translate c Lang.Help_Option_Tags_Tree
139 run :: C.Context -> [String] -> IO ()
141 (ctx, inputs) <- Args.parse c usage options (nil, args)
143 liftM Data.Either.partitionEithers $ do
144 CLI.Env.paths c $ ctx_input ctx ++ inputs
147 liftIO $ runExceptT $ Ledger.Read.file
149 ( ctx_filter_transaction ctx
154 Left ko -> return $ Left (path, ko)
155 Right ok -> return $ Right ok
156 case read_journals of
157 (errs@(_:_), _journals) ->
158 forM_ errs $ \(_path, err) -> do
161 let files = ledger_tags ctx journals
162 Write.write c Write.style [(Write.Mode_Append, "-")] $ do
167 -> [ Ledger.Journal (Tags (Ledger.Chart_With Ledger.Transaction)) ]
168 -> Tags (Ledger.Chart_With Ledger.Transaction)
171 (flip $ Ledger.Journal.fold
172 (\Ledger.Journal{Ledger.journal_sections=ts} ->
179 -> Tags (Ledger.Chart_With Ledger.Transaction)
181 doc_tags _context ctx =
182 (case ctx_tree ctx of
184 Data.Map.foldlWithKey
186 doc <> foldMap (\s -> W.dullyellow (W.toDoc () s) <> (W.bold $ W.dullblack ":")) p <>
187 " (" <> (W.toDoc () (Data.Map.size vs)) <> ")" <>
189 Data.Map.foldlWithKey
191 doc' <> W.line <> W.dullred (W.toDoc () v) <>
192 " (" <> (W.toDoc () vn) <> ")"
199 Data.Map.foldlWithKey
202 Data.Map.foldlWithKey
205 foldMap (\s -> W.dullyellow (W.toDoc () s) <> (W.bold $ W.dullblack ":")) p <>
206 W.dullred (W.toDoc () v) <> W.line
213 -- * Requirements' interface
215 -- ** Class 'Posting'
217 class Posting p where
218 posting_account :: p -> Ledger.Account
220 instance Posting Ledger.Posting where
221 posting_account = Ledger.posting_account
223 -- ** Class 'Transaction'
226 ( Posting (Transaction_Posting t)
227 , Foldable (Transaction_Postings t)
229 => Transaction t where
230 type Transaction_Posting t
231 type Transaction_Postings t :: * -> *
232 -- transaction_postings :: t -> Transaction_Postings t (Transaction_Posting t)
233 transaction_tags :: t -> Transaction_Tags
235 instance Transaction Ledger.Transaction where
236 type Transaction_Posting Ledger.Transaction = Ledger.Posting
237 type Transaction_Postings Ledger.Transaction = Compose [] (Compose (Map Ledger.Account) [])
238 transaction_tags = Ledger.transaction_tags
240 instance Transaction (Ledger.Chart_With Ledger.Transaction) where
241 type Transaction_Posting (Ledger.Chart_With Ledger.Transaction) = Transaction_Posting Ledger.Transaction
242 type Transaction_Postings (Ledger.Chart_With Ledger.Transaction) = Transaction_Postings Ledger.Transaction
243 transaction_tags = transaction_tags . Ledger.with_chart
247 data Transaction t => Tags t
249 { tags :: Map Tag.Path (Map Tag.Value Integer)
253 instance Transaction t => Monoid (Tags t) where
257 { tags = Data.Map.unionWith
258 (Data.Map.unionWith (+))
262 instance NFData t => NFData (Tags t) where
265 instance Transaction t
266 => Consable (Filter.Simplified Filter.Filter_Tags)
270 let Transaction_Tags (Tag.Tags ttags) = transaction_tags t in
271 case Filter.simplified f of
273 Right True -> ts{ tags = merge ttags (tags ts) }
276 (Data.Map.mapMaybeWithKey
280 Data.Map.singleton p vs
281 then Just $ Data.List.filter
282 (\v -> Filter.test fT $
284 Data.Map.singleton p [v]) vs
291 :: Map Tag.Path [Tag.Value]
292 -> Map Tag.Path (Map Tag.Value Integer)
293 -> Map Tag.Path (Map Tag.Value Integer)
295 Data.Map.mergeWithKey
297 Data.Map.unionWith (+) x2 $
298 Data.Map.fromListWith (+) $ (, 1) <$> x1)
299 ((Data.Map.fromListWith (+) . ((, 1) <$>)) <$>)
302 ( Filter.Transaction t
307 (Filter.Filter_Transaction t))
308 , Filter.Simplified Filter.Filter_Tags
311 mcons (ft, fT) t !ts =