mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-08 07:09:28 +03:00
refactor: parser cleanup
This commit is contained in:
parent
27510b0106
commit
8fd94ef6f5
@ -3,151 +3,7 @@
|
||||
|
||||
Parsers for standard ledger and timelog files.
|
||||
|
||||
-}
|
||||
|
||||
module Ledger.Parse
|
||||
where
|
||||
import Control.Monad.Error (ErrorT(..), MonadIO, liftIO, throwError, catchError)
|
||||
import Text.ParserCombinators.Parsec
|
||||
import Text.ParserCombinators.Parsec.Char
|
||||
import Text.ParserCombinators.Parsec.Combinator
|
||||
import System.Directory
|
||||
#if __GLASGOW_HASKELL__ <= 610
|
||||
import Prelude hiding (readFile, putStr, putStrLn, print, getContents)
|
||||
import System.IO.UTF8
|
||||
#endif
|
||||
import Ledger.Utils
|
||||
import Ledger.Types
|
||||
import Ledger.Dates
|
||||
import Ledger.AccountName (accountNameFromComponents,accountNameComponents)
|
||||
import Ledger.Amount
|
||||
import Ledger.Transaction
|
||||
import Ledger.Posting
|
||||
import Ledger.Journal
|
||||
import Ledger.Commodity (dollars,dollar,unknown)
|
||||
import System.FilePath(takeDirectory,combine)
|
||||
|
||||
|
||||
-- utils
|
||||
|
||||
-- | Some context kept during parsing.
|
||||
data LedgerFileCtx = Ctx {
|
||||
ctxYear :: !(Maybe Integer) -- ^ the default year most recently specified with Y
|
||||
, ctxCommod :: !(Maybe String) -- ^ I don't know
|
||||
, ctxAccount :: ![String] -- ^ the current stack of "container" accounts specified by !account
|
||||
} deriving (Read, Show)
|
||||
|
||||
emptyCtx :: LedgerFileCtx
|
||||
emptyCtx = Ctx { ctxYear = Nothing, ctxCommod = Nothing, ctxAccount = [] }
|
||||
|
||||
-- containing accounts "nest" hierarchically
|
||||
|
||||
pushParentAccount :: String -> GenParser tok LedgerFileCtx ()
|
||||
pushParentAccount parent = updateState addParentAccount
|
||||
where addParentAccount ctx0 = ctx0 { ctxAccount = normalize parent : ctxAccount ctx0 }
|
||||
normalize = (++ ":")
|
||||
|
||||
popParentAccount :: GenParser tok LedgerFileCtx ()
|
||||
popParentAccount = do ctx0 <- getState
|
||||
case ctxAccount ctx0 of
|
||||
[] -> unexpected "End of account block with no beginning"
|
||||
(_:rest) -> setState $ ctx0 { ctxAccount = rest }
|
||||
|
||||
getParentAccount :: GenParser tok LedgerFileCtx String
|
||||
getParentAccount = liftM (concat . reverse . ctxAccount) getState
|
||||
|
||||
setYear :: Integer -> GenParser tok LedgerFileCtx ()
|
||||
setYear y = updateState (\ctx -> ctx{ctxYear=Just y})
|
||||
|
||||
getYear :: GenParser tok LedgerFileCtx (Maybe Integer)
|
||||
getYear = liftM ctxYear getState
|
||||
|
||||
-- let's get to it
|
||||
|
||||
parseLedgerFile :: LocalTime -> FilePath -> ErrorT String IO Journal
|
||||
parseLedgerFile t "-" = liftIO getContents >>= parseLedger t "-"
|
||||
parseLedgerFile t f = liftIO (readFile f) >>= parseLedger t f
|
||||
|
||||
-- | Parses the contents of a ledger file, or gives an error. Requires
|
||||
-- the current (local) time to calculate any unfinished timelog sessions,
|
||||
-- we pass it in for repeatability.
|
||||
parseLedger :: LocalTime -> FilePath -> String -> ErrorT String IO Journal
|
||||
parseLedger reftime inname intxt =
|
||||
case runParser ledgerFile emptyCtx inname intxt of
|
||||
Right m -> liftM (journalConvertTimeLog reftime) $ m `ap` return nulljournal
|
||||
Left err -> throwError $ show err
|
||||
|
||||
|
||||
ledgerFile :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerFile = do items <- many ledgerItem
|
||||
eof
|
||||
return $ liftM (foldr (.) id) $ sequence items
|
||||
where
|
||||
-- As all ledger line types can be distinguished by the first
|
||||
-- character, excepting transactions versus empty (blank or
|
||||
-- comment-only) lines, can use choice w/o try
|
||||
ledgerItem = choice [ ledgerDirective
|
||||
, liftM (return . addTransaction) ledgerTransaction
|
||||
, liftM (return . addModifierTransaction) ledgerModifierTransaction
|
||||
, liftM (return . addPeriodicTransaction) ledgerPeriodicTransaction
|
||||
, liftM (return . addHistoricalPrice) ledgerHistoricalPrice
|
||||
, ledgerDefaultYear
|
||||
, ledgerIgnoredPrice
|
||||
, ledgerTagDirective
|
||||
, ledgerEndTagDirective
|
||||
, emptyLine >> return (return id)
|
||||
, liftM (return . addTimeLogEntry) timelogentry
|
||||
]
|
||||
|
||||
ledgerDirective :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerDirective = do char '!' <?> "directive"
|
||||
directive <- many nonspace
|
||||
case directive of
|
||||
"include" -> ledgerInclude
|
||||
"account" -> ledgerAccountBegin
|
||||
"end" -> ledgerAccountEnd
|
||||
_ -> mzero
|
||||
|
||||
ledgerInclude :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerInclude = do many1 spacenonewline
|
||||
filename <- restofline
|
||||
outerState <- getState
|
||||
outerPos <- getPosition
|
||||
let inIncluded = show outerPos ++ " in included file " ++ show filename ++ ":\n"
|
||||
return $ do contents <- expandPath outerPos filename >>= readFileE outerPos
|
||||
case runParser ledgerFile outerState filename contents of
|
||||
Right l -> l `catchError` (throwError . (inIncluded ++))
|
||||
Left perr -> throwError $ inIncluded ++ show perr
|
||||
where readFileE outerPos filename = ErrorT $ liftM Right (readFile filename) `catch` leftError
|
||||
where leftError err = return $ Left $ currentPos ++ whileReading ++ show err
|
||||
currentPos = show outerPos
|
||||
whileReading = " reading " ++ show filename ++ ":\n"
|
||||
|
||||
expandPath :: (MonadIO m) => SourcePos -> FilePath -> m FilePath
|
||||
expandPath pos fp = liftM mkRelative (expandHome fp)
|
||||
where
|
||||
mkRelative = combine (takeDirectory (sourceName pos))
|
||||
expandHome inname | "~/" `isPrefixOf` inname = do homedir <- liftIO getHomeDirectory
|
||||
return $ homedir ++ drop 1 inname
|
||||
| otherwise = return inname
|
||||
|
||||
ledgerAccountBegin :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerAccountBegin = do many1 spacenonewline
|
||||
parent <- ledgeraccountname
|
||||
newline
|
||||
pushParentAccount parent
|
||||
return $ return id
|
||||
|
||||
ledgerAccountEnd :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerAccountEnd = popParentAccount >> return (return id)
|
||||
|
||||
-- parsers
|
||||
|
||||
-- | Parse a Journal from either a ledger file or a timelog file.
|
||||
-- It tries first the timelog parser then the ledger parser; this means
|
||||
-- parse errors for ledgers are useful while those for timelogs are not.
|
||||
|
||||
{-| Parse a ledger file. Here is the ledger grammar from the ledger 2.5 manual:
|
||||
Here is the ledger grammar from the ledger 2.5 manual:
|
||||
|
||||
@
|
||||
The ledger file format is quite simple, but also very flexible. It supports
|
||||
@ -245,9 +101,147 @@ i, o, b, h
|
||||
timelog files.
|
||||
@
|
||||
|
||||
See "Tests" for sample data.
|
||||
Here is the timelog grammar from timeclock.el 2.6:
|
||||
|
||||
@
|
||||
A timelog contains data in the form of a single entry per line.
|
||||
Each entry has the form:
|
||||
|
||||
CODE YYYY/MM/DD HH:MM:SS [COMMENT]
|
||||
|
||||
CODE is one of: b, h, i, o or O. COMMENT is optional when the code is
|
||||
i, o or O. The meanings of the codes are:
|
||||
|
||||
b Set the current time balance, or \"time debt\". Useful when
|
||||
archiving old log data, when a debt must be carried forward.
|
||||
The COMMENT here is the number of seconds of debt.
|
||||
|
||||
h Set the required working time for the given day. This must
|
||||
be the first entry for that day. The COMMENT in this case is
|
||||
the number of hours in this workday. Floating point amounts
|
||||
are allowed.
|
||||
|
||||
i Clock in. The COMMENT in this case should be the name of the
|
||||
project worked on.
|
||||
|
||||
o Clock out. COMMENT is unnecessary, but can be used to provide
|
||||
a description of how the period went, for example.
|
||||
|
||||
O Final clock out. Whatever project was being worked on, it is
|
||||
now finished. Useful for creating summary reports.
|
||||
@
|
||||
|
||||
Example:
|
||||
|
||||
i 2007/03/10 12:26:00 hledger
|
||||
o 2007/03/10 17:26:02
|
||||
|
||||
-}
|
||||
|
||||
module Ledger.Parse
|
||||
where
|
||||
import Control.Monad.Error (ErrorT(..), MonadIO, liftIO, throwError, catchError)
|
||||
import Text.ParserCombinators.Parsec
|
||||
import Text.ParserCombinators.Parsec.Char
|
||||
import Text.ParserCombinators.Parsec.Combinator
|
||||
import System.Directory
|
||||
#if __GLASGOW_HASKELL__ <= 610
|
||||
import Prelude hiding (readFile, putStr, putStrLn, print, getContents)
|
||||
import System.IO.UTF8
|
||||
#endif
|
||||
import Ledger.Utils
|
||||
import Ledger.Types
|
||||
import Ledger.Dates
|
||||
import Ledger.AccountName (accountNameFromComponents,accountNameComponents)
|
||||
import Ledger.Amount
|
||||
import Ledger.Transaction
|
||||
import Ledger.Posting
|
||||
import Ledger.Journal
|
||||
import Ledger.Commodity (dollars,dollar,unknown)
|
||||
import System.FilePath(takeDirectory,combine)
|
||||
|
||||
|
||||
-- | Some context kept during parsing.
|
||||
data LedgerFileCtx = Ctx {
|
||||
ctxYear :: !(Maybe Integer) -- ^ the default year most recently specified with Y
|
||||
, ctxCommod :: !(Maybe String) -- ^ I don't know
|
||||
, ctxAccount :: ![String] -- ^ the current stack of parent accounts specified by !account
|
||||
} deriving (Read, Show)
|
||||
|
||||
emptyCtx :: LedgerFileCtx
|
||||
emptyCtx = Ctx { ctxYear = Nothing, ctxCommod = Nothing, ctxAccount = [] }
|
||||
|
||||
setYear :: Integer -> GenParser tok LedgerFileCtx ()
|
||||
setYear y = updateState (\ctx -> ctx{ctxYear=Just y})
|
||||
|
||||
getYear :: GenParser tok LedgerFileCtx (Maybe Integer)
|
||||
getYear = liftM ctxYear getState
|
||||
|
||||
pushParentAccount :: String -> GenParser tok LedgerFileCtx ()
|
||||
pushParentAccount parent = updateState addParentAccount
|
||||
where addParentAccount ctx0 = ctx0 { ctxAccount = normalize parent : ctxAccount ctx0 }
|
||||
normalize = (++ ":")
|
||||
|
||||
popParentAccount :: GenParser tok LedgerFileCtx ()
|
||||
popParentAccount = do ctx0 <- getState
|
||||
case ctxAccount ctx0 of
|
||||
[] -> unexpected "End of account block with no beginning"
|
||||
(_:rest) -> setState $ ctx0 { ctxAccount = rest }
|
||||
|
||||
getParentAccount :: GenParser tok LedgerFileCtx String
|
||||
getParentAccount = liftM (concat . reverse . ctxAccount) getState
|
||||
|
||||
expandPath :: (MonadIO m) => SourcePos -> FilePath -> m FilePath
|
||||
expandPath pos fp = liftM mkRelative (expandHome fp)
|
||||
where
|
||||
mkRelative = combine (takeDirectory (sourceName pos))
|
||||
expandHome inname | "~/" `isPrefixOf` inname = do homedir <- liftIO getHomeDirectory
|
||||
return $ homedir ++ drop 1 inname
|
||||
| otherwise = return inname
|
||||
|
||||
-- let's get to it
|
||||
|
||||
-- | Parses a ledger file or timelog file to a "Journal", or gives an
|
||||
-- error. Requires the current (local) time to calculate any unfinished
|
||||
-- timelog sessions, we pass it in for repeatability.
|
||||
parseLedgerFile :: LocalTime -> FilePath -> ErrorT String IO Journal
|
||||
parseLedgerFile t "-" = liftIO getContents >>= parseLedger t "-"
|
||||
parseLedgerFile t f = liftIO (readFile f) >>= parseLedger t f
|
||||
|
||||
-- | Like parseLedgerFile, but parses a string. A file path is still
|
||||
-- provided to save in the resulting journal.
|
||||
parseLedger :: LocalTime -> FilePath -> String -> ErrorT String IO Journal
|
||||
parseLedger reftime inname intxt =
|
||||
case runParser ledgerFile emptyCtx inname intxt of
|
||||
Right m -> liftM (journalConvertTimeLog reftime) $ m `ap` return nulljournal
|
||||
Left err -> throwError $ show err -- XXX raises an uncaught exception if we have a parsec user error, eg from many ?
|
||||
|
||||
-- parsers
|
||||
|
||||
-- | Top-level journal parser. Returns a mighty composite, I/O performing,
|
||||
-- error-raising journal transformation, which should be applied to a
|
||||
-- journal to get the final result.
|
||||
ledgerFile :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerFile = do items <- many ledgerItem
|
||||
eof
|
||||
return $ liftM (foldr (.) id) $ sequence items
|
||||
where
|
||||
-- As all ledger line types can be distinguished by the first
|
||||
-- character, excepting transactions versus empty (blank or
|
||||
-- comment-only) lines, can use choice w/o try
|
||||
ledgerItem = choice [ ledgerExclamationDirective
|
||||
, liftM (return . addTransaction) ledgerTransaction
|
||||
, liftM (return . addModifierTransaction) ledgerModifierTransaction
|
||||
, liftM (return . addPeriodicTransaction) ledgerPeriodicTransaction
|
||||
, liftM (return . addHistoricalPrice) ledgerHistoricalPrice
|
||||
, ledgerDefaultYear
|
||||
, ledgerIgnoredPriceCommodity
|
||||
, ledgerTagDirective
|
||||
, ledgerEndTagDirective
|
||||
, emptyLine >> return (return id)
|
||||
, liftM (return . addTimeLogEntry) timelogentry
|
||||
]
|
||||
|
||||
emptyLine :: GenParser Char st ()
|
||||
emptyLine = do many spacenonewline
|
||||
optional $ (char ';' <?> "comment") >> many (noneOf "\n")
|
||||
@ -270,6 +264,41 @@ ledgercommentline = do
|
||||
return s
|
||||
<?> "comment"
|
||||
|
||||
ledgerExclamationDirective :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerExclamationDirective = do
|
||||
char '!' <?> "directive"
|
||||
directive <- many nonspace
|
||||
case directive of
|
||||
"include" -> ledgerInclude
|
||||
"account" -> ledgerAccountBegin
|
||||
"end" -> ledgerAccountEnd
|
||||
_ -> mzero
|
||||
|
||||
ledgerInclude :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerInclude = do many1 spacenonewline
|
||||
filename <- restofline
|
||||
outerState <- getState
|
||||
outerPos <- getPosition
|
||||
let inIncluded = show outerPos ++ " in included file " ++ show filename ++ ":\n"
|
||||
return $ do contents <- expandPath outerPos filename >>= readFileE outerPos
|
||||
case runParser ledgerFile outerState filename contents of
|
||||
Right l -> l `catchError` (throwError . (inIncluded ++))
|
||||
Left perr -> throwError $ inIncluded ++ show perr
|
||||
where readFileE outerPos filename = ErrorT $ liftM Right (readFile filename) `catch` leftError
|
||||
where leftError err = return $ Left $ currentPos ++ whileReading ++ show err
|
||||
currentPos = show outerPos
|
||||
whileReading = " reading " ++ show filename ++ ":\n"
|
||||
|
||||
ledgerAccountBegin :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerAccountBegin = do many1 spacenonewline
|
||||
parent <- ledgeraccountname
|
||||
newline
|
||||
pushParentAccount parent
|
||||
return $ return id
|
||||
|
||||
ledgerAccountEnd :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerAccountEnd = popParentAccount >> return (return id)
|
||||
|
||||
ledgerModifierTransaction :: GenParser Char LedgerFileCtx ModifierTransaction
|
||||
ledgerModifierTransaction = do
|
||||
char '=' <?> "modifier transaction"
|
||||
@ -298,8 +327,8 @@ ledgerHistoricalPrice = do
|
||||
restofline
|
||||
return $ HistoricalPrice date symbol price
|
||||
|
||||
ledgerIgnoredPrice :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerIgnoredPrice = do
|
||||
ledgerIgnoredPriceCommodity :: GenParser Char LedgerFileCtx (ErrorT String IO (Journal -> Journal))
|
||||
ledgerIgnoredPriceCommodity = do
|
||||
char 'N' <?> "ignored-price commodity"
|
||||
many1 spacenonewline
|
||||
commoditysymbol
|
||||
@ -331,8 +360,8 @@ ledgerDefaultYear = do
|
||||
setYear y'
|
||||
return $ return id
|
||||
|
||||
-- | Try to parse a ledger entry. If we successfully parse an entry, ensure it is balanced,
|
||||
-- and if we cannot, raise an error.
|
||||
-- | Try to parse a ledger entry. If we successfully parse an entry,
|
||||
-- check it can be balanced, and fail if not.
|
||||
ledgerTransaction :: GenParser Char LedgerFileCtx Transaction
|
||||
ledgerTransaction = do
|
||||
date <- ledgerdate <?> "transaction"
|
||||
@ -356,8 +385,8 @@ ledgerfulldate = do
|
||||
(y,m,d) <- ymd
|
||||
return $ fromGregorian (read y) (read m) (read d)
|
||||
|
||||
-- | Match a partial M/D date in a ledger. Warning, this terminates the
|
||||
-- program if it finds a match when there is no default year specified.
|
||||
-- | Match a partial M/D date in a ledger, and also require that a default
|
||||
-- year directive was previously encountered.
|
||||
ledgerpartialdate :: GenParser Char LedgerFileCtx Day
|
||||
ledgerpartialdate = do
|
||||
(_,m,d) <- md
|
||||
@ -381,7 +410,7 @@ ledgerdatetime = do
|
||||
ledgereffectivedate :: Day -> GenParser Char LedgerFileCtx (Maybe Day)
|
||||
ledgereffectivedate actualdate = do
|
||||
char '='
|
||||
-- kludgily use actual date for default year
|
||||
-- kludgy way to use actual date for default year
|
||||
let withDefaultYear d p = do
|
||||
y <- getYear
|
||||
let (y',_,_) = toGregorian d in setYear y'
|
||||
@ -397,9 +426,9 @@ ledgerstatus = try (do { many1 spacenonewline; char '*' <?> "status"; return Tru
|
||||
ledgercode :: GenParser Char st String
|
||||
ledgercode = try (do { many1 spacenonewline; char '(' <?> "code"; code <- anyChar `manyTill` char ')'; return code } ) <|> return ""
|
||||
|
||||
-- Complicated to handle intermixed comment lines.. please make me better.
|
||||
ledgerpostings :: GenParser Char LedgerFileCtx [Posting]
|
||||
ledgerpostings = do
|
||||
-- complicated to handle intermixed comment lines.. please make me better.
|
||||
ctx <- getState
|
||||
let parses p = isRight . parseWithCtx ctx p
|
||||
ls <- many1 $ try linebeginningwithspaces
|
||||
@ -427,15 +456,15 @@ ledgerposting = do
|
||||
newline
|
||||
return (Posting status account' amount comment ptype Nothing)
|
||||
|
||||
-- Qualify with the parent account from parsing context
|
||||
-- qualify with the parent account from parsing context
|
||||
transactionaccountname :: GenParser Char LedgerFileCtx AccountName
|
||||
transactionaccountname = liftM2 (++) getParentAccount ledgeraccountname
|
||||
|
||||
-- | Account names may have single spaces inside 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 separator char.
|
||||
|
||||
ledgeraccountname :: GenParser Char st String
|
||||
-- | Parse an account name. Account names may have single spaces inside
|
||||
-- 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
|
||||
-- separator char.
|
||||
ledgeraccountname :: GenParser Char st AccountName
|
||||
ledgeraccountname = do
|
||||
a <- many1 (nonspace <|> singlespace)
|
||||
let a' = striptrailingspace a
|
||||
@ -450,6 +479,8 @@ ledgeraccountname = do
|
||||
-- accountnamechar = notFollowedBy (oneOf "()[]") >> nonspace
|
||||
-- <?> "account name character (non-bracket, non-parenthesis, non-whitespace)"
|
||||
|
||||
-- | Parse an amount, with an optional left or right currency symbol and
|
||||
-- optional price.
|
||||
postingamount :: GenParser Char st MixedAmount
|
||||
postingamount =
|
||||
try (do
|
||||
@ -512,7 +543,7 @@ priceamount =
|
||||
|
||||
-- gawd.. trying to parse a ledger number without error:
|
||||
|
||||
-- | parse a ledger-style numeric quantity and also return the number of
|
||||
-- | Parse a ledger-style numeric quantity and also return the number of
|
||||
-- digits to the right of the decimal point and whether thousands are
|
||||
-- separated by comma.
|
||||
amountquantity :: GenParser Char st (Double, Int, Bool)
|
||||
@ -551,42 +582,7 @@ numberpartsstartingwithpoint = do
|
||||
return ("",frac)
|
||||
|
||||
|
||||
{-| Parse a timelog entry. Here is the timelog grammar from timeclock.el 2.6:
|
||||
|
||||
@
|
||||
A timelog contains data in the form of a single entry per line.
|
||||
Each entry has the form:
|
||||
|
||||
CODE YYYY/MM/DD HH:MM:SS [COMMENT]
|
||||
|
||||
CODE is one of: b, h, i, o or O. COMMENT is optional when the code is
|
||||
i, o or O. The meanings of the codes are:
|
||||
|
||||
b Set the current time balance, or \"time debt\". Useful when
|
||||
archiving old log data, when a debt must be carried forward.
|
||||
The COMMENT here is the number of seconds of debt.
|
||||
|
||||
h Set the required working time for the given day. This must
|
||||
be the first entry for that day. The COMMENT in this case is
|
||||
the number of hours in this workday. Floating point amounts
|
||||
are allowed.
|
||||
|
||||
i Clock in. The COMMENT in this case should be the name of the
|
||||
project worked on.
|
||||
|
||||
o Clock out. COMMENT is unnecessary, but can be used to provide
|
||||
a description of how the period went, for example.
|
||||
|
||||
O Final clock out. Whatever project was being worked on, it is
|
||||
now finished. Useful for creating summary reports.
|
||||
@
|
||||
|
||||
Example:
|
||||
|
||||
i 2007/03/10 12:26:00 hledger
|
||||
o 2007/03/10 17:26:02
|
||||
|
||||
-}
|
||||
-- | Parse a timelog entry.
|
||||
timelogentry :: GenParser Char LedgerFileCtx TimeLogEntry
|
||||
timelogentry = do
|
||||
code <- oneOf "bhioO"
|
||||
@ -596,10 +592,8 @@ timelogentry = do
|
||||
return $ TimeLogEntry (read [code]) datetime (fromMaybe "" comment)
|
||||
|
||||
|
||||
-- misc parsing
|
||||
|
||||
-- | Parse a --display expression which is a simple date predicate, like
|
||||
-- "d>[DATE]" or "d<=[DATE]", and return a posting-matching predicate.
|
||||
-- | Parse a hledger display expression, which is a simple date test like
|
||||
-- "d>[DATE]" or "d<=[DATE]", and return a "Posting"-matching predicate.
|
||||
datedisplayexpr :: GenParser Char st (Posting -> Bool)
|
||||
datedisplayexpr = do
|
||||
char 'd'
|
||||
@ -660,8 +654,8 @@ tests_Parse = TestList [
|
||||
assertParseEqual (parseWithCtx emptyCtx postingamount " $1.")
|
||||
(Mixed [Amount Commodity {symbol="$",side=L,spaced=False,comma=False,precision=0} 1 Nothing])
|
||||
|
||||
,"ledgerIgnoredPrice" ~: do
|
||||
assertParse (parseWithCtx emptyCtx ledgerIgnoredPrice "N $\n")
|
||||
,"ledgerIgnoredPriceCommodity" ~: do
|
||||
assertParse (parseWithCtx emptyCtx ledgerIgnoredPriceCommodity "N $\n")
|
||||
|
||||
,"ledgerTagDirective" ~: do
|
||||
assertParse (parseWithCtx emptyCtx ledgerTagDirective "tag foo\n")
|
||||
|
Loading…
Reference in New Issue
Block a user