{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} module Hcompta.CLI.Format where import Control.Applicative (Const(..)) import Control.Monad.Trans.Except (runExceptT) import Data.Bool (Bool(..), not) import qualified Data.Char as Char import Data.Decimal (Decimal) import Data.Either (Either(..)) import Data.Function (($), (.), id) import Data.Functor (Functor(..), (<$>)) import qualified Data.List as List import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.Map.Strict as Map import Data.Map.Strict (Map) import Data.Maybe (Maybe(..)) import Data.Monoid (Monoid(..)) import Data.Ord (Ord(..)) import Data.Sequence (Seq) import qualified Data.Text as Text import Data.Text (Text) import System.IO (FilePath, IO) import Text.Show (Show) import qualified Hcompta.CLI.Lang as Lang import qualified Hcompta.Lib.Leijen as W import qualified Hcompta.Lib.TreeMap as TreeMap import Hcompta.Lib.TreeMap (TreeMap) import qualified Hcompta.Journal as Journal import qualified Hcompta.Tag as Tag import qualified Hcompta.Balance as Balance import qualified Hcompta.GL as GL import qualified Hcompta.Stats as Stats import qualified Hcompta.Chart as Chart import qualified Hcompta.Account as Account import qualified Hcompta.Posting as Posting import qualified Hcompta.Transaction as Transaction -- import qualified Hcompta.Filter.Read as Filter.Read import Hcompta.Date (Date) import qualified Hcompta.Polarize as Polarize import qualified Hcompta.Format.JCC as JCC import qualified Hcompta.Format.JCC.Journal as JCC.Journal import qualified Hcompta.Format.JCC.Read as JCC.Read import qualified Hcompta.Format.JCC.Amount.Style as JCC.Amount.Style import qualified Hcompta.Format.Ledger as Ledger import qualified Hcompta.Format.Ledger.Read as Ledger import qualified Hcompta.Lib.Parsec as R import Hcompta.Lib.Consable (Consable) import qualified Hcompta.CLI.Lib.Leijen.Table as Leijen.Table -- * Type 'Format' data Format jcc ledger = Format_JCC jcc | Format_Ledger ledger deriving (Show) type Formats = Format () () instance ( Convert jcc ledger , Convert ledger jcc , Monoid jcc , Monoid ledger ) => Monoid (Format jcc ledger) where mempty = Format_JCC mempty mappend x y = case x of Format_JCC xj -> Format_JCC $ case y of Format_JCC yj -> mappend xj yj Format_Ledger yj -> mappend xj (convert yj) Format_Ledger xj -> Format_Ledger $ case y of Format_JCC yj -> mappend xj (convert yj) Format_Ledger yj -> mappend xj yj format :: Formats format = Format_JCC () -- * Type family 'Journal_Account' type family Journal_Account (j:: * -> *) type instance Journal_Account JCC.Journal = JCC.Account type instance Journal_Account Ledger.Journal = Ledger.Account -- * Type family 'Journal_Account_Section' type family Journal_Account_Section (j:: * -> *) type instance Journal_Account_Section JCC.Journal = JCC.Account_Section type instance Journal_Account_Section Ledger.Journal = Ledger.Account_Section -- * Type family 'Journal_Charted' type family Journal_Charted (j:: * -> *) :: * -> * type instance Journal_Charted JCC.Journal = JCC.Charted type instance Journal_Charted Ledger.Journal = Ledger.Charted -- * Type family 'Journal_Quantity' type family Journal_Quantity (j:: * -> *) type instance Journal_Quantity JCC.Journal = JCC.Quantity type instance Journal_Quantity Ledger.Journal = Ledger.Quantity -- * Type family 'Journal_Unit' type family Journal_Unit (j:: * -> *) type instance Journal_Unit JCC.Journal = JCC.Unit type instance Journal_Unit Ledger.Journal = Ledger.Unit -- * Type family 'Journal_Posting' type family Journal_Posting (j:: * -> *) type instance Journal_Posting JCC.Journal = JCC.Posting type instance Journal_Posting Ledger.Journal = Ledger.Posting -- * Type family 'Journal_Transaction' type family Journal_Transaction (j:: * -> *) type instance Journal_Transaction JCC.Journal = JCC.Transaction type instance Journal_Transaction Ledger.Journal = Ledger.Transaction -- * Class 'Journal' class Journal j where type Journal_Format j journal_format :: j -> Journal_Format j -- * Class 'Journal_Empty' class Journal_Empty j where journal_empty :: Formats -> j -- * Class 'Journal_Files' class Journal_Files j where journal_files :: forall m. j m -> [FilePath] instance Journal_Files JCC.Journal where journal_files j = [JCC.journal_file j] -- FIXME: JCC.journal_files instance Journal_Files Ledger.Journal where journal_files = Ledger.journal_files -- * Class 'Journal_Read' class Journal_Read (j:: * -> *) where type Journal_Read_Error j type Journal_Read_Transaction j journal_read :: forall c m. (Monoid m, Consable c m) => (Journal_Read_Transaction j -> c) -> FilePath -> IO (Either (Journal_Read_Error j) (j m)) instance Journal_Read JCC.Journal where type Journal_Read_Error JCC.Journal = [R.Error JCC.Read.Error] type Journal_Read_Transaction JCC.Journal = JCC.Charted JCC.Transaction journal_read cons = runExceptT . JCC.Read.file (JCC.Read.context cons JCC.journal) instance Journal_Read Ledger.Journal where type Journal_Read_Error Ledger.Journal = [R.Error Ledger.Read_Error] type Journal_Read_Transaction Ledger.Journal = Ledger.Charted Ledger.Transaction journal_read cons = runExceptT . Ledger.read (Ledger.read_context cons Ledger.journal) -- * Class 'Journal_Chart' class Journal_Chart (j:: * -> *) where journal_chart :: forall m. j m -> Chart.Chart (NonEmpty (Journal_Account_Section j)) instance Journal_Chart JCC.Journal where journal_chart = JCC.journal_chart instance Journal_Chart Ledger.Journal where journal_chart = Ledger.journal_chart -- * Class 'Journal_Monoid' class Journal_Monoid j where journal_flatten :: j -> j journal_fold :: (j -> a -> a) -> j -> a -> a instance Monoid m => Journal_Monoid (JCC.Journal m) where journal_flatten = JCC.Journal.flatten journal_fold = JCC.Journal.fold instance Monoid m => Journal_Monoid (Ledger.Journal m) where journal_flatten = Ledger.journal_flatten journal_fold = Ledger.journal_fold -- * Class 'Journal_Filter' class Functor j => Journal_Filter context j m where journal_filter :: context -> j m -> j m -- * Class 'Journal_Functor' class Journal_Functor x y where journal_functor_map :: x -> y journal_fmap :: forall j. Functor j => j x -> j y journal_fmap = fmap journal_functor_map -- * Class 'Journal_Table' -- | A class to render a journal -- into 'Leijen.Table.Cell's. class Journal_Leijen_Table_Cells j m where journal_leijen_table_cells :: j m -> [[Leijen.Table.Cell]] -> [[Leijen.Table.Cell]] -- * Class 'Journal_Wrap' -- | A class dedicated to transform a journal -- to another one using existential quantification -- to gather multiple journals under a single type, -- by writing instances between fully monomorphic types, -- which ease a lot meeting the requirements -- of the constraints in the wrap type. class Journal_Wrap j wrap where journal_wrap :: j -> wrap class Journal_Content j where journal_content :: forall m. j m -> m instance Journal_Content JCC.Journal where journal_content = JCC.journal_content instance Journal_Content Ledger.Journal where journal_content = Ledger.journal_content -- * Type 'Message' -- data Journal jnl m = forall j. jnl j => Journal (j m) data Message w = forall msg. Lang.Translate msg w => Message msg instance Lang.Translate (Message W.Doc) W.Doc where translate lang (Message x) = Lang.translate lang x -- * Class 'Convert' -- | Generic class dedicated to transform any type -- into another one encoding more or less -- the same data. class Convert from to where convert :: from -> to instance Convert () () where convert = id -- Journal instance ( Convert ledger jcc , Monoid jcc , Monoid ledger ) => Convert (Ledger.Journal ledger) (JCC.Journal jcc) where convert Ledger.Journal { Ledger.journal_amount_styles , Ledger.journal_chart = chart , Ledger.journal_files=jf , Ledger.journal_includes , Ledger.journal_last_read_time , Ledger.journal_content = content } = JCC.Journal { JCC.journal_amount_styles = convert journal_amount_styles , JCC.journal_chart = chart , JCC.journal_file = List.head jf -- FIXME: JCC.journal_files , JCC.journal_includes = fmap convert $ journal_includes , JCC.journal_last_read_time , JCC.journal_content = convert content } instance ( Convert jcc ledger , Monoid jcc , Monoid ledger ) => Convert (JCC.Journal jcc) (Ledger.Journal ledger) where convert JCC.Journal { JCC.journal_amount_styles , JCC.journal_chart = chart , JCC.journal_file , JCC.journal_includes , JCC.journal_last_read_time , JCC.journal_content = content } = Ledger.Journal { Ledger.journal_amount_styles = convert journal_amount_styles , Ledger.journal_chart = chart , Ledger.journal_files = [journal_file] -- FIXME: JCC.journal_files , Ledger.journal_includes = fmap convert $ journal_includes , Ledger.journal_last_read_time , Ledger.journal_content = convert content } instance Convert ledger jcc => Convert (Journal.Journal ledger) (Journal.Journal jcc) where convert (Journal.Journal j) = Journal.Journal $ fmap convert $ Map.mapKeysMonotonic convert j -- Unit instance Convert Ledger.Unit JCC.Unit where convert (Ledger.Unit u) = JCC.Unit $ Text.map (\c -> case Char.generalCategory c of Char.CurrencySymbol -> c Char.LowercaseLetter -> c Char.ModifierLetter -> c Char.OtherLetter -> c Char.TitlecaseLetter -> c Char.UppercaseLetter -> c _ -> '_') u instance Convert JCC.Unit Ledger.Unit where convert (JCC.Unit u) = Ledger.Unit u -- Account instance Convert Account.Account_Anchor Account.Account_Anchor where convert = id instance Convert Account.Account_Tags Account.Account_Tags where convert = id -- Amount Style instance Convert Ledger.Amount_Styles JCC.Styles where convert (Ledger.Amount_Styles sty) = JCC.Amount.Style.Styles $ convert sty instance Convert JCC.Styles Ledger.Amount_Styles where convert (JCC.Amount.Style.Styles sty) = Ledger.Amount_Styles $ convert sty instance Convert Ledger.Amount_Style JCC.Style where convert Ledger.Amount_Style { Ledger.amount_style_fractioning=f , Ledger.amount_style_grouping_integral=gi , Ledger.amount_style_grouping_fractional=gf , Ledger.amount_style_unit_side=unit_side , Ledger.amount_style_unit_spaced=unit_spaced } = JCC.Amount.Style.Style { JCC.Amount.Style.fractioning=f , JCC.Amount.Style.grouping_integral = fmap (\(Ledger.Amount_Style_Grouping c l) -> JCC.Amount.Style.Grouping c l) gi , JCC.Amount.Style.grouping_fractional = fmap (\(Ledger.Amount_Style_Grouping c l) -> JCC.Amount.Style.Grouping c l) gf , JCC.Amount.Style.unit_side = fmap (\s -> case s of Ledger.Amount_Style_Side_Left -> JCC.Amount.Style.Side_Left Ledger.Amount_Style_Side_Right -> JCC.Amount.Style.Side_Right ) unit_side , JCC.Amount.Style.unit_spaced } instance Convert JCC.Style Ledger.Amount_Style where convert JCC.Amount.Style.Style { JCC.Amount.Style.fractioning=f , JCC.Amount.Style.grouping_integral=gi , JCC.Amount.Style.grouping_fractional=gf , JCC.Amount.Style.unit_side=unit_side , JCC.Amount.Style.unit_spaced=unit_spaced } = Ledger.Amount_Style { Ledger.amount_style_fractioning=f , Ledger.amount_style_grouping_integral = fmap (\(JCC.Amount.Style.Grouping c l) -> Ledger.Amount_Style_Grouping c l) gi , Ledger.amount_style_grouping_fractional = fmap (\(JCC.Amount.Style.Grouping c l) -> Ledger.Amount_Style_Grouping c l) gf , Ledger.amount_style_unit_side = fmap (\s -> case s of JCC.Amount.Style.Side_Left -> Ledger.Amount_Style_Side_Left JCC.Amount.Style.Side_Right -> Ledger.Amount_Style_Side_Right ) unit_side , Ledger.amount_style_unit_spaced=unit_spaced } -- Transaction instance Convert Ledger.Transaction JCC.Transaction where convert Ledger.Transaction { Ledger.transaction_code , Ledger.transaction_comments_after , Ledger.transaction_comments_before , Ledger.transaction_dates , Ledger.transaction_postings , Ledger.transaction_sourcepos , Ledger.transaction_status , Ledger.transaction_tags , Ledger.transaction_wording } = JCC.Transaction { JCC.transaction_anchors = mempty , JCC.transaction_comments = List.filter (not . Text.all Char.isSpace) $ Ledger.comments_without_tags $ mappend transaction_comments_before transaction_comments_after , JCC.transaction_dates , JCC.transaction_postings = fmap (fmap convert) transaction_postings , JCC.transaction_sourcepos , JCC.transaction_tags = (case transaction_code of t | Text.null t -> id t -> Transaction.tag_cons (Transaction.tag ("Code":|[]) t) ) $ case transaction_status of True -> Transaction.tag_cons (Transaction.tag ("Status":|[]) "") transaction_tags False -> transaction_tags , JCC.transaction_wording } instance Convert JCC.Transaction Ledger.Transaction where convert JCC.Transaction { JCC.transaction_anchors=_transaction_anchors , JCC.transaction_comments , JCC.transaction_dates , JCC.transaction_postings , JCC.transaction_sourcepos , JCC.transaction_tags = Transaction.Transaction_Tags (Tag.Tags tags) , JCC.transaction_wording } = Ledger.Transaction { Ledger.transaction_code = mconcat $ Map.findWithDefault [""] ("Code":|[]) tags , Ledger.transaction_comments_after = mempty , Ledger.transaction_comments_before = transaction_comments , Ledger.transaction_dates , Ledger.transaction_postings = fmap (fmap convert) transaction_postings , Ledger.transaction_sourcepos , Ledger.transaction_status = case Map.lookup ("Status":|[]) tags of Nothing -> False Just _ -> True , Ledger.transaction_tags = Transaction.Transaction_Tags $ Tag.Tags $ Map.delete ("Code":|[]) $ Map.delete ("Status":|[]) $ tags , Ledger.transaction_wording } -- Posting instance Convert Ledger.Posting JCC.Posting where convert Ledger.Posting { Ledger.posting_account , Ledger.posting_amounts , Ledger.posting_comments , Ledger.posting_dates , Ledger.posting_status , Ledger.posting_sourcepos , Ledger.posting_tags } = JCC.Posting { JCC.posting_account , JCC.posting_account_anchor = Nothing , JCC.posting_amounts = fmap convert $ Map.mapKeysMonotonic convert $ posting_amounts , JCC.posting_anchors = mempty , JCC.posting_comments = List.filter (not . Text.all Char.isSpace) $ Ledger.comments_without_tags posting_comments , JCC.posting_dates , JCC.posting_sourcepos , JCC.posting_tags = case posting_status of True -> Posting.tag_cons (Posting.tag ("Status":|[]) "") posting_tags False -> posting_tags } instance Convert JCC.Posting Ledger.Posting where convert JCC.Posting { JCC.posting_account , JCC.posting_account_anchor=_ , JCC.posting_amounts , JCC.posting_anchors = _posting_anchors , JCC.posting_comments , JCC.posting_dates , JCC.posting_sourcepos , JCC.posting_tags = Posting.Posting_Tags (Tag.Tags tags) } = Ledger.Posting { Ledger.posting_account , Ledger.posting_amounts = fmap convert $ Map.mapKeysMonotonic convert $ posting_amounts , Ledger.posting_comments , Ledger.posting_dates , Ledger.posting_status = case Map.lookup ("Status":|[]) tags of Nothing -> False Just _ -> True , Ledger.posting_sourcepos , Ledger.posting_tags = Posting.Posting_Tags $ Tag.Tags $ Map.delete ("Status":|[]) $ tags } -- Chart instance Convert (Chart.Chart x) (Chart.Chart x) where convert = id {- instance Convert (Chart.Chart JCC.Account) (Chart.Chart Ledger.Account) where convert Chart.Chart { Chart.chart_accounts , Chart.chart_anchors } = Chart.Chart { Chart.chart_accounts = convert chart_accounts , Chart.chart_anchors = convert chart_anchors } -} instance ( Convert (Chart.Chart a0) (Chart.Chart a1) , Convert x y ) => Convert (Chart.Charted a0 x) (Chart.Charted a1 y) where convert (Chart.Charted a x) = Chart.Charted (convert a) (convert x) -- Balance instance ( Convert unit unit_ , Convert quantity quantity_ ) => Convert (Balance.Account_Sum unit quantity) (Balance.Account_Sum unit_ quantity_) where convert (Balance.Account_Sum m) = Balance.Account_Sum $ fmap convert $ Map.mapKeysMonotonic convert m -- * GL -- ** Class 'GL' class ( Convert (Account.Account_Section (GL.Posting_Account (GL.Transaction_Posting x))) (Account.Account_Section (GL.Posting_Account (GL.Transaction_Posting y))) ) => GL x y instance GL JCC.Transaction Ledger.Transaction instance GL Ledger.Transaction JCC.Transaction instance GL ( JCC.Charted JCC.Transaction) (Ledger.Charted Ledger.Transaction) instance GL (Ledger.Charted Ledger.Transaction) (JCC.Charted JCC.Transaction) instance ( GL x y , GL_Line x y , GL.Transaction x , GL.Transaction y , Convert x y ) => Convert (GL.GL x) (GL.GL y) where convert (GL.GL m) = GL.GL $ TreeMap.map_monotonic convert (fmap convert) m -- NOTE: Date does not need to be converted, -- thus avoid a useless Map.mapKeysMonotonic -- from the Convert instance on Map. -- *** Class 'GL_Line' class ( Convert (GL.Transaction_Line x) (GL.Transaction_Line y) , Convert (GL.Transaction_Posting x) (GL.Transaction_Posting y) , Convert (GL.Posting_Quantity (GL.Transaction_Posting x)) (GL.Posting_Quantity (GL.Transaction_Posting y)) ) => GL_Line x y instance GL_Line JCC.Transaction Ledger.Transaction instance GL_Line Ledger.Transaction JCC.Transaction instance GL_Line ( JCC.Charted JCC.Transaction) (Ledger.Charted Ledger.Transaction) instance GL_Line (Ledger.Charted Ledger.Transaction) (JCC.Charted JCC.Transaction) instance ( GL_Line x y , GL.Transaction x , GL.Transaction y , Convert x y ) => Convert (GL.GL_Line x) (GL.GL_Line y) where convert GL.GL_Line { GL.gl_line_transaction , GL.gl_line_posting , GL.gl_line_sum } = GL.GL_Line { GL.gl_line_transaction = convert gl_line_transaction , GL.gl_line_posting = convert gl_line_posting , GL.gl_line_sum = convert gl_line_sum } -- Class 'GL_Expanded' instance ( GL x y , GL_Line x y , GL.Transaction x , GL.Transaction y , Convert x y ) => Convert (GL.Expanded x) (GL.Expanded y) where convert (GL.Expanded m) = GL.Expanded $ convert m -- Class 'GL_Line_Expanded' instance ( GL_Line x y , GL.Transaction x , GL.Transaction y , Convert x y ) => Convert (GL.GL_Line_Expanded x) (GL.GL_Line_Expanded y) where convert GL.GL_Line_Expanded { GL.exclusive , GL.inclusive } = GL.GL_Line_Expanded { GL.exclusive = convert <$> exclusive , GL.inclusive = convert <$> inclusive } -- Const instance Convert x y => Convert (Const x w) (Const y w_) where convert (Const x) = Const $ convert x -- Polarized instance Convert x y => Convert (Polarize.Polarized x) (Polarize.Polarized y) where convert = fmap convert -- Date instance Convert Date Date where convert = id -- Quantity instance Convert Decimal Decimal where convert = id -- Text instance Convert Text Text where convert = id -- List instance Convert x y => Convert [x] [y] where convert = fmap convert instance Convert x y => Convert (NonEmpty x) (NonEmpty y) where convert = fmap convert -- TreeMap instance (Convert kx ky, Convert x y, Ord kx, Ord ky) => Convert (TreeMap kx x) (TreeMap ky y) where convert = TreeMap.map_monotonic convert convert -- Map instance (Convert kx ky, Convert x y, Ord kx) => Convert (Map kx x) (Map ky y) where convert = Map.mapKeysMonotonic convert . fmap convert -- Seq instance Convert x y => Convert (Seq x) (Seq y) where convert = fmap convert -- * Stats -- ** Class 'Stats' class ( Convert (Stats.Posting_Account (Stats.Transaction_Posting x)) (Stats.Posting_Account (Stats.Transaction_Posting y)) , Convert (Stats.Posting_Unit (Stats.Transaction_Posting x)) (Stats.Posting_Unit (Stats.Transaction_Posting y)) ) => Stats x y instance Stats JCC.Transaction Ledger.Transaction instance Stats Ledger.Transaction JCC.Transaction instance Stats ( JCC.Charted JCC.Transaction) (Ledger.Charted Ledger.Transaction) instance Stats (Ledger.Charted Ledger.Transaction) (JCC.Charted JCC.Transaction) instance ( Stats x y , Stats.Transaction x , Stats.Transaction y ) => Convert (Stats.Stats x) (Stats.Stats y) where convert s@Stats.Stats { Stats.stats_accounts , Stats.stats_units } = s { Stats.stats_accounts = Map.mapKeysMonotonic convert stats_accounts , Stats.stats_units = Map.mapKeysMonotonic convert stats_units }