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 Data.Map.Strict (Map)
22 import qualified Data.Map.Strict as Map
23 import Data.Maybe (Maybe(..))
24 import Data.Monoid (Monoid(..))
25 import Data.Ord (Ord(..))
26 import qualified Data.Text as Text
27 import qualified Data.Text.Lazy as TL
28 import Prelude (($), (.), Bounded(..), Int, IO, Num(..), flip, uncurry)
29 import System.IO (Handle)
30 import Text.Parsec (Stream, ParsecT)
31 import qualified Text.Parsec as R hiding (satisfy, char)
33 import qualified Hcompta.Account as Account
34 import Hcompta.Chart (Chart)
35 import qualified Hcompta.Chart as Chart
36 import qualified Hcompta.Format.Ledger as Ledger
37 import Hcompta.Format.Ledger
47 import qualified Hcompta.Format.Ledger.Account.Read as Account.Read
48 import qualified Hcompta.Format.Ledger.Amount as Amount
49 import qualified Hcompta.Format.Ledger.Amount.Write as Amount.Write
50 import qualified Hcompta.Format.Ledger.Date.Write as Date.Write
51 import qualified Hcompta.Format.Ledger.Read as Read
52 -- import Hcompta.Lib.Consable (Consable(..))
53 import Hcompta.Lib.Leijen (Doc, (<>))
54 import qualified Hcompta.Lib.Leijen as W
55 import qualified Hcompta.Lib.Parsec as R
56 import Hcompta.Tag (Tag)
57 import qualified Hcompta.Tag as Tag
58 import qualified Hcompta.Lib.TreeMap as TreeMap
62 account :: Posting_Type -> Account -> Doc
65 Posting_Type_Regular -> account_
66 Posting_Type_Virtual -> \acct ->
67 W.char Read.posting_type_virtual_begin <> do
69 W.char Read.posting_type_virtual_end
70 Posting_Type_Virtual_Balanced -> \acct ->
71 W.char Read.posting_type_virtual_balanced_begin <> do
73 W.char Read.posting_type_virtual_balanced_end
75 account_ :: Account -> Doc
78 Data.List.NonEmpty.toList $
79 Data.List.NonEmpty.intersperse
80 (W.bold $ W.yellow $ W.char Account.Read.section_sep)
81 (Data.List.NonEmpty.map account_section acct)
83 account_section :: Account.Account_Section Account -> Doc
84 account_section = W.strict_text
86 -- ** Measure 'Account'
88 account_length :: Posting_Type -> Account -> Int
89 account_length type_ acct =
91 (\acc -> (1 +) . (acc +) . Text.length)
94 Posting_Type_Regular -> 0
95 Posting_Type_Virtual -> 2
96 Posting_Type_Virtual_Balanced -> 2
100 amounts :: Amount.Styles -> Map Unit Quantity -> Doc
106 else doc <> W.space <>
107 (W.bold $ W.yellow $ W.char Read.amount_sep) <>
109 (Amount.Write.amount $
110 Amount.style styles $
111 Ledger.Amount unit qty))
114 -- ** Measure 'Amount's
116 amounts_length :: Amount.Styles -> Map Unit Quantity -> Int
117 amounts_length styles amts =
122 (\unit qty -> (3 +) . (+)
123 (Amount.Write.amount_length $
124 Amount.style styles $
125 Ledger.Amount unit qty))
130 comment :: Comment -> Doc
133 W.char Read.comment_begin
134 <> (case Text.uncons com of
135 Just (c, _) | not $ Data.Char.isSpace c -> W.space
137 <> do W.if_color colorize (W.strict_text com)
142 pre <- R.many $ R.try $ do
143 ns <- R.many $ R.satisfy (\c -> c /= Read.tag_value_sep && not (Data.Char.isSpace c))
144 sh <- R.space_horizontal
146 ((W.text $ TL.pack $ concat pre) <>) <$> tags <* R.eof)
148 Left _ -> W.strict_text com
150 tags :: Stream s m Char => ParsecT s u m Doc
154 <*> (W.hcat <$> R.many (R.try (tag_sep >>= (\s -> (s <>) <$> tag_))))
156 tag_sep :: Stream s m Char => ParsecT s u m Doc
158 s <- R.char Read.tag_sep
159 sh <- R.many R.space_horizontal
162 <> do W.text $ TL.pack sh
163 tag_ :: Stream s m Char => ParsecT s u m Doc
167 foldMap (\s -> W.dullyellow (W.strict_text s) <> do
168 W.bold $ W.dullblack $ W.char Read.tag_value_sep) p <>
169 (W.red $ W.strict_text v)
171 comments :: Doc -> [Comment] -> Doc
174 Data.List.intersperse W.line .
175 Data.List.map (\c -> prefix <> comment c)
181 foldMap (\s -> W.dullyellow (W.strict_text s) <> W.char Read.tag_value_sep) p <>
182 (W.dullred $ W.strict_text v)
186 posting :: Amount.Styles -> Posting_Lengths -> Posting_Type -> Posting -> Doc
187 posting styles max_posting_length type_
189 { posting_account=acct
191 , posting_comments=cmts
193 , posting_status=status_
198 case Map.null posting_amounts of
199 True -> account type_ acct
201 let len_acct = account_length type_ acct in
202 let len_amts = amounts_length styles posting_amounts in
203 account type_ acct <> do
204 W.fill (2 + max_posting_length - (len_acct + len_amts)) (W.space <> W.space) <> do
205 amounts styles posting_amounts
208 [c] -> W.space <> comment c
209 _ -> W.line <> do comments (W.text "\t ") cmts)
211 status :: Ledger.Status -> Doc
212 status = \x -> case x of
216 -- ** Measure 'Posting'
218 type Posting_Lengths = (Int)
223 -> Map Account [Posting]
226 postings_lengths styles type_ ps pl =
230 ( account_length type_ (posting_account p)
231 + amounts_length styles (posting_amounts p) )
233 (Data.Functor.Compose.Compose ps)
235 -- * Write 'Transaction'
237 transaction :: Amount.Styles -> Transaction -> Doc
238 transaction styles t = transaction_with_lengths styles (transaction_lengths styles t 0) t
240 transactions :: Foldable ts => Amount.Styles -> ts Transaction -> Doc
241 transactions styles ts = do
242 let transaction_lengths_ =
243 Data.Foldable.foldr (transaction_lengths styles) 0 ts
246 transaction_with_lengths styles transaction_lengths_ t <>
247 (if W.is_empty doc then W.empty else W.line <> doc)
252 transaction_with_lengths
254 -> Transaction_Lengths
255 -> Transaction -> Doc
256 transaction_with_lengths
260 { transaction_code=code_
261 , transaction_comments_before
262 , transaction_comments_after
263 , transaction_dates=(first_date, dates)
264 , transaction_description
265 , transaction_postings
266 , transaction_virtual_postings
267 , transaction_balanced_virtual_postings
268 , transaction_status=status_
269 -- , transaction_tags
271 (case transaction_comments_before of
273 _ -> comments W.space transaction_comments_before <> W.line) <> do
275 Data.List.intersperse
276 (W.char Read.date_sep)
277 (Data.List.map Date.Write.date (first_date:dates))) <> do
279 True -> W.space <> status status_
280 False -> W.empty) <> do
282 (case transaction_description of
284 _ -> W.space <> (W.dullmagenta $ W.strict_text transaction_description)) <> do
286 (case transaction_comments_after of
288 _ -> comments W.space transaction_comments_after <> W.line) <> do
292 (W.intercalate W.line
293 (W.vsep . fmap (posting styles posting_lengths_ type_))
297 [ (Posting_Type_Regular , transaction_postings)
298 , (Posting_Type_Virtual , transaction_virtual_postings)
299 , (Posting_Type_Virtual_Balanced, transaction_balanced_virtual_postings)
303 code :: Ledger.Code -> Doc
304 code = \x -> case x of
306 t -> W.space <> W.char '(' <> W.strict_text t <> W.char ')'
308 -- ** Measure 'Transaction'
310 type Transaction_Lengths = Posting_Lengths
312 transaction_lengths :: Amount.Styles -> Transaction -> Posting_Lengths -> Posting_Lengths
316 { transaction_postings
317 , transaction_virtual_postings
318 , transaction_balanced_virtual_postings
319 } posting_lengths_ = do
321 (flip (uncurry $ postings_lengths styles))
323 [ (Posting_Type_Regular, transaction_postings)
324 , (Posting_Type_Virtual, transaction_virtual_postings)
325 , (Posting_Type_Virtual_Balanced, transaction_balanced_virtual_postings)
332 , Monoid (ts Transaction)
333 ) => Journal (ts Transaction) -> Doc
334 journal Journal{ journal_sections, journal_amount_styles } =
335 transactions journal_amount_styles journal_sections
339 chart :: Chart Account -> Doc
341 TreeMap.foldl_with_Path
344 account Posting_Type_Regular acct <> W.line <>
350 ddd <> W.char '\t' <> tag (tn, tv) <> W.line)
363 { style_align :: Bool
364 , style_color :: Bool
373 show :: Style -> Doc -> TL.Text
374 show Style{style_color, style_align} =
377 then W.renderPretty style_color 1.0 maxBound
378 else W.renderCompact style_color
380 put :: Style -> Doc -> Handle -> IO ()
381 put Style{style_color, style_align} doc handle =
384 then W.renderPretty style_color 1.0 maxBound doc
385 else W.renderCompact style_color doc