1 {-# LANGUAGE NoMonomorphismRestriction #-}
2 {-# OPTIONS_GHC -fno-warn-missing-signatures #-}
3 module Hcompta.LCC.Write.Compta where
5 import Control.Monad (Monad(..))
7 import Data.Char (Char)
9 import Data.Eq (Eq(..))
10 import Data.Foldable (Foldable(..))
11 import Data.Function (($), (.), flip, id)
12 import Data.Functor ((<$>))
13 import Data.Functor.Compose (Compose(..))
14 import Data.Map.Strict (Map)
15 import Data.Maybe (Maybe(..), fromMaybe)
16 import Data.Monoid (Monoid(..))
17 import Data.Ord (Ord(..))
18 import Data.Semigroup (Semigroup(..))
19 import Data.Tuple (fst)
20 import GHC.Exts (Int(..))
21 import Prelude (Integer, Num(..), RealFrac(..), Show(..), Integral(..), fromIntegral)
23 import qualified Data.ByteString as BS
24 import qualified Data.Char as Char
25 import qualified Data.List as L
26 import qualified Data.Map.Strict as Map
27 import qualified Data.MonoTraversable as MT
28 import qualified Data.NonNull as NonNull
29 import qualified Data.Strict as S
30 import qualified Data.Text as Text
31 import qualified Data.Text.Encoding as Enc
32 import qualified Data.TreeMap.Strict as TreeMap
34 import qualified Language.Symantic.Document as D
36 import qualified Hcompta as H
38 import Hcompta.LCC.Account
39 import Hcompta.LCC.Amount
40 import Hcompta.LCC.Chart
41 import Hcompta.LCC.Compta
43 import Hcompta.LCC.Journal
44 import Hcompta.LCC.Name
45 import Hcompta.LCC.Posting
46 import Hcompta.LCC.Tag
47 import Hcompta.LCC.Transaction
48 import qualified Hcompta.LCC.Read.Compta as G
51 class Writable d a where
53 -- widthWrite :: forall d a. Writable d a => a -> Integer
54 widthWrite = D.width . D.dim . write
56 -- import Debug.Trace (trace)
57 -- dbg msg x = trace (msg <> " = " <> show x) x
59 -- * Type 'Context_Write'
62 { context_write_account_ref :: Bool
63 , context_write_amounts :: Style_Amounts
64 , context_write_width_acct_amt :: Int
67 context_write :: Context_Write
70 { context_write_account_ref = True
71 , context_write_amounts = Style_Amounts Map.empty
72 , context_write_width_acct_amt = 0
75 instance (D.Doc_Text d, D.Doc_Color d) =>
78 let (y, mo, d) = H.gregorianOf dat in
79 (if y == 0 then D.empty else D.integer y <> sep G.char_ymd_sep) <>
81 sep G.char_ymd_sep <> int2 d <>
90 (if s < 10 then D.charH '0' else D.empty) <>
91 D.integer ((truncate s::Integer))))
93 int2 i = (if i < 10 then D.charH '0' else D.empty) <> D.int i
94 sep = D.blacker . D.charH
95 instance (D.Doc_Text d, D.Doc_Color d, Monoid d) =>
96 Writable d Account where
98 (`MT.ofoldMap` acct) $ \a ->
99 D.blacker (D.charH G.char_account_sep) <>
101 instance D.Doc_Text d =>
102 Writable d NameAccount where
103 write = D.textH . unName
104 instance (D.Doc_Text d, D.Doc_Color d) =>
105 Writable d Tag_Path where
106 write (Tag_Path path) =
108 (:) (D.yellower $ D.charH G.char_account_tag_prefix) $
110 (D.yellower $ D.charH G.char_tag_sep)
111 (D.textH . unName <$> NonNull.toNullable path)
112 instance (D.Doc_Text d, D.Doc_Color d) =>
113 Writable d Account_Tag where
114 write (Account_Tag (Tag (Tag_Path path) (Tag_Data value))) =
116 (:) (D.yellower $ D.charH G.char_account_tag_prefix) $
118 (D.yellower $ D.charH G.char_tag_sep)
119 (D.textH . unName <$> NonNull.toNullable path) ) <>
123 D.yellower (D.charH G.char_tag_data_prefix) <>
125 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d) =>
126 Writable d (Styled_Amount Amount) where
129 { style_amount_unit_side = uside
130 , style_amount_unit_spaced = uspaced
138 S.Just True | not (H.null u) -> D.space
145 S.Just True | not (H.null u) -> D.space
150 S.Just True | not (H.null u) -> D.space
154 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d) =>
155 Writable d Unit where
159 (\c -> case Char.generalCategory c of
160 Char.CurrencySymbol -> True
161 Char.LowercaseLetter -> True
162 Char.ModifierLetter -> True
163 Char.OtherLetter -> True
164 Char.TitlecaseLetter -> True
165 Char.UppercaseLetter -> True
169 else D.dquote $ D.textH t
170 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d) =>
171 Writable d (Styled_Amount Quantity) where
174 { style_amount_fractioning
175 , style_amount_grouping_integral
176 , style_amount_grouping_fractional
179 let Decimal e n = qty
180 let num = show $ abs n
181 let sign = D.bold $ D.yellow $ D.textH (if n < 0 then "-" else "")
183 then sign <> D.bold (D.blue $ D.stringH num)
185 let num_len = L.length num
188 [ L.replicate (fromIntegral e + 1 - num_len) '0'
190 -- , replicate (fromIntegral precision - fromIntegral e) '0'
192 let (int, frac) = L.splitAt (max 1 (num_len - fromIntegral e)) padded
193 let default_fractioning =
195 del_grouping_sep style_amount_grouping_integral $
196 del_grouping_sep style_amount_grouping_fractional $
200 D.stringH (S.maybe id
201 (\g -> L.reverse . group g . L.reverse)
202 style_amount_grouping_integral $ int) <>
203 D.yellow (D.charH (S.fromMaybe default_fractioning style_amount_fractioning)) <>
204 D.stringH (S.maybe id group style_amount_grouping_fractional frac))
206 group :: Style_Amount_Grouping -> [Char] -> [Char]
207 group (Style_Amount_Grouping sep sizes_) =
208 L.concat . L.reverse .
209 L.map L.reverse . fst .
211 (flip (\digit x -> case x of
212 ([], sizes) -> ([[digit]], sizes)
213 (digits:groups, []) -> ((digit:digits):groups, [])
214 (digits:groups, curr_sizes@(size:sizes)) ->
215 if L.length digits < size
216 then ( (digit:digits):groups, curr_sizes)
217 else ([digit]:[sep]:digits:groups, if L.null sizes then curr_sizes else sizes)
220 del_grouping_sep grouping =
222 S.Just (Style_Amount_Grouping sep _) -> L.delete sep
224 instance (D.Doc_Text d, D.Doc_Color d) =>
225 Writable d Comment where
226 write (Comment com) =
228 D.charH G.char_comment_prefix
229 <> (case Text.uncons com of
230 Just (c, _) | not $ Char.isSpace c -> D.space
233 instance (D.Doc_Text d, D.Doc_Color d) =>
234 Writable d (d, [Comment]) where
235 write (prefix, com) =
237 L.intersperse D.eol $
238 (\c -> prefix <> write c) <$> com
239 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d, Monoid d) =>
240 Writable d (Context_Write, Posting) where
243 , posting_account_ref
249 let d_indent = D.spaces 2 in
251 let (d_acct, w_acct) =
252 case posting_account_ref of
253 S.Just (a S.:!: sa) | context_write_account_ref ctx ->
254 ( write a <> S.maybe D.empty write sa
255 , widthWrite a + S.maybe 0 widthWrite sa )
256 _ -> (write posting_account, widthWrite posting_account) in
257 (case posting_amounts of
258 Amounts amts | Map.null amts -> d_acct
262 (\mdoc unit qty -> Just $
263 let amt = styled_amount (context_write_amounts ctx) $ Amount unit qty in
264 let pad = max 0 $ context_write_width_acct_amt ctx - (w_acct + widthWrite amt) in
267 Just doc -> doc <> D.eol <> d_indent) <>
268 d_acct <> D.spaces pad <> D.space <> write amt
270 (case posting_comments of
272 [c] -> D.space <> write c
273 _ -> D.eol <> write (d_indent <> D.space :: d, posting_comments))
274 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d, Monoid d) =>
275 Writable d (Context_Write, Transaction) where
278 { transaction_comments
280 , transaction_wording = Wording transaction_wording
281 , transaction_postings = Postings transaction_postings
282 , transaction_tags = Transaction_Tags (Tags tags)
284 let ctx' = ctx { context_write_width_acct_amt =
285 let w = context_write_width_acct_amt ctx in
287 then w_Transaction ctx txn
291 (D.charH G.char_transaction_date_sep)
292 (write <$> NonNull.toNullable transaction_dates)) <>
293 (case transaction_wording of
295 _ -> D.space <> D.magenta (D.textH transaction_wording)) <>
297 (case transaction_comments of
299 _ -> write (D.space :: d, transaction_comments) <> D.eol) <>
300 TreeMap.foldr_with_Path
302 foldr (\value -> (<>) (D.spaces 2 <>
303 write (Transaction_Tag (Tag (Tag_Path path) value)) <> D.eol)))
305 D.catV (write . (ctx',) <$> Compose transaction_postings)
306 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d, Monoid d) =>
307 Writable d (Context_Write, Map Date [Transaction]) where
309 let ctx' = ctx{context_write_width_acct_amt =
310 foldr (max . w_Transaction ctx) 0 $ Compose txns} in
312 foldl (\mdoc txn -> Just $
316 Just doc -> D.eol <> D.eol <> doc
317 ) Nothing (Compose txns)
318 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d, Monoid d) =>
319 Writable d Transaction_Tag where
320 write (Transaction_Tag (Tag (Tag_Path path) (Tag_Data value))) =
322 (:) (D.yellower $ D.charH G.char_tag_prefix) $
324 (D.yellower $ D.charH G.char_tag_sep)
325 (D.bold . D.textH . unName <$> NonNull.toNullable path)) <>
328 else D.yellower (D.charH G.char_tag_data_prefix) <> D.textH value
329 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d, Monoid d, Writable d (ctx, j)) =>
330 Writable d (ctx, Journal j) where
331 write (ctx, jnl) = write (ctx, journal_content jnl)
332 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d, Monoid d, Writable d (ctx, j)) =>
333 Writable d (ctx, Journals j) where
334 write (ctx, Journals js) =
336 (\doc j@Journal{journal_file=PathFile jf} ->
338 write (Comment $ Text.pack jf) <> D.eol <>
339 D.eol <> write (ctx, j)
341 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d, Monoid d) =>
342 Writable d Chart where
344 TreeMap.foldl_with_Path
345 (\doc acct (Account_Tags (Tags ca)) ->
347 write (H.to acct :: Account) <> D.eol <>
348 TreeMap.foldl_with_Path
353 doc'' <> D.spaces 2 <>
354 write (Account_Tag (Tag (Tag_Path tp) tv)) <>
362 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d, Monoid d) =>
363 Writable d Terms where
365 Map.foldl (\doc t -> doc <> D.text t <> D.eol) D.empty ts
366 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d, Monoid d, Writable d (Context_Write, j)) =>
367 Writable d (Context_Write, Compta src ss j) where
369 { compta_journals = js
370 , compta_chart = c@Chart{chart_accounts=ca}
371 , compta_style_amounts = amts
372 , compta_terms = terms
374 (if null terms then D.empty else (write terms <> D.eol)) <>
375 (if TreeMap.null ca then D.empty else (write c <> D.eol)) <>
376 write (ctx{context_write_amounts = context_write_amounts ctx <> amts}, js)
377 instance (D.Doc_Text d, D.Doc_Color d, D.Doc_Decoration d) =>
378 Writable (IO d) SourcePos where
379 write (SourcePos p (PosFile l) (PosFile c)) = do
380 content <- Enc.decodeUtf8 <$> BS.readFile p
381 let ls = Text.lines content
382 let ll = max 1 $ l - size_ctx
384 L.take (intFrom $ (l - ll) + 1 + size_ctx) $
385 L.drop (intFrom $ ll-1) ls
386 let ns = show <$> L.take (L.length qs) [ll..]
387 let max_len_n = maximum $ 0 : (L.length <$> ns)
388 let ns' = (<$> ns) $ \n ->
389 L.replicate (max_len_n - L.length n) ' ' <> n
392 L.zipWith (\(n, sn) q ->
393 D.spaces 2 <> D.blacker (D.stringH sn) <>
394 D.spaces 2 <> (if n == l then mark q else D.textH q)
395 ) (L.zip [ll..] ns') qs
396 return $ quote <> D.eol
399 intFrom = fromInteger . toInteger
401 let (b, a) = Text.splitAt (intFrom c - 1) q in
403 case Text.uncons a of
404 Nothing -> D.red D.space
405 Just (a0, a') -> D.red (D.charH a0) <> D.textH a'
407 -- | Return the width of given 'Postings',
408 -- considering only 'Account's and 'Amount's (not indentation, 'Comment's, nor 'Posting_Tag's).
409 w_Transaction :: Context_Write -> Transaction -> Int
410 -- w_Postings ctx = MT.ofoldr (max . widthWrite ctx) 0
414 , posting_account_ref
418 case posting_account_ref of
419 S.Just (a S.:!: sa) | context_write_account_ref ctx ->
420 widthWrite a + S.maybe 0 widthWrite sa
421 _ -> widthWrite posting_account in
423 case posting_amounts of
424 Amounts amts | Map.null amts -> 0
428 let amt = styled_amount (context_write_amounts ctx) $ Amount unit qty in