2016-04-23 21:27:39 +03:00
|
|
|
--- * doc
|
2016-04-28 23:23:20 +03:00
|
|
|
-- Lines beginning "--- *" are collapsible orgstruct nodes. Emacs users,
|
2016-04-23 03:43:16 +03:00
|
|
|
-- (add-hook 'haskell-mode-hook
|
2016-04-23 21:27:39 +03:00
|
|
|
-- (lambda () (set-variable 'orgstruct-heading-prefix-regexp "--- " t))
|
2016-04-23 03:43:16 +03:00
|
|
|
-- 'orgstruct-mode)
|
2016-04-28 23:23:20 +03:00
|
|
|
-- and press TAB on nodes to expand/collapse.
|
2016-04-23 03:43:16 +03:00
|
|
|
|
2008-10-01 04:29:58 +04:00
|
|
|
{-|
|
2008-10-03 06:28:58 +04:00
|
|
|
|
2012-03-24 22:08:11 +04:00
|
|
|
A reader for hledger's journal file format
|
|
|
|
(<http://hledger.org/MANUAL.html#the-journal-file>). hledger's journal
|
|
|
|
format is a compatible subset of c++ ledger's
|
|
|
|
(<http://ledger-cli.org/3.0/doc/ledger3.html#Journal-Format>), so this
|
|
|
|
reader should handle many ledger files as well. Example:
|
2007-03-12 10:40:33 +03:00
|
|
|
|
2008-10-01 05:40:32 +04:00
|
|
|
@
|
2012-03-24 22:08:11 +04:00
|
|
|
2012\/3\/24 gift
|
|
|
|
expenses:gifts $10
|
|
|
|
assets:cash
|
2008-10-01 05:40:32 +04:00
|
|
|
@
|
2008-10-01 04:29:58 +04:00
|
|
|
|
2016-05-18 05:46:54 +03:00
|
|
|
Journal format supports the include directive which can read files in
|
|
|
|
other formats, so the other file format readers need to be importable
|
|
|
|
here. Some low-level journal syntax parsers which those readers also
|
|
|
|
use are therefore defined separately in Hledger.Read.Common, avoiding
|
|
|
|
import cycles.
|
|
|
|
|
2007-02-09 04:23:12 +03:00
|
|
|
-}
|
2007-07-04 16:05:54 +04:00
|
|
|
|
2016-04-23 21:27:39 +03:00
|
|
|
--- * module
|
|
|
|
|
2018-06-04 22:30:43 +03:00
|
|
|
{-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns, NoMonoLocalBinds, ScopedTypeVariables, FlexibleContexts, TupleSections, OverloadedStrings, PackageImports #-}
|
2016-04-23 03:43:16 +03:00
|
|
|
|
2010-11-15 10:01:46 +03:00
|
|
|
module Hledger.Read.JournalReader (
|
2016-04-28 23:23:20 +03:00
|
|
|
--- * exports
|
|
|
|
|
2012-03-24 22:08:11 +04:00
|
|
|
-- * Reader
|
|
|
|
reader,
|
2016-04-23 03:43:16 +03:00
|
|
|
|
|
|
|
-- * Parsing utils
|
2015-06-29 02:20:28 +03:00
|
|
|
genericSourcePos,
|
2016-04-23 03:43:16 +03:00
|
|
|
parseAndFinaliseJournal,
|
2016-05-07 02:58:07 +03:00
|
|
|
runJournalParser,
|
|
|
|
rjp,
|
2016-04-23 03:43:16 +03:00
|
|
|
|
|
|
|
-- * Parsers used elsewhere
|
2012-03-24 22:08:11 +04:00
|
|
|
getParentAccount,
|
2015-10-17 21:51:45 +03:00
|
|
|
journalp,
|
|
|
|
directivep,
|
|
|
|
defaultyeardirectivep,
|
|
|
|
marketpricedirectivep,
|
2014-02-06 06:55:38 +04:00
|
|
|
datetimep,
|
2016-02-20 02:14:25 +03:00
|
|
|
datep,
|
2016-05-23 10:32:55 +03:00
|
|
|
-- codep,
|
|
|
|
-- accountnamep,
|
2015-09-25 03:23:52 +03:00
|
|
|
modifiedaccountnamep,
|
2014-02-06 01:02:24 +04:00
|
|
|
postingp,
|
2016-05-23 10:32:55 +03:00
|
|
|
-- amountp,
|
|
|
|
-- amountp',
|
|
|
|
-- mamountp',
|
|
|
|
-- numberp,
|
2015-05-16 21:51:35 +03:00
|
|
|
statusp,
|
2014-02-27 23:47:36 +04:00
|
|
|
emptyorcommentlinep,
|
2018-04-18 01:58:53 +03:00
|
|
|
followingcommentp
|
2016-05-18 05:46:54 +03:00
|
|
|
|
2012-03-24 22:08:11 +04:00
|
|
|
-- * Tests
|
2015-06-11 20:13:27 +03:00
|
|
|
,tests_Hledger_Read_JournalReader
|
2016-05-18 05:46:54 +03:00
|
|
|
|
2010-05-31 05:15:18 +04:00
|
|
|
)
|
2010-03-13 02:46:20 +03:00
|
|
|
where
|
2016-04-23 21:27:39 +03:00
|
|
|
--- * imports
|
2015-04-23 10:39:29 +03:00
|
|
|
import Prelude ()
|
2018-06-05 02:28:28 +03:00
|
|
|
import "base-compat-batteries" Prelude.Compat hiding (readFile)
|
2012-03-30 01:19:35 +04:00
|
|
|
import qualified Control.Exception as C
|
2016-05-23 10:32:55 +03:00
|
|
|
import Control.Monad
|
2018-06-06 09:21:00 +03:00
|
|
|
import Control.Monad.Except (ExceptT(..))
|
2016-07-29 18:57:10 +03:00
|
|
|
import Control.Monad.State.Strict
|
2018-06-12 22:29:22 +03:00
|
|
|
import Data.Bifunctor (first)
|
2016-05-07 19:54:01 +03:00
|
|
|
import qualified Data.Map.Strict as M
|
lib: textification: parse stream
10% more allocation, but 35% lower maximum residency, and slightly quicker.
hledger -f data/100x100x10.journal stats
<<ghc: 39327768 bytes, 77 GCs, 196834/269496 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.010 elapsed), 0.020 MUT (0.092 elapsed), 0.014 GC (0.119 elapsed) :ghc>>
<<ghc: 42842136 bytes, 84 GCs, 194010/270912 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.009 elapsed), 0.016 MUT (0.029 elapsed), 0.012 GC (0.120 elapsed) :ghc>>
hledger -f data/1000x1000x10.journal stats
<<ghc: 314291440 bytes, 612 GCs, 2070776/6628048 avg/max bytes residency (7 samples), 16M in use, 0.000 INIT (0.000 elapsed), 0.128 MUT (0.144 elapsed), 0.059 GC (0.070 elapsed) :ghc>>
<<ghc: 349558872 bytes, 681 GCs, 1397597/4106384 avg/max bytes residency (7 samples), 11M in use, 0.000 INIT (0.004 elapsed), 0.124 MUT (0.133 elapsed), 0.047 GC (0.053 elapsed) :ghc>>
hledger -f data/10000x1000x10.journal stats
<<ghc: 3070026824 bytes, 5973 GCs, 12698030/62951784 avg/max bytes residency (10 samples), 124M in use, 0.000 INIT (0.002 elapsed), 1.268 MUT (1.354 elapsed), 0.514 GC (0.587 elapsed) :ghc>>
<<ghc: 3424013128 bytes, 6658 GCs, 11405501/41071624 avg/max bytes residency (11 samples), 111M in use, 0.000 INIT (0.001 elapsed), 1.343 MUT (1.406 elapsed), 0.511 GC (0.573 elapsed) :ghc>>
hledger -f data/100000x1000x10.journal stats
<<ghc: 30753387392 bytes, 59811 GCs, 117615462/666703600 avg/max bytes residency (14 samples), 1588M in use, 0.000 INIT (0.000 elapsed), 12.068 MUT (12.238 elapsed), 6.015 GC (7.190 elapsed) :ghc>>
<<ghc: 34306530696 bytes, 66727 GCs, 76806196/414629312 avg/max bytes residency (14 samples), 1009M in use, 0.000 INIT (0.010 elapsed), 14.357 MUT (16.370 elapsed), 5.298 GC (6.534 elapsed) :ghc>>
2016-05-25 01:58:23 +03:00
|
|
|
import Data.Text (Text)
|
2017-11-03 03:53:37 +03:00
|
|
|
import Data.String
|
|
|
|
import Data.List
|
lib: textification begins! account names
The first of several conversions from String to (strict) Text, hopefully
reducing space and time usage.
This one shows a small improvement, with GHC 7.10.3 and text-1.2.2.1:
hledger -f data/100x100x10.journal stats
string: <<ghc: 39471064 bytes, 77 GCs, 198421/275048 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.001 elapsed), 0.015 MUT (0.020 elapsed), 0.010 GC (0.014 elapsed) :ghc>>
text: <<ghc: 39268024 bytes, 77 GCs, 197018/270840 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.002 elapsed), 0.016 MUT (0.022 elapsed), 0.009 GC (0.011 elapsed) :ghc>>
hledger -f data/1000x100x10.journal stats
string: <<ghc: 318555920 bytes, 617 GCs, 2178997/7134472 avg/max bytes residency (7 samples), 16M in use, 0.000 INIT (0.001 elapsed), 0.129 MUT (0.136 elapsed), 0.067 GC (0.077 elapsed) :ghc>>
text: <<ghc: 314248496 bytes, 612 GCs, 2074045/6617960 avg/max bytes residency (7 samples), 16M in use, 0.000 INIT (0.003 elapsed), 0.137 MUT (0.145 elapsed), 0.067 GC (0.079 elapsed) :ghc>>
hledger -f data/10000x100x10.journal stats
string: <<ghc: 3114763608 bytes, 6026 GCs, 18858950/75552024 avg/max bytes residency (11 samples), 201M in use, 0.000 INIT (0.000 elapsed), 1.331 MUT (1.372 elapsed), 0.699 GC (0.812 elapsed) :ghc>>
text: <<ghc: 3071468920 bytes, 5968 GCs, 14120344/62951360 avg/max bytes residency (9 samples), 124M in use, 0.000 INIT (0.003 elapsed), 1.272 MUT (1.349 elapsed), 0.513 GC (0.578 elapsed) :ghc>>
hledger -f data/100000x100x10.journal stats
string: <<ghc: 31186579432 bytes, 60278 GCs, 135332581/740228992 avg/max bytes residency (13 samples), 1697M in use, 0.000 INIT (0.008 elapsed), 14.677 MUT (15.508 elapsed), 7.081 GC (8.074 elapsed) :ghc>>
text: <<ghc: 30753427672 bytes, 59763 GCs, 117595958/666457240 avg/max bytes residency (14 samples), 1588M in use, 0.000 INIT (0.008 elapsed), 13.713 MUT (13.966 elapsed), 6.220 GC (7.108 elapsed) :ghc>>
2016-05-24 04:16:21 +03:00
|
|
|
import qualified Data.Text as T
|
2011-05-28 08:11:44 +04:00
|
|
|
import Data.Time.Calendar
|
|
|
|
import Data.Time.LocalTime
|
2016-04-28 23:23:20 +03:00
|
|
|
import Safe
|
2015-06-11 20:13:27 +03:00
|
|
|
import Test.HUnit
|
2012-12-06 04:28:23 +04:00
|
|
|
#ifdef TESTS
|
|
|
|
import Test.Framework
|
2016-07-29 18:57:10 +03:00
|
|
|
import Text.Megaparsec.Error
|
2012-12-06 04:28:23 +04:00
|
|
|
#endif
|
2018-05-22 01:47:56 +03:00
|
|
|
import Text.Megaparsec hiding (parse)
|
|
|
|
import Text.Megaparsec.Char
|
2018-06-11 22:49:14 +03:00
|
|
|
import Text.Megaparsec.Custom
|
2011-05-28 08:11:44 +04:00
|
|
|
import Text.Printf
|
2012-03-24 22:08:11 +04:00
|
|
|
import System.FilePath
|
2018-07-24 20:53:13 +03:00
|
|
|
import "Glob" System.FilePath.Glob hiding (match)
|
2010-11-15 10:18:35 +03:00
|
|
|
|
|
|
|
import Hledger.Data
|
2016-05-18 05:46:54 +03:00
|
|
|
import Hledger.Read.Common
|
|
|
|
import Hledger.Read.TimeclockReader (timeclockfilep)
|
|
|
|
import Hledger.Read.TimedotReader (timedotfilep)
|
2011-05-28 08:11:44 +04:00
|
|
|
import Hledger.Utils
|
2010-03-13 02:46:20 +03:00
|
|
|
|
2016-05-25 04:28:26 +03:00
|
|
|
-- $setup
|
|
|
|
-- >>> :set -XOverloadedStrings
|
2010-03-13 02:46:20 +03:00
|
|
|
|
2016-04-23 21:27:39 +03:00
|
|
|
--- * reader
|
2010-03-13 02:46:20 +03:00
|
|
|
|
2010-06-25 18:56:48 +04:00
|
|
|
reader :: Reader
|
2016-11-19 00:24:57 +03:00
|
|
|
reader = Reader
|
|
|
|
{rFormat = "journal"
|
|
|
|
,rExtensions = ["journal", "j", "hledger", "ledger"]
|
|
|
|
,rParser = parse
|
2016-11-20 21:42:12 +03:00
|
|
|
,rExperimental = False
|
2016-11-19 00:24:57 +03:00
|
|
|
}
|
2010-06-25 18:56:48 +04:00
|
|
|
|
2010-05-30 23:11:58 +04:00
|
|
|
-- | Parse and post-process a "Journal" from hledger's journal file
|
2010-05-31 05:15:18 +04:00
|
|
|
-- format, or give an error.
|
2018-04-17 00:47:04 +03:00
|
|
|
parse :: InputOpts -> FilePath -> Text -> ExceptT String IO Journal
|
2018-04-18 01:58:53 +03:00
|
|
|
parse iopts = parseAndFinaliseJournal journalp' iopts
|
|
|
|
where
|
|
|
|
journalp' = do
|
|
|
|
-- reverse parsed aliases to ensure that they are applied in order given on commandline
|
|
|
|
mapM_ addAccountAlias (reverse $ aliasesFromOpts iopts)
|
|
|
|
journalp
|
|
|
|
|
|
|
|
-- | Get the account name aliases from options, if any.
|
|
|
|
aliasesFromOpts :: InputOpts -> [AccountAlias]
|
|
|
|
aliasesFromOpts = map (\a -> fromparse $ runParser accountaliasp ("--alias "++quoteIfNeeded a) $ T.pack a)
|
|
|
|
. aliases_
|
2010-03-13 02:46:20 +03:00
|
|
|
|
2016-04-23 21:27:39 +03:00
|
|
|
--- * parsers
|
|
|
|
--- ** journal
|
2012-03-24 22:08:11 +04:00
|
|
|
|
2016-05-23 10:32:55 +03:00
|
|
|
-- | A journal parser. Accumulates and returns a "ParsedJournal",
|
|
|
|
-- which should be finalised/validated before use.
|
|
|
|
--
|
2018-06-06 21:21:17 +03:00
|
|
|
-- >>> rjp (journalp <* eof) "2015/1/1\n a 0\n"
|
2016-05-23 10:32:55 +03:00
|
|
|
-- Right Journal with 1 transactions, 1 accounts
|
|
|
|
--
|
2018-06-06 09:29:52 +03:00
|
|
|
journalp :: MonadIO m => JournalParser m ParsedJournal
|
2015-10-17 21:51:45 +03:00
|
|
|
journalp = do
|
2016-05-23 10:32:55 +03:00
|
|
|
many addJournalItemP
|
2010-11-13 18:03:40 +03:00
|
|
|
eof
|
2016-07-29 18:57:10 +03:00
|
|
|
get
|
2016-05-23 10:32:55 +03:00
|
|
|
|
|
|
|
-- | A side-effecting parser; parses any kind of journal item
|
|
|
|
-- and updates the parse state accordingly.
|
2018-06-06 09:29:52 +03:00
|
|
|
addJournalItemP :: MonadIO m => JournalParser m ()
|
lib: textification: comments and tags
No change.
hledger -f data/100x100x10.journal stats
<<ghc: 42859576 bytes, 84 GCs, 193781/269984 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.001 elapsed), 0.016 MUT (0.020 elapsed), 0.009 GC (0.011 elapsed) :ghc>>
<<ghc: 42859576 bytes, 84 GCs, 193781/269984 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.001 elapsed), 0.015 MUT (0.018 elapsed), 0.009 GC (0.013 elapsed) :ghc>>
hledger -f data/1000x1000x10.journal stats
<<ghc: 349576344 bytes, 681 GCs, 1407388/4091680 avg/max bytes residency (7 samples), 11M in use, 0.000 INIT (0.000 elapsed), 0.124 MUT (0.130 elapsed), 0.047 GC (0.055 elapsed) :ghc>>
<<ghc: 349576280 bytes, 681 GCs, 1407388/4091680 avg/max bytes residency (7 samples), 11M in use, 0.000 INIT (0.000 elapsed), 0.126 MUT (0.132 elapsed), 0.049 GC (0.058 elapsed) :ghc>>
hledger -f data/10000x1000x10.journal stats
<<ghc: 3424030664 bytes, 6658 GCs, 11403359/41071624 avg/max bytes residency (11 samples), 111M in use, 0.000 INIT (0.000 elapsed), 1.207 MUT (1.228 elapsed), 0.473 GC (0.528 elapsed) :ghc>>
<<ghc: 3424030760 bytes, 6658 GCs, 11403874/41077288 avg/max bytes residency (11 samples), 111M in use, 0.000 INIT (0.002 elapsed), 1.234 MUT (1.256 elapsed), 0.470 GC (0.520 elapsed) :ghc>>
hledger -f data/100000x1000x10.journal stats
<<ghc: 34306547448 bytes, 66727 GCs, 76805504/414629288 avg/max bytes residency (14 samples), 1009M in use, 0.000 INIT (0.003 elapsed), 12.615 MUT (12.813 elapsed), 4.656 GC (5.291 elapsed) :ghc>>
<<ghc: 34306547320 bytes, 66727 GCs, 76805504/414629288 avg/max bytes residency (14 samples), 1009M in use, 0.000 INIT (0.009 elapsed), 12.802 MUT (13.065 elapsed), 4.774 GC (5.441 elapsed) :ghc>>
2016-05-25 03:09:20 +03:00
|
|
|
addJournalItemP =
|
2016-05-23 10:32:55 +03:00
|
|
|
-- all journal line types can be distinguished by the first
|
|
|
|
-- character, can use choice without backtracking
|
|
|
|
choice [
|
|
|
|
directivep
|
2016-07-29 18:57:10 +03:00
|
|
|
, transactionp >>= modify' . addTransaction
|
|
|
|
, modifiertransactionp >>= modify' . addModifierTransaction
|
|
|
|
, periodictransactionp >>= modify' . addPeriodicTransaction
|
|
|
|
, marketpricedirectivep >>= modify' . addMarketPrice
|
2018-05-16 03:59:49 +03:00
|
|
|
, void (lift emptyorcommentlinep)
|
|
|
|
, void (lift multilinecommentp)
|
2016-05-23 10:32:55 +03:00
|
|
|
] <?> "transaction or directive"
|
2010-09-23 03:02:19 +04:00
|
|
|
|
2016-04-23 21:27:39 +03:00
|
|
|
--- ** directives
|
2016-04-23 03:43:16 +03:00
|
|
|
|
2016-05-23 10:32:55 +03:00
|
|
|
-- | Parse any journal directive and update the parse state accordingly.
|
|
|
|
-- Cf http://hledger.org/manual.html#directives,
|
|
|
|
-- http://ledger-cli.org/3.0/doc/ledger3.html#Command-Directives
|
2018-06-06 09:29:52 +03:00
|
|
|
directivep :: MonadIO m => JournalParser m ()
|
2016-05-27 01:51:59 +03:00
|
|
|
directivep = (do
|
2011-08-03 03:28:53 +04:00
|
|
|
optional $ char '!'
|
2017-11-03 03:53:37 +03:00
|
|
|
choice [
|
2015-10-17 21:51:45 +03:00
|
|
|
includedirectivep
|
|
|
|
,aliasdirectivep
|
|
|
|
,endaliasesdirectivep
|
|
|
|
,accountdirectivep
|
2016-04-04 20:18:59 +03:00
|
|
|
,applyaccountdirectivep
|
2016-05-07 19:54:01 +03:00
|
|
|
,commoditydirectivep
|
2016-04-04 20:18:59 +03:00
|
|
|
,endapplyaccountdirectivep
|
2015-10-17 21:51:45 +03:00
|
|
|
,tagdirectivep
|
|
|
|
,endtagdirectivep
|
|
|
|
,defaultyeardirectivep
|
|
|
|
,defaultcommoditydirectivep
|
|
|
|
,commodityconversiondirectivep
|
|
|
|
,ignoredpricecommoditydirectivep
|
2011-08-03 03:28:53 +04:00
|
|
|
]
|
2016-05-27 01:51:59 +03:00
|
|
|
) <?> "directive"
|
2010-03-13 02:46:20 +03:00
|
|
|
|
2018-06-06 09:29:52 +03:00
|
|
|
includedirectivep :: MonadIO m => JournalParser m ()
|
2015-10-17 21:51:45 +03:00
|
|
|
includedirectivep = do
|
2011-08-04 11:49:10 +04:00
|
|
|
string "include"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2018-07-23 14:48:45 +03:00
|
|
|
filename <- T.unpack <$> takeWhileP Nothing (/= '\n') -- don't consume newline yet
|
2018-06-06 09:21:00 +03:00
|
|
|
|
|
|
|
parentpos <- getPosition
|
|
|
|
|
2018-07-24 20:41:50 +03:00
|
|
|
filepaths <- getFilePaths parentpos filename
|
2018-07-23 12:39:01 +03:00
|
|
|
|
2018-07-23 12:30:10 +03:00
|
|
|
forM_ filepaths $ parseChild parentpos
|
2018-06-06 09:21:00 +03:00
|
|
|
|
|
|
|
void newline
|
|
|
|
|
2018-07-23 12:30:10 +03:00
|
|
|
where
|
2018-07-24 20:41:50 +03:00
|
|
|
getFilePaths parserpos filename = do
|
|
|
|
curdir <- lift $ expandPath (takeDirectory $ sourceName parserpos) ""
|
|
|
|
`orRethrowIOError` (show parserpos ++ " locating " ++ filename)
|
2018-07-23 15:36:45 +03:00
|
|
|
-- Compiling filename as a glob pattern works even if it is a literal
|
|
|
|
fileglob <- case tryCompileWith compDefault{errorRecovery=False} filename of
|
|
|
|
Right x -> pure x
|
2018-07-24 20:41:50 +03:00
|
|
|
Left e -> parseErrorAt parserpos $ "Invalid glob pattern: " ++ e
|
2018-07-24 23:05:38 +03:00
|
|
|
-- Get all matching files in the current working directory, sorting in
|
|
|
|
-- lexicographic order to simulate the output of 'ls'.
|
|
|
|
filepaths <- liftIO $ sort <$> globDir1 fileglob curdir
|
2018-07-23 15:36:45 +03:00
|
|
|
if (not . null) filepaths
|
|
|
|
then pure filepaths
|
2018-07-24 20:41:50 +03:00
|
|
|
else parseErrorAt parserpos$ "No existing files match pattern: " ++ filename
|
2018-07-23 15:36:45 +03:00
|
|
|
|
2018-07-23 12:30:10 +03:00
|
|
|
parseChild parentpos filepath = do
|
|
|
|
childInput <- lift $ readFilePortably filepath
|
|
|
|
`orRethrowIOError` (show parentpos ++ " reading " ++ filepath)
|
|
|
|
|
|
|
|
-- save parent state
|
|
|
|
parentParserState <- getParserState
|
|
|
|
parentj <- get
|
|
|
|
|
|
|
|
let childj = newJournalWithParseStateFrom parentj
|
|
|
|
|
|
|
|
-- set child state
|
|
|
|
setInput childInput
|
|
|
|
pushPosition $ initialPos filepath
|
|
|
|
put childj
|
|
|
|
|
|
|
|
-- parse include file
|
|
|
|
let parsers = [ journalp
|
|
|
|
, timeclockfilep
|
|
|
|
, timedotfilep
|
|
|
|
] -- can't include a csv file yet, that reader is special
|
|
|
|
updatedChildj <- journalAddFile (filepath, childInput) <$>
|
|
|
|
region (withSource childInput) (choiceInState parsers)
|
|
|
|
|
|
|
|
-- restore parent state, prepending the child's parse info
|
|
|
|
setParserState parentParserState
|
|
|
|
put $ updatedChildj <> parentj
|
|
|
|
-- discard child's parse info, prepend its (reversed) list data, combine other fields
|
|
|
|
|
2016-05-23 11:02:19 +03:00
|
|
|
|
|
|
|
newJournalWithParseStateFrom :: Journal -> Journal
|
|
|
|
newJournalWithParseStateFrom j = mempty{
|
|
|
|
jparsedefaultyear = jparsedefaultyear j
|
|
|
|
,jparsedefaultcommodity = jparsedefaultcommodity j
|
|
|
|
,jparseparentaccounts = jparseparentaccounts j
|
|
|
|
,jparsealiases = jparsealiases j
|
2017-12-04 22:48:20 +03:00
|
|
|
,jcommodities = jcommodities j
|
2016-08-14 22:44:19 +03:00
|
|
|
-- ,jparsetransactioncount = jparsetransactioncount j
|
2016-05-23 11:02:19 +03:00
|
|
|
,jparsetimeclockentries = jparsetimeclockentries j
|
|
|
|
}
|
2016-05-23 10:32:55 +03:00
|
|
|
|
|
|
|
-- | Lift an IO action into the exception monad, rethrowing any IO
|
|
|
|
-- error with the given message prepended.
|
2018-06-06 09:21:00 +03:00
|
|
|
orRethrowIOError :: MonadIO m => IO a -> String -> TextParser m a
|
|
|
|
orRethrowIOError io msg = do
|
|
|
|
eResult <- liftIO $ (Right <$> io) `C.catch` \(e::C.IOException) -> pure $ Left $ printf "%s:\n%s" msg (show e)
|
|
|
|
case eResult of
|
|
|
|
Right res -> pure res
|
|
|
|
Left errMsg -> fail errMsg
|
2016-05-23 10:32:55 +03:00
|
|
|
|
2017-07-27 14:59:55 +03:00
|
|
|
accountdirectivep :: JournalParser m ()
|
2015-10-17 21:51:45 +03:00
|
|
|
accountdirectivep = do
|
2016-04-04 20:18:59 +03:00
|
|
|
string "account"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2018-06-27 13:26:08 +03:00
|
|
|
acct <- modifiedaccountnamep -- account directives can be modified by alias/apply account
|
2018-03-25 16:53:44 +03:00
|
|
|
macode' :: Maybe String <- (optional $ lift $ skipSome spacenonewline >> some digitChar)
|
2018-01-19 23:37:46 +03:00
|
|
|
let macode :: Maybe AccountCode = read <$> macode'
|
2016-04-04 20:18:59 +03:00
|
|
|
newline
|
2018-06-06 08:44:02 +03:00
|
|
|
skipMany indentedlinep
|
2018-01-19 23:37:46 +03:00
|
|
|
|
|
|
|
modify' (\j -> j{jaccounts = (acct, macode) : jaccounts j})
|
2016-12-10 02:57:17 +03:00
|
|
|
|
2017-07-27 14:59:55 +03:00
|
|
|
indentedlinep :: JournalParser m String
|
2018-03-25 16:53:44 +03:00
|
|
|
indentedlinep = lift (skipSome spacenonewline) >> (rstrip <$> lift restofline)
|
2016-05-07 19:54:01 +03:00
|
|
|
|
2016-05-09 06:56:34 +03:00
|
|
|
-- | Parse a one-line or multi-line commodity directive.
|
|
|
|
--
|
2018-06-06 21:21:17 +03:00
|
|
|
-- >>> Right _ <- rjp commoditydirectivep "commodity $1.00"
|
|
|
|
-- >>> Right _ <- rjp commoditydirectivep "commodity $\n format $1.00"
|
|
|
|
-- >>> Right _ <- rjp commoditydirectivep "commodity $\n\n" -- a commodity with no format
|
|
|
|
-- >>> Right _ <- rjp commoditydirectivep "commodity $1.00\n format $1.00" -- both, what happens ?
|
2018-06-06 08:52:28 +03:00
|
|
|
commoditydirectivep :: JournalParser m ()
|
2018-06-11 23:29:18 +03:00
|
|
|
commoditydirectivep = commoditydirectiveonelinep <|> commoditydirectivemultilinep
|
2016-05-09 06:56:34 +03:00
|
|
|
|
|
|
|
-- | Parse a one-line commodity directive.
|
|
|
|
--
|
2018-06-06 21:21:17 +03:00
|
|
|
-- >>> Right _ <- rjp commoditydirectiveonelinep "commodity $1.00"
|
|
|
|
-- >>> Right _ <- rjp commoditydirectiveonelinep "commodity $1.00 ; blah\n"
|
2018-06-06 08:52:28 +03:00
|
|
|
commoditydirectiveonelinep :: JournalParser m ()
|
2016-05-09 06:56:34 +03:00
|
|
|
commoditydirectiveonelinep = do
|
2018-06-11 23:29:18 +03:00
|
|
|
(pos, Amount{acommodity,astyle}) <- try $ do
|
|
|
|
string "commodity"
|
|
|
|
lift (skipSome spacenonewline)
|
|
|
|
pos <- getPosition
|
|
|
|
amount <- amountp
|
|
|
|
pure $ (pos, amount)
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipMany spacenonewline)
|
2018-05-16 23:18:10 +03:00
|
|
|
_ <- lift followingcommentp
|
2018-04-21 07:56:06 +03:00
|
|
|
let comm = Commodity{csymbol=acommodity, cformat=Just $ dbg2 "style from commodity directive" astyle}
|
|
|
|
if asdecimalpoint astyle == Nothing
|
2018-06-05 23:25:30 +03:00
|
|
|
then parseErrorAt pos pleaseincludedecimalpoint
|
2018-04-21 07:56:06 +03:00
|
|
|
else modify' (\j -> j{jcommodities=M.insert acommodity comm $ jcommodities j})
|
|
|
|
|
|
|
|
pleaseincludedecimalpoint :: String
|
|
|
|
pleaseincludedecimalpoint = "to avoid ambiguity, please include a decimal point in commodity directives"
|
2016-05-09 06:56:34 +03:00
|
|
|
|
|
|
|
-- | Parse a multi-line commodity directive, containing 0 or more format subdirectives.
|
|
|
|
--
|
2018-06-06 21:21:17 +03:00
|
|
|
-- >>> Right _ <- rjp commoditydirectivemultilinep "commodity $ ; blah \n format $1.00 ; blah"
|
2018-06-06 08:52:28 +03:00
|
|
|
commoditydirectivemultilinep :: JournalParser m ()
|
2016-05-09 06:56:34 +03:00
|
|
|
commoditydirectivemultilinep = do
|
2016-05-07 19:54:01 +03:00
|
|
|
string "commodity"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2016-07-29 18:57:10 +03:00
|
|
|
sym <- lift commoditysymbolp
|
2018-05-16 23:18:10 +03:00
|
|
|
_ <- lift followingcommentp
|
2016-05-07 19:54:01 +03:00
|
|
|
mformat <- lastMay <$> many (indented $ formatdirectivep sym)
|
|
|
|
let comm = Commodity{csymbol=sym, cformat=mformat}
|
2016-07-29 18:57:10 +03:00
|
|
|
modify' (\j -> j{jcommodities=M.insert sym comm $ jcommodities j})
|
2016-05-23 10:32:55 +03:00
|
|
|
where
|
2018-03-25 16:53:44 +03:00
|
|
|
indented = (lift (skipSome spacenonewline) >>)
|
2016-05-07 19:54:01 +03:00
|
|
|
|
|
|
|
-- | Parse a format (sub)directive, throwing a parse error if its
|
|
|
|
-- symbol does not match the one given.
|
2018-06-06 08:52:28 +03:00
|
|
|
formatdirectivep :: CommoditySymbol -> JournalParser m AmountStyle
|
2016-05-07 19:54:01 +03:00
|
|
|
formatdirectivep expectedsym = do
|
|
|
|
string "format"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2016-05-07 19:54:01 +03:00
|
|
|
pos <- getPosition
|
|
|
|
Amount{acommodity,astyle} <- amountp
|
2018-05-16 23:18:10 +03:00
|
|
|
_ <- lift followingcommentp
|
2016-05-07 19:54:01 +03:00
|
|
|
if acommodity==expectedsym
|
2018-04-21 07:56:06 +03:00
|
|
|
then
|
|
|
|
if asdecimalpoint astyle == Nothing
|
2018-06-05 23:25:30 +03:00
|
|
|
then parseErrorAt pos pleaseincludedecimalpoint
|
2018-04-21 07:56:06 +03:00
|
|
|
else return $ dbg2 "style from format subdirective" astyle
|
2018-06-05 23:25:30 +03:00
|
|
|
else parseErrorAt pos $
|
2016-05-07 19:54:01 +03:00
|
|
|
printf "commodity directive symbol \"%s\" and format directive symbol \"%s\" should be the same" expectedsym acommodity
|
|
|
|
|
2017-11-03 03:53:37 +03:00
|
|
|
keywordp :: String -> JournalParser m ()
|
|
|
|
keywordp = (() <$) . string . fromString
|
|
|
|
|
|
|
|
spacesp :: JournalParser m ()
|
2018-03-25 16:53:44 +03:00
|
|
|
spacesp = () <$ lift (skipSome spacenonewline)
|
2017-11-03 03:53:37 +03:00
|
|
|
|
|
|
|
-- | Backtracking parser similar to string, but allows varying amount of space between words
|
|
|
|
keywordsp :: String -> JournalParser m ()
|
|
|
|
keywordsp = try . sequence_ . intersperse spacesp . map keywordp . words
|
|
|
|
|
2017-07-27 14:59:55 +03:00
|
|
|
applyaccountdirectivep :: JournalParser m ()
|
2016-04-04 20:18:59 +03:00
|
|
|
applyaccountdirectivep = do
|
2017-11-03 03:53:37 +03:00
|
|
|
keywordsp "apply account" <?> "apply account directive"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2016-07-29 18:57:10 +03:00
|
|
|
parent <- lift accountnamep
|
2011-08-04 11:49:10 +04:00
|
|
|
newline
|
|
|
|
pushParentAccount parent
|
2010-03-13 02:46:20 +03:00
|
|
|
|
2017-07-27 14:59:55 +03:00
|
|
|
endapplyaccountdirectivep :: JournalParser m ()
|
2016-04-04 20:18:59 +03:00
|
|
|
endapplyaccountdirectivep = do
|
2017-11-03 03:53:37 +03:00
|
|
|
keywordsp "end apply account" <?> "end apply account directive"
|
2011-08-04 11:49:10 +04:00
|
|
|
popParentAccount
|
2010-03-13 02:46:20 +03:00
|
|
|
|
2017-07-27 14:59:55 +03:00
|
|
|
aliasdirectivep :: JournalParser m ()
|
2015-10-17 21:51:45 +03:00
|
|
|
aliasdirectivep = do
|
2011-08-04 12:45:18 +04:00
|
|
|
string "alias"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2016-07-29 18:57:10 +03:00
|
|
|
alias <- lift accountaliasp
|
2015-05-14 22:50:32 +03:00
|
|
|
addAccountAlias alias
|
2011-08-04 12:45:18 +04:00
|
|
|
|
2016-07-29 18:57:10 +03:00
|
|
|
accountaliasp :: TextParser m AccountAlias
|
2015-05-14 22:50:32 +03:00
|
|
|
accountaliasp = regexaliasp <|> basicaliasp
|
|
|
|
|
2016-07-29 18:57:10 +03:00
|
|
|
basicaliasp :: TextParser m AccountAlias
|
2015-05-14 22:50:32 +03:00
|
|
|
basicaliasp = do
|
2018-07-16 17:28:58 +03:00
|
|
|
-- dbgparse 0 "basicaliasp"
|
2016-07-29 18:57:10 +03:00
|
|
|
old <- rstrip <$> (some $ noneOf ("=" :: [Char]))
|
2015-05-14 22:50:32 +03:00
|
|
|
char '='
|
2018-03-25 16:53:44 +03:00
|
|
|
skipMany spacenonewline
|
2017-09-23 05:43:03 +03:00
|
|
|
new <- rstrip <$> anyChar `manyTill` eolof -- eol in journal, eof in command lines, normally
|
lib: textification begins! account names
The first of several conversions from String to (strict) Text, hopefully
reducing space and time usage.
This one shows a small improvement, with GHC 7.10.3 and text-1.2.2.1:
hledger -f data/100x100x10.journal stats
string: <<ghc: 39471064 bytes, 77 GCs, 198421/275048 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.001 elapsed), 0.015 MUT (0.020 elapsed), 0.010 GC (0.014 elapsed) :ghc>>
text: <<ghc: 39268024 bytes, 77 GCs, 197018/270840 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.002 elapsed), 0.016 MUT (0.022 elapsed), 0.009 GC (0.011 elapsed) :ghc>>
hledger -f data/1000x100x10.journal stats
string: <<ghc: 318555920 bytes, 617 GCs, 2178997/7134472 avg/max bytes residency (7 samples), 16M in use, 0.000 INIT (0.001 elapsed), 0.129 MUT (0.136 elapsed), 0.067 GC (0.077 elapsed) :ghc>>
text: <<ghc: 314248496 bytes, 612 GCs, 2074045/6617960 avg/max bytes residency (7 samples), 16M in use, 0.000 INIT (0.003 elapsed), 0.137 MUT (0.145 elapsed), 0.067 GC (0.079 elapsed) :ghc>>
hledger -f data/10000x100x10.journal stats
string: <<ghc: 3114763608 bytes, 6026 GCs, 18858950/75552024 avg/max bytes residency (11 samples), 201M in use, 0.000 INIT (0.000 elapsed), 1.331 MUT (1.372 elapsed), 0.699 GC (0.812 elapsed) :ghc>>
text: <<ghc: 3071468920 bytes, 5968 GCs, 14120344/62951360 avg/max bytes residency (9 samples), 124M in use, 0.000 INIT (0.003 elapsed), 1.272 MUT (1.349 elapsed), 0.513 GC (0.578 elapsed) :ghc>>
hledger -f data/100000x100x10.journal stats
string: <<ghc: 31186579432 bytes, 60278 GCs, 135332581/740228992 avg/max bytes residency (13 samples), 1697M in use, 0.000 INIT (0.008 elapsed), 14.677 MUT (15.508 elapsed), 7.081 GC (8.074 elapsed) :ghc>>
text: <<ghc: 30753427672 bytes, 59763 GCs, 117595958/666457240 avg/max bytes residency (14 samples), 1588M in use, 0.000 INIT (0.008 elapsed), 13.713 MUT (13.966 elapsed), 6.220 GC (7.108 elapsed) :ghc>>
2016-05-24 04:16:21 +03:00
|
|
|
return $ BasicAlias (T.pack old) (T.pack new)
|
2015-05-14 22:50:32 +03:00
|
|
|
|
2016-07-29 18:57:10 +03:00
|
|
|
regexaliasp :: TextParser m AccountAlias
|
2015-05-14 22:50:32 +03:00
|
|
|
regexaliasp = do
|
2018-07-16 17:28:58 +03:00
|
|
|
-- dbgparse 0 "regexaliasp"
|
2015-05-14 22:50:32 +03:00
|
|
|
char '/'
|
2016-07-29 18:57:10 +03:00
|
|
|
re <- some $ noneOf ("/\n\r" :: [Char]) -- paranoid: don't try to read past line end
|
2015-05-14 22:50:32 +03:00
|
|
|
char '/'
|
2018-03-25 16:53:44 +03:00
|
|
|
skipMany spacenonewline
|
2015-05-14 22:50:32 +03:00
|
|
|
char '='
|
2018-03-25 16:53:44 +03:00
|
|
|
skipMany spacenonewline
|
2017-09-23 05:43:03 +03:00
|
|
|
repl <- anyChar `manyTill` eolof
|
2015-05-14 22:50:32 +03:00
|
|
|
return $ RegexAlias re repl
|
|
|
|
|
2017-07-27 14:59:55 +03:00
|
|
|
endaliasesdirectivep :: JournalParser m ()
|
2015-10-17 21:51:45 +03:00
|
|
|
endaliasesdirectivep = do
|
2017-11-03 03:53:37 +03:00
|
|
|
keywordsp "end aliases" <?> "end aliases directive"
|
2011-08-04 12:45:18 +04:00
|
|
|
clearAccountAliases
|
|
|
|
|
2017-07-27 14:59:55 +03:00
|
|
|
tagdirectivep :: JournalParser m ()
|
2015-10-17 21:51:45 +03:00
|
|
|
tagdirectivep = do
|
2011-08-04 11:49:10 +04:00
|
|
|
string "tag" <?> "tag directive"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2016-07-29 18:57:10 +03:00
|
|
|
_ <- lift $ some nonspace
|
|
|
|
lift restofline
|
2016-05-23 10:32:55 +03:00
|
|
|
return ()
|
2007-02-09 04:23:12 +03:00
|
|
|
|
2017-07-27 14:59:55 +03:00
|
|
|
endtagdirectivep :: JournalParser m ()
|
2015-10-17 21:51:45 +03:00
|
|
|
endtagdirectivep = do
|
2017-11-03 03:53:37 +03:00
|
|
|
(keywordsp "end tag" <|> keywordp "pop") <?> "end tag or pop directive"
|
2016-07-29 18:57:10 +03:00
|
|
|
lift restofline
|
2016-05-23 10:32:55 +03:00
|
|
|
return ()
|
2011-08-04 11:49:10 +04:00
|
|
|
|
2017-07-27 14:59:55 +03:00
|
|
|
defaultyeardirectivep :: JournalParser m ()
|
2015-10-17 21:51:45 +03:00
|
|
|
defaultyeardirectivep = do
|
2011-08-04 11:49:10 +04:00
|
|
|
char 'Y' <?> "default year"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipMany spacenonewline)
|
2016-07-29 18:57:10 +03:00
|
|
|
y <- some digitChar
|
2011-08-04 11:49:10 +04:00
|
|
|
let y' = read y
|
|
|
|
failIfInvalidYear y
|
|
|
|
setYear y'
|
|
|
|
|
2018-06-06 08:52:28 +03:00
|
|
|
defaultcommoditydirectivep :: JournalParser m ()
|
2015-10-17 21:51:45 +03:00
|
|
|
defaultcommoditydirectivep = do
|
2011-08-04 11:49:10 +04:00
|
|
|
char 'D' <?> "default commodity"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2018-04-21 07:56:06 +03:00
|
|
|
pos <- getPosition
|
|
|
|
Amount{acommodity,astyle} <- amountp
|
2016-07-29 18:57:10 +03:00
|
|
|
lift restofline
|
2018-04-21 07:56:06 +03:00
|
|
|
if asdecimalpoint astyle == Nothing
|
2018-06-05 23:25:30 +03:00
|
|
|
then parseErrorAt pos pleaseincludedecimalpoint
|
2018-04-21 07:56:06 +03:00
|
|
|
else setDefaultCommodityAndStyle (acommodity, astyle)
|
2007-02-09 04:23:12 +03:00
|
|
|
|
2018-06-06 08:52:28 +03:00
|
|
|
marketpricedirectivep :: JournalParser m MarketPrice
|
2015-10-17 21:51:45 +03:00
|
|
|
marketpricedirectivep = do
|
2015-08-10 02:20:02 +03:00
|
|
|
char 'P' <?> "market price"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipMany spacenonewline)
|
2014-08-08 18:27:32 +04:00
|
|
|
date <- try (do {LocalTime d _ <- datetimep; return d}) <|> datep -- a time is ignored
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2016-07-29 18:57:10 +03:00
|
|
|
symbol <- lift commoditysymbolp
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipMany spacenonewline)
|
2012-11-20 01:20:10 +04:00
|
|
|
price <- amountp
|
2016-07-29 18:57:10 +03:00
|
|
|
lift restofline
|
2015-08-10 02:20:02 +03:00
|
|
|
return $ MarketPrice date symbol price
|
2008-12-16 13:54:20 +03:00
|
|
|
|
2017-07-27 14:59:55 +03:00
|
|
|
ignoredpricecommoditydirectivep :: JournalParser m ()
|
2015-10-17 21:51:45 +03:00
|
|
|
ignoredpricecommoditydirectivep = do
|
2010-03-13 01:52:57 +03:00
|
|
|
char 'N' <?> "ignored-price commodity"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2016-07-29 18:57:10 +03:00
|
|
|
lift commoditysymbolp
|
|
|
|
lift restofline
|
2016-05-23 10:32:55 +03:00
|
|
|
return ()
|
2010-03-13 01:52:57 +03:00
|
|
|
|
2018-06-06 08:52:28 +03:00
|
|
|
commodityconversiondirectivep :: JournalParser m ()
|
2015-10-17 21:51:45 +03:00
|
|
|
commodityconversiondirectivep = do
|
2010-03-13 04:10:10 +03:00
|
|
|
char 'C' <?> "commodity conversion"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipSome spacenonewline)
|
2012-11-20 01:20:10 +04:00
|
|
|
amountp
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipMany spacenonewline)
|
2010-03-13 04:10:10 +03:00
|
|
|
char '='
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipMany spacenonewline)
|
2012-11-20 01:20:10 +04:00
|
|
|
amountp
|
2016-07-29 18:57:10 +03:00
|
|
|
lift restofline
|
2016-05-23 10:32:55 +03:00
|
|
|
return ()
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2016-04-23 21:27:39 +03:00
|
|
|
--- ** transactions
|
2016-04-23 03:43:16 +03:00
|
|
|
|
2018-07-26 10:29:02 +03:00
|
|
|
-- TODO transactionmodifierp ? transactionrewritep ?
|
2018-06-06 08:52:28 +03:00
|
|
|
modifiertransactionp :: JournalParser m ModifierTransaction
|
2015-10-17 21:51:45 +03:00
|
|
|
modifiertransactionp = do
|
2011-08-04 11:49:10 +04:00
|
|
|
char '=' <?> "modifier transaction"
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipMany spacenonewline)
|
2018-07-26 10:29:02 +03:00
|
|
|
querytxt <- T.pack <$> lift restofline -- TODO should not consume a same-line comment
|
|
|
|
(_cmt, _tags) <- lift transactioncommentp -- TODO apply these to modified txns ?
|
2016-04-28 23:23:20 +03:00
|
|
|
postings <- postingsp Nothing
|
2018-07-26 10:29:02 +03:00
|
|
|
return $ ModifierTransaction querytxt postings
|
2009-01-23 02:42:34 +03:00
|
|
|
|
2018-05-15 03:50:35 +03:00
|
|
|
-- | Parse a periodic transaction
|
2018-06-09 05:45:29 +03:00
|
|
|
periodictransactionp :: MonadIO m => JournalParser m PeriodicTransaction
|
2015-10-17 21:51:45 +03:00
|
|
|
periodictransactionp = do
|
2018-06-13 02:39:56 +03:00
|
|
|
|
|
|
|
-- first line
|
2011-08-04 11:49:10 +04:00
|
|
|
char '~' <?> "periodic transaction"
|
2018-06-09 05:45:29 +03:00
|
|
|
lift $ skipMany spacenonewline
|
2018-06-13 02:39:56 +03:00
|
|
|
-- a period expression
|
2018-06-09 05:45:29 +03:00
|
|
|
pos <- getPosition
|
|
|
|
d <- liftIO getCurrentDay
|
2018-06-12 22:29:22 +03:00
|
|
|
(periodtxt, (interval, span)) <- lift $ first T.strip <$> match (periodexprp d)
|
2018-06-09 05:45:29 +03:00
|
|
|
-- In periodic transactions, the period expression has an additional constraint:
|
|
|
|
case checkPeriodicTransactionStartDate interval span periodtxt of
|
2018-06-12 22:29:22 +03:00
|
|
|
Just e -> parseErrorAt pos e
|
|
|
|
Nothing -> pure ()
|
2018-06-13 02:39:56 +03:00
|
|
|
-- The line can end here, or it can continue with one or more spaces
|
|
|
|
-- and then zero or more of the following fields. A bit awkward.
|
|
|
|
(status, code, description, (comment, tags)) <-
|
|
|
|
(lift eolof >> return (Unmarked, "", "", ("", [])))
|
|
|
|
<|>
|
|
|
|
(do
|
|
|
|
lift $ skipSome spacenonewline
|
|
|
|
s <- lift statusp
|
|
|
|
c <- lift codep
|
|
|
|
desc <- lift $ T.strip <$> descriptionp
|
|
|
|
(cmt, ts) <- lift transactioncommentp
|
|
|
|
return (s,c,desc,(cmt,ts))
|
|
|
|
)
|
|
|
|
|
|
|
|
-- next lines
|
2018-06-12 22:29:22 +03:00
|
|
|
postings <- postingsp (Just $ first3 $ toGregorian d)
|
2018-06-13 02:39:56 +03:00
|
|
|
|
2018-06-12 22:29:22 +03:00
|
|
|
return $ nullperiodictransaction{
|
2018-06-13 02:39:56 +03:00
|
|
|
ptperiodexpr=periodtxt
|
2018-06-12 22:29:22 +03:00
|
|
|
,ptinterval=interval
|
|
|
|
,ptspan=span
|
|
|
|
,ptstatus=status
|
|
|
|
,ptcode=code
|
|
|
|
,ptdescription=description
|
|
|
|
,ptcomment=comment
|
|
|
|
,pttags=tags
|
|
|
|
,ptpostings=postings
|
|
|
|
}
|
2010-11-13 02:54:21 +03:00
|
|
|
|
2012-05-15 05:49:05 +04:00
|
|
|
-- | Parse a (possibly unbalanced) transaction.
|
2018-06-06 08:52:28 +03:00
|
|
|
transactionp :: JournalParser m Transaction
|
2015-10-17 21:51:45 +03:00
|
|
|
transactionp = do
|
2018-07-16 17:28:58 +03:00
|
|
|
-- dbgparse 0 "transactionp"
|
2018-06-09 05:35:27 +03:00
|
|
|
startpos <- getPosition
|
2014-08-08 18:27:32 +04:00
|
|
|
date <- datep <?> "transaction"
|
2018-05-24 07:36:19 +03:00
|
|
|
edate <- optional (lift $ secondarydatep date) <?> "secondary date"
|
2016-07-29 18:57:10 +03:00
|
|
|
lookAhead (lift spacenonewline <|> newline) <?> "whitespace or newline"
|
|
|
|
status <- lift statusp <?> "cleared status"
|
2018-05-22 04:52:34 +03:00
|
|
|
code <- lift codep <?> "transaction code"
|
2018-06-06 08:52:28 +03:00
|
|
|
description <- lift $ T.strip <$> descriptionp
|
2018-06-06 08:44:02 +03:00
|
|
|
(comment, tags) <- lift transactioncommentp
|
|
|
|
let year = first3 $ toGregorian date
|
|
|
|
postings <- postingsp (Just year)
|
2018-06-09 05:35:27 +03:00
|
|
|
endpos <- getPosition
|
|
|
|
let sourcepos = journalSourcePos startpos endpos
|
2016-08-14 22:44:19 +03:00
|
|
|
return $ txnTieKnot $ Transaction 0 sourcepos date edate status code description comment tags postings ""
|
2007-02-09 04:23:12 +03:00
|
|
|
|
2012-12-06 04:28:23 +04:00
|
|
|
#ifdef TESTS
|
2015-10-17 21:51:45 +03:00
|
|
|
test_transactionp = do
|
2012-05-15 05:49:05 +04:00
|
|
|
let s `gives` t = do
|
2016-05-23 10:32:55 +03:00
|
|
|
let p = parseWithState mempty transactionp s
|
2012-12-06 04:28:23 +04:00
|
|
|
assertBool $ isRight p
|
2012-05-15 05:49:05 +04:00
|
|
|
let Right t2 = p
|
2012-12-06 04:28:23 +04:00
|
|
|
-- same f = assertEqual (f t) (f t2)
|
|
|
|
assertEqual (tdate t) (tdate t2)
|
2012-12-06 08:43:41 +04:00
|
|
|
assertEqual (tdate2 t) (tdate2 t2)
|
2012-12-06 04:28:23 +04:00
|
|
|
assertEqual (tstatus t) (tstatus t2)
|
|
|
|
assertEqual (tcode t) (tcode t2)
|
|
|
|
assertEqual (tdescription t) (tdescription t2)
|
|
|
|
assertEqual (tcomment t) (tcomment t2)
|
|
|
|
assertEqual (ttags t) (ttags t2)
|
|
|
|
assertEqual (tpreceding_comment_lines t) (tpreceding_comment_lines t2)
|
|
|
|
assertEqual (show $ tpostings t) (show $ tpostings t2)
|
2014-09-11 00:07:53 +04:00
|
|
|
-- "0000/01/01\n\n" `gives` nulltransaction
|
2012-05-15 05:49:05 +04:00
|
|
|
unlines [
|
|
|
|
"2012/05/14=2012/05/15 (code) desc ; tcomment1",
|
|
|
|
" ; tcomment2",
|
|
|
|
" ; ttag1: val1",
|
|
|
|
" * a $1.00 ; pcomment1",
|
|
|
|
" ; pcomment2",
|
|
|
|
" ; ptag1: val1",
|
|
|
|
" ; ptag2: val2"
|
|
|
|
]
|
|
|
|
`gives`
|
|
|
|
nulltransaction{
|
|
|
|
tdate=parsedate "2012/05/14",
|
2012-12-06 08:43:41 +04:00
|
|
|
tdate2=Just $ parsedate "2012/05/15",
|
2017-06-16 02:25:37 +03:00
|
|
|
tstatus=Unmarked,
|
2012-05-15 05:49:05 +04:00
|
|
|
tcode="code",
|
|
|
|
tdescription="desc",
|
2012-12-06 04:28:23 +04:00
|
|
|
tcomment=" tcomment1\n tcomment2\n ttag1: val1\n",
|
2012-05-28 02:59:06 +04:00
|
|
|
ttags=[("ttag1","val1")],
|
2012-05-15 05:49:05 +04:00
|
|
|
tpostings=[
|
|
|
|
nullposting{
|
2015-05-16 21:51:35 +03:00
|
|
|
pstatus=Cleared,
|
2012-05-15 05:49:05 +04:00
|
|
|
paccount="a",
|
2012-11-20 01:20:10 +04:00
|
|
|
pamount=Mixed [usd 1],
|
2012-12-06 04:28:23 +04:00
|
|
|
pcomment=" pcomment1\n pcomment2\n ptag1: val1\n ptag2: val2\n",
|
2012-05-15 05:49:05 +04:00
|
|
|
ptype=RegularPosting,
|
2012-05-28 02:59:06 +04:00
|
|
|
ptags=[("ptag1","val1"),("ptag2","val2")],
|
2012-05-15 05:49:05 +04:00
|
|
|
ptransaction=Nothing
|
|
|
|
}
|
|
|
|
],
|
|
|
|
tpreceding_comment_lines=""
|
|
|
|
}
|
2015-06-11 20:13:27 +03:00
|
|
|
unlines [
|
|
|
|
"2015/1/1",
|
|
|
|
]
|
|
|
|
`gives`
|
|
|
|
nulltransaction{
|
|
|
|
tdate=parsedate "2015/01/01",
|
|
|
|
}
|
2012-05-15 05:49:05 +04:00
|
|
|
|
2016-05-23 10:32:55 +03:00
|
|
|
assertRight $ parseWithState mempty transactionp $ unlines
|
2012-12-06 04:28:23 +04:00
|
|
|
["2007/01/28 coopportunity"
|
|
|
|
," expenses:food:groceries $47.18"
|
|
|
|
," assets:checking $-47.18"
|
|
|
|
,""
|
|
|
|
]
|
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
-- transactionp should not parse just a date
|
2016-05-23 10:32:55 +03:00
|
|
|
assertLeft $ parseWithState mempty transactionp "2009/1/1\n"
|
2012-12-06 04:28:23 +04:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
-- transactionp should not parse just a date and description
|
2016-05-23 10:32:55 +03:00
|
|
|
assertLeft $ parseWithState mempty transactionp "2009/1/1 a\n"
|
2012-12-06 04:28:23 +04:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
-- transactionp should not parse a following comment as part of the description
|
2016-05-23 10:32:55 +03:00
|
|
|
let p = parseWithState mempty transactionp "2009/1/1 a ;comment\n b 1\n"
|
2012-12-06 04:28:23 +04:00
|
|
|
assertRight p
|
|
|
|
assertEqual "a" (let Right p' = p in tdescription p')
|
|
|
|
|
|
|
|
-- parse transaction with following whitespace line
|
2016-05-23 10:32:55 +03:00
|
|
|
assertRight $ parseWithState mempty transactionp $ unlines
|
2012-12-06 04:28:23 +04:00
|
|
|
["2012/1/1"
|
2012-05-27 22:14:20 +04:00
|
|
|
," a 1"
|
|
|
|
," b"
|
|
|
|
," "
|
|
|
|
]
|
2014-09-11 00:07:53 +04:00
|
|
|
|
2016-05-23 10:32:55 +03:00
|
|
|
let p = parseWithState mempty transactionp $ unlines
|
2012-12-06 04:28:23 +04:00
|
|
|
["2009/1/1 x ; transaction comment"
|
|
|
|
," a 1 ; posting 1 comment"
|
|
|
|
," ; posting 1 comment 2"
|
|
|
|
," b"
|
|
|
|
," ; posting 2 comment"
|
|
|
|
]
|
|
|
|
assertRight p
|
|
|
|
assertEqual 2 (let Right t = p in length $ tpostings t)
|
2014-09-11 00:07:53 +04:00
|
|
|
#endif
|
2012-05-15 05:49:05 +04:00
|
|
|
|
2016-04-23 21:27:39 +03:00
|
|
|
--- ** postings
|
2007-02-09 04:23:12 +03:00
|
|
|
|
2016-04-28 23:23:20 +03:00
|
|
|
-- Parse the following whitespace-beginning lines as postings, posting
|
|
|
|
-- tags, and/or comments (inferring year, if needed, from the given date).
|
2018-06-06 08:52:28 +03:00
|
|
|
postingsp :: Maybe Year -> JournalParser m [Posting]
|
2018-06-06 08:44:02 +03:00
|
|
|
postingsp mTransactionYear = many (postingp mTransactionYear) <?> "postings"
|
2014-09-11 00:07:53 +04:00
|
|
|
|
2018-06-06 08:52:28 +03:00
|
|
|
-- linebeginningwithspaces :: JournalParser m String
|
2012-05-14 22:52:22 +04:00
|
|
|
-- linebeginningwithspaces = do
|
2018-03-25 16:53:44 +03:00
|
|
|
-- sp <- lift (skipSome spacenonewline)
|
2012-05-14 22:52:22 +04:00
|
|
|
-- c <- nonspace
|
2016-07-29 18:57:10 +03:00
|
|
|
-- cs <- lift restofline
|
2012-05-14 22:52:22 +04:00
|
|
|
-- return $ sp ++ (c:cs) ++ "\n"
|
2007-02-09 04:23:12 +03:00
|
|
|
|
2018-06-06 08:52:28 +03:00
|
|
|
postingp :: Maybe Year -> JournalParser m Posting
|
2018-06-06 08:44:02 +03:00
|
|
|
postingp mTransactionYear = do
|
2018-07-26 10:29:02 +03:00
|
|
|
-- lift $ dbgparse 0 "postingp"
|
2018-05-10 07:40:33 +03:00
|
|
|
(status, account) <- try $ do
|
|
|
|
lift (skipSome spacenonewline)
|
|
|
|
status <- lift statusp
|
|
|
|
lift (skipMany spacenonewline)
|
|
|
|
account <- modifiedaccountnamep
|
|
|
|
return (status, account)
|
lib: textification begins! account names
The first of several conversions from String to (strict) Text, hopefully
reducing space and time usage.
This one shows a small improvement, with GHC 7.10.3 and text-1.2.2.1:
hledger -f data/100x100x10.journal stats
string: <<ghc: 39471064 bytes, 77 GCs, 198421/275048 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.001 elapsed), 0.015 MUT (0.020 elapsed), 0.010 GC (0.014 elapsed) :ghc>>
text: <<ghc: 39268024 bytes, 77 GCs, 197018/270840 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.002 elapsed), 0.016 MUT (0.022 elapsed), 0.009 GC (0.011 elapsed) :ghc>>
hledger -f data/1000x100x10.journal stats
string: <<ghc: 318555920 bytes, 617 GCs, 2178997/7134472 avg/max bytes residency (7 samples), 16M in use, 0.000 INIT (0.001 elapsed), 0.129 MUT (0.136 elapsed), 0.067 GC (0.077 elapsed) :ghc>>
text: <<ghc: 314248496 bytes, 612 GCs, 2074045/6617960 avg/max bytes residency (7 samples), 16M in use, 0.000 INIT (0.003 elapsed), 0.137 MUT (0.145 elapsed), 0.067 GC (0.079 elapsed) :ghc>>
hledger -f data/10000x100x10.journal stats
string: <<ghc: 3114763608 bytes, 6026 GCs, 18858950/75552024 avg/max bytes residency (11 samples), 201M in use, 0.000 INIT (0.000 elapsed), 1.331 MUT (1.372 elapsed), 0.699 GC (0.812 elapsed) :ghc>>
text: <<ghc: 3071468920 bytes, 5968 GCs, 14120344/62951360 avg/max bytes residency (9 samples), 124M in use, 0.000 INIT (0.003 elapsed), 1.272 MUT (1.349 elapsed), 0.513 GC (0.578 elapsed) :ghc>>
hledger -f data/100000x100x10.journal stats
string: <<ghc: 31186579432 bytes, 60278 GCs, 135332581/740228992 avg/max bytes residency (13 samples), 1697M in use, 0.000 INIT (0.008 elapsed), 14.677 MUT (15.508 elapsed), 7.081 GC (8.074 elapsed) :ghc>>
text: <<ghc: 30753427672 bytes, 59763 GCs, 117595958/666457240 avg/max bytes residency (14 samples), 1588M in use, 0.000 INIT (0.008 elapsed), 13.713 MUT (13.966 elapsed), 6.220 GC (7.108 elapsed) :ghc>>
2016-05-24 04:16:21 +03:00
|
|
|
let (ptype, account') = (accountNamePostingType account, textUnbracket account)
|
2018-06-21 03:33:29 +03:00
|
|
|
lift (skipMany spacenonewline)
|
|
|
|
amount <- option missingmixedamt $ Mixed . (:[]) <$> amountp
|
2015-10-17 21:51:45 +03:00
|
|
|
massertion <- partialbalanceassertionp
|
|
|
|
_ <- fixedlotpricep
|
2018-03-25 16:53:44 +03:00
|
|
|
lift (skipMany spacenonewline)
|
2018-06-06 08:44:02 +03:00
|
|
|
(comment,tags,mdate,mdate2) <- lift $ postingcommentp mTransactionYear
|
2014-11-03 08:52:12 +03:00
|
|
|
return posting
|
2016-04-28 23:23:20 +03:00
|
|
|
{ pdate=mdate
|
|
|
|
, pdate2=mdate2
|
2014-11-03 08:52:12 +03:00
|
|
|
, pstatus=status
|
|
|
|
, paccount=account'
|
|
|
|
, pamount=amount
|
|
|
|
, pcomment=comment
|
|
|
|
, ptype=ptype
|
|
|
|
, ptags=tags
|
|
|
|
, pbalanceassertion=massertion
|
|
|
|
}
|
2012-12-06 04:28:23 +04:00
|
|
|
|
|
|
|
#ifdef TESTS
|
|
|
|
test_postingp = do
|
|
|
|
let s `gives` ep = do
|
2016-05-23 10:32:55 +03:00
|
|
|
let parse = parseWithState mempty (postingp Nothing) s
|
2014-09-11 00:07:53 +04:00
|
|
|
assertBool -- "postingp parser"
|
2012-12-06 04:28:23 +04:00
|
|
|
$ isRight parse
|
|
|
|
let Right ap = parse
|
|
|
|
same f = assertEqual (f ep) (f ap)
|
|
|
|
same pdate
|
2012-05-15 05:49:05 +04:00
|
|
|
same pstatus
|
|
|
|
same paccount
|
|
|
|
same pamount
|
|
|
|
same pcomment
|
|
|
|
same ptype
|
2012-05-28 02:59:06 +04:00
|
|
|
same ptags
|
2012-05-15 05:49:05 +04:00
|
|
|
same ptransaction
|
2012-12-06 04:28:23 +04:00
|
|
|
" expenses:food:dining $10.00 ; a: a a \n ; b: b b \n" `gives`
|
|
|
|
posting{paccount="expenses:food:dining", pamount=Mixed [usd 10], pcomment=" a: a a \n b: b b \n", ptags=[("a","a a"), ("b","b b")]}
|
|
|
|
|
2014-09-11 00:07:53 +04:00
|
|
|
" a 1 ; [2012/11/28]\n" `gives`
|
2012-12-06 04:28:23 +04:00
|
|
|
("a" `post` num 1){pcomment=" [2012/11/28]\n"
|
|
|
|
,ptags=[("date","2012/11/28")]
|
|
|
|
,pdate=parsedateM "2012/11/28"}
|
|
|
|
|
2014-09-11 00:07:53 +04:00
|
|
|
" a 1 ; a:a, [=2012/11/28]\n" `gives`
|
2012-12-06 04:28:23 +04:00
|
|
|
("a" `post` num 1){pcomment=" a:a, [=2012/11/28]\n"
|
|
|
|
,ptags=[("a","a"), ("date2","2012/11/28")]
|
|
|
|
,pdate=Nothing}
|
|
|
|
|
2014-09-11 00:07:53 +04:00
|
|
|
" a 1 ; a:a\n ; [2012/11/28=2012/11/29],b:b\n" `gives`
|
2012-12-06 04:28:23 +04:00
|
|
|
("a" `post` num 1){pcomment=" a:a\n [2012/11/28=2012/11/29],b:b\n"
|
|
|
|
,ptags=[("a","a"), ("date","2012/11/28"), ("date2","2012/11/29"), ("b","b")]
|
|
|
|
,pdate=parsedateM "2012/11/28"}
|
2014-09-11 00:07:53 +04:00
|
|
|
|
2012-12-06 04:28:23 +04:00
|
|
|
assertBool -- "postingp parses a quoted commodity with numbers"
|
2016-05-23 10:32:55 +03:00
|
|
|
(isRight $ parseWithState mempty (postingp Nothing) " a 1 \"DE123\"\n")
|
2012-12-06 04:28:23 +04:00
|
|
|
|
|
|
|
-- ,"postingp parses balance assertions and fixed lot prices" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertBool (isRight $ parseWithState mempty (postingp Nothing) " a 1 \"DE123\" =$1 { =2.2 EUR} \n")
|
2012-12-06 04:28:23 +04:00
|
|
|
|
2016-05-23 10:32:55 +03:00
|
|
|
-- let parse = parseWithState mempty postingp " a\n ;next-line comment\n"
|
2013-05-29 03:18:15 +04:00
|
|
|
-- assertRight parse
|
|
|
|
-- let Right p = parse
|
|
|
|
-- assertEqual "next-line comment\n" (pcomment p)
|
|
|
|
-- assertEqual (Just nullmixedamt) (pbalanceassertion p)
|
2014-09-11 00:07:53 +04:00
|
|
|
#endif
|
2012-05-15 05:49:05 +04:00
|
|
|
|
2016-04-28 23:23:20 +03:00
|
|
|
--- * more tests
|
2016-04-23 03:43:16 +03:00
|
|
|
|
2015-06-11 20:13:27 +03:00
|
|
|
tests_Hledger_Read_JournalReader = TestList $ concat [
|
|
|
|
-- test_numberp
|
2017-02-04 05:20:00 +03:00
|
|
|
[
|
|
|
|
"showParsedMarketPrice" ~: do
|
|
|
|
let mp = parseWithState mempty marketpricedirectivep "P 2017/01/30 BTC $922.83\n"
|
|
|
|
mpString = (fmap . fmap) showMarketPrice mp
|
|
|
|
mpString `is` (Just (Right "P 2017/01/30 BTC $922.83"))
|
|
|
|
]
|
2015-06-11 20:13:27 +03:00
|
|
|
]
|
|
|
|
|
2012-12-06 04:28:23 +04:00
|
|
|
{- old hunit tests
|
|
|
|
|
2015-06-11 20:13:27 +03:00
|
|
|
tests_Hledger_Read_JournalReader = TestList $ concat [
|
2014-02-06 06:55:38 +04:00
|
|
|
test_numberp,
|
2012-12-06 04:28:23 +04:00
|
|
|
test_amountp,
|
2015-10-17 21:51:45 +03:00
|
|
|
test_spaceandamountormissingp,
|
2012-12-06 04:28:23 +04:00
|
|
|
test_tagcomment,
|
|
|
|
test_inlinecomment,
|
2014-02-27 23:47:36 +04:00
|
|
|
test_comments,
|
2012-12-06 04:28:23 +04:00
|
|
|
test_ledgerDateSyntaxToTags,
|
|
|
|
test_postingp,
|
2015-10-17 21:51:45 +03:00
|
|
|
test_transactionp,
|
2012-05-15 05:49:05 +04:00
|
|
|
[
|
2015-10-17 21:51:45 +03:00
|
|
|
"modifiertransactionp" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty modifiertransactionp "= (some value expr)\n some:postings 1\n")
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
,"periodictransactionp" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty periodictransactionp "~ (some period expr)\n some:postings 1\n")
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
,"directivep" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty directivep "!include /some/file.x\n")
|
|
|
|
assertParse (parseWithState mempty directivep "account some:account\n")
|
|
|
|
assertParse (parseWithState mempty (directivep >> directivep) "!account a\nend\n")
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2014-02-27 23:47:36 +04:00
|
|
|
,"comment" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty comment "; some comment \n")
|
|
|
|
assertParse (parseWithState mempty comment " \t; x\n")
|
|
|
|
assertParse (parseWithState mempty comment "#x")
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2014-08-08 18:27:32 +04:00
|
|
|
,"datep" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty datep "2011/1/1")
|
|
|
|
assertParseFailure (parseWithState mempty datep "1/1")
|
|
|
|
assertParse (parseWithState mempty{jpsYear=Just 2011} datep "1/1")
|
2011-05-31 23:49:37 +04:00
|
|
|
|
2014-02-06 06:55:38 +04:00
|
|
|
,"datetimep" ~: do
|
|
|
|
let p = do {t <- datetimep; eof; return t}
|
2016-05-23 10:32:55 +03:00
|
|
|
bad = assertParseFailure . parseWithState mempty p
|
|
|
|
good = assertParse . parseWithState mempty p
|
2011-06-01 05:50:04 +04:00
|
|
|
bad "2011/1/1"
|
|
|
|
bad "2011/1/1 24:00:00"
|
|
|
|
bad "2011/1/1 00:60:00"
|
|
|
|
bad "2011/1/1 00:00:60"
|
|
|
|
good "2011/1/1 00:00"
|
|
|
|
good "2011/1/1 23:59:59"
|
|
|
|
good "2011/1/1 3:5:7"
|
|
|
|
-- timezone is parsed but ignored
|
|
|
|
let startofday = LocalTime (fromGregorian 2011 1 1) (TimeOfDay 0 0 (fromIntegral 0))
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParseEqual (parseWithState mempty p "2011/1/1 00:00-0800") startofday
|
|
|
|
assertParseEqual (parseWithState mempty p "2011/1/1 00:00+1234") startofday
|
2011-06-01 02:45:54 +04:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
,"defaultyeardirectivep" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty defaultyeardirectivep "Y 2010\n")
|
|
|
|
assertParse (parseWithState mempty defaultyeardirectivep "Y 10001\n")
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
,"marketpricedirectivep" ~:
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParseEqual (parseWithState mempty marketpricedirectivep "P 2004/05/01 XYZ $55.00\n") (MarketPrice (parsedate "2004/05/01") "XYZ" $ usd 55)
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
,"ignoredpricecommoditydirectivep" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty ignoredpricecommoditydirectivep "N $\n")
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
,"defaultcommoditydirectivep" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty defaultcommoditydirectivep "D $1,000.0\n")
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
,"commodityconversiondirectivep" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty commodityconversiondirectivep "C 1h = $50.00\n")
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
,"tagdirectivep" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty tagdirectivep "tag foo \n")
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
,"endtagdirectivep" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParse (parseWithState mempty endtagdirectivep "end tag \n")
|
|
|
|
assertParse (parseWithState mempty endtagdirectivep "pop \n")
|
2010-03-13 04:10:10 +03:00
|
|
|
|
2014-02-06 06:55:38 +04:00
|
|
|
,"accountnamep" ~: do
|
|
|
|
assertBool "accountnamep parses a normal account name" (isRight $ parsewith accountnamep "a:b:c")
|
|
|
|
assertBool "accountnamep rejects an empty inner component" (isLeft $ parsewith accountnamep "a::c")
|
|
|
|
assertBool "accountnamep rejects an empty leading component" (isLeft $ parsewith accountnamep ":b:c")
|
|
|
|
assertBool "accountnamep rejects an empty trailing component" (isLeft $ parsewith accountnamep "a:b:")
|
2010-03-11 20:16:03 +03:00
|
|
|
|
2015-10-17 21:51:45 +03:00
|
|
|
,"leftsymbolamountp" ~: do
|
2016-05-23 10:32:55 +03:00
|
|
|
assertParseEqual (parseWithState mempty leftsymbolamountp "$1") (usd 1 `withPrecision` 0)
|
|
|
|
assertParseEqual (parseWithState mempty leftsymbolamountp "$-1") (usd (-1) `withPrecision` 0)
|
|
|
|
assertParseEqual (parseWithState mempty leftsymbolamountp "-$1") (usd (-1) `withPrecision` 0)
|
2010-05-27 03:44:08 +04:00
|
|
|
|
2012-05-27 22:14:20 +04:00
|
|
|
,"amount" ~: do
|
2012-11-20 03:17:55 +04:00
|
|
|
let -- | compare a parse result with an expected amount, showing the debug representation for clarity
|
|
|
|
assertAmountParse parseresult amount =
|
|
|
|
(either (const "parse error") showAmountDebug parseresult) ~?= (showAmountDebug amount)
|
2016-05-23 10:32:55 +03:00
|
|
|
assertAmountParse (parseWithState mempty amountp "1 @ $2")
|
2012-11-20 06:22:20 +04:00
|
|
|
(num 1 `withPrecision` 0 `at` (usd 2 `withPrecision` 0))
|
2012-05-27 22:14:20 +04:00
|
|
|
|
2012-05-09 19:34:05 +04:00
|
|
|
]]
|
2012-12-06 04:28:23 +04:00
|
|
|
-}
|