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 qualified Data.Char (isSpace)
11 import qualified Data.Functor.Compose
12 import qualified Data.Foldable
13 -- import Data.Foldable (Foldable)
14 import qualified Data.List
15 import qualified Data.List.NonEmpty
16 import qualified Data.Map.Strict as Data.Map
17 import qualified Data.Text.Lazy as TL
18 import qualified Data.Text as Text
19 import qualified Hcompta.Lib.Leijen as W
20 import Hcompta.Lib.Leijen (Doc, (<>))
21 import System.IO (Handle)
22 import qualified Text.Parsec as R hiding (satisfy, char)
23 import Text.Parsec (Stream, ParsecT)
25 import qualified Hcompta.Account as Account
26 import Hcompta.Account (Account)
27 import qualified Hcompta.Account.Read as Account.Read
28 import qualified Hcompta.Amount as Amount
29 import qualified Hcompta.Amount.Write as Amount.Write
30 import qualified Hcompta.Format.Ledger as Ledger
31 import Hcompta.Format.Ledger
34 , Posting(..), Posting_by_Account
38 import qualified Hcompta.Date.Write as Date.Write
39 import qualified Hcompta.Format.Ledger.Read as Read
40 -- import Hcompta.Lib.Consable (Consable(..))
41 import qualified Hcompta.Lib.Parsec as R
42 import Hcompta.Posting (Posting_Type(..))
46 account :: Posting_Type -> Account -> Doc
49 Posting_Type_Regular -> account_
50 Posting_Type_Virtual -> \acct ->
51 W.char Read.posting_type_virtual_begin <> do
53 W.char Read.posting_type_virtual_end
54 Posting_Type_Virtual_Balanced -> \acct ->
55 W.char Read.posting_type_virtual_balanced_begin <> do
57 W.char Read.posting_type_virtual_balanced_end
59 account_ :: Account -> Doc
62 Data.List.NonEmpty.toList $
63 Data.List.NonEmpty.intersperse
64 (W.bold $ W.yellow $ W.char Account.Read.section_sep)
65 (Data.List.NonEmpty.map account_name acct)
67 account_name :: Account.Name -> Doc
68 account_name = W.strict_text
70 -- ** Measure 'Account'
72 account_length :: Posting_Type -> Account -> Int
73 account_length type_ acct =
75 (\acc -> (1 +) . (acc +) . Text.length)
78 Posting_Type_Regular -> 0
79 Posting_Type_Virtual -> 2
80 Posting_Type_Virtual_Balanced -> 2
82 -- ** Measure 'Amount's
84 amounts_length :: Amount.By_Unit -> Int
90 (\n -> (3 +) . (+) (Amount.Write.amount_length n))
95 comment :: Comment -> Doc
98 W.char Read.comment_begin
99 <> (case Text.uncons com of
100 Just (c, _) | not $ Data.Char.isSpace c -> W.space
102 <> do W.if_color colorize (W.strict_text com)
107 pre <- R.many $ R.try $ do
108 ns <- R.many $ R.satisfy (\c -> c /= Read.tag_value_sep && not (Data.Char.isSpace c))
109 sh <- R.space_horizontal
111 ((W.text $ TL.pack $ concat pre) <>) <$> tags <* R.eof)
113 Left _ -> W.strict_text com
115 tags :: Stream s m Char => ParsecT s u m Doc
118 xs <- W.hcat <$> R.many (R.try (tag_sep >>= (\s -> (s <>) <$> tag_)))
121 tag_sep :: Stream s m Char => ParsecT s u m Doc
123 s <- R.char Read.tag_sep
124 sh <- R.many R.space_horizontal
126 do W.bold $ W.dullblack $ W.char s
127 <> do W.text $ TL.pack sh
128 tag_ :: Stream s m Char => ParsecT s u m Doc
131 s <- R.char Read.tag_value_sep
134 (W.yellow $ W.strict_text n)
135 <> (W.bold $ W.dullblack $ W.char s)
136 <> (W.red $ W.strict_text v)
138 comments :: Doc -> [Comment] -> Doc
141 Data.List.intersperse W.line .
142 Data.List.map (\c -> prefix <> comment c)
148 (W.dullyellow $ W.strict_text n)
149 <> W.char Read.tag_value_sep
150 <> (W.dullred $ W.strict_text v)
154 posting :: Posting_Lengths -> Posting_Type -> Posting -> Doc
155 posting max_posting_length type_
157 { posting_account=acct
159 , posting_comments=cmts
161 , posting_status=status_
166 case Data.Map.null posting_amounts of
167 True -> account type_ acct
169 let len_acct = account_length type_ acct in
170 let len_amts = amounts_length posting_amounts in
171 account type_ acct <> do
172 W.fill (2 + max_posting_length - (len_acct + len_amts)) (W.space <> W.space) <> do
174 (W.space <> (W.bold $ W.yellow $ W.char Read.amount_sep) <> W.space)
175 Amount.Write.amount posting_amounts
178 [c] -> W.space <> comment c
179 _ -> W.line <> do comments (W.text "\t ") cmts)
181 status :: Ledger.Status -> Doc
182 status = \x -> case x of
186 -- ** Measure 'Posting'
188 type Posting_Lengths = (Int)
192 -> Posting_by_Account
195 postings_lengths type_ ps pl =
199 ( account_length type_ (posting_account p)
200 + amounts_length (posting_amounts p) )
202 (Data.Functor.Compose.Compose ps)
204 -- * Write 'Transaction'
206 transaction :: Transaction -> Doc
207 transaction t = transaction_with_lengths (transaction_lengths t 0) t
209 transactions :: Foldable ts => ts Transaction -> Doc
211 let transaction_lengths_ =
212 Data.Foldable.foldr transaction_lengths 0 ts
215 transaction_with_lengths transaction_lengths_ t <>
216 (if W.is_empty doc then W.empty else W.line <> doc)
221 transaction_with_lengths :: Transaction_Lengths -> Transaction -> Doc
222 transaction_with_lengths
225 { transaction_code=code_
226 , transaction_comments_before
227 , transaction_comments_after
228 , transaction_dates=(first_date, dates)
229 , transaction_description
230 , transaction_postings
231 , transaction_virtual_postings
232 , transaction_balanced_virtual_postings
233 , transaction_status=status_
234 -- , transaction_tags
236 (case transaction_comments_before of
238 _ -> comments W.space transaction_comments_before <> W.line) <> do
240 Data.List.intersperse
241 (W.char Read.date_sep)
242 (Data.List.map Date.Write.date (first_date:dates))) <> do
244 True -> W.space <> status status_
245 False -> W.empty) <> do
247 (case transaction_description of
249 _ -> W.space <> (W.dullmagenta $ W.strict_text transaction_description)) <> do
251 (case transaction_comments_after of
253 _ -> comments W.space transaction_comments_after <> W.line) <> do
257 (W.intercalate W.line
258 (W.vsep . fmap (posting posting_lengths_ type_))
262 [ (Posting_Type_Regular , transaction_postings)
263 , (Posting_Type_Virtual , transaction_virtual_postings)
264 , (Posting_Type_Virtual_Balanced, transaction_balanced_virtual_postings)
268 code :: Ledger.Code -> Doc
269 code = \x -> case x of
271 t -> W.space <> W.char '(' <> W.strict_text t <> W.char ')'
273 -- ** Measure 'Transaction'
275 type Transaction_Lengths = Posting_Lengths
277 transaction_lengths :: Transaction -> Posting_Lengths -> Posting_Lengths
280 { transaction_postings
281 , transaction_virtual_postings
282 , transaction_balanced_virtual_postings
283 } posting_lengths_ = do
285 (flip (uncurry postings_lengths))
287 [ (Posting_Type_Regular, transaction_postings)
288 , (Posting_Type_Virtual, transaction_virtual_postings)
289 , (Posting_Type_Virtual_Balanced, transaction_balanced_virtual_postings)
296 , Monoid (ts Transaction)
297 ) => Journal (ts Transaction) -> Doc
298 journal Journal{ journal_transactions } =
299 transactions journal_transactions
305 { style_align :: Bool
306 , style_color :: Bool
315 show :: Style -> Doc -> TL.Text
316 show Style{style_color, style_align} =
319 then W.renderPretty style_color 1.0 maxBound
320 else W.renderCompact style_color
322 put :: Style -> Handle -> Doc -> IO ()
323 put Style{style_color, style_align} handle =
326 then W.renderPretty style_color 1.0 maxBound
327 else W.renderCompact style_color