lib: rename and expose more journal file parsers
This commit is contained in:
parent
ac95930b15
commit
5223bc5c41
@ -19,11 +19,12 @@ module Hledger.Read (
|
|||||||
ensureJournalFileExists,
|
ensureJournalFileExists,
|
||||||
-- * Parsers used elsewhere
|
-- * Parsers used elsewhere
|
||||||
postingp,
|
postingp,
|
||||||
accountname,
|
accountnamep,
|
||||||
amountp,
|
amountp,
|
||||||
amountp',
|
amountp',
|
||||||
mamountp',
|
mamountp',
|
||||||
code,
|
numberp,
|
||||||
|
codep,
|
||||||
-- * Tests
|
-- * Tests
|
||||||
samplejournal,
|
samplejournal,
|
||||||
tests_Hledger_Read,
|
tests_Hledger_Read,
|
||||||
|
|||||||
@ -26,13 +26,14 @@ module Hledger.Read.JournalReader (
|
|||||||
directive,
|
directive,
|
||||||
defaultyeardirective,
|
defaultyeardirective,
|
||||||
historicalpricedirective,
|
historicalpricedirective,
|
||||||
datetime,
|
datetimep,
|
||||||
code,
|
codep,
|
||||||
accountname,
|
accountnamep,
|
||||||
postingp,
|
postingp,
|
||||||
amountp,
|
amountp,
|
||||||
amountp',
|
amountp',
|
||||||
mamountp',
|
mamountp',
|
||||||
|
numberp,
|
||||||
emptyline
|
emptyline
|
||||||
#ifdef TESTS
|
#ifdef TESTS
|
||||||
-- * Tests
|
-- * Tests
|
||||||
@ -207,7 +208,7 @@ accountdirective :: GenParser Char JournalContext JournalUpdate
|
|||||||
accountdirective = do
|
accountdirective = do
|
||||||
string "account"
|
string "account"
|
||||||
many1 spacenonewline
|
many1 spacenonewline
|
||||||
parent <- accountname
|
parent <- accountnamep
|
||||||
newline
|
newline
|
||||||
pushParentAccount parent
|
pushParentAccount parent
|
||||||
return $ return id
|
return $ return id
|
||||||
@ -272,7 +273,7 @@ historicalpricedirective :: GenParser Char JournalContext HistoricalPrice
|
|||||||
historicalpricedirective = do
|
historicalpricedirective = do
|
||||||
char 'P' <?> "historical price"
|
char 'P' <?> "historical price"
|
||||||
many spacenonewline
|
many spacenonewline
|
||||||
date <- try (do {LocalTime d _ <- datetime; return d}) <|> date -- a time is ignored
|
date <- try (do {LocalTime d _ <- datetimep; return d}) <|> date -- a time is ignored
|
||||||
many1 spacenonewline
|
many1 spacenonewline
|
||||||
symbol <- commoditysymbol
|
symbol <- commoditysymbol
|
||||||
many spacenonewline
|
many spacenonewline
|
||||||
@ -323,7 +324,7 @@ transaction = do
|
|||||||
date <- date <?> "transaction"
|
date <- date <?> "transaction"
|
||||||
edate <- optionMaybe (secondarydate date) <?> "secondary date"
|
edate <- optionMaybe (secondarydate date) <?> "secondary date"
|
||||||
status <- status <?> "cleared flag"
|
status <- status <?> "cleared flag"
|
||||||
code <- code <?> "transaction code"
|
code <- codep <?> "transaction code"
|
||||||
description <- descriptionp >>= return . strip
|
description <- descriptionp >>= return . strip
|
||||||
comment <- try followingcomment <|> (newline >> return "")
|
comment <- try followingcomment <|> (newline >> return "")
|
||||||
let tags = tagsInComment comment
|
let tags = tagsInComment comment
|
||||||
@ -443,8 +444,8 @@ date = do
|
|||||||
-- timezone will be ignored; the time is treated as local time. Fewer
|
-- timezone will be ignored; the time is treated as local time. Fewer
|
||||||
-- digits are allowed, except in the timezone. The year may be omitted if
|
-- digits are allowed, except in the timezone. The year may be omitted if
|
||||||
-- a default year has already been set.
|
-- a default year has already been set.
|
||||||
datetime :: GenParser Char JournalContext LocalTime
|
datetimep :: GenParser Char JournalContext LocalTime
|
||||||
datetime = do
|
datetimep = do
|
||||||
day <- date
|
day <- date
|
||||||
many1 spacenonewline
|
many1 spacenonewline
|
||||||
h <- many1 digit
|
h <- many1 digit
|
||||||
@ -487,8 +488,8 @@ secondarydate primarydate = do
|
|||||||
status :: GenParser Char JournalContext Bool
|
status :: GenParser Char JournalContext Bool
|
||||||
status = try (do { many spacenonewline; (char '*' <|> char '!') <?> "status"; return True } ) <|> return False
|
status = try (do { many spacenonewline; (char '*' <|> char '!') <?> "status"; return True } ) <|> return False
|
||||||
|
|
||||||
code :: GenParser Char JournalContext String
|
codep :: GenParser Char JournalContext String
|
||||||
code = try (do { many1 spacenonewline; char '(' <?> "code"; code <- anyChar `manyTill` char ')'; return code } ) <|> return ""
|
codep = try (do { many1 spacenonewline; char '(' <?> "codep"; code <- anyChar `manyTill` char ')'; return code } ) <|> return ""
|
||||||
|
|
||||||
-- Parse the following whitespace-beginning lines as postings, posting tags, and/or comments.
|
-- Parse the following whitespace-beginning lines as postings, posting tags, and/or comments.
|
||||||
postings :: GenParser Char JournalContext [Posting]
|
postings :: GenParser Char JournalContext [Posting]
|
||||||
@ -570,7 +571,7 @@ test_postingp = do
|
|||||||
-- | Parse an account name, then apply any parent account prefix and/or account aliases currently in effect.
|
-- | Parse an account name, then apply any parent account prefix and/or account aliases currently in effect.
|
||||||
modifiedaccountname :: GenParser Char JournalContext AccountName
|
modifiedaccountname :: GenParser Char JournalContext AccountName
|
||||||
modifiedaccountname = do
|
modifiedaccountname = do
|
||||||
a <- accountname
|
a <- accountnamep
|
||||||
prefix <- getParentAccount
|
prefix <- getParentAccount
|
||||||
let prefixed = prefix `joinAccountNames` a
|
let prefixed = prefix `joinAccountNames` a
|
||||||
aliases <- getAccountAliases
|
aliases <- getAccountAliases
|
||||||
@ -580,12 +581,12 @@ modifiedaccountname = do
|
|||||||
-- them, and are terminated by two or more spaces. They should have one or
|
-- them, and are terminated by two or more spaces. They should have one or
|
||||||
-- more components of at least one character, separated by the account
|
-- more components of at least one character, separated by the account
|
||||||
-- separator char.
|
-- separator char.
|
||||||
accountname :: GenParser Char st AccountName
|
accountnamep :: GenParser Char st AccountName
|
||||||
accountname = do
|
accountnamep = do
|
||||||
a <- many1 (nonspace <|> singlespace)
|
a <- many1 (nonspace <|> singlespace)
|
||||||
let a' = striptrailingspace a
|
let a' = striptrailingspace a
|
||||||
when (accountNameFromComponents (accountNameComponents a') /= a')
|
when (accountNameFromComponents (accountNameComponents a') /= a')
|
||||||
(fail $ "accountname seems ill-formed: "++a')
|
(fail $ "account name seems ill-formed: "++a')
|
||||||
return a'
|
return a'
|
||||||
where
|
where
|
||||||
singlespace = try (do {spacenonewline; do {notFollowedBy spacenonewline; return ' '}})
|
singlespace = try (do {spacenonewline; do {notFollowedBy spacenonewline; return ' '}})
|
||||||
@ -653,7 +654,7 @@ leftsymbolamount = do
|
|||||||
let applysign = if isJust sign then negate else id
|
let applysign = if isJust sign then negate else id
|
||||||
c <- commoditysymbol
|
c <- commoditysymbol
|
||||||
sp <- many spacenonewline
|
sp <- many spacenonewline
|
||||||
(q,prec,dec,sep,seppos) <- number
|
(q,prec,dec,sep,seppos) <- numberp
|
||||||
let s = amountstyle{ascommodityside=L, ascommodityspaced=not $ null sp, asdecimalpoint=dec, asprecision=prec, asseparator=sep, asseparatorpositions=seppos}
|
let s = amountstyle{ascommodityside=L, ascommodityspaced=not $ null sp, asdecimalpoint=dec, asprecision=prec, asseparator=sep, asseparatorpositions=seppos}
|
||||||
p <- priceamount
|
p <- priceamount
|
||||||
return $ applysign $ Amount c q p s
|
return $ applysign $ Amount c q p s
|
||||||
@ -661,7 +662,7 @@ leftsymbolamount = do
|
|||||||
|
|
||||||
rightsymbolamount :: GenParser Char JournalContext Amount
|
rightsymbolamount :: GenParser Char JournalContext Amount
|
||||||
rightsymbolamount = do
|
rightsymbolamount = do
|
||||||
(q,prec,dec,sep,seppos) <- number
|
(q,prec,dec,sep,seppos) <- numberp
|
||||||
sp <- many spacenonewline
|
sp <- many spacenonewline
|
||||||
c <- commoditysymbol
|
c <- commoditysymbol
|
||||||
p <- priceamount
|
p <- priceamount
|
||||||
@ -671,7 +672,7 @@ rightsymbolamount = do
|
|||||||
|
|
||||||
nosymbolamount :: GenParser Char JournalContext Amount
|
nosymbolamount :: GenParser Char JournalContext Amount
|
||||||
nosymbolamount = do
|
nosymbolamount = do
|
||||||
(q,prec,dec,sep,seppos) <- number
|
(q,prec,dec,sep,seppos) <- numberp
|
||||||
p <- priceamount
|
p <- priceamount
|
||||||
defcs <- getCommodityAndStyle
|
defcs <- getCommodityAndStyle
|
||||||
let (c,s) = case defcs of
|
let (c,s) = case defcs of
|
||||||
@ -744,8 +745,8 @@ fixedlotprice =
|
|||||||
-- and separator characters (defaulting to . and ,), and the positions of
|
-- and separator characters (defaulting to . and ,), and the positions of
|
||||||
-- separators (counting leftward from the decimal point, the last is
|
-- separators (counting leftward from the decimal point, the last is
|
||||||
-- assumed to repeat).
|
-- assumed to repeat).
|
||||||
number :: GenParser Char JournalContext (Quantity, Int, Char, Char, [Int])
|
numberp :: GenParser Char JournalContext (Quantity, Int, Char, Char, [Int])
|
||||||
number = do
|
numberp = do
|
||||||
sign <- optionMaybe $ string "-"
|
sign <- optionMaybe $ string "-"
|
||||||
parts <- many1 $ choice' [many1 digit, many1 $ char ',', many1 $ char '.']
|
parts <- many1 $ choice' [many1 digit, many1 $ char ',', many1 $ char '.']
|
||||||
let numeric = isNumber . headDef '_'
|
let numeric = isNumber . headDef '_'
|
||||||
@ -781,12 +782,12 @@ number = do
|
|||||||
(Nothing, Just ',') -> ('.',',')
|
(Nothing, Just ',') -> ('.',',')
|
||||||
_ -> ('.',',')
|
_ -> ('.',',')
|
||||||
return (quantity,precision,decimalpoint,separator,separatorpositions)
|
return (quantity,precision,decimalpoint,separator,separatorpositions)
|
||||||
<?> "number"
|
<?> "numberp"
|
||||||
|
|
||||||
#ifdef TESTS
|
#ifdef TESTS
|
||||||
test_number = do
|
test_numberp = do
|
||||||
let s `is` n = assertParseEqual' (parseWithCtx nullctx number s) n
|
let s `is` n = assertParseEqual' (parseWithCtx nullctx numberp s) n
|
||||||
assertFails = assertBool . isLeft . parseWithCtx nullctx number
|
assertFails = assertBool . isLeft . parseWithCtx nullctx numberp
|
||||||
assertFails ""
|
assertFails ""
|
||||||
"0" `is` (0, 0, '.', ',', [])
|
"0" `is` (0, 0, '.', ',', [])
|
||||||
"1" `is` (1, 0, '.', ',', [])
|
"1" `is` (1, 0, '.', ',', [])
|
||||||
@ -893,7 +894,7 @@ date2ValueFromTags ts = maybe Nothing (Just . snd) $ find ((=="date2") . fst) ts
|
|||||||
{- old hunit tests
|
{- old hunit tests
|
||||||
|
|
||||||
test_Hledger_Read_JournalReader = TestList $ concat [
|
test_Hledger_Read_JournalReader = TestList $ concat [
|
||||||
test_number,
|
test_numberp,
|
||||||
test_amountp,
|
test_amountp,
|
||||||
test_spaceandamountormissing,
|
test_spaceandamountormissing,
|
||||||
test_tagcomment,
|
test_tagcomment,
|
||||||
@ -924,8 +925,8 @@ test_Hledger_Read_JournalReader = TestList $ concat [
|
|||||||
assertParseFailure (parseWithCtx nullctx date "1/1")
|
assertParseFailure (parseWithCtx nullctx date "1/1")
|
||||||
assertParse (parseWithCtx nullctx{ctxYear=Just 2011} date "1/1")
|
assertParse (parseWithCtx nullctx{ctxYear=Just 2011} date "1/1")
|
||||||
|
|
||||||
,"datetime" ~: do
|
,"datetimep" ~: do
|
||||||
let p = do {t <- datetime; eof; return t}
|
let p = do {t <- datetimep; eof; return t}
|
||||||
bad = assertParseFailure . parseWithCtx nullctx p
|
bad = assertParseFailure . parseWithCtx nullctx p
|
||||||
good = assertParse . parseWithCtx nullctx p
|
good = assertParse . parseWithCtx nullctx p
|
||||||
bad "2011/1/1"
|
bad "2011/1/1"
|
||||||
@ -963,11 +964,11 @@ test_Hledger_Read_JournalReader = TestList $ concat [
|
|||||||
assertParse (parseWithCtx nullctx endtagdirective "end tag \n")
|
assertParse (parseWithCtx nullctx endtagdirective "end tag \n")
|
||||||
assertParse (parseWithCtx nullctx endtagdirective "pop \n")
|
assertParse (parseWithCtx nullctx endtagdirective "pop \n")
|
||||||
|
|
||||||
,"accountname" ~: do
|
,"accountnamep" ~: do
|
||||||
assertBool "accountname parses a normal accountname" (isRight $ parsewith accountname "a:b:c")
|
assertBool "accountnamep parses a normal account name" (isRight $ parsewith accountnamep "a:b:c")
|
||||||
assertBool "accountname rejects an empty inner component" (isLeft $ parsewith accountname "a::c")
|
assertBool "accountnamep rejects an empty inner component" (isLeft $ parsewith accountnamep "a::c")
|
||||||
assertBool "accountname rejects an empty leading component" (isLeft $ parsewith accountname ":b:c")
|
assertBool "accountnamep rejects an empty leading component" (isLeft $ parsewith accountnamep ":b:c")
|
||||||
assertBool "accountname rejects an empty trailing component" (isLeft $ parsewith accountname "a:b:")
|
assertBool "accountnamep rejects an empty trailing component" (isLeft $ parsewith accountnamep "a:b:")
|
||||||
|
|
||||||
,"leftsymbolamount" ~: do
|
,"leftsymbolamount" ~: do
|
||||||
assertParseEqual (parseWithCtx nullctx leftsymbolamount "$1") (usd 1 `withPrecision` 0)
|
assertParseEqual (parseWithCtx nullctx leftsymbolamount "$1") (usd 1 `withPrecision` 0)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user