.gitignore
[comptalang.git] / cli / Hcompta / CLI / Command / Journal.hs
index af92adffcb8283be13a70e4dd4bf8792ef617926..52926e014cc6e5af11a2abfdbc78c8634675449f 100644 (file)
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Rank2Types #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
 module Hcompta.CLI.Command.Journal where
 
-import           Prelude hiding (foldr)
--- import           Control.Arrow (first)
--- import           Control.Applicative ((<$>))
--- import           Control.Monad ((>=>))
+import           Control.Arrow ((+++))
+import           Control.Monad (Monad(..), liftM, mapM)
 import           Control.Monad.IO.Class (liftIO)
-import           Control.Monad.Trans.Except (runExceptT)
-import qualified Data.Either
-import           Data.Functor.Compose (Compose(..))
-import qualified Data.List
-import qualified Data.Map.Strict as Data.Map
-import           Data.Foldable (foldr)
+import           Data.Bool
+import           Data.Either (Either(..), partitionEithers)
+import           Data.Foldable (Foldable(..))
+import           Data.Function (($), (.), on)
+import           Data.Functor (Functor(..), (<$>))
+import           Data.List ((++))
+import           Data.Maybe (Maybe(..))
+import           Data.Monoid (Monoid(..))
+import           Data.String (String)
+import           Prelude (Bounded(..), unlines)
 import           System.Console.GetOpt
                  ( ArgDescr(..)
                  , OptDescr(..)
                  , usageInfo )
 import           System.Environment as Env (getProgName)
-import           System.Exit (exitWith, ExitCode(..))
+import           System.Exit (exitSuccess)
 import qualified System.IO as IO
+import           System.IO (FilePath, IO)
 
 import qualified Hcompta.CLI.Args as Args
-import qualified Hcompta.CLI.Context as Context
-import qualified Hcompta.CLI.Format.Ledger as CLI.Ledger
+import qualified Hcompta.CLI.Context as C
+import qualified Hcompta.CLI.Env as CLI.Env
+import qualified Hcompta.CLI.Format as Format
+import           Hcompta.CLI.Format.JCC ()
+import           Hcompta.CLI.Format.Ledger ()
+import           Hcompta.CLI.Format (Format(..), Formats)
+import qualified Hcompta.CLI.Lang as Lang
 import qualified Hcompta.CLI.Write as Write
-import qualified Hcompta.Format.Ledger as Ledger
-import qualified Hcompta.Format.Ledger.Journal as Ledger.Journal
-import qualified Hcompta.Format.Ledger.Read as Ledger.Read
-import qualified Hcompta.Format.Ledger.Write as Ledger.Write
-import qualified Hcompta.Lib.Leijen as W
+import qualified Hcompta.Chart as Chart
+import qualified Hcompta.Posting as Posting
 import qualified Hcompta.Filter as Filter
+import qualified Hcompta.Filter.Amount as Filter.Amount
 import qualified Hcompta.Filter.Read as Filter.Read
+import qualified Hcompta.Format.JCC as JCC
+import qualified Hcompta.Format.JCC.Write as JCC.Write
+import qualified Hcompta.Format.Ledger       as Ledger
+import qualified Hcompta.Format.Ledger.Write as Ledger
+import qualified Hcompta.Journal as Journal
+-- import           Hcompta.Lib.Consable (Consable(..))
+import qualified Hcompta.Lib.Leijen as W
+import qualified Hcompta.Lib.Parsec as R
 
-data Ctx
- =   Ctx
+data Context
+ =   Context
  { ctx_input              :: [FilePath]
+ , ctx_input_format       :: Formats
+ , ctx_output             :: [(Write.Mode, FilePath)]
+ , ctx_output_format      :: Maybe Formats
  , ctx_align              :: Bool
- , ctx_transaction_filter :: Filter.Test_Bool (Filter.Test_Transaction Ledger.Transaction)
- } deriving (Show)
-
-nil :: Ctx
-nil =
-       Ctx
-        { ctx_input = []
-        , ctx_align = True
-        , ctx_transaction_filter = Filter.Any
+ , ctx_reduce_date        :: Bool
+ , ctx_filter_transaction :: forall t.
+                             ( Filter.Transaction t
+                             , Filter.Amount_Quantity
+                               (Posting.Posting_Amount
+                               (Filter.Transaction_Posting t))
+                               ~ Filter.Amount.Quantity
+                             ) => Journal_Filter t
+ }
+
+context :: Context
+context =
+       Context
+        { ctx_input              = []
+        , ctx_input_format       = mempty
+        , ctx_output             = []
+        , ctx_output_format      = Nothing
+        , ctx_align              = True
+        , ctx_reduce_date        = True
+        , ctx_filter_transaction = Filter.Simplified $ Right True
         }
 
-usage :: IO String
-usage = do
+usage :: C.Context -> IO String
+usage = do
        bin <- Env.getProgName
-       return $unlines $
-               [ "SYNTAX "
-               , "  "++bin++" journal [-t TRANSACTION_FILTER]"
+       return $ unlines $
+               [ C.translate c Lang.Section_Description
+               , "  "++C.translate c Lang.Help_Command_Journal
                , ""
-               , usageInfo "OPTIONS" options
+               , C.translate c Lang.Section_Syntax
+               , "  "++bin++" journal ["++C.translate c Lang.Type_Option++"] [...]"++
+                                    " ["++C.translate c Lang.Type_File_Journal++"] [...]"
+               , ""
+               , usageInfo (C.translate c Lang.Section_Options) (options c)
                ]
 
-options :: Args.Options Ctx
-options =
+options :: C.Context -> Args.Options Context
+options =
        [ Option "h" ["help"]
-        (NoArg (\_context _ctx -> do
-               usage >>= IO.hPutStr IO.stderr
-               exitWith ExitSuccess))
-        "show this help"
+        (NoArg (\_ctx -> do
+               usage >>= IO.hPutStr IO.stderr
+               exitSuccess)) $
+               C.translate c Lang.Help_Option_Help
        , Option "i" ["input"]
-        (ReqArg (\s _context ctx -> do
-               return $ ctx{ctx_input=s:ctx_input ctx}) "FILE")
-        "read data from given file, multiple uses merge the data as would a concatenation do"
+        (ReqArg (\s ctx -> do
+               return $ ctx{ctx_input=s:ctx_input ctx}) $
+               C.translate c Lang.Type_File_Journal) $
+               C.translate c Lang.Help_Option_Input
+       , Option "if" ["input-format"]
+        (OptArg (\arg ctx -> do
+               ctx_input_format <- case arg of
+                Nothing       -> return $ Format_JCC ()
+                Just "jcc"    -> return $ Format_JCC ()
+                Just "ledger" -> return $ Format_Ledger ()
+                Just _        -> Write.fatal c $
+                       W.text "--input-format option expects \"jcc\", or \"ledger\" as value"
+               return $ ctx{ctx_input_format})
+         "[jcc|ledger]")
+         "input format"
+       , Option "o" ["output"]
+        (ReqArg (\s ctx -> do
+               return $ ctx{ctx_output=(Write.Mode_Append, s):ctx_output ctx}) $
+               C.translate c Lang.Type_File) $
+               C.translate c Lang.Help_Option_Output
+       , Option "of" ["output-format"]
+        (OptArg (\arg ctx -> do
+               ctx_output_format <- case arg of
+                Nothing       -> return $ Just $ Format_JCC ()
+                Just "jcc"    -> return $ Just $ Format_JCC ()
+                Just "ledger" -> return $ Just $ Format_Ledger ()
+                Just _        -> Write.fatal c $
+                       W.text "--output-format option expects \"jcc\", or \"ledger\" as value"
+               return $ ctx{ctx_output_format})
+         "[jcc|ledger]")
+         "input format"
+       , Option "O" ["overwrite"]
+        (ReqArg (\s ctx -> do
+               return $ ctx{ctx_output=(Write.Mode_Over, s):ctx_output ctx}) $
+               C.translate c Lang.Type_File) $
+               C.translate c Lang.Help_Option_Overwrite
        , Option "" ["align"]
-        (OptArg (\arg context ctx -> do
+        (OptArg (\arg ctx -> do
                ctx_align <- case arg of
                 Nothing    -> return $ True
                 Just "yes" -> return $ True
                 Just "no"  -> return $ False
-                Just _     -> Write.fatal context $
+                Just _     -> Write.fatal c $
                        W.text "--align option expects \"yes\", or \"no\" as value"
                return $ ctx{ctx_align})
          "[yes|no]")
         "align output"
-       , Option "t" ["transaction-filter"]
-        (ReqArg (\s context ctx -> do
-               ctx_transaction_filter <-
-                       fmap (Filter.And $ ctx_transaction_filter ctx) $
-                       liftIO $ Filter.Read.read Filter.Read.test_transaction s
-                       >>= \f -> case f of
-                        Left  ko -> Write.fatal context $ ko
-                        Right ok -> return ok
-               return $ ctx{ctx_transaction_filter}) "FILTER")
-        "filter at transaction level, multiple uses are merged with a logical and"
+       {- NOTE: not used so far.
+       , Option "" ["reduce-date"]
+        (OptArg (\arg ctx -> do
+               ctx_reduce_date <- case arg of
+                Nothing    -> return $ True
+                Just "yes" -> return $ True
+                Just "no"  -> return $ False
+                Just _     -> Write.fatal c $
+                       W.text "--reduce-date option expects \"yes\", or \"no\" as value"
+               return $ ctx{ctx_reduce_date})
+         "[yes|no]")
+        "use advanced date reducer to speed up filtering"
+       -}
+       , Option "t" ["filter-transaction"]
+        (ReqArg (\s ctx -> do
+               filter <-
+                       R.runParserT_with_Error
+                        Filter.Read.filter_transaction
+                        Filter.Read.context "" s
+               case filter of
+                Left ko -> Write.fatal c ko
+                Right flt ->
+                       return $
+                               ctx{ctx_filter_transaction =
+                                       Filter.and (ctx_filter_transaction ctx) $
+                                       (Filter.simplify $
+                                               Filter.Read.get_Forall_Filter_Transaction_Decimal <$> flt)
+                                }) $
+               C.translate c Lang.Type_Filter_Transaction) $
+               C.translate c Lang.Help_Option_Filter_Transaction
        ]
 
-run :: Context.Context -> [String] -> IO ()
-run context args = do
-       (ctx, _args) <- Args.parse context usage options (nil, args)
-       read_journals <- do
-               CLI.Ledger.paths context $ ctx_input ctx
-               >>= do
-                       mapM $ \path -> do
-                               liftIO $ runExceptT $ Ledger.Read.file path
-                               >>= \x -> case x of
-                                Left  ko -> return $ Left (path, ko)
-                                Right ok -> return $ Right ok
-               >>= return . Data.Either.partitionEithers
-       case read_journals of
-        (errs@(_:_), _journals) ->
-               (flip mapM_) errs $ \(_path, err) -> do
-                       Write.fatal context $ err
-        ([], journals) -> do
-               Write.debug context $ "transaction_filter: " ++ show (ctx_transaction_filter ctx)
-               style_color <- Write.with_color context IO.stdout
-               let sty = Ledger.Write.Style
-                        { Ledger.Write.style_align = ctx_align ctx
-                        , Ledger.Write.style_color
-                        }
-               let transactions =
-                       foldr
-                        (Ledger.Journal.fold
-                                (\j ->
-                                       let ts = Ledger.journal_transactions j in
-                                       Data.Map.unionWith (++) $
-                                       case ctx_transaction_filter ctx of
-                                        Filter.Any -> ts
-                                        _ ->
-                                               Data.Map.mapMaybe
-                                                (\lt ->
-                                                       case Data.List.filter
-                                                        (Filter.test (ctx_transaction_filter ctx)) lt of
-                                                        [] -> Nothing
-                                                        l -> Just l
-                                                )
-                                                ts
-                                ))
-                        Data.Map.empty
-                        journals
-               Ledger.Write.put sty IO.stdout $ do
-               Ledger.Write.transactions (Compose transactions)
+run :: C.Context -> [String] -> IO ()
+run c args = do
+       (ctx, inputs) <- Args.parse c usage options (context, args)
+       input_paths <- CLI.Env.paths c $ ctx_input ctx ++ inputs
+       read_journals <- mapM (liftIO . journal_read ctx) input_paths
+       case partitionEithers read_journals of
+        (errs@(_:_), _journals) -> Write.fatals c errs
+        ([], (journals::[Forall_Journal])) -> do
+               with_color <- Write.with_color c IO.stdout
+               W.displayIO IO.stdout $
+                       W.renderPretty with_color 1.0 maxBound $
+                       journal_write $ mconcat $
+                       Format.journal_flatten <$>
+                               case ctx_output_format ctx of
+                                Nothing -> journals
+                                Just f -> Format.journal_empty f:journals
+
+-- * Type 'Format_Journal'
+
+type Format_Journal
+ = Format
+   (   JCC.Journal Journal_JCC)
+   (Ledger.Journal Journal_Ledger)
+
+type Journal_JCC    = Journal.Journal (   JCC.Charted    JCC.Transaction)
+type Journal_Ledger = Journal.Journal (Ledger.Charted Ledger.Transaction)
+
+-- * Class 'Journal'
+
+class Journal j where
+       journal_write :: j -> W.Doc
+
+instance Format.Journal (JCC.Journal Journal_JCC) where
+       type Journal_Format   (JCC.Journal Journal_JCC) = Format_Journal
+       journal_format = Format_JCC
+instance Journal (JCC.Journal Journal_JCC) where
+       journal_write j =
+               JCC.Write.transactions (JCC.journal_amount_styles j) $
+               fmap Chart.charted $
+               JCC.journal_content j
+
+instance Format.Journal (Ledger.Journal Journal_Ledger) where
+       type Journal_Format   (Ledger.Journal Journal_Ledger) = Format_Journal
+       journal_format = Format_Ledger
+instance Journal (Ledger.Journal Journal_Ledger) where
+       journal_write j =
+               Ledger.write_transactions (Ledger.journal_amount_styles j) $
+               fmap Chart.charted $
+               Ledger.journal_content j
+
+-- * Type 'Forall_Journal'
+
+data Forall_Journal
+ = forall j m. ( Journal (j m)
+               , Format.Journal (j m)
+               , Format.Journal_Read j
+               , Format.Journal_Monoid (j m)
+               , Format.Journal_Format (j m) ~ Format_Journal )
+ => Forall_Journal (j m)
+
+instance Format.Journal Forall_Journal where
+       type Journal_Format   Forall_Journal = Format_Journal
+       journal_format  (Forall_Journal j) = Format.journal_format j
+instance Format.Journal_Empty Forall_Journal where
+       journal_empty f =
+               case f of
+                Format_JCC    () -> Forall_Journal (mempty::JCC.Journal Journal_JCC)
+                Format_Ledger () -> Forall_Journal (mempty::Ledger.Journal Journal_Ledger)
+
+instance Format.Journal_Monoid Forall_Journal where
+       journal_flatten (Forall_Journal j) = Forall_Journal $ Format.journal_flatten j
+       journal_fold f  (Forall_Journal j) = Format.journal_fold (f . Forall_Journal) j
+instance Journal Forall_Journal where
+       journal_write (Forall_Journal j) = journal_write j
+instance Monoid Forall_Journal where
+       mempty = Forall_Journal (mempty::JCC.Journal Journal_JCC)
+       mappend x y =
+               case (mappend `on` Format.journal_format) x y of
+                Format_JCC    j -> Forall_Journal j
+                Format_Ledger j -> Forall_Journal j
+       mconcat js =
+               case js of
+                [] -> mempty
+                j:jn -> foldl' mappend j jn
+
+type Journal_Filter transaction
+ = Filter.Simplified
+   (Filter.Filter_Bool
+   (Filter.Filter_Transaction transaction))
+type Journal_Read_Cons txn
+ = txn -> Filter.Filtered (Journal_Filter txn) txn
+
+journal_read
+ :: Context -> FilePath
+ -> IO (Either (Format.Message W.Doc) Forall_Journal)
+journal_read ctx =
+       case ctx_input_format ctx of
+        Format_JCC () ->
+               let wrap (j::JCC.Journal Journal_JCC) = Forall_Journal j in
+               let cons :: Journal_Read_Cons (JCC.Charted JCC.Transaction)
+                        = Filter.Filtered (ctx_filter_transaction ctx) in
+               liftM ((+++) Format.Message wrap) .
+               Format.journal_read cons
+        Format_Ledger () ->
+               let wrap (j::Ledger.Journal Journal_Ledger) = Forall_Journal j in
+               let cons :: Journal_Read_Cons (Ledger.Charted Ledger.Transaction)
+                        = Filter.Filtered (ctx_filter_transaction ctx) in
+               liftM ((+++) Format.Message wrap) .
+               Format.journal_read cons