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 (Monad(..), forM_, liftM, mapM)
13 import Control.Monad.IO.Class (liftIO)
14 import Control.Monad.Trans.Except (runExceptT)
16 import Data.Either (Either(..), partitionEithers)
17 import Data.Foldable (Foldable(..))
18 import Data.Functor ((<$>))
19 import Data.Functor.Compose (Compose(..))
20 import Data.List ((++), replicate)
21 import qualified Data.List (filter)
22 import Data.Map.Strict (Map)
23 import qualified Data.Map.Strict as Data.Map
24 import Data.Maybe (Maybe(..))
25 import Data.Monoid (Monoid(..), (<>))
26 import Data.String (String)
27 import Data.Tuple (snd)
28 import Prelude (($), (.), Bounded(..), 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 Hcompta.Account (Account)
39 import Hcompta.Chart (Chart)
40 import qualified Hcompta.CLI.Args as Args
41 import Hcompta.CLI.Context (Context)
42 import qualified Hcompta.CLI.Context as Context
43 import qualified Hcompta.CLI.Format.Ledger as CLI.Ledger
44 import qualified Hcompta.CLI.Write as Write
45 import qualified Hcompta.Filter as Filter
46 import qualified Hcompta.Filter.Read as Filter.Read
47 import qualified Hcompta.Format.Ledger as Ledger
48 import qualified Hcompta.Format.Ledger.Journal as Ledger.Journal
49 import qualified Hcompta.Format.Ledger.Read as Ledger.Read
50 import Hcompta.Lib.Consable (Consable(..))
51 import qualified Hcompta.Lib.Leijen as W
52 import qualified Hcompta.Tag as Tag
56 { ctx_input :: [FilePath]
57 , ctx_filter_transaction :: Filter.Simplified
59 (Filter.Filter_Transaction
60 (Chart, Ledger.Transaction)))
61 , ctx_filter_tag :: Filter.Simplified
70 , ctx_filter_transaction = mempty
71 , ctx_filter_tag = mempty
77 bin <- Env.getProgName
78 let pad = replicate (length bin) ' '
81 , " "++bin++" tags [-i FILE_JOURNAL]"
82 , " "++pad++" [-t FILTER_TRANSACTION_FILTER]"
83 , " "++pad++" [-T FILTER_TAG]"
84 , " "++pad++" [FILE_JOURNAL] [...]"
86 , usageInfo "OPTIONS" options
89 options :: Args.Options Ctx
92 (NoArg (\_context _ctx -> do
93 usage >>= IO.hPutStr IO.stderr
96 , Option "i" ["input"]
97 (ReqArg (\s _context ctx -> do
98 return $ ctx{ctx_input=s:ctx_input ctx}) "FILE")
99 "read data from given file, multiple uses merge the data as would a concatenation do"
100 , Option "t" ["transaction-filter"]
101 (ReqArg (\s context ctx -> do
102 ctx_filter_transaction <-
103 liftM ((ctx_filter_transaction ctx <>) . Filter.simplify) $
104 liftIO $ Filter.Read.read Filter.Read.filter_transaction s
106 Left ko -> Write.fatal context $ ko
108 Write.debug context $ "filter: transaction: " ++ show ok
110 return $ ctx{ctx_filter_transaction}) "FILTER")
111 "filter at transaction level, multiple uses are merged with a logical AND"
112 , Option "T" ["tag-filter"]
113 (ReqArg (\s context ctx -> do
115 liftM ((ctx_filter_tag ctx <>) . Filter.simplify) $
116 liftIO $ Filter.Read.read Filter.Read.filter_tag s
118 Left ko -> Write.fatal context $ ko
120 Write.debug context $ "filter: tag: " ++ show ok
122 return $ ctx{ctx_filter_tag}) "FILTER")
123 "filter at transaction level, multiple uses are merged with a logical AND"
125 (OptArg (\arg context ctx -> do
126 ctx_tree <- case arg of
127 Nothing -> return $ True
128 Just "yes" -> return $ True
129 Just "no" -> return $ False
130 Just _ -> Write.fatal context $
131 W.text "--tree option expects \"yes\", or \"no\" as value"
132 return $ ctx{ctx_tree})
134 "print tags as a tree"
137 run :: Context.Context -> [String] -> IO ()
138 run context args = do
139 (ctx, inputs) <- Args.parse context usage options (nil, args)
141 liftM Data.Either.partitionEithers $ do
142 CLI.Ledger.paths context $ ctx_input ctx ++ inputs
145 liftIO $ runExceptT $ Ledger.Read.file
147 ( ctx_filter_transaction ctx
152 Left ko -> return $ Left (path, ko)
153 Right ok -> return $ Right ok
154 case read_journals of
155 (errs@(_:_), _journals) ->
156 forM_ errs $ \(_path, err) -> do
157 Write.fatal context $ err
159 let files = ledger_tags ctx journals
160 style_color <- Write.with_color context IO.stdout
161 W.displayIO IO.stdout $ do
162 W.renderPretty style_color 1.0 maxBound $ do
163 doc_tags context ctx files
167 -> [ Ledger.Journal (Tags (Chart, Ledger.Transaction)) ]
168 -> Tags (Chart, Ledger.Transaction)
171 (flip $ Ledger.Journal.fold
172 (\Ledger.Journal{Ledger.journal_sections=ts} ->
179 -> Tags (Chart, 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 -> 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 -> Tag.Tags
235 instance Transaction Ledger.Transaction where
236 type Transaction_Posting Ledger.Transaction = Ledger.Posting
237 type Transaction_Postings Ledger.Transaction = Compose [] (Compose (Map Account) [])
238 transaction_tags = Ledger.transaction_tags
240 instance Transaction (Chart, Ledger.Transaction) where
241 type Transaction_Posting (Chart, Ledger.Transaction) = Transaction_Posting Ledger.Transaction
242 type Transaction_Postings (Chart, Ledger.Transaction) = Transaction_Postings Ledger.Transaction
243 transaction_tags = transaction_tags . snd
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 Transaction t
263 => Consable (Filter.Simplified Filter.Filter_Tags)
267 case Filter.simplified f of
269 Right True -> ts{ tags = merge (Tag.unTags $ transaction_tags t) (tags ts) }
272 (Data.Map.mapMaybeWithKey
274 if Filter.test fT $ Tag.Tags $ Data.Map.singleton p vs
275 then Just $ Data.List.filter (\v -> Filter.test fT $ Tag.Tags $ Data.Map.singleton p [v]) vs
277 (Tag.unTags $ transaction_tags t))
282 :: Map Tag.Path [Tag.Value]
283 -> Map Tag.Path (Map Tag.Value Integer)
284 -> Map Tag.Path (Map Tag.Value Integer)
286 Data.Map.mergeWithKey
288 Data.Map.unionWith (+) x2 $
289 Data.Map.fromListWith (+) $ (, 1) <$> x1)
290 ((Data.Map.fromListWith (+) . ((, 1) <$>)) <$>)
293 ( Filter.Transaction t
298 (Filter.Filter_Transaction t))
299 , Filter.Simplified Filter.Filter_Tags
302 mcons (ft, fT) t !ts =