mirror of
https://github.com/simonmichael/hledger.git
synced 2024-09-20 02:37:12 +03:00
New ledger parser with file inclusion
This commit is contained in:
parent
157f47c592
commit
ee4a2a1c1e
193
Ledger/Parse.hs
193
Ledger/Parse.hs
@ -6,6 +6,8 @@ Parsers for standard ledger and timelog files.
|
||||
|
||||
module Ledger.Parse
|
||||
where
|
||||
import Control.Monad
|
||||
import Control.Monad.Error
|
||||
import Text.ParserCombinators.Parsec
|
||||
import Text.ParserCombinators.Parsec.Char
|
||||
import Text.ParserCombinators.Parsec.Language
|
||||
@ -20,51 +22,71 @@ import Ledger.Amount
|
||||
import Ledger.Entry
|
||||
import Ledger.Commodity
|
||||
import Ledger.TimeLog
|
||||
import Ledger.RawLedger
|
||||
import Data.Time.LocalTime
|
||||
import Data.Time.Calendar
|
||||
|
||||
|
||||
-- utils
|
||||
|
||||
parseLedgerFile :: String -> IO (Either ParseError RawLedger)
|
||||
parseLedgerFile "-" = fmap (parse ledgerfile "-") $ hGetContents stdin
|
||||
parseLedgerFile f = parseFromFile ledgerfile f
|
||||
parseLedgerFile :: FilePath -> ErrorT String IO RawLedger
|
||||
parseLedgerFile "-" = liftIO (hGetContents stdin) >>= parseLedger "-"
|
||||
parseLedgerFile f = liftIO (readFile f) >>= parseLedger f
|
||||
|
||||
printParseError :: (Show a) => a -> IO ()
|
||||
printParseError e = do putStr "ledger parse error at "; print e
|
||||
|
||||
-- set up token parsing, though we're not yet using these much
|
||||
ledgerLanguageDef = LanguageDef {
|
||||
commentStart = ""
|
||||
, commentEnd = ""
|
||||
, commentLine = ";"
|
||||
, nestedComments = False
|
||||
, identStart = letter <|> char '_'
|
||||
, identLetter = alphaNum <|> oneOf "_':"
|
||||
, opStart = opLetter emptyDef
|
||||
, opLetter = oneOf "!#$%&*+./<=>?@\\^|-~"
|
||||
, reservedOpNames= []
|
||||
, reservedNames = []
|
||||
, caseSensitive = False
|
||||
}
|
||||
lexer = P.makeTokenParser ledgerLanguageDef
|
||||
whiteSpace = P.whiteSpace lexer
|
||||
lexeme = P.lexeme lexer
|
||||
--symbol = P.symbol lexer
|
||||
natural = P.natural lexer
|
||||
parens = P.parens lexer
|
||||
semi = P.semi lexer
|
||||
identifier = P.identifier lexer
|
||||
reserved = P.reserved lexer
|
||||
reservedOp = P.reservedOp lexer
|
||||
-- Default accounts "nest" hierarchically
|
||||
|
||||
data LedgerFileCtx = Ctx { ctxYear :: !(Maybe Integer)
|
||||
, ctxCommod :: !(Maybe String)
|
||||
, ctxAccount :: ![String]
|
||||
} deriving (Read, Show)
|
||||
|
||||
emptyCtx :: LedgerFileCtx
|
||||
emptyCtx = Ctx { ctxYear = Nothing, ctxCommod = Nothing, ctxAccount = [] }
|
||||
|
||||
parseLedger :: FilePath -> String -> ErrorT String IO RawLedger
|
||||
parseLedger inname intxt = case runParser ledgerFile emptyCtx inname intxt of
|
||||
Right m -> m `ap` (return rawLedgerEmpty)
|
||||
Left err -> throwError $ show err
|
||||
|
||||
ledgerFile :: GenParser Char LedgerFileCtx (ErrorT String IO (RawLedger -> RawLedger))
|
||||
ledgerFile = do entries <- many1 ledgerAnyEntry
|
||||
eof
|
||||
return $ liftM (foldr1 (.)) $ sequence entries
|
||||
where ledgerAnyEntry = choice [ ledgerInclude
|
||||
, liftM (return . addEntry) ledgerEntry
|
||||
, liftM (return . addModifierEntry) ledgerModifierEntry
|
||||
, liftM (return . addPeriodicEntry) ledgerPeriodicEntry
|
||||
, blankline >> return (return id)
|
||||
, commentline >> return (return id)
|
||||
]
|
||||
|
||||
ledgerInclude :: GenParser Char LedgerFileCtx (ErrorT String IO (RawLedger -> RawLedger))
|
||||
ledgerInclude = do string "!include"
|
||||
many1 spacenonewline
|
||||
filename <- restofline
|
||||
outerState <- getState
|
||||
outerPos <- getPosition
|
||||
let inIncluded = show outerPos ++ " in included file " ++ show filename ++ ":\n"
|
||||
return $ do contents <- readFileE outerPos filename
|
||||
case runParser ledgerFile outerState filename contents of
|
||||
Right l -> l `catchError` (\err -> throwError $ inIncluded ++ err)
|
||||
Left perr -> throwError $ inIncluded ++ show perr
|
||||
where readFileE outerPos filename = ErrorT $ do (liftM Right $ readFile filename) `catch` leftError
|
||||
where leftError err = return $ Left $ currentPos ++ whileReading ++ show err
|
||||
currentPos = show outerPos
|
||||
whileReading = " reading " ++ show filename ++ ":\n"
|
||||
|
||||
|
||||
--ledgerEntry = return $ throwError "unimplemented"
|
||||
|
||||
-- parsers
|
||||
|
||||
-- | Parse a RawLedger 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.
|
||||
ledgerfile :: Parser RawLedger
|
||||
ledgerfile = try ledgerfromtimelog <|> ledger
|
||||
|
||||
{-| Parse a ledger file. Here is the ledger grammar from the ledger 2.5 manual:
|
||||
|
||||
@ -166,38 +188,18 @@ i, o, b, h
|
||||
|
||||
See "Tests" for sample data.
|
||||
-}
|
||||
ledger :: Parser RawLedger
|
||||
ledger = do
|
||||
-- we expect these to come first, unlike ledger
|
||||
modifier_entries <- many ledgermodifierentry
|
||||
periodic_entries <- many ledgerperiodicentry
|
||||
|
||||
entries <- (many $ try ledgerentry) <?> "entry"
|
||||
final_comment_lines <- ledgernondatalines
|
||||
eof
|
||||
return $ RawLedger modifier_entries periodic_entries entries (unlines final_comment_lines)
|
||||
blankline :: GenParser Char st String
|
||||
blankline = (do { s <- many spacenonewline; newline; return s }) <?> "blank line"
|
||||
|
||||
ledgernondatalines :: Parser [String]
|
||||
ledgernondatalines = many (try ledgerdirective <|> -- treat as comments
|
||||
try commentline <|>
|
||||
blankline)
|
||||
|
||||
ledgerdirective :: Parser String
|
||||
ledgerdirective = char '!' >> restofline <?> "directive"
|
||||
|
||||
blankline :: Parser String
|
||||
blankline =
|
||||
do {s <- many1 spacenonewline; newline; return s} <|>
|
||||
do {newline; return ""} <?> "blank line"
|
||||
|
||||
commentline :: Parser String
|
||||
commentline :: GenParser Char st String
|
||||
commentline = do
|
||||
many spacenonewline
|
||||
char ';' <?> "comment line"
|
||||
l <- restofline
|
||||
return $ ";" ++ l
|
||||
|
||||
ledgercomment :: Parser String
|
||||
ledgercomment :: GenParser Char st String
|
||||
ledgercomment =
|
||||
try (do
|
||||
char ';'
|
||||
@ -206,25 +208,24 @@ ledgercomment =
|
||||
)
|
||||
<|> return "" <?> "comment"
|
||||
|
||||
ledgermodifierentry :: Parser ModifierEntry
|
||||
ledgermodifierentry = do
|
||||
char '=' <?> "entry"
|
||||
ledgerModifierEntry :: GenParser Char LedgerFileCtx ModifierEntry
|
||||
ledgerModifierEntry = do
|
||||
char '=' <?> "modifier entry"
|
||||
many spacenonewline
|
||||
valueexpr <- restofline
|
||||
transactions <- ledgertransactions
|
||||
return (ModifierEntry valueexpr transactions)
|
||||
return $ ModifierEntry valueexpr transactions
|
||||
|
||||
ledgerperiodicentry :: Parser PeriodicEntry
|
||||
ledgerperiodicentry = do
|
||||
ledgerPeriodicEntry :: GenParser Char LedgerFileCtx PeriodicEntry
|
||||
ledgerPeriodicEntry = do
|
||||
char '~' <?> "entry"
|
||||
many spacenonewline
|
||||
periodexpr <- restofline
|
||||
transactions <- ledgertransactions
|
||||
return (PeriodicEntry periodexpr transactions)
|
||||
return $ PeriodicEntry periodexpr transactions
|
||||
|
||||
ledgerentry :: Parser Entry
|
||||
ledgerentry = do
|
||||
preceding <- ledgernondatalines
|
||||
ledgerEntry :: GenParser Char LedgerFileCtx Entry
|
||||
ledgerEntry = do
|
||||
date <- ledgerdate <?> "entry"
|
||||
status <- ledgerstatus
|
||||
code <- ledgercode
|
||||
@ -235,9 +236,9 @@ ledgerentry = do
|
||||
comment <- ledgercomment
|
||||
restofline
|
||||
transactions <- ledgertransactions
|
||||
return $ balanceEntry $ Entry date status code description comment transactions (unlines preceding)
|
||||
return $ balanceEntry $ Entry date status code description comment transactions ""
|
||||
|
||||
ledgerdate :: Parser Day
|
||||
ledgerdate :: GenParser Char st Day
|
||||
ledgerdate = do
|
||||
y <- many1 digit
|
||||
char '/'
|
||||
@ -247,7 +248,7 @@ ledgerdate = do
|
||||
many spacenonewline
|
||||
return (fromGregorian (read y) (read m) (read d))
|
||||
|
||||
ledgerdatetime :: Parser UTCTime
|
||||
ledgerdatetime :: GenParser Char st UTCTime
|
||||
ledgerdatetime = do
|
||||
day <- ledgerdate
|
||||
h <- many1 digit
|
||||
@ -260,20 +261,20 @@ ledgerdatetime = do
|
||||
return $ mkUTCTime day (TimeOfDay (read h) (read m) (maybe 0 (fromIntegral.read) s))
|
||||
|
||||
|
||||
ledgerstatus :: Parser Bool
|
||||
ledgerstatus :: GenParser Char st Bool
|
||||
ledgerstatus = try (do { char '*'; many1 spacenonewline; return True } ) <|> return False
|
||||
|
||||
ledgercode :: Parser String
|
||||
ledgercode :: GenParser Char st String
|
||||
ledgercode = try (do { char '('; code <- anyChar `manyTill` char ')'; many1 spacenonewline; return code } ) <|> return ""
|
||||
|
||||
ledgertransactions :: Parser [RawTransaction]
|
||||
ledgertransactions =
|
||||
((try virtualtransaction <|> try balancedvirtualtransaction <|> ledgertransaction) <?> "transaction")
|
||||
`manyTill` (do {newline <?> "blank line"; return ()} <|> eof)
|
||||
ledgertransactions :: GenParser Char st [RawTransaction]
|
||||
ledgertransactions = many $ try ledgertransaction
|
||||
|
||||
ledgertransaction :: Parser RawTransaction
|
||||
ledgertransaction = do
|
||||
many1 spacenonewline
|
||||
ledgertransaction :: GenParser Char st RawTransaction
|
||||
ledgertransaction = many1 spacenonewline >> choice [ normaltransaction, virtualtransaction, balancedvirtualtransaction ]
|
||||
|
||||
normaltransaction :: GenParser Char st RawTransaction
|
||||
normaltransaction = do
|
||||
account <- ledgeraccountname
|
||||
amount <- transactionamount
|
||||
many spacenonewline
|
||||
@ -281,9 +282,8 @@ ledgertransaction = do
|
||||
restofline
|
||||
return (RawTransaction account amount comment RegularTransaction)
|
||||
|
||||
virtualtransaction :: Parser RawTransaction
|
||||
virtualtransaction :: GenParser Char st RawTransaction
|
||||
virtualtransaction = do
|
||||
many1 spacenonewline
|
||||
char '('
|
||||
account <- ledgeraccountname
|
||||
char ')'
|
||||
@ -293,9 +293,8 @@ virtualtransaction = do
|
||||
restofline
|
||||
return (RawTransaction account amount comment VirtualTransaction)
|
||||
|
||||
balancedvirtualtransaction :: Parser RawTransaction
|
||||
balancedvirtualtransaction :: GenParser Char st RawTransaction
|
||||
balancedvirtualtransaction = do
|
||||
many1 spacenonewline
|
||||
char '['
|
||||
account <- ledgeraccountname
|
||||
char ']'
|
||||
@ -306,7 +305,7 @@ balancedvirtualtransaction = do
|
||||
return (RawTransaction account amount comment BalancedVirtualTransaction)
|
||||
|
||||
-- | account names may have single spaces inside them, and are terminated by two or more spaces
|
||||
ledgeraccountname :: Parser String
|
||||
ledgeraccountname :: GenParser Char st String
|
||||
ledgeraccountname = do
|
||||
accountname <- many1 (accountnamechar <|> singlespace)
|
||||
return $ striptrailingspace accountname
|
||||
@ -318,7 +317,7 @@ ledgeraccountname = do
|
||||
accountnamechar = notFollowedBy (oneOf "()[]") >> nonspace
|
||||
<?> "account name character (non-bracket, non-parenthesis, non-whitespace)"
|
||||
|
||||
transactionamount :: Parser MixedAmount
|
||||
transactionamount :: GenParser Char st MixedAmount
|
||||
transactionamount =
|
||||
try (do
|
||||
many1 spacenonewline
|
||||
@ -328,7 +327,7 @@ transactionamount =
|
||||
|
||||
someamount = try leftsymbolamount <|> try rightsymbolamount <|> nosymbolamount
|
||||
|
||||
leftsymbolamount :: Parser MixedAmount
|
||||
leftsymbolamount :: GenParser Char st MixedAmount
|
||||
leftsymbolamount = do
|
||||
sym <- commoditysymbol
|
||||
sp <- many spacenonewline
|
||||
@ -338,7 +337,7 @@ leftsymbolamount = do
|
||||
return $ Mixed [Amount c q pri]
|
||||
<?> "left-symbol amount"
|
||||
|
||||
rightsymbolamount :: Parser MixedAmount
|
||||
rightsymbolamount :: GenParser Char st MixedAmount
|
||||
rightsymbolamount = do
|
||||
(q,p,comma) <- amountquantity
|
||||
sp <- many spacenonewline
|
||||
@ -348,7 +347,7 @@ rightsymbolamount = do
|
||||
return $ Mixed [Amount c q pri]
|
||||
<?> "right-symbol amount"
|
||||
|
||||
nosymbolamount :: Parser MixedAmount
|
||||
nosymbolamount :: GenParser Char st MixedAmount
|
||||
nosymbolamount = do
|
||||
(q,p,comma) <- amountquantity
|
||||
pri <- priceamount
|
||||
@ -356,10 +355,10 @@ nosymbolamount = do
|
||||
return $ Mixed [Amount c q pri]
|
||||
<?> "no-symbol amount"
|
||||
|
||||
commoditysymbol :: Parser String
|
||||
commoditysymbol :: GenParser Char st String
|
||||
commoditysymbol = many1 (noneOf "-.0123456789;\n ") <?> "commodity symbol"
|
||||
|
||||
priceamount :: Parser (Maybe MixedAmount)
|
||||
priceamount :: GenParser Char st (Maybe MixedAmount)
|
||||
priceamount =
|
||||
try (do
|
||||
many spacenonewline
|
||||
@ -374,7 +373,7 @@ priceamount =
|
||||
-- | 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 :: Parser (Double, Int, Bool)
|
||||
amountquantity :: GenParser Char st (Double, Int, Bool)
|
||||
amountquantity = do
|
||||
sign <- optionMaybe $ string "-"
|
||||
(intwithcommas,frac) <- numberparts
|
||||
@ -392,10 +391,10 @@ amountquantity = do
|
||||
-- | parse the two strings of digits before and after a possible decimal
|
||||
-- point. The integer part may contain commas, or either part may be
|
||||
-- empty, or there may be no point.
|
||||
numberparts :: Parser (String,String)
|
||||
numberparts :: GenParser Char st (String,String)
|
||||
numberparts = numberpartsstartingwithdigit <|> numberpartsstartingwithpoint
|
||||
|
||||
numberpartsstartingwithdigit :: Parser (String,String)
|
||||
numberpartsstartingwithdigit :: GenParser Char st (String,String)
|
||||
numberpartsstartingwithdigit = do
|
||||
let digitorcomma = digit <|> char ','
|
||||
first <- digit
|
||||
@ -403,7 +402,7 @@ numberpartsstartingwithdigit = do
|
||||
frac <- try (do {char '.'; many digit >>= return}) <|> return ""
|
||||
return (first:rest,frac)
|
||||
|
||||
numberpartsstartingwithpoint :: Parser (String,String)
|
||||
numberpartsstartingwithpoint :: GenParser Char st (String,String)
|
||||
numberpartsstartingwithpoint = do
|
||||
char '.'
|
||||
frac <- many1 digit
|
||||
@ -446,13 +445,13 @@ i 2007/03/10 12:26:00 hledger
|
||||
o 2007/03/10 17:26:02
|
||||
|
||||
-}
|
||||
timelog :: Parser TimeLog
|
||||
timelog :: GenParser Char st TimeLog
|
||||
timelog = do
|
||||
entries <- many timelogentry <?> "timelog entry"
|
||||
eof
|
||||
return $ TimeLog entries
|
||||
|
||||
timelogentry :: Parser TimeLogEntry
|
||||
timelogentry :: GenParser Char st TimeLogEntry
|
||||
timelogentry = do
|
||||
many (commentline <|> blankline)
|
||||
code <- oneOf "bhioO"
|
||||
@ -461,17 +460,17 @@ timelogentry = do
|
||||
comment <- restofline
|
||||
return $ TimeLogEntry code datetime comment
|
||||
|
||||
ledgerfromtimelog :: Parser RawLedger
|
||||
ledgerfromtimelog = do
|
||||
tl <- timelog
|
||||
return $ ledgerFromTimeLog tl
|
||||
--ledgerfromtimelog :: GenParser Char st RawLedger
|
||||
--ledgerfromtimelog = do
|
||||
-- tl <- timelog
|
||||
-- return $ ledgerFromTimeLog tl
|
||||
|
||||
|
||||
-- misc parsing
|
||||
|
||||
-- | Parse a --display expression which is a simple date predicate, like
|
||||
-- "d>[DATE]" or "d<=[DATE]", and return a transaction-matching predicate.
|
||||
datedisplayexpr :: Parser (Transaction -> Bool)
|
||||
datedisplayexpr :: GenParser Char st (Transaction -> Bool)
|
||||
datedisplayexpr = do
|
||||
char 'd'
|
||||
op <- compareop
|
||||
|
64
Tests.hs
64
Tests.hs
@ -55,10 +55,10 @@ misc_tests = TestList [
|
||||
assertequal (Amount (comm "$") 0 Nothing) (sum [a1,a2,a3,-a3])
|
||||
,
|
||||
"ledgertransaction" ~: do
|
||||
assertparseequal rawtransaction1 (parsewith ledgertransaction rawtransaction1_str)
|
||||
assertparseequal rawtransaction1 (parseWithCtx ledgertransaction rawtransaction1_str)
|
||||
,
|
||||
"ledgerentry" ~: do
|
||||
assertparseequal entry1 (parsewith ledgerentry entry1_str)
|
||||
assertparseequal entry1 (parseWithCtx ledgerEntry entry1_str)
|
||||
,
|
||||
"balanceEntry" ~: do
|
||||
assertequal
|
||||
@ -87,15 +87,15 @@ misc_tests = TestList [
|
||||
assertequal 15 (length $ Map.keys $ accountmap $ cacheLedger [] rawledger7)
|
||||
,
|
||||
"transactionamount" ~: do
|
||||
assertparseequal (Mixed [dollars 47.18]) (parsewith transactionamount " $47.18")
|
||||
assertparseequal (Mixed [Amount (Commodity {symbol="$",side=L,spaced=False,comma=False,precision=0}) 1 Nothing]) (parsewith transactionamount " $1.")
|
||||
assertparseequal (Mixed [dollars 47.18]) (parseWithCtx transactionamount " $47.18")
|
||||
assertparseequal (Mixed [Amount (Commodity {symbol="$",side=L,spaced=False,comma=False,precision=0}) 1 Nothing]) (parseWithCtx transactionamount " $1.")
|
||||
,
|
||||
"canonicaliseAmounts" ~: do
|
||||
-- all amounts use the greatest precision
|
||||
assertequal [2,2] (rawLedgerPrecisions $ canonicaliseAmounts False $ rawLedgerWithAmounts ["1","2.00"])
|
||||
,
|
||||
"timeLog" ~: do
|
||||
assertparseequal timelog1 (parsewith timelog timelog1_str)
|
||||
assertparseequal timelog1 (parseWithCtx timelog timelog1_str)
|
||||
,
|
||||
"smart dates" ~: do
|
||||
let todaysdate = parsedate "2008/11/26" -- wednesday
|
||||
@ -238,7 +238,7 @@ balancereportacctnames_tests = TestList
|
||||
,"balancereportacctnames8" ~: ("-s",["-e"]) `gives` []
|
||||
] where
|
||||
gives (opt,pats) e = do
|
||||
let l = sampleledger
|
||||
l <- sampleledger
|
||||
let t = pruneZeroBalanceLeaves $ ledgerAccountTree 999 l
|
||||
assertequal e (balancereportacctnames l (opt=="-s") pats t)
|
||||
|
||||
@ -375,15 +375,15 @@ balancecommand_tests = TestList [
|
||||
"")
|
||||
,
|
||||
"balance report with cost basis" ~: do
|
||||
let l = cacheLedger [] $
|
||||
filterRawLedger (DateSpan Nothing Nothing) [] False False $
|
||||
canonicaliseAmounts True $ -- enable cost basis adjustment
|
||||
rawledgerfromstring
|
||||
rawl <- rawledgerfromstring
|
||||
("" ++
|
||||
"2008/1/1 test \n" ++
|
||||
" a:b 10h @ $50\n" ++
|
||||
" c:d \n" ++
|
||||
"\n")
|
||||
let l = cacheLedger [] $
|
||||
filterRawLedger (DateSpan Nothing Nothing) [] False False $
|
||||
canonicaliseAmounts True rawl -- enable cost basis adjustment
|
||||
assertequal
|
||||
(" $500 a\n" ++
|
||||
" $-500 c\n" ++
|
||||
@ -392,14 +392,14 @@ balancecommand_tests = TestList [
|
||||
(showBalanceReport [] [] l)
|
||||
] where
|
||||
gives (opts,args) e = do
|
||||
let l = sampleledgerwithopts [] args
|
||||
l <- sampleledgerwithopts [] args
|
||||
assertequal e (showBalanceReport opts args l)
|
||||
|
||||
printcommand_tests = TestList [
|
||||
"print with account patterns" ~:
|
||||
do
|
||||
let args = ["expenses"]
|
||||
let l = sampleledgerwithopts [] args
|
||||
l <- sampleledgerwithopts [] args
|
||||
assertequal (
|
||||
"2008/06/03 * eat & shop\n" ++
|
||||
" expenses:food $1\n" ++
|
||||
@ -412,6 +412,7 @@ printcommand_tests = TestList [
|
||||
registercommand_tests = TestList [
|
||||
"register report" ~:
|
||||
do
|
||||
l <- sampleledger
|
||||
assertequal (
|
||||
"2008/01/01 income assets:checking $1 $1\n" ++
|
||||
" income:salary $-1 0\n" ++
|
||||
@ -425,17 +426,21 @@ registercommand_tests = TestList [
|
||||
"2008/12/31 pay off liabilities:debts $1 $1\n" ++
|
||||
" assets:checking $-1 0\n" ++
|
||||
"")
|
||||
$ showRegisterReport [] [] sampleledger
|
||||
$ showRegisterReport [] [] l
|
||||
,
|
||||
"register report with account pattern" ~:
|
||||
do
|
||||
l <- sampleledger
|
||||
assertequal (
|
||||
"2008/06/03 eat & shop assets:cash $-2 $-2\n" ++
|
||||
"")
|
||||
$ showRegisterReport [] ["cash"] sampleledger
|
||||
$ showRegisterReport [] ["cash"] l
|
||||
,
|
||||
"register report with display expression" ~:
|
||||
do
|
||||
l <- sampleledger
|
||||
let expr `displayexprgives` dates = assertequal dates (datesfromregister r)
|
||||
where r = showRegisterReport [Display expr] [] l
|
||||
"d<[2008/6/2]" `displayexprgives` ["2008/01/01","2008/06/01"]
|
||||
"d<=[2008/6/2]" `displayexprgives` ["2008/01/01","2008/06/01","2008/06/02"]
|
||||
"d=[2008/6/2]" `displayexprgives` ["2008/06/02"]
|
||||
@ -444,12 +449,14 @@ registercommand_tests = TestList [
|
||||
,
|
||||
"register report with period expression" ~:
|
||||
do
|
||||
l <- sampleledger
|
||||
let expr `displayexprgives` dates = assertequal dates (datesfromregister r)
|
||||
where r = showRegisterReport [Display expr] [] l
|
||||
"" `periodexprgives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]
|
||||
"2008" `periodexprgives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]
|
||||
"2007" `periodexprgives` []
|
||||
"june" `periodexprgives` ["2008/06/01","2008/06/02","2008/06/03"]
|
||||
"monthly" `periodexprgives` ["2008/01/01","2008/06/01","2008/12/01"]
|
||||
|
||||
assertequal (
|
||||
"2008/01/01 - 2008/12/31 assets:cash $-2 $-2\n" ++
|
||||
" assets:saving $1 $-1\n" ++
|
||||
@ -459,25 +466,18 @@ registercommand_tests = TestList [
|
||||
" income:salary $-1 $-1\n" ++
|
||||
" liabilities:debts $1 0\n" ++
|
||||
"")
|
||||
(showRegisterReport [Period "yearly"] [] sampleledger)
|
||||
|
||||
(showRegisterReport [Period "yearly"] [] l)
|
||||
assertequal ["2008/01/01","2008/04/01","2008/10/01"]
|
||||
(datesfromregister $ showRegisterReport [Period "quarterly"] [] sampleledger)
|
||||
(datesfromregister $ showRegisterReport [Period "quarterly"] [] l)
|
||||
assertequal ["2008/01/01","2008/04/01","2008/07/01","2008/10/01"]
|
||||
(datesfromregister $ showRegisterReport [Period "quarterly",Empty] [] sampleledger)
|
||||
(datesfromregister $ showRegisterReport [Period "quarterly",Empty] [] l)
|
||||
|
||||
]
|
||||
where
|
||||
expr `displayexprgives` dates =
|
||||
where datesfromregister = filter (not . null) . map (strip . take 10) . lines
|
||||
expr `periodexprgives` dates = do lopts <- sampleledgerwithopts [Period expr] []
|
||||
let r = showRegisterReport [Period expr] [] lopts
|
||||
assertequal dates (datesfromregister r)
|
||||
where
|
||||
r = showRegisterReport [Display expr] [] sampleledger
|
||||
expr `periodexprgives` dates =
|
||||
assertequal dates (datesfromregister r)
|
||||
where
|
||||
r = showRegisterReport [Period expr] [] l
|
||||
l = sampleledgerwithopts [Period expr] []
|
||||
datesfromregister = filter (not . null) . map (strip . take 10) . lines
|
||||
|
||||
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
@ -486,7 +486,7 @@ registercommand_tests = TestList [
|
||||
refdate = parsedate "2008/11/26"
|
||||
sampleledger = ledgerfromstringwithopts [] [] refdate sample_ledger_str
|
||||
sampleledgerwithopts opts args = ledgerfromstringwithopts opts args refdate sample_ledger_str
|
||||
sampleledgerwithoptsanddate opts args date = ledgerfromstringwithopts opts args date sample_ledger_str
|
||||
--sampleledgerwithoptsanddate opts args date = unsafePerformIO $ ledgerfromstringwithopts opts args date sample_ledger_str
|
||||
|
||||
sample_ledger_str = (
|
||||
"; A sample ledger file.\n" ++
|
||||
@ -816,6 +816,7 @@ rawledger7 = RawLedger
|
||||
epreceding_comment_lines=""
|
||||
}
|
||||
]
|
||||
[]
|
||||
""
|
||||
|
||||
ledger7 = cacheLedger [] rawledger7
|
||||
@ -878,6 +879,7 @@ rawLedgerWithAmounts as =
|
||||
[]
|
||||
[]
|
||||
[nullentry{edescription=a,etransactions=[nullrawtxn{tamount=parse a}]} | a <- as]
|
||||
[]
|
||||
""
|
||||
where parse = fromparse . parsewith transactionamount . (" "++)
|
||||
where parse = fromparse . parseWithCtx transactionamount . (" "++)
|
||||
|
||||
|
14
Utils.hs
14
Utils.hs
@ -6,6 +6,7 @@ Utilities for top-level modules and/or ghci. See also "Ledger.Utils".
|
||||
|
||||
module Utils
|
||||
where
|
||||
import Control.Monad.Error
|
||||
import qualified Data.Map as Map (lookup)
|
||||
import Text.ParserCombinators.Parsec
|
||||
import System.IO
|
||||
@ -26,18 +27,18 @@ prepareLedger opts args refdate rl =
|
||||
cb = CostBasis `elem` opts
|
||||
|
||||
-- | Get a RawLedger from the given string, or raise an error.
|
||||
rawledgerfromstring :: String -> RawLedger
|
||||
rawledgerfromstring = fromparse . parsewith ledgerfile
|
||||
rawledgerfromstring :: String -> IO RawLedger
|
||||
rawledgerfromstring = liftM (either error id) . runErrorT . parseLedger "(string)"
|
||||
|
||||
-- | Get a Ledger from the given string and options, or raise an error.
|
||||
ledgerfromstringwithopts :: [Opt] -> [String] -> Day -> String -> Ledger
|
||||
ledgerfromstringwithopts :: [Opt] -> [String] -> Day -> String -> IO Ledger
|
||||
ledgerfromstringwithopts opts args refdate s =
|
||||
prepareLedger opts args refdate $ rawledgerfromstring s
|
||||
liftM (prepareLedger opts args refdate) $ rawledgerfromstring s
|
||||
|
||||
-- | Get a Ledger from the given file path and options, or raise an error.
|
||||
ledgerfromfilewithopts :: [Opt] -> [String] -> FilePath -> IO Ledger
|
||||
ledgerfromfilewithopts opts args f = do
|
||||
rl <- readFile f >>= return . rawledgerfromstring
|
||||
rl <- readFile f >>= rawledgerfromstring
|
||||
refdate <- today
|
||||
return $ prepareLedger opts args refdate rl
|
||||
|
||||
@ -45,3 +46,6 @@ ledgerfromfilewithopts opts args f = do
|
||||
-- Assumes no options.
|
||||
myledger :: IO Ledger
|
||||
myledger = ledgerFilePathFromOpts [] >>= ledgerfromfilewithopts [] []
|
||||
|
||||
parseWithCtx :: GenParser Char LedgerFileCtx a -> String -> Either ParseError a
|
||||
parseWithCtx p ts = runParser p emptyCtx "" ts
|
Loading…
Reference in New Issue
Block a user