lib: rename JournalContext to JournalParseState

This commit is contained in:
Simon Michael 2016-05-18 20:57:34 -07:00
parent ea383d88d6
commit 7f5e09096f
13 changed files with 167 additions and 167 deletions

View File

@ -51,7 +51,7 @@ type PostingExpr = (AccountName, AmountExpr)
data AmountExpr = AmountLiteral String | AmountMultiplier Quantity deriving (Show)
addPostingExprsFromOpts :: RawOpts -> [PostingExpr]
addPostingExprsFromOpts = map (either parseerror id . runParser (postingexprp <* eof) nullctx "") . map stripquotes . listofstringopt "add-posting"
addPostingExprsFromOpts = map (either parseerror id . runParser (postingexprp <* eof) nulljps "") . map stripquotes . listofstringopt "add-posting"
postingexprp = do
a <- accountnamep

View File

@ -52,7 +52,7 @@ module Hledger.Data.Journal (
-- * Misc
canonicalStyleFrom,
matchpats,
nullctx,
nulljps,
nulljournal,
-- * Tests
samplejournal,
@ -120,27 +120,27 @@ instance Show Journal where
-- ,show $ open_timeclock_entries j
-- ,show $ jmarketprices j
-- ,show $ final_comment_lines j
-- ,show $ jContext j
-- ,show $ jparsestate j
-- ,show $ map fst $ files j
-- ]
-- The monoid instance for Journal concatenates the list fields,
-- combines the map fields, keeps the final comment lines of the
-- second journal, and keeps the latest of their last read times.
-- See JournalContext for how the final parse contexts are combined.
-- See JournalParseState for how the final parse states are combined.
instance Monoid Journal where
mempty = nulljournal
mappend j1 j2 =
Journal{jmodifiertxns = jmodifiertxns j1 <> jmodifiertxns j2 -- [ModifierTransaction]
,jperiodictxns = jperiodictxns j1 <> jperiodictxns j2 -- [PeriodicTransaction]
,jtxns = jtxns j1 <> jtxns j2 -- [Transaction]
,jcommoditystyles = jcommoditystyles j1 <> jcommoditystyles j2 -- M.Map CommoditySymbol AmountStyle
,jcommodities = jcommodities j1 <> jcommodities j2 -- M.Map CommoditySymbol Commodity
,open_timeclock_entries = open_timeclock_entries j1 <> open_timeclock_entries j2 -- [TimeclockEntry]
,jmarketprices = jmarketprices j1 <> jmarketprices j2 -- [MarketPrice]
,final_comment_lines = final_comment_lines j1 <> final_comment_lines j2 -- String
,jContext = jContext j1 <> jContext j2 -- JournalContext
,files = files j1 <> files j2 -- [(FilePath, String)]
Journal{jmodifiertxns = jmodifiertxns j1 <> jmodifiertxns j2
,jperiodictxns = jperiodictxns j1 <> jperiodictxns j2
,jtxns = jtxns j1 <> jtxns j2
,jcommoditystyles = jcommoditystyles j1 <> jcommoditystyles j2
,jcommodities = jcommodities j1 <> jcommodities j2
,open_timeclock_entries = open_timeclock_entries j1 <> open_timeclock_entries j2
,jmarketprices = jmarketprices j1 <> jmarketprices j2
,final_comment_lines = final_comment_lines j1 <> final_comment_lines j2
,jparsestate = jparsestate j1 <> jparsestate j2
,files = files j1 <> files j2
,filereadtime = max (filereadtime j1) (filereadtime j2)
}
@ -152,30 +152,30 @@ nulljournal = Journal { jmodifiertxns = []
, open_timeclock_entries = []
, jmarketprices = []
, final_comment_lines = []
, jContext = nullctx
, jparsestate = nulljps
, files = []
, filereadtime = TOD 0 0
, jcommoditystyles = M.fromList []
}
-- The monoid instance for JournalContext assumes the second context
-- is that of an included journal, so it is mostly discarded except
-- the accounts defined by account directives are concatenated, and
-- the transaction indices (counts of transactions parsed, if any) are
-- added.
instance Monoid JournalContext where
mempty = nullctx
-- The monoid instance for JournalParseState mostly discards the
-- second parse state, except the accounts defined by account
-- directives are concatenated, and the transaction indices (counts of
-- transactions parsed, if any) are added.
instance Monoid JournalParseState where
mempty = nulljps
mappend c1 c2 =
Ctx { ctxYear = ctxYear c1
, ctxDefaultCommodityAndStyle = ctxDefaultCommodityAndStyle c1
, ctxAccounts = ctxAccounts c1 ++ ctxAccounts c2
, ctxParentAccount = ctxParentAccount c1
, ctxAliases = ctxAliases c1
, ctxTransactionIndex = ctxTransactionIndex c1 + ctxTransactionIndex c2
JournalParseState {
jpsYear = jpsYear c1
, jpsDefaultCommodityAndStyle = jpsDefaultCommodityAndStyle c1
, jpsAccounts = jpsAccounts c1 ++ jpsAccounts c2
, jpsParentAccount = jpsParentAccount c1
, jpsAliases = jpsAliases c1
, jpsTransactionIndex = jpsTransactionIndex c1 + jpsTransactionIndex c2
}
nullctx :: JournalContext
nullctx = Ctx{ctxYear=Nothing, ctxDefaultCommodityAndStyle=Nothing, ctxAccounts=[], ctxParentAccount=[], ctxAliases=[], ctxTransactionIndex=0}
nulljps :: JournalParseState
nulljps = JournalParseState{jpsYear=Nothing, jpsDefaultCommodityAndStyle=Nothing, jpsAccounts=[], jpsParentAccount=[], jpsAliases=[], jpsTransactionIndex=0}
journalFilePath :: Journal -> FilePath
journalFilePath = fst . mainfile
@ -455,14 +455,14 @@ journalApplyAliases aliases j@Journal{jtxns=ts} =
-- | Do post-parse processing on a journal to make it ready for use: check
-- all transactions balance, canonicalise amount formats, close any open
-- timeclock entries, maybe check balance assertions and so on.
journalFinalise :: ClockTime -> LocalTime -> FilePath -> String -> JournalContext -> Bool -> Journal -> Either String Journal
journalFinalise tclock tlocal path txt ctx assrt j@Journal{files=fs} = do
journalFinalise :: ClockTime -> LocalTime -> FilePath -> String -> JournalParseState -> Bool -> Journal -> Either String Journal
journalFinalise tclock tlocal path txt jps assrt j@Journal{files=fs} = do
(journalBalanceTransactions $
journalApplyCommodityStyles $
journalCloseTimeclockEntries tlocal $
j{ files=(path,txt):fs
, filereadtime=tclock
, jContext=ctx
, jparsestate=jps
, jtxns=reverse $ jtxns j -- NOTE: see addTransaction
, jmodifiertxns=reverse $ jmodifiertxns j -- NOTE: see addModifierTransaction
, jperiodictxns=reverse $ jperiodictxns j -- NOTE: see addPeriodicTransaction

View File

@ -222,23 +222,23 @@ instance NFData MarketPrice
type Year = Integer
-- | A journal "context" is some data which can change in the course of
-- parsing a journal. An example is the default year, which changes when a
-- Y directive is encountered. At the end of parsing, the final context
-- is saved for later use by eg the add command.
data JournalContext = Ctx {
ctxYear :: !(Maybe Year) -- ^ the default year most recently specified with Y
, ctxDefaultCommodityAndStyle :: !(Maybe (CommoditySymbol,AmountStyle)) -- ^ the default commodity and amount style most recently specified with D
, ctxAccounts :: ![AccountName] -- ^ the accounts that have been defined with account directives so far
, ctxParentAccount :: ![AccountName] -- ^ the current stack of parent accounts/account name components
-- | Journal parse state is data we want to keep track of in the
-- course of parsing a journal. An example is the default year, which
-- changes when a Y directive is encountered. At the end of parsing,
-- the final state is saved for later use by eg the add command.
data JournalParseState = JournalParseState {
jpsYear :: !(Maybe Year) -- ^ the default year most recently specified with Y
, jpsDefaultCommodityAndStyle :: !(Maybe (CommoditySymbol,AmountStyle)) -- ^ the default commodity and amount style most recently specified with D
, jpsAccounts :: ![AccountName] -- ^ the accounts that have been defined with account directives so far
, jpsParentAccount :: ![AccountName] -- ^ the current stack of parent accounts/account name components
-- specified with "apply account" directive(s). Concatenated, these
-- are the account prefix prepended to parsed account names.
, ctxAliases :: ![AccountAlias] -- ^ the current list of account name aliases in effect
, ctxTransactionIndex :: !Integer -- ^ the number of transactions read so far. (Does not count
, jpsAliases :: ![AccountAlias] -- ^ the current list of account name aliases in effect
, jpsTransactionIndex :: !Integer -- ^ the number of transactions read so far. (Does not count
-- timeclock/timedot/CSV entries, currently).
} deriving (Read, Show, Eq, Data, Typeable, Generic)
instance NFData JournalContext
instance NFData JournalParseState
deriving instance Data (ClockTime)
deriving instance Typeable (ClockTime)
@ -255,7 +255,7 @@ data Journal = Journal {
open_timeclock_entries :: [TimeclockEntry],
jmarketprices :: [MarketPrice],
final_comment_lines :: String, -- ^ any trailing comments from the journal file
jContext :: JournalContext, -- ^ the context (parse state) at the end of parsing
jparsestate :: JournalParseState, -- ^ the final parse state
files :: [(FilePath, String)], -- ^ the file path and raw text of the main and
-- any included journal files. The main file is
-- first followed by any included files in the

View File

@ -47,7 +47,7 @@ import Test.HUnit
import Text.Printf
import Hledger.Data.Dates (getCurrentDay)
import Hledger.Data.Journal (nullctx)
import Hledger.Data.Journal (nulljps)
import Hledger.Data.Types
import Hledger.Read.JournalReader as JournalReader
import Hledger.Read.TimedotReader as TimedotReader
@ -259,7 +259,7 @@ tests_Hledger_Read = TestList $
tests_Hledger_Read_CsvReader,
"journal" ~: do
r <- runExceptT $ parseWithCtx nullctx JournalReader.journalp ""
r <- runExceptT $ parseWithState nulljps JournalReader.journalp ""
assertBool "journalp should parse an empty file" (isRight $ r)
jE <- readJournal Nothing Nothing True Nothing "" -- don't know how to get it from journal
either error' (assertBool "journalp parsing an empty file should give an empty journal" . null . jtxns) jE

View File

@ -43,7 +43,7 @@ import Hledger.Utils
type StringParser u m a = ParsecT String u m a
-- | A string parser with journal-parsing state.
type JournalParser m a = StringParser JournalContext m a
type JournalParser m a = StringParser JournalParseState m a
-- | A journal parser that runs in IO and can throw an error mid-parse.
type ErroringJournalParser a = JournalParser (ExceptT String IO) a
@ -55,7 +55,7 @@ rsp = runStringParser
-- | Run a journal parser with a null journal-parsing state.
runJournalParser, rjp :: Monad m => JournalParser m a -> String -> m (Either ParseError a)
runJournalParser p s = runParserT p nullctx "" s
runJournalParser p s = runParserT p nulljps "" s
rjp = runJournalParser
-- | Run an error-raising journal parser with a null journal-parsing state.
@ -127,68 +127,68 @@ combineJournalUpdates us = foldl' (flip (.)) id <$> sequence us
-- | Given a JournalUpdate-generating parsec parser, file path and data string,
-- parse and post-process a Journal so that it's ready to use, or give an error.
parseAndFinaliseJournal :: ErroringJournalParser (JournalUpdate,JournalContext) -> Bool -> FilePath -> String -> ExceptT String IO Journal
parseAndFinaliseJournal :: ErroringJournalParser (JournalUpdate,JournalParseState) -> Bool -> FilePath -> String -> ExceptT String IO Journal
parseAndFinaliseJournal parser assrt f s = do
tc <- liftIO getClockTime
tl <- liftIO getCurrentLocalTime
y <- liftIO getCurrentYear
r <- runParserT parser nullctx{ctxYear=Just y} f s
r <- runParserT parser nulljps{jpsYear=Just y} f s
case r of
Right (updates,ctx) -> do
Right (updates,jps) -> do
j <- ap updates (return nulljournal)
case journalFinalise tc tl f s ctx assrt j of
case journalFinalise tc tl f s jps assrt j of
Right j' -> return j'
Left estr -> throwError estr
Left e -> throwError $ show e
setYear :: Monad m => Integer -> JournalParser m ()
setYear y = modifyState (\ctx -> ctx{ctxYear=Just y})
setYear y = modifyState (\jps -> jps{jpsYear=Just y})
getYear :: Monad m => JournalParser m (Maybe Integer)
getYear = fmap ctxYear getState
getYear = fmap jpsYear getState
setDefaultCommodityAndStyle :: Monad m => (CommoditySymbol,AmountStyle) -> JournalParser m ()
setDefaultCommodityAndStyle cs = modifyState (\ctx -> ctx{ctxDefaultCommodityAndStyle=Just cs})
setDefaultCommodityAndStyle cs = modifyState (\jps -> jps{jpsDefaultCommodityAndStyle=Just cs})
getDefaultCommodityAndStyle :: Monad m => JournalParser m (Maybe (CommoditySymbol,AmountStyle))
getDefaultCommodityAndStyle = ctxDefaultCommodityAndStyle `fmap` getState
getDefaultCommodityAndStyle = jpsDefaultCommodityAndStyle `fmap` getState
pushAccount :: Monad m => String -> JournalParser m ()
pushAccount acct = modifyState addAccount
where addAccount ctx0 = ctx0 { ctxAccounts = acct : ctxAccounts ctx0 }
where addAccount jps0 = jps0 { jpsAccounts = acct : jpsAccounts jps0 }
pushParentAccount :: Monad m => String -> JournalParser m ()
pushParentAccount parent = modifyState addParentAccount
where addParentAccount ctx0 = ctx0 { ctxParentAccount = parent : ctxParentAccount ctx0 }
where addParentAccount jps0 = jps0 { jpsParentAccount = parent : jpsParentAccount jps0 }
popParentAccount :: Monad m => JournalParser m ()
popParentAccount = do ctx0 <- getState
case ctxParentAccount ctx0 of
popParentAccount = do jps0 <- getState
case jpsParentAccount jps0 of
[] -> unexpected "End of apply account block with no beginning"
(_:rest) -> setState $ ctx0 { ctxParentAccount = rest }
(_:rest) -> setState $ jps0 { jpsParentAccount = rest }
getParentAccount :: Monad m => JournalParser m String
getParentAccount = fmap (concatAccountNames . reverse . ctxParentAccount) getState
getParentAccount = fmap (concatAccountNames . reverse . jpsParentAccount) getState
addAccountAlias :: Monad m => AccountAlias -> JournalParser m ()
addAccountAlias a = modifyState (\(ctx@Ctx{..}) -> ctx{ctxAliases=a:ctxAliases})
addAccountAlias a = modifyState (\(jps@JournalParseState{..}) -> jps{jpsAliases=a:jpsAliases})
getAccountAliases :: Monad m => JournalParser m [AccountAlias]
getAccountAliases = fmap ctxAliases getState
getAccountAliases = fmap jpsAliases getState
clearAccountAliases :: Monad m => JournalParser m ()
clearAccountAliases = modifyState (\(ctx@Ctx{..}) -> ctx{ctxAliases=[]})
clearAccountAliases = modifyState (\(jps@JournalParseState{..}) -> jps{jpsAliases=[]})
getTransactionIndex :: Monad m => JournalParser m Integer
getTransactionIndex = fmap ctxTransactionIndex getState
getTransactionIndex = fmap jpsTransactionIndex getState
setTransactionIndex :: Monad m => Integer -> JournalParser m ()
setTransactionIndex i = modifyState (\ctx -> ctx{ctxTransactionIndex=i})
setTransactionIndex i = modifyState (\jps -> jps{jpsTransactionIndex=i})
-- | Increment the transaction index by one and return the new value.
incrementTransactionIndex :: Monad m => JournalParser m Integer
incrementTransactionIndex = do
modifyState (\ctx -> ctx{ctxTransactionIndex=ctxTransactionIndex ctx + 1})
modifyState (\jps -> jps{jpsTransactionIndex=jpsTransactionIndex jps + 1})
getTransactionIndex
journalAddFile :: (FilePath,String) -> Journal -> Journal
@ -368,10 +368,10 @@ is' :: (Eq a, Show a) => a -> a -> Assertion
a `is'` e = assertEqual e a
test_spaceandamountormissingp = do
assertParseEqual' (parseWithCtx nullctx spaceandamountormissingp " $47.18") (Mixed [usd 47.18])
assertParseEqual' (parseWithCtx nullctx spaceandamountormissingp "$47.18") missingmixedamt
assertParseEqual' (parseWithCtx nullctx spaceandamountormissingp " ") missingmixedamt
assertParseEqual' (parseWithCtx nullctx spaceandamountormissingp "") missingmixedamt
assertParseEqual' (parseWithState nulljps spaceandamountormissingp " $47.18") (Mixed [usd 47.18])
assertParseEqual' (parseWithState nulljps spaceandamountormissingp "$47.18") missingmixedamt
assertParseEqual' (parseWithState nulljps spaceandamountormissingp " ") missingmixedamt
assertParseEqual' (parseWithState nulljps spaceandamountormissingp "") missingmixedamt
#endif
-- | Parse a single-commodity amount, with optional symbol on the left or
@ -382,22 +382,22 @@ amountp = try leftsymbolamountp <|> try rightsymbolamountp <|> nosymbolamountp
#ifdef TESTS
test_amountp = do
assertParseEqual' (parseWithCtx nullctx amountp "$47.18") (usd 47.18)
assertParseEqual' (parseWithCtx nullctx amountp "$1.") (usd 1 `withPrecision` 0)
assertParseEqual' (parseWithState nulljps amountp "$47.18") (usd 47.18)
assertParseEqual' (parseWithState nulljps amountp "$1.") (usd 1 `withPrecision` 0)
-- ,"amount with unit price" ~: do
assertParseEqual'
(parseWithCtx nullctx amountp "$10 @ €0.5")
(parseWithState nulljps amountp "$10 @ €0.5")
(usd 10 `withPrecision` 0 `at` (eur 0.5 `withPrecision` 1))
-- ,"amount with total price" ~: do
assertParseEqual'
(parseWithCtx nullctx amountp "$10 @@ €5")
(parseWithState nulljps amountp "$10 @@ €5")
(usd 10 `withPrecision` 0 @@ (eur 5 `withPrecision` 0))
#endif
-- | Parse an amount from a string, or get an error.
amountp' :: String -> Amount
amountp' s =
case runParser (amountp <* eof) nullctx "" s of
case runParser (amountp <* eof) nulljps "" s of
Right t -> t
Left err -> error' $ show err -- XXX should throwError
@ -572,8 +572,8 @@ numberp = do
numeric = isNumber . headDef '_'
-- test_numberp = do
-- let s `is` n = assertParseEqual (parseWithCtx nullctx numberp s) n
-- assertFails = assertBool . isLeft . parseWithCtx nullctx numberp
-- let s `is` n = assertParseEqual (parseWithState nulljps numberp s) n
-- assertFails = assertBool . isLeft . parseWithState nulljps numberp
-- assertFails ""
-- "0" `is` (0, 0, '.', ',', [])
-- "1" `is` (1, 0, '.', ',', [])
@ -796,9 +796,9 @@ datetagp mdefdate = do
startpos <- getPosition
v <- tagvaluep
-- re-parse value as a date.
ctx <- getState
ep <- parseWithCtx
ctx{ctxYear=first3.toGregorian <$> mdefdate}
jps <- getState
ep <- parseWithState
jps{jpsYear=first3.toGregorian <$> mdefdate}
-- The value extends to a comma, newline, or end of file.
-- It seems like ignoring any extra stuff following a date
-- gives better errors here.
@ -855,9 +855,9 @@ bracketeddatetagsp mdefdate = do
-- looks sufficiently like a bracketed date, now we
-- re-parse as dates and throw any errors
ctx <- getState
ep <- parseWithCtx
ctx{ctxYear=first3.toGregorian <$> mdefdate}
jps <- getState
ep <- parseWithState
jps{jpsYear=first3.toGregorian <$> mdefdate}
(do
setPosition startpos
md1 <- optionMaybe datep

View File

@ -605,7 +605,7 @@ transactionFromCsvRecord sourcepos rules record = t
status =
case mfieldtemplate "status" of
Nothing -> Uncleared
Just str -> either statuserror id $ runParser (statusp <* eof) nullctx "" $ render str
Just str -> either statuserror id $ runParser (statusp <* eof) nulljps "" $ render str
where
statuserror err = error' $ unlines
["error: could not parse \""++str++"\" as a cleared status (should be *, ! or empty)"
@ -617,7 +617,7 @@ transactionFromCsvRecord sourcepos rules record = t
precomment = maybe "" render $ mfieldtemplate "precomment"
currency = maybe (fromMaybe "" mdefaultcurrency) render $ mfieldtemplate "currency"
amountstr = (currency++) $ negateIfParenthesised $ getAmountStr rules record
amount = either amounterror (Mixed . (:[])) $ runParser (amountp <* eof) nullctx "" amountstr
amount = either amounterror (Mixed . (:[])) $ runParser (amountp <* eof) nulljps "" amountstr
amounterror err = error' $ unlines
["error: could not parse \""++amountstr++"\" as an amount"
,showRecord record
@ -780,20 +780,20 @@ test_parser = [
assertParseEqual (parseCsvRules "unknown" "") rules
-- ,"convert rules parsing: accountrule" ~: do
-- assertParseEqual (parseWithCtx rules accountrule "A\na\n") -- leading blank line required
-- assertParseEqual (parseWithState rules accountrule "A\na\n") -- leading blank line required
-- ([("A",Nothing)], "a")
,"convert rules parsing: trailing comments" ~: do
assertParse (parseWithCtx rules rulesp "skip\n# \n#\n")
assertParse (parseWithState rules rulesp "skip\n# \n#\n")
,"convert rules parsing: trailing blank lines" ~: do
assertParse (parseWithCtx rules rulesp "skip\n\n \n")
assertParse (parseWithState rules rulesp "skip\n\n \n")
-- not supported
-- ,"convert rules parsing: no final newline" ~: do
-- assertParse (parseWithCtx rules csvrulesfile "A\na")
-- assertParse (parseWithCtx rules csvrulesfile "A\na\n# \n#")
-- assertParse (parseWithCtx rules csvrulesfile "A\na\n\n ")
-- assertParse (parseWithState rules csvrulesfile "A\na")
-- assertParse (parseWithState rules csvrulesfile "A\na\n# \n#")
-- assertParse (parseWithState rules csvrulesfile "A\na\n\n ")
-- (rules{
-- -- dateField=Maybe FieldPosition,

View File

@ -122,14 +122,14 @@ parse _ = parseAndFinaliseJournal journalp
--- ** journal
-- | Top-level journal parser. Returns a single composite, I/O performing,
-- error-raising "JournalUpdate" (and final "JournalContext") which can be
-- error-raising "JournalUpdate" (and final "JournalParseState") which can be
-- applied to an empty journal to get the final result.
journalp :: ErroringJournalParser (JournalUpdate,JournalContext)
journalp :: ErroringJournalParser (JournalUpdate,JournalParseState)
journalp = do
journalupdates <- many journalItem
eof
finalctx <- getState
return (combineJournalUpdates journalupdates, finalctx)
finaljps <- getState
return (combineJournalUpdates journalupdates, finaljps)
where
-- As all journal line types can be distinguished by the first
-- character, excepting transactions versus empty (blank or
@ -175,7 +175,7 @@ includedirectivep = do
outerPos <- getPosition
let curdir = takeDirectory (sourceName outerPos)
-- XXX clean this up, probably after getting rid of JournalUpdate
let (u::ExceptT String IO (Journal -> Journal, JournalContext)) = do
let (u::ExceptT String IO (Journal -> Journal, JournalParseState)) = do
filepath <- expandPath curdir filename
txt <- readFileOrError outerPos filepath
let inIncluded = show outerPos ++ " in included file " ++ show filename ++ ":\n"
@ -188,11 +188,11 @@ includedirectivep = do
outerState filepath txt
case r of
Right (ju, ctx) -> do
Right (ju, jps) -> do
u <- combineJournalUpdates [ return $ journalAddFile (filepath,txt)
, ju
] `catchError` (throwError . (inIncluded ++))
return (u, ctx)
return (u, jps)
Left err -> throwError $ inIncluded ++ show err
where readFileOrError pos fp =
ExceptT $ fmap Right (readFile' fp) `C.catch`
@ -200,7 +200,7 @@ includedirectivep = do
r <- liftIO $ runExceptT u
case r of
Left err -> return $ throwError err
Right (ju, _finalparsectx) -> return $ ExceptT $ return $ Right ju
Right (ju, _finalparsejps) -> return $ ExceptT $ return $ Right ju
accountdirectivep :: ErroringJournalParser JournalUpdate
accountdirectivep = do
@ -422,7 +422,7 @@ transactionp = do
#ifdef TESTS
test_transactionp = do
let s `gives` t = do
let p = parseWithCtx nullctx transactionp s
let p = parseWithState nulljps transactionp s
assertBool $ isRight p
let Right t2 = p
-- same f = assertEqual (f t) (f t2)
@ -475,7 +475,7 @@ test_transactionp = do
tdate=parsedate "2015/01/01",
}
assertRight $ parseWithCtx nullctx transactionp $ unlines
assertRight $ parseWithState nulljps transactionp $ unlines
["2007/01/28 coopportunity"
," expenses:food:groceries $47.18"
," assets:checking $-47.18"
@ -483,25 +483,25 @@ test_transactionp = do
]
-- transactionp should not parse just a date
assertLeft $ parseWithCtx nullctx transactionp "2009/1/1\n"
assertLeft $ parseWithState nulljps transactionp "2009/1/1\n"
-- transactionp should not parse just a date and description
assertLeft $ parseWithCtx nullctx transactionp "2009/1/1 a\n"
assertLeft $ parseWithState nulljps transactionp "2009/1/1 a\n"
-- transactionp should not parse a following comment as part of the description
let p = parseWithCtx nullctx transactionp "2009/1/1 a ;comment\n b 1\n"
let p = parseWithState nulljps transactionp "2009/1/1 a ;comment\n b 1\n"
assertRight p
assertEqual "a" (let Right p' = p in tdescription p')
-- parse transaction with following whitespace line
assertRight $ parseWithCtx nullctx transactionp $ unlines
assertRight $ parseWithState nulljps transactionp $ unlines
["2012/1/1"
," a 1"
," b"
," "
]
let p = parseWithCtx nullctx transactionp $ unlines
let p = parseWithState nulljps transactionp $ unlines
["2009/1/1 x ; transaction comment"
," a 1 ; posting 1 comment"
," ; posting 1 comment 2"
@ -555,7 +555,7 @@ postingp mtdate = do
#ifdef TESTS
test_postingp = do
let s `gives` ep = do
let parse = parseWithCtx nullctx (postingp Nothing) s
let parse = parseWithState nulljps (postingp Nothing) s
assertBool -- "postingp parser"
$ isRight parse
let Right ap = parse
@ -587,12 +587,12 @@ test_postingp = do
,pdate=parsedateM "2012/11/28"}
assertBool -- "postingp parses a quoted commodity with numbers"
(isRight $ parseWithCtx nullctx (postingp Nothing) " a 1 \"DE123\"\n")
(isRight $ parseWithState nulljps (postingp Nothing) " a 1 \"DE123\"\n")
-- ,"postingp parses balance assertions and fixed lot prices" ~: do
assertBool (isRight $ parseWithCtx nullctx (postingp Nothing) " a 1 \"DE123\" =$1 { =2.2 EUR} \n")
assertBool (isRight $ parseWithState nulljps (postingp Nothing) " a 1 \"DE123\" =$1 { =2.2 EUR} \n")
-- let parse = parseWithCtx nullctx postingp " a\n ;next-line comment\n"
-- let parse = parseWithState nulljps postingp " a\n ;next-line comment\n"
-- assertRight parse
-- let Right p = parse
-- assertEqual "next-line comment\n" (pcomment p)
@ -619,30 +619,30 @@ tests_Hledger_Read_JournalReader = TestList $ concat [
test_transactionp,
[
"modifiertransactionp" ~: do
assertParse (parseWithCtx nullctx modifiertransactionp "= (some value expr)\n some:postings 1\n")
assertParse (parseWithState nulljps modifiertransactionp "= (some value expr)\n some:postings 1\n")
,"periodictransactionp" ~: do
assertParse (parseWithCtx nullctx periodictransactionp "~ (some period expr)\n some:postings 1\n")
assertParse (parseWithState nulljps periodictransactionp "~ (some period expr)\n some:postings 1\n")
,"directivep" ~: do
assertParse (parseWithCtx nullctx directivep "!include /some/file.x\n")
assertParse (parseWithCtx nullctx directivep "account some:account\n")
assertParse (parseWithCtx nullctx (directivep >> directivep) "!account a\nend\n")
assertParse (parseWithState nulljps directivep "!include /some/file.x\n")
assertParse (parseWithState nulljps directivep "account some:account\n")
assertParse (parseWithState nulljps (directivep >> directivep) "!account a\nend\n")
,"comment" ~: do
assertParse (parseWithCtx nullctx comment "; some comment \n")
assertParse (parseWithCtx nullctx comment " \t; x\n")
assertParse (parseWithCtx nullctx comment "#x")
assertParse (parseWithState nulljps comment "; some comment \n")
assertParse (parseWithState nulljps comment " \t; x\n")
assertParse (parseWithState nulljps comment "#x")
,"datep" ~: do
assertParse (parseWithCtx nullctx datep "2011/1/1")
assertParseFailure (parseWithCtx nullctx datep "1/1")
assertParse (parseWithCtx nullctx{ctxYear=Just 2011} datep "1/1")
assertParse (parseWithState nulljps datep "2011/1/1")
assertParseFailure (parseWithState nulljps datep "1/1")
assertParse (parseWithState nulljps{jpsYear=Just 2011} datep "1/1")
,"datetimep" ~: do
let p = do {t <- datetimep; eof; return t}
bad = assertParseFailure . parseWithCtx nullctx p
good = assertParse . parseWithCtx nullctx p
bad = assertParseFailure . parseWithState nulljps p
good = assertParse . parseWithState nulljps p
bad "2011/1/1"
bad "2011/1/1 24:00:00"
bad "2011/1/1 00:60:00"
@ -652,31 +652,31 @@ tests_Hledger_Read_JournalReader = TestList $ concat [
good "2011/1/1 3:5:7"
-- timezone is parsed but ignored
let startofday = LocalTime (fromGregorian 2011 1 1) (TimeOfDay 0 0 (fromIntegral 0))
assertParseEqual (parseWithCtx nullctx p "2011/1/1 00:00-0800") startofday
assertParseEqual (parseWithCtx nullctx p "2011/1/1 00:00+1234") startofday
assertParseEqual (parseWithState nulljps p "2011/1/1 00:00-0800") startofday
assertParseEqual (parseWithState nulljps p "2011/1/1 00:00+1234") startofday
,"defaultyeardirectivep" ~: do
assertParse (parseWithCtx nullctx defaultyeardirectivep "Y 2010\n")
assertParse (parseWithCtx nullctx defaultyeardirectivep "Y 10001\n")
assertParse (parseWithState nulljps defaultyeardirectivep "Y 2010\n")
assertParse (parseWithState nulljps defaultyeardirectivep "Y 10001\n")
,"marketpricedirectivep" ~:
assertParseEqual (parseWithCtx nullctx marketpricedirectivep "P 2004/05/01 XYZ $55.00\n") (MarketPrice (parsedate "2004/05/01") "XYZ" $ usd 55)
assertParseEqual (parseWithState nulljps marketpricedirectivep "P 2004/05/01 XYZ $55.00\n") (MarketPrice (parsedate "2004/05/01") "XYZ" $ usd 55)
,"ignoredpricecommoditydirectivep" ~: do
assertParse (parseWithCtx nullctx ignoredpricecommoditydirectivep "N $\n")
assertParse (parseWithState nulljps ignoredpricecommoditydirectivep "N $\n")
,"defaultcommoditydirectivep" ~: do
assertParse (parseWithCtx nullctx defaultcommoditydirectivep "D $1,000.0\n")
assertParse (parseWithState nulljps defaultcommoditydirectivep "D $1,000.0\n")
,"commodityconversiondirectivep" ~: do
assertParse (parseWithCtx nullctx commodityconversiondirectivep "C 1h = $50.00\n")
assertParse (parseWithState nulljps commodityconversiondirectivep "C 1h = $50.00\n")
,"tagdirectivep" ~: do
assertParse (parseWithCtx nullctx tagdirectivep "tag foo \n")
assertParse (parseWithState nulljps tagdirectivep "tag foo \n")
,"endtagdirectivep" ~: do
assertParse (parseWithCtx nullctx endtagdirectivep "end tag \n")
assertParse (parseWithCtx nullctx endtagdirectivep "pop \n")
assertParse (parseWithState nulljps endtagdirectivep "end tag \n")
assertParse (parseWithState nulljps endtagdirectivep "pop \n")
,"accountnamep" ~: do
assertBool "accountnamep parses a normal account name" (isRight $ parsewith accountnamep "a:b:c")
@ -685,15 +685,15 @@ tests_Hledger_Read_JournalReader = TestList $ concat [
assertBool "accountnamep rejects an empty trailing component" (isLeft $ parsewith accountnamep "a:b:")
,"leftsymbolamountp" ~: do
assertParseEqual (parseWithCtx nullctx leftsymbolamountp "$1") (usd 1 `withPrecision` 0)
assertParseEqual (parseWithCtx nullctx leftsymbolamountp "$-1") (usd (-1) `withPrecision` 0)
assertParseEqual (parseWithCtx nullctx leftsymbolamountp "-$1") (usd (-1) `withPrecision` 0)
assertParseEqual (parseWithState nulljps leftsymbolamountp "$1") (usd 1 `withPrecision` 0)
assertParseEqual (parseWithState nulljps leftsymbolamountp "$-1") (usd (-1) `withPrecision` 0)
assertParseEqual (parseWithState nulljps leftsymbolamountp "-$1") (usd (-1) `withPrecision` 0)
,"amount" ~: do
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)
assertAmountParse (parseWithCtx nullctx amountp "1 @ $2")
assertAmountParse (parseWithState nulljps amountp "1 @ $2")
(num 1 `withPrecision` 0 `at` (usd 2 `withPrecision` 0))
]]

View File

@ -85,11 +85,11 @@ detect f s
parse :: Maybe FilePath -> Bool -> FilePath -> String -> ExceptT String IO Journal
parse _ = parseAndFinaliseJournal timeclockfilep
timeclockfilep :: ParsecT [Char] JournalContext (ExceptT String IO) (JournalUpdate, JournalContext)
timeclockfilep :: ParsecT [Char] JournalParseState (ExceptT String IO) (JournalUpdate, JournalParseState)
timeclockfilep = do items <- many timeclockitemp
eof
ctx <- getState
return (liftM (foldl' (\acc new x -> new (acc x)) id) $ sequence items, ctx)
jps <- getState
return (liftM (foldl' (\acc new x -> new (acc x)) id) $ sequence items, jps)
where
-- As all ledger line types can be distinguished by the first
-- character, excepting transactions versus empty (blank or
@ -100,7 +100,7 @@ timeclockfilep = do items <- many timeclockitemp
] <?> "timeclock entry, or default year or historical price directive"
-- | Parse a timeclock entry.
timeclockentryp :: ParsecT [Char] JournalContext (ExceptT String IO) TimeclockEntry
timeclockentryp :: ParsecT [Char] JournalParseState (ExceptT String IO) TimeclockEntry
timeclockentryp = do
sourcepos <- genericSourcePos <$> getPosition
code <- oneOf "bhioO"

View File

@ -69,11 +69,11 @@ detect f s
parse :: Maybe FilePath -> Bool -> FilePath -> String -> ExceptT String IO Journal
parse _ = parseAndFinaliseJournal timedotfilep
timedotfilep :: ParsecT [Char] JournalContext (ExceptT String IO) (JournalUpdate, JournalContext)
timedotfilep :: ParsecT [Char] JournalParseState (ExceptT String IO) (JournalUpdate, JournalParseState)
timedotfilep = do items <- many timedotfileitemp
eof
ctx <- getState
return (liftM (foldl' (\acc new x -> new (acc x)) id) $ sequence items, ctx)
jps <- getState
return (liftM (foldl' (\acc new x -> new (acc x)) id) $ sequence items, jps)
where
timedotfileitemp = do
ptrace "timedotfileitemp"
@ -92,7 +92,7 @@ addTransactions ts j = foldl' (flip ($)) j (map addTransaction ts)
-- biz.research .
-- inc.client1 .... .... .... .... .... ....
-- @
timedotdayp :: ParsecT [Char] JournalContext (ExceptT String IO) [Transaction]
timedotdayp :: ParsecT [Char] JournalParseState (ExceptT String IO) [Transaction]
timedotdayp = do
ptrace " timedotdayp"
d <- datep <* eolof
@ -104,7 +104,7 @@ timedotdayp = do
-- @
-- fos.haskell .... ..
-- @
timedotentryp :: ParsecT [Char] JournalContext (ExceptT String IO) Transaction
timedotentryp :: ParsecT [Char] JournalParseState (ExceptT String IO) Transaction
timedotentryp = do
ptrace " timedotentryp"
pos <- genericSourcePos <$> getPosition
@ -128,14 +128,14 @@ timedotentryp = do
}
return t
timedotdurationp :: ParsecT [Char] JournalContext (ExceptT String IO) Quantity
timedotdurationp :: ParsecT [Char] JournalParseState (ExceptT String IO) Quantity
timedotdurationp = try timedotnumberp <|> timedotdotsp
-- | Parse a duration written as a decimal number of hours (optionally followed by the letter h).
-- @
-- 1.5h
-- @
timedotnumberp :: ParsecT [Char] JournalContext (ExceptT String IO) Quantity
timedotnumberp :: ParsecT [Char] JournalParseState (ExceptT String IO) Quantity
timedotnumberp = do
(q, _, _, _) <- numberp
many spacenonewline
@ -147,7 +147,7 @@ timedotnumberp = do
-- @
-- .... ..
-- @
timedotdotsp :: ParsecT [Char] JournalContext (ExceptT String IO) Quantity
timedotdotsp :: ParsecT [Char] JournalParseState (ExceptT String IO) Quantity
timedotdotsp = do
dots <- filter (not.isSpace) <$> many (oneOf ". ")
return $ (/4) $ fromIntegral $ length dots

View File

@ -16,8 +16,8 @@ choice' = choice . map Text.Parsec.try
parsewith :: Parsec [Char] () a -> String -> Either ParseError a
parsewith p = runParser p () ""
parseWithCtx :: Stream s m t => u -> ParsecT s u m a -> s -> m (Either ParseError a)
parseWithCtx ctx p = runParserT p ctx ""
parseWithState :: Stream s m t => u -> ParsecT s u m a -> s -> m (Either ParseError a)
parseWithState jps p = runParserT p jps ""
fromparse :: Either ParseError a -> a
fromparse = either parseerror id

View File

@ -96,7 +96,7 @@ postAddForm = do
map fst amtparams `elem` [[1..num], [1..num-1]] = []
| otherwise = ["the posting parameters are malformed"]
eaccts = map (parsewith (accountnamep <* eof) . strip . T.unpack . snd) acctparams
eamts = map (runParser (amountp <* eof) nullctx "" . strip . T.unpack . snd) amtparams
eamts = map (runParser (amountp <* eof) nulljps "" . strip . T.unpack . snd) amtparams
(accts, acctErrs) = (rights eaccts, map show $ lefts eaccts)
(amts', amtErrs) = (rights eamts, map show $ lefts eamts)
amts | length amts' == num = amts'

View File

@ -74,7 +74,7 @@ tests_Hledger_Cli = TestList
let ignoresourcepos j = j{jtxns=map (\t -> t{tsourcepos=nullsourcepos}) (jtxns j)} in
let sameParse str1 str2 = do j1 <- readJournal Nothing Nothing True Nothing str1 >>= either error' (return . ignoresourcepos)
j2 <- readJournal Nothing Nothing True Nothing str2 >>= either error' (return . ignoresourcepos)
j1 `is` j2{filereadtime=filereadtime j1, files=files j1, jContext=jContext j1}
j1 `is` j2{filereadtime=filereadtime j1, files=files j1, jparsestate=jparsestate j1}
in TestList
[
"apply account directive 1" ~: sameParse

View File

@ -181,8 +181,8 @@ dateAndCodeWizard EntryState{..} = do
where
parseSmartDateAndCode refdate s = either (const Nothing) (\(d,c) -> return (fixSmartDate refdate d, c)) edc
where
edc = runParser (dateandcodep <* eof) nullctx "" $ lowercase s
dateandcodep :: Stream [Char] m t => ParsecT [Char] JournalContext m (SmartDate, String)
edc = runParser (dateandcodep <* eof) nulljps "" $ lowercase s
dateandcodep :: Stream [Char] m t => ParsecT [Char] JournalParseState m (SmartDate, String)
dateandcodep = do
d <- smartdate
c <- optionMaybe codep
@ -245,7 +245,7 @@ accountWizard EntryState{..} = do
parseAccountOrDotOrNull _ _ "." = dbg1 $ Just "." -- . always signals end of txn
parseAccountOrDotOrNull "" True "" = dbg1 $ Just "" -- when there's no default and txn is balanced, "" also signals end of txn
parseAccountOrDotOrNull def@(_:_) _ "" = dbg1 $ Just def -- when there's a default, "" means use that
parseAccountOrDotOrNull _ _ s = dbg1 $ either (const Nothing) validateAccount $ runParser (accountnamep <* eof) (jContext esJournal) "" s -- otherwise, try to parse the input as an accountname
parseAccountOrDotOrNull _ _ s = dbg1 $ either (const Nothing) validateAccount $ runParser (accountnamep <* eof) (jparsestate esJournal) "" s -- otherwise, try to parse the input as an accountname
dbg1 = id -- strace
validateAccount s | no_new_accounts_ esOpts && not (s `elem` journalAccountNames esJournal) = Nothing
| otherwise = Just s
@ -269,9 +269,9 @@ amountAndCommentWizard EntryState{..} = do
maybeRestartTransaction $
line $ green $ printf "Amount %d%s: " pnum (showDefault def)
where
parseAmountAndComment = either (const Nothing) Just . runParser (amountandcommentp <* eof) nodefcommodityctx ""
nodefcommodityctx = (jContext esJournal){ctxDefaultCommodityAndStyle=Nothing}
amountandcommentp :: Stream [Char] m t => ParsecT [Char] JournalContext m (Amount, String)
parseAmountAndComment = either (const Nothing) Just . runParser (amountandcommentp <* eof) noDefCommodityJPS ""
noDefCommodityJPS = (jparsestate esJournal){jpsDefaultCommodityAndStyle=Nothing}
amountandcommentp :: Stream [Char] m t => ParsecT [Char] JournalParseState m (Amount, String)
amountandcommentp = do
a <- amountp
many spacenonewline
@ -290,11 +290,11 @@ amountAndCommentWizard EntryState{..} = do
maxprecisionwithpoint
--
-- let -- (amt,comment) = (strip a, strip $ dropWhile (==';') b) where (a,b) = break (==';') amtcmt
-- a = fromparse $ runParser (amountp <|> return missingamt) (jContext esJournal) "" amt
-- awithoutctx = fromparse $ runParser (amountp <|> return missingamt) nullctx "" amt
-- a = fromparse $ runParser (amountp <|> return missingamt) (jparsestate esJournal) "" amt
-- awithoutjps = fromparse $ runParser (amountp <|> return missingamt) nulljps "" amt
-- defamtaccepted = Just (showAmount a) == mdefamt
-- es2 = if defamtaccepted then es1 else es1{esHistoricalPostings=Nothing}
-- mdefaultcommodityapplied = if acommodity a == acommodity awithoutctx then Nothing else Just $ acommodity a
-- mdefaultcommodityapplied = if acommodity a == acommodity awithoutjps then Nothing else Just $ acommodity a
-- when (isJust mdefaultcommodityapplied) $
-- liftIO $ hPutStrLn stderr $ printf "using default commodity (%s)" (fromJust mdefaultcommodityapplied)