1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE FlexibleInstances #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE OverloadedStrings #-}
5 {-# LANGUAGE ScopedTypeVariables #-}
6 module Hcompta.Format.Ledger.Write where
8 -- import Control.Applicative ((<$>), (<*))
9 import qualified Data.Char (isSpace)
10 import qualified Data.Functor.Compose
11 import qualified Data.Foldable
12 -- import Data.Foldable (Foldable)
13 import qualified Data.List
14 import qualified Data.List.NonEmpty
15 import qualified Data.Map.Strict as Data.Map
16 import qualified Data.Text.Lazy as TL
17 import qualified Data.Text as Text
18 import qualified Hcompta.Lib.Leijen as W
19 import Hcompta.Lib.Leijen (Doc, (<>))
20 import System.IO (Handle)
21 import qualified Text.Parsec as R hiding (satisfy, char)
22 import Text.Parsec (Stream, ParsecT)
24 import qualified Hcompta.Account as Account
25 import Hcompta.Account (Account)
26 import qualified Hcompta.Amount as Amount
27 import qualified Hcompta.Amount.Write as Amount.Write
28 import qualified Hcompta.Format.Ledger as Ledger
29 import Hcompta.Format.Ledger
32 , Posting(..), Posting_by_Account, Posting_Type(..)
36 import qualified Hcompta.Date.Write as Date.Write
37 import qualified Hcompta.Format.Ledger.Read as Read
38 import qualified Hcompta.Lib.Parsec as R
42 account :: Posting_Type -> Account -> Doc
45 Posting_Type_Regular -> account_
46 Posting_Type_Virtual -> \acct ->
47 W.char Read.posting_type_virtual_begin <> do
49 W.char Read.posting_type_virtual_end
50 Posting_Type_Virtual_Balanced -> \acct ->
51 W.char Read.posting_type_virtual_balanced_begin <> do
53 W.char Read.posting_type_virtual_balanced_end
55 account_ :: Account -> Doc
58 Data.List.NonEmpty.toList $
59 Data.List.NonEmpty.intersperse
60 (W.bold $ W.yellow $ W.char Read.account_name_sep)
61 (Data.List.NonEmpty.map account_name acct)
63 account_name :: Account.Name -> Doc
64 account_name = W.strict_text
66 -- ** Measure 'Account'
68 account_length :: Posting_Type -> Account -> Int
69 account_length type_ acct =
71 (\acc -> (1 +) . (acc +) . Text.length)
74 Posting_Type_Regular -> 0
75 Posting_Type_Virtual -> 2
76 Posting_Type_Virtual_Balanced -> 2
78 -- ** Measure 'Amount's
80 amounts_length :: Amount.By_Unit -> Int
86 (\n -> (3 +) . (+) (Amount.Write.amount_length n))
91 comment :: Comment -> Doc
94 W.char Read.comment_begin
95 <> (case Text.uncons com of
96 Just (c, _) | not $ Data.Char.isSpace c -> W.space
98 <> do W.if_color colorize (W.strict_text com)
103 pre <- R.many $ R.try $ do
104 ns <- R.many $ R.satisfy (\c -> c /= Read.tag_value_sep && not (Data.Char.isSpace c))
105 sh <- R.space_horizontal
107 ((W.text $ TL.pack $ concat pre) <>) <$> tags <* R.eof)
109 Left _ -> W.strict_text com
111 tags :: Stream s m Char => ParsecT s u m Doc
114 xs <- W.hcat <$> R.many (R.try (tag_sep >>= (\s -> (s <>) <$> tag_)))
117 tag_sep :: Stream s m Char => ParsecT s u m Doc
119 s <- R.char Read.tag_sep
120 sh <- R.many R.space_horizontal
122 do W.bold $ W.dullblack $ W.char s
123 <> do W.text $ TL.pack sh
124 tag_ :: Stream s m Char => ParsecT s u m Doc
127 s <- R.char Read.tag_value_sep
130 (W.yellow $ W.strict_text n)
131 <> (W.bold $ W.dullblack $ W.char s)
132 <> (W.red $ W.strict_text v)
134 comments :: Doc -> [Comment] -> Doc
137 Data.List.intersperse W.line .
138 Data.List.map (\c -> prefix <> comment c)
144 (W.dullyellow $ W.strict_text n)
145 <> W.char Read.tag_value_sep
146 <> (W.dullred $ W.strict_text v)
150 posting :: Posting_Lengths -> Posting_Type -> Posting -> Doc
151 posting max_posting_length type_
153 { posting_account=acct
155 , posting_comments=cmts
157 , posting_status=status_
162 case Data.Map.null posting_amounts of
163 True -> account type_ acct
165 let len_acct = account_length type_ acct in
166 let len_amts = amounts_length posting_amounts in
167 account type_ acct <> do
168 W.fill (2 + max_posting_length - (len_acct + len_amts)) (W.space <> W.space) <> do
170 (W.space <> (W.bold $ W.yellow $ W.char Read.amount_sep) <> W.space)
171 Amount.Write.amount posting_amounts
174 [c] -> W.space <> comment c
175 _ -> W.line <> do comments (W.text "\t ") cmts)
177 status :: Ledger.Status -> Doc
178 status = \x -> case x of
182 -- ** Measure 'Posting'
184 type Posting_Lengths = (Int)
188 -> Posting_by_Account
191 postings_lengths type_ ps pl =
195 ( account_length type_ (posting_account p)
196 + amounts_length (posting_amounts p) )
198 (Data.Functor.Compose.Compose ps)
200 -- * Write 'Transaction'
202 transaction :: Transaction -> Doc
203 transaction t = transaction_with_lengths (transaction_lengths t 0) t
205 transactions :: Foldable f => f Transaction -> Doc
207 let transaction_lengths_ =
208 Data.Foldable.foldr transaction_lengths 0 ts
211 transaction_with_lengths transaction_lengths_ t <> W.line <>
212 (if W.is_empty doc then W.empty else W.line <> doc)
217 transaction_with_lengths :: Transaction_Lengths -> Transaction -> Doc
218 transaction_with_lengths
221 { transaction_code=code_
222 , transaction_comments_before
223 , transaction_comments_after
224 , transaction_dates=(first_date, dates)
225 , transaction_description
226 , transaction_postings
227 , transaction_virtual_postings
228 , transaction_balanced_virtual_postings
229 , transaction_status=status_
230 -- , transaction_tags
232 (case transaction_comments_before of
234 _ -> comments W.space transaction_comments_before <> W.line) <> do
236 Data.List.intersperse
237 (W.char Read.date_sep)
238 (Data.List.map Date.Write.date (first_date:dates))) <> do
240 True -> W.space <> status status_
241 False -> W.empty) <> do
243 (case transaction_description of
245 _ -> W.space <> (W.dullmagenta $ W.strict_text transaction_description)) <> do
247 (case transaction_comments_after of
249 _ -> comments W.space transaction_comments_after <> W.line) <> do
250 W.vsep $ Data.List.map
253 (W.intercalate W.line
254 (W.vsep . Data.List.map
255 (posting posting_lengths_ type_)))
256 (Ledger.posting_by_Signs_and_Account ps))
257 [ (Posting_Type_Regular, transaction_postings)
258 , (Posting_Type_Virtual, transaction_virtual_postings)
259 , (Posting_Type_Virtual_Balanced, transaction_balanced_virtual_postings)
262 code :: Ledger.Code -> Doc
263 code = \x -> case x of
265 t -> W.space <> W.char '(' <> W.strict_text t <> W.char ')'
267 -- ** Measure 'Transaction'
269 type Transaction_Lengths = Posting_Lengths
271 transaction_lengths :: Transaction -> Posting_Lengths -> Posting_Lengths
274 { transaction_postings
275 , transaction_virtual_postings
276 , transaction_balanced_virtual_postings
277 } posting_lengths_ = do
279 (flip (\(type_, ps) -> postings_lengths type_ ps))
281 [ (Posting_Type_Regular, transaction_postings)
282 , (Posting_Type_Virtual, transaction_virtual_postings)
283 , (Posting_Type_Virtual_Balanced, transaction_balanced_virtual_postings)
288 journal :: Journal -> Doc
289 journal Journal { journal_transactions } =
290 transactions (Data.Functor.Compose.Compose journal_transactions)
296 { style_align :: Bool
297 , style_color :: Bool
306 show :: Style -> Doc -> TL.Text
307 show Style{style_color, style_align} =
310 then W.renderPretty style_color 1.0 maxBound
311 else W.renderCompact style_color
313 put :: Style -> Handle -> Doc -> IO ()
314 put Style{style_color, style_align} handle =
317 then W.renderPretty style_color 1.0 maxBound
318 else W.renderCompact style_color