-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathModule.hs
469 lines (406 loc) · 10.9 KB
/
Module.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Centjes.Module
( LModule,
Module (..),
LImport,
Import (..),
LDeclaration,
Declaration (..),
LCurrencyDeclaration,
CurrencyDeclaration (..),
CurrencySymbol (..),
LAccountDeclaration,
AccountDeclaration (..),
LAccountAssertion,
AccountAssertion (..),
AccountType (..),
LTagDeclaration,
TagDeclaration (..),
LPriceDeclaration,
PriceDeclaration (..),
priceDeclarationCurrencySymbols,
LCostExpression,
CostExpression (..),
LPercentageExpression,
PercentageExpression (..),
LRationalExpression,
RationalExpression (..),
LTransaction,
Transaction (..),
transactionCurrencySymbols,
Timestamp (..),
Description (..),
LPosting,
Posting (..),
LTransactionExtra,
TransactionExtra (..),
LExtraAttachment,
ExtraAttachment (..),
LAttachment,
Attachment (..),
LExtraAssertion,
ExtraAssertion (..),
LAssertion,
Assertion (..),
LExtraTag,
ExtraTag (..),
LTag,
Tag (..),
AccountName (..),
DecimalLiteral (..),
)
where
import Autodocodec
import Centjes.AccountName
import Centjes.AccountType
import Centjes.CurrencySymbol
import Centjes.Description
import Centjes.Location
import Centjes.Tag
import Centjes.Timestamp
import Control.Arrow (left)
import Data.Set (Set)
import qualified Data.Set as S
import Data.Text (Text)
import Data.Validity
import Data.Validity.Path ()
import Data.Validity.Text ()
import Data.Validity.Time ()
import GHC.Generics (Generic)
import Money.Amount (Rounding (..))
import Numeric.DecimalLiteral
import Path
type LModule = Module SourceSpan
-- | Module
--
-- A file with imports and declarations
data Module ann = Module
{ -- | Import declaration
--
-- @
-- import bank.cent
-- @
moduleImports :: [GenLocated ann (Import ann)],
-- | Other declarations such as currencies,accounts,transactions,...
moduleDeclarations :: [GenLocated ann (Declaration ann)]
}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (Module ann)
type LDeclaration = LLocated Declaration
-- | Declaration
--
-- A single declaration.
data Declaration ann
= -- | Comment
--
-- @
-- -- This is a comment
-- @
DeclarationComment !(GenLocated ann Text)
| -- | Currency declaration
--
-- @
-- currency EUR 0.01
-- @
DeclarationCurrency !(GenLocated ann (CurrencyDeclaration ann))
| -- | Account declaration
--
-- @
-- account assets
-- @
DeclarationAccount !(GenLocated ann (AccountDeclaration ann))
| -- | Tag declaration
--
-- @
-- tag deductible
-- @
DeclarationTag !(GenLocated ann (TagDeclaration ann))
| -- | Price declaration
--
-- @
-- price 2024-05-16 USD 1.00 EUR
-- @
DeclarationPrice !(GenLocated ann (PriceDeclaration ann))
| -- | Transaction declaration
--
-- @
-- 2024-05-16
-- | Description
-- * assets -5 USD @ 1 EUR
-- * expenses -5 EUR
-- + attach receipt.pdf
-- + tag deductible
-- + assert assets = 0 USD
-- @
DeclarationTransaction !(GenLocated ann (Transaction ann))
deriving stock (Show, Generic)
instance (Validity ann) => Validity (Declaration ann)
type LImport = LLocated Import
-- | Import declaration
--
-- @
-- bank.cent
-- @
newtype Import ann = Import {importFile :: GenLocated ann (Path Rel File)}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (Import ann)
type LCurrencyDeclaration = LLocated CurrencyDeclaration
-- | Currency declaration
--
-- @
-- USD 0.01
-- @
data CurrencyDeclaration ann = CurrencyDeclaration
{ currencyDeclarationSymbol :: !(GenLocated ann CurrencySymbol),
currencyDeclarationQuantisationFactor :: !(GenLocated ann DecimalLiteral)
}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (CurrencyDeclaration ann)
type LAccountDeclaration = LLocated AccountDeclaration
-- | Account declaration declaration
--
-- @
-- assets
-- @
--
-- or
--
-- @
-- fancyname assets
-- @
data AccountDeclaration ann = AccountDeclaration
{ accountDeclarationName :: !(GenLocated ann AccountName),
accountDeclarationType :: !(Maybe (GenLocated ann AccountType)),
accountDeclarationAssertions :: ![GenLocated ann (AccountAssertion ann)]
}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (AccountDeclaration ann)
type LAccountAssertion = LLocated AccountAssertion
data AccountAssertion ann
= AccountAssertionCurrency !(GenLocated ann CurrencySymbol)
deriving stock (Show, Generic)
instance (Validity ann) => Validity (AccountAssertion ann)
type LTagDeclaration = LLocated TagDeclaration
-- | Tag declaration
--
-- @
-- deductible
-- @
newtype TagDeclaration ann = TagDeclaration
{ tagDeclarationTag :: GenLocated ann Tag
}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (TagDeclaration ann)
type LPriceDeclaration = LLocated PriceDeclaration
-- | Price declaration
--
-- @
-- 2024-05-16 USD 1.00 CHF
-- @
data PriceDeclaration ann = PriceDeclaration
{ priceDeclarationTimestamp :: !(GenLocated ann Timestamp),
priceDeclarationCurrencySymbol :: !(GenLocated ann CurrencySymbol),
-- | How many olds for one new
-- This is of unit: old/new
priceDeclarationCost :: !(GenLocated ann (CostExpression ann))
}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (PriceDeclaration ann)
priceDeclarationCurrencySymbols :: PriceDeclaration ann -> Set CurrencySymbol
priceDeclarationCurrencySymbols PriceDeclaration {..} =
let Located _ ps = priceDeclarationCurrencySymbol
Located _ cd = priceDeclarationCost
Located _ cs = costExpressionCurrencySymbol cd
in S.fromList [ps, cs]
type LTransaction = LLocated Transaction
-- | Transaction
--
-- @
-- 2024-05-16
-- | Description
-- * assets -5 USD @ 1 EUR
-- * expenses -5 EUR
-- + attach receipt.pdf
-- + tag deductible
-- + assert assets = 0 USD
-- @
data Transaction ann = Transaction
{ transactionTimestamp :: !(GenLocated ann Timestamp),
transactionDescription :: !(Maybe (GenLocated ann Description)),
transactionPostings :: ![GenLocated ann (Posting ann)],
transactionExtras :: ![GenLocated ann (TransactionExtra ann)]
}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (Transaction ann)
transactionCurrencySymbols :: Transaction ann -> Set CurrencySymbol
transactionCurrencySymbols Transaction {..} =
S.unions $
map
( \(Located _ Posting {..}) ->
S.unions
[ S.singleton (locatedValue postingCurrencySymbol),
maybe
S.empty
( S.singleton
. locatedValue
. costExpressionCurrencySymbol
. locatedValue
)
postingCost
]
)
transactionPostings
type LPosting = LLocated Posting
-- | Posting
--
-- @
-- assets -5 USD @ 1 EUR
-- @
data Posting ann = Posting
{ postingReal :: !Bool,
postingAccountName :: !(GenLocated ann AccountName),
postingAccount :: !(GenLocated ann DecimalLiteral),
postingCurrencySymbol :: !(GenLocated ann CurrencySymbol),
postingCost :: !(Maybe (GenLocated ann (CostExpression ann))),
postingPercentage :: !(Maybe (GenLocated ann (PercentageExpression ann)))
}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (Posting ann)
type LCostExpression = LLocated CostExpression
-- | Cost expression
--
-- @
-- 1 EUR
-- @
--
-- or
--
-- @
-- 1 / 1 EUR
-- @
data CostExpression ann = CostExpression
{ costExpressionConversionRate :: !(GenLocated ann (RationalExpression ann)),
costExpressionCurrencySymbol :: !(GenLocated ann CurrencySymbol)
}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (CostExpression ann)
type LPercentageExpression = LLocated PercentageExpression
-- | Percentage expression
--
-- @
-- 50 %
-- @
--
-- or
--
-- @
-- 1 / 2 %
-- @
data PercentageExpression ann = PercentageExpression
{ percentageExpressionInclusive :: !(Maybe Bool),
percentageExpressionRounding :: !(Maybe Rounding),
percentageExpressionRationalExpression :: !(GenLocated ann (RationalExpression ann))
}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (PercentageExpression ann)
type LRationalExpression = LLocated RationalExpression
-- | Rational expression
--
-- @
-- 50 %
-- @
--
-- or
--
-- @
-- 1 / 2 %
-- @
data RationalExpression ann
= RationalExpressionDecimal !(GenLocated ann DecimalLiteral)
| RationalExpressionFraction
-- | Numerator
!(GenLocated ann DecimalLiteral)
-- | Denominator
!(GenLocated ann DecimalLiteral)
deriving stock (Show, Generic)
instance (Validity ann) => Validity (RationalExpression ann)
type LTransactionExtra = LLocated TransactionExtra
-- | Transaction extra
data TransactionExtra ann
= -- | Attachment
--
-- @
-- + attach receipt.pdf
-- @
TransactionAttachment (GenLocated ann (ExtraAttachment ann))
| -- | Assertion
--
-- @
-- + assert assets = 5 USD
-- @
TransactionAssertion (GenLocated ann (ExtraAssertion ann))
| -- | Tag
--
-- @
-- + tag deductible
-- @
TransactionTag (GenLocated ann (ExtraTag ann))
deriving stock (Show, Generic)
instance (Validity ann) => Validity (TransactionExtra ann)
-- | Attachmnet
--
-- @
-- attach receipt.pdf
-- @
type LExtraAttachment = LLocated ExtraAttachment
newtype ExtraAttachment ann = ExtraAttachment {unExtraAttachment :: GenLocated ann (Attachment ann)}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (ExtraAttachment ann)
type LAttachment = LLocated Attachment
-- | Attachmnet
--
-- @
-- receipt.pdf
-- @
newtype Attachment ann = Attachment {attachmentPath :: GenLocated ann (Path Rel File)}
deriving stock (Show, Eq, Generic)
instance (Validity ann) => Validity (Attachment ann)
type LExtraAssertion = LLocated ExtraAssertion
-- | Assertion
--
-- @
-- assert assets = 5 USD
-- @
newtype ExtraAssertion ann = ExtraAssertion {unExtraAssertion :: GenLocated ann (Assertion ann)}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (ExtraAssertion ann)
type LAssertion = LLocated Assertion
-- | Assertion
--
-- @
-- assets = 5 USD
-- @
data Assertion ann
= AssertionEquals
!(GenLocated ann AccountName)
!(GenLocated ann DecimalLiteral)
!(GenLocated ann CurrencySymbol)
deriving stock (Show, Generic)
instance (Validity ann) => Validity (Assertion ann)
type LExtraTag = LLocated ExtraTag
-- | Tag
--
-- @
-- tag deductible
-- @
newtype ExtraTag ann = ExtraTag {unExtraTag :: GenLocated ann Tag}
deriving stock (Show, Generic)
instance (Validity ann) => Validity (ExtraTag ann)
type LTag = Located Tag
instance HasCodec (Path Rel File) where
codec = bimapCodec (left show . parseRelFile) fromRelFile codec <?> "relative filepath"