1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE FlexibleInstances #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE OverloadedStrings #-}
5 {-# LANGUAGE ScopedTypeVariables #-}
6 {-# LANGUAGE TypeFamilies #-}
7 module Hcompta.Format.Ledger.Write where
9 import Control.Applicative ((<$>), (<*>), (<*))
10 import Control.Monad (Monad(..))
12 import Data.Char (Char, isSpace)
13 import Data.Either (Either(..))
14 import Data.Eq (Eq(..))
15 import qualified Data.Foldable
16 import Data.Foldable (Foldable(..))
17 import Data.Functor (Functor(..))
18 import qualified Data.Functor.Compose
20 import qualified Data.List.NonEmpty
21 import qualified Data.Map.Strict as Data.Map
22 import Data.Maybe (Maybe(..))
23 import Data.Monoid (Monoid(..))
24 import Data.Ord (Ord(..))
25 import qualified Data.Text as Text
26 import qualified Data.Text.Lazy as TL
27 import Prelude (($), (.), Bounded(..), Int, IO, Num(..), flip, uncurry)
28 import System.IO (Handle)
29 import Text.Parsec (Stream, ParsecT)
30 import qualified Text.Parsec as R hiding (satisfy, char)
32 import Hcompta.Account (Account)
33 import qualified Hcompta.Account as Account
34 import qualified Hcompta.Account.Read as Account.Read
35 import qualified Hcompta.Amount as Amount
36 import qualified Hcompta.Amount.Write as Amount.Write
37 import qualified Hcompta.Date.Write as Date.Write
38 import qualified Hcompta.Format.Ledger as Ledger
39 import Hcompta.Format.Ledger
42 , Posting(..), Posting_by_Account
45 import qualified Hcompta.Format.Ledger.Read as Read
46 -- import Hcompta.Lib.Consable (Consable(..))
47 import Hcompta.Lib.Leijen (Doc, (<>))
48 import qualified Hcompta.Lib.Leijen as W
49 import qualified Hcompta.Lib.Parsec as R
50 import Hcompta.Posting (Posting_Type(..))
51 import Hcompta.Tag (Tag)
55 account :: Posting_Type -> Account -> Doc
58 Posting_Type_Regular -> account_
59 Posting_Type_Virtual -> \acct ->
60 W.char Read.posting_type_virtual_begin <> do
62 W.char Read.posting_type_virtual_end
63 Posting_Type_Virtual_Balanced -> \acct ->
64 W.char Read.posting_type_virtual_balanced_begin <> do
66 W.char Read.posting_type_virtual_balanced_end
68 account_ :: Account -> Doc
71 Data.List.NonEmpty.toList $
72 Data.List.NonEmpty.intersperse
73 (W.bold $ W.yellow $ W.char Account.Read.section_sep)
74 (Data.List.NonEmpty.map account_name acct)
76 account_name :: Account.Name -> Doc
77 account_name = W.strict_text
79 -- ** Measure 'Account'
81 account_length :: Posting_Type -> Account -> Int
82 account_length type_ acct =
84 (\acc -> (1 +) . (acc +) . Text.length)
87 Posting_Type_Regular -> 0
88 Posting_Type_Virtual -> 2
89 Posting_Type_Virtual_Balanced -> 2
91 -- ** Measure 'Amount's
93 amounts_length :: Amount.By_Unit -> Int
99 (\n -> (3 +) . (+) (Amount.Write.amount_length n))
104 comment :: Comment -> Doc
107 W.char Read.comment_begin
108 <> (case Text.uncons com of
109 Just (c, _) | not $ Data.Char.isSpace c -> W.space
111 <> do W.if_color colorize (W.strict_text com)
116 pre <- R.many $ R.try $ do
117 ns <- R.many $ R.satisfy (\c -> c /= Read.tag_value_sep && not (Data.Char.isSpace c))
118 sh <- R.space_horizontal
120 ((W.text $ TL.pack $ concat pre) <>) <$> tags <* R.eof)
122 Left _ -> W.strict_text com
124 tags :: Stream s m Char => ParsecT s u m Doc
128 <*> (W.hcat <$> R.many (R.try (tag_sep >>= (\s -> (s <>) <$> tag_))))
130 tag_sep :: Stream s m Char => ParsecT s u m Doc
132 s <- R.char Read.tag_sep
133 sh <- R.many R.space_horizontal
136 <> do W.text $ TL.pack sh
137 tag_ :: Stream s m Char => ParsecT s u m Doc
141 foldMap (\s -> W.dullyellow (W.strict_text s) <> do
142 W.bold $ W.dullblack $ W.char Read.tag_value_sep) p <>
143 (W.red $ W.strict_text v)
145 comments :: Doc -> [Comment] -> Doc
148 Data.List.intersperse W.line .
149 Data.List.map (\c -> prefix <> comment c)
155 foldMap (\s -> W.dullyellow (W.strict_text s) <> W.char Read.tag_value_sep) p <>
156 (W.dullred $ W.strict_text v)
160 posting :: Posting_Lengths -> Posting_Type -> Posting -> Doc
161 posting max_posting_length type_
163 { posting_account=acct
165 , posting_comments=cmts
167 , posting_status=status_
172 case Data.Map.null posting_amounts of
173 True -> account type_ acct
175 let len_acct = account_length type_ acct in
176 let len_amts = amounts_length posting_amounts in
177 account type_ acct <> do
178 W.fill (2 + max_posting_length - (len_acct + len_amts)) (W.space <> W.space) <> do
180 (W.space <> (W.bold $ W.yellow $ W.char Read.amount_sep) <> W.space)
181 Amount.Write.amount posting_amounts
184 [c] -> W.space <> comment c
185 _ -> W.line <> do comments (W.text "\t ") cmts)
187 status :: Ledger.Status -> Doc
188 status = \x -> case x of
192 -- ** Measure 'Posting'
194 type Posting_Lengths = (Int)
198 -> Posting_by_Account
201 postings_lengths type_ ps pl =
205 ( account_length type_ (posting_account p)
206 + amounts_length (posting_amounts p) )
208 (Data.Functor.Compose.Compose ps)
210 -- * Write 'Transaction'
212 transaction :: Transaction -> Doc
213 transaction t = transaction_with_lengths (transaction_lengths t 0) t
215 transactions :: Foldable ts => ts Transaction -> Doc
217 let transaction_lengths_ =
218 Data.Foldable.foldr transaction_lengths 0 ts
221 transaction_with_lengths transaction_lengths_ t <>
222 (if W.is_empty doc then W.empty else W.line <> doc)
227 transaction_with_lengths :: Transaction_Lengths -> Transaction -> Doc
228 transaction_with_lengths
231 { transaction_code=code_
232 , transaction_comments_before
233 , transaction_comments_after
234 , transaction_dates=(first_date, dates)
235 , transaction_description
236 , transaction_postings
237 , transaction_virtual_postings
238 , transaction_balanced_virtual_postings
239 , transaction_status=status_
240 -- , transaction_tags
242 (case transaction_comments_before of
244 _ -> comments W.space transaction_comments_before <> W.line) <> do
246 Data.List.intersperse
247 (W.char Read.date_sep)
248 (Data.List.map Date.Write.date (first_date:dates))) <> do
250 True -> W.space <> status status_
251 False -> W.empty) <> do
253 (case transaction_description of
255 _ -> W.space <> (W.dullmagenta $ W.strict_text transaction_description)) <> do
257 (case transaction_comments_after of
259 _ -> comments W.space transaction_comments_after <> W.line) <> do
263 (W.intercalate W.line
264 (W.vsep . fmap (posting posting_lengths_ type_))
268 [ (Posting_Type_Regular , transaction_postings)
269 , (Posting_Type_Virtual , transaction_virtual_postings)
270 , (Posting_Type_Virtual_Balanced, transaction_balanced_virtual_postings)
274 code :: Ledger.Code -> Doc
275 code = \x -> case x of
277 t -> W.space <> W.char '(' <> W.strict_text t <> W.char ')'
279 -- ** Measure 'Transaction'
281 type Transaction_Lengths = Posting_Lengths
283 transaction_lengths :: Transaction -> Posting_Lengths -> Posting_Lengths
286 { transaction_postings
287 , transaction_virtual_postings
288 , transaction_balanced_virtual_postings
289 } posting_lengths_ = do
291 (flip (uncurry postings_lengths))
293 [ (Posting_Type_Regular, transaction_postings)
294 , (Posting_Type_Virtual, transaction_virtual_postings)
295 , (Posting_Type_Virtual_Balanced, transaction_balanced_virtual_postings)
302 , Monoid (ts Transaction)
303 ) => Journal (ts Transaction) -> Doc
304 journal Journal{ journal_transactions } =
305 transactions journal_transactions
311 { style_align :: Bool
312 , style_color :: Bool
321 show :: Style -> Doc -> TL.Text
322 show Style{style_color, style_align} =
325 then W.renderPretty style_color 1.0 maxBound
326 else W.renderCompact style_color
328 put :: Style -> Handle -> Doc -> IO ()
329 put Style{style_color, style_align} handle =
332 then W.renderPretty style_color 1.0 maxBound
333 else W.renderCompact style_color