split into modules
This commit is contained in:
parent
9b20778b90
commit
1fa5e09dfd
@ -2,6 +2,8 @@ module Options where
|
|||||||
|
|
||||||
import System.Console.GetOpt
|
import System.Console.GetOpt
|
||||||
import Data.Maybe ( fromMaybe )
|
import Data.Maybe ( fromMaybe )
|
||||||
|
import System.Environment (getEnv)
|
||||||
|
--import TildeExpand -- confuses my ghc 6.7
|
||||||
|
|
||||||
data Flag = File String | Version deriving Show
|
data Flag = File String | Version deriving Show
|
||||||
|
|
||||||
@ -23,3 +25,10 @@ getOptions argv =
|
|||||||
|
|
||||||
get_content :: Flag -> Maybe String
|
get_content :: Flag -> Maybe String
|
||||||
get_content (File s) = Just s
|
get_content (File s) = Just s
|
||||||
|
|
||||||
|
--defaultLedgerFile = tildeExpand "~/ledger.dat"
|
||||||
|
defaultLedgerFile = "ledger.dat"
|
||||||
|
|
||||||
|
ledgerFile :: IO String
|
||||||
|
ledgerFile = do
|
||||||
|
getEnv "LEDGER" `catch` \_ -> return defaultLedgerFile >>= return
|
||||||
|
|||||||
245
Parse.hs
Normal file
245
Parse.hs
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
{-
|
||||||
|
Here's the ledger 2.5 grammar:
|
||||||
|
"The ledger file format is quite simple, but also very flexible. It supports
|
||||||
|
many options, though typically the user can ignore most of them. They are
|
||||||
|
summarized below. The initial character of each line determines what the
|
||||||
|
line means, and how it should be interpreted. Allowable initial characters
|
||||||
|
are:
|
||||||
|
|
||||||
|
NUMBER A line beginning with a number denotes an entry. It may be followed by any
|
||||||
|
number of lines, each beginning with whitespace, to denote the entry’s account
|
||||||
|
transactions. The format of the first line is:
|
||||||
|
|
||||||
|
DATE[=EDATE] [*|!] [(CODE)] DESC
|
||||||
|
|
||||||
|
If ‘*’ appears after the date (with optional effective date), it indicates the entry
|
||||||
|
is “cleared”, which can mean whatever the user wants it t omean. If ‘!’ appears
|
||||||
|
after the date, it indicates d the entry is “pending”; i.e., tentatively cleared from
|
||||||
|
the user’s point of view, but not yet actually cleared. If a ‘CODE’ appears in
|
||||||
|
parentheses, it may be used to indicate a check number, or the type of the
|
||||||
|
transaction. Following these is the payee, or a description of the transaction.
|
||||||
|
The format of each following transaction is:
|
||||||
|
|
||||||
|
ACCOUNT AMOUNT [; NOTE]
|
||||||
|
|
||||||
|
The ‘ACCOUNT’ may be surrounded by parentheses if it is a virtual
|
||||||
|
transactions, or square brackets if it is a virtual transactions that must
|
||||||
|
balance. The ‘AMOUNT’ can be followed by a per-unit transaction cost,
|
||||||
|
by specifying ‘ AMOUNT’, or a complete transaction cost with ‘@ AMOUNT’.
|
||||||
|
Lastly, the ‘NOTE’ may specify an actual and/or effective date for the
|
||||||
|
transaction by using the syntax ‘[ACTUAL_DATE]’ or ‘[=EFFECTIVE_DATE]’ or
|
||||||
|
‘[ACTUAL_DATE=EFFECtIVE_DATE]’.
|
||||||
|
|
||||||
|
= An automated entry. A value expression must appear after the equal sign.
|
||||||
|
After this initial line there should be a set of one or more transactions, just as
|
||||||
|
if it were normal entry. If the amounts of the transactions have no commodity,
|
||||||
|
they will be applied as modifiers to whichever real transaction is matched by
|
||||||
|
the value expression.
|
||||||
|
|
||||||
|
~ A period entry. A period expression must appear after the tilde.
|
||||||
|
After this initial line there should be a set of one or more transactions, just as
|
||||||
|
if it were normal entry.
|
||||||
|
|
||||||
|
|
||||||
|
! A line beginning with an exclamation mark denotes a command directive. It
|
||||||
|
must be immediately followed by the command word. The supported commands
|
||||||
|
are:
|
||||||
|
|
||||||
|
‘!include’
|
||||||
|
Include the stated ledger file.
|
||||||
|
‘!account’
|
||||||
|
The account name is given is taken to be the parent of all transac-
|
||||||
|
tions that follow, until ‘!end’ is seen.
|
||||||
|
‘!end’ Ends an account block.
|
||||||
|
|
||||||
|
; A line beginning with a colon indicates a comment, and is ignored.
|
||||||
|
|
||||||
|
Y If a line begins with a capital Y, it denotes the year used for all subsequent
|
||||||
|
entries that give a date without a year. The year should appear immediately
|
||||||
|
after the Y, for example: ‘Y2004’. This is useful at the beginning of a file, to
|
||||||
|
specify the year for that file. If all entries specify a year, however, this command
|
||||||
|
has no effect.
|
||||||
|
|
||||||
|
|
||||||
|
P Specifies a historical price for a commodity. These are usually found in a pricing
|
||||||
|
history file (see the ‘-Q’ option). The syntax is:
|
||||||
|
|
||||||
|
P DATE SYMBOL PRICE
|
||||||
|
|
||||||
|
N SYMBOL Indicates that pricing information is to be ignored for a given symbol, nor will
|
||||||
|
quotes ever be downloaded for that symbol. Useful with a home currency, such
|
||||||
|
as the dollar ($). It is recommended that these pricing options be set in the price
|
||||||
|
database file, which defaults to ‘~/.pricedb’. The syntax for this command is:
|
||||||
|
|
||||||
|
N SYMBOL
|
||||||
|
|
||||||
|
|
||||||
|
D AMOUNT Specifies the default commodity to use, by specifying an amount in the expected
|
||||||
|
format. The entry command will use this commodity as the default when none
|
||||||
|
other can be determined. This command may be used multiple times, to set
|
||||||
|
the default flags for different commodities; whichever is seen last is used as the
|
||||||
|
default commodity. For example, to set US dollars as the default commodity,
|
||||||
|
while also setting the thousands flag and decimal flag for that commodity, use:
|
||||||
|
|
||||||
|
D $1,000.00
|
||||||
|
|
||||||
|
C AMOUNT1 = AMOUNT2
|
||||||
|
Specifies a commodity conversion, where the first amount is given to be equiv-
|
||||||
|
alent to the second amount. The first amount should use the decimal precision
|
||||||
|
desired during reporting:
|
||||||
|
|
||||||
|
C 1.00 Kb = 1024 bytes
|
||||||
|
|
||||||
|
i, o, b, h
|
||||||
|
These four relate to timeclock support, which permits ledger to read timelog
|
||||||
|
files. See the timeclock’s documentation for more info on the syntax of its
|
||||||
|
timelog files."
|
||||||
|
-}
|
||||||
|
-- parsec example: http://pandoc.googlecode.com/svn/trunk/src/Text/Pandoc/Readers/RST.hs
|
||||||
|
|
||||||
|
module Parse where
|
||||||
|
|
||||||
|
import Text.ParserCombinators.Parsec
|
||||||
|
import qualified Text.ParserCombinators.Parsec.Token as P
|
||||||
|
import Text.ParserCombinators.Parsec.Language
|
||||||
|
|
||||||
|
import Types
|
||||||
|
|
||||||
|
-- see sample data in Tests.hs
|
||||||
|
|
||||||
|
-- set up token parsers, though we're not using these heavily yet
|
||||||
|
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
|
||||||
|
|
||||||
|
-- ledger file parsers
|
||||||
|
|
||||||
|
ledger :: Parser Ledger
|
||||||
|
ledger = do
|
||||||
|
ledgernondatalines
|
||||||
|
-- for now these must come first, unlike ledger
|
||||||
|
modifier_entries <- many ledgermodifierentry
|
||||||
|
periodic_entries <- many ledgerperiodicentry
|
||||||
|
--
|
||||||
|
entries <- (many ledgerentry) <?> "entry"
|
||||||
|
eof
|
||||||
|
return (Ledger modifier_entries periodic_entries entries)
|
||||||
|
|
||||||
|
ledgernondatalines :: Parser [String]
|
||||||
|
ledgernondatalines = many (ledgerdirective <|> ledgercomment <|> do {whiteSpace1; return []})
|
||||||
|
|
||||||
|
ledgercomment :: Parser String
|
||||||
|
ledgercomment = char ';' >> restofline <?> "comment"
|
||||||
|
|
||||||
|
ledgerdirective :: Parser String
|
||||||
|
ledgerdirective = char '!' >> restofline <?> "directive"
|
||||||
|
|
||||||
|
ledgermodifierentry :: Parser ModifierEntry
|
||||||
|
ledgermodifierentry = do
|
||||||
|
char '=' <?> "entry"
|
||||||
|
many spacenonewline
|
||||||
|
valueexpr <- restofline
|
||||||
|
transactions <- ledgertransactions
|
||||||
|
ledgernondatalines
|
||||||
|
return (ModifierEntry valueexpr transactions)
|
||||||
|
|
||||||
|
ledgerperiodicentry :: Parser PeriodicEntry
|
||||||
|
ledgerperiodicentry = do
|
||||||
|
char '~' <?> "entry"
|
||||||
|
many spacenonewline
|
||||||
|
periodexpr <- restofline
|
||||||
|
transactions <- ledgertransactions
|
||||||
|
ledgernondatalines
|
||||||
|
return (PeriodicEntry periodexpr transactions)
|
||||||
|
|
||||||
|
ledgerentry :: Parser Entry
|
||||||
|
ledgerentry = do
|
||||||
|
date <- ledgerdate
|
||||||
|
status <- ledgerstatus
|
||||||
|
code <- ledgercode
|
||||||
|
description <- anyChar `manyTill` ledgereol
|
||||||
|
transactions <- ledgertransactions
|
||||||
|
ledgernondatalines
|
||||||
|
return (Entry date status code description transactions)
|
||||||
|
|
||||||
|
ledgerdate :: Parser String
|
||||||
|
ledgerdate = do date <- many1 (digit <|> char '/'); many1 spacenonewline; return date
|
||||||
|
|
||||||
|
ledgerstatus :: Parser Bool
|
||||||
|
ledgerstatus = try (do { char '*'; many1 spacenonewline; return True } ) <|> return False
|
||||||
|
|
||||||
|
ledgercode :: Parser String
|
||||||
|
ledgercode = try (do { char '('; code <- anyChar `manyTill` char ')'; many1 spacenonewline; return code } ) <|> return ""
|
||||||
|
|
||||||
|
ledgertransactions :: Parser [Transaction]
|
||||||
|
ledgertransactions = (ledgertransaction <?> "transaction") `manyTill` (newline <?> "blank line")
|
||||||
|
-- => unlike ledger, we need to end the file with a blank line
|
||||||
|
|
||||||
|
ledgertransaction :: Parser Transaction
|
||||||
|
ledgertransaction = do
|
||||||
|
many1 spacenonewline
|
||||||
|
account <- ledgeraccount <?> "account"
|
||||||
|
amount <- ledgeramount <?> "amount"
|
||||||
|
many spacenonewline
|
||||||
|
ledgereol
|
||||||
|
many ledgercomment
|
||||||
|
return (Transaction account amount)
|
||||||
|
|
||||||
|
-- account names may have single spaces in them, and are terminated by two or more spaces
|
||||||
|
ledgeraccount :: Parser String
|
||||||
|
ledgeraccount = many1 (alphaNum <|> char ':' <|> try (do {spacenonewline; do {notFollowedBy spacenonewline; return ' '}}))
|
||||||
|
|
||||||
|
ledgeramount :: Parser Amount
|
||||||
|
ledgeramount = try (do
|
||||||
|
many1 spacenonewline
|
||||||
|
currency <- many (noneOf "-.0123456789\n") <?> "currency"
|
||||||
|
quantity <- many1 (oneOf "-.0123456789") <?> "quantity"
|
||||||
|
return (Amount currency (read quantity))
|
||||||
|
) <|>
|
||||||
|
return (Amount "" 0)
|
||||||
|
|
||||||
|
ledgereol :: Parser String
|
||||||
|
ledgereol = ledgercomment <|> do {newline; return []}
|
||||||
|
|
||||||
|
spacenonewline :: Parser Char
|
||||||
|
spacenonewline = satisfy (\c -> c `elem` " \v\f\t")
|
||||||
|
|
||||||
|
restofline :: Parser String
|
||||||
|
restofline = anyChar `manyTill` newline
|
||||||
|
|
||||||
|
whiteSpace1 :: Parser ()
|
||||||
|
whiteSpace1 = do space; whiteSpace
|
||||||
|
|
||||||
|
|
||||||
|
-- ok, what can we do with it ?
|
||||||
|
|
||||||
|
printParseResult r =
|
||||||
|
case r of
|
||||||
|
Left err -> do putStr "ledger parse error at "; print err
|
||||||
|
Right x -> do print x
|
||||||
|
|
||||||
|
parseLedgerFile :: IO String -> IO (Either ParseError Ledger)
|
||||||
|
parseLedgerFile filepath = do
|
||||||
|
f <- filepath
|
||||||
|
parseFromFile ledger f >>= return
|
||||||
|
|
||||||
153
Tests.hs
Normal file
153
Tests.hs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
module Tests where
|
||||||
|
|
||||||
|
import Test.QuickCheck
|
||||||
|
import Test.HUnit
|
||||||
|
import Text.ParserCombinators.Parsec
|
||||||
|
--import Control.Exception (assert)
|
||||||
|
|
||||||
|
import Parse
|
||||||
|
import Options
|
||||||
|
|
||||||
|
-- sample data
|
||||||
|
|
||||||
|
sample_entry = "\
|
||||||
|
\2007/01/27 * joes diner\n\
|
||||||
|
\ expenses:food:dining $10.00\n\
|
||||||
|
\ expenses:gifts $10.00\n\
|
||||||
|
\ assets:checking $-20.00\n\
|
||||||
|
\\n" --"
|
||||||
|
|
||||||
|
sample_entry2 = "\
|
||||||
|
\2007/01/28 coopportunity\n\
|
||||||
|
\ expenses:food:groceries $47.18\n\
|
||||||
|
\ assets:checking\n\
|
||||||
|
\\n" --"
|
||||||
|
|
||||||
|
sample_entry3 = "\
|
||||||
|
\2007/01/01 * opening balance\n\
|
||||||
|
\ assets:cash $4.82\n\
|
||||||
|
\ equity:opening balances\n\
|
||||||
|
\\n\
|
||||||
|
\2007/01/01 * opening balance\n\
|
||||||
|
\ assets:cash $4.82\n\
|
||||||
|
\ equity:opening balances\n\
|
||||||
|
\\n\
|
||||||
|
\2007/01/28 coopportunity\n\
|
||||||
|
\ expenses:food:groceries $47.18\n\
|
||||||
|
\ assets:checking\n\
|
||||||
|
\\n" --"
|
||||||
|
|
||||||
|
sample_periodic_entry = "\
|
||||||
|
\~ monthly from 2007/2/2\n\
|
||||||
|
\ assets:saving $200.00\n\
|
||||||
|
\ assets:checking\n\
|
||||||
|
\\n" --"
|
||||||
|
|
||||||
|
sample_periodic_entry2 = "\
|
||||||
|
\~ monthly from 2007/2/2\n\
|
||||||
|
\ assets:saving $200.00 ;auto savings\n\
|
||||||
|
\ assets:checking\n\
|
||||||
|
\\n" --"
|
||||||
|
|
||||||
|
sample_periodic_entry3 = "\
|
||||||
|
\~ monthly from 2007/01/01\n\
|
||||||
|
\ assets:cash $4.82\n\
|
||||||
|
\ equity:opening balances\n\
|
||||||
|
\\n\
|
||||||
|
\~ monthly from 2007/01/01\n\
|
||||||
|
\ assets:cash $4.82\n\
|
||||||
|
\ equity:opening balances\n\
|
||||||
|
\\n" --"
|
||||||
|
|
||||||
|
sample_transaction = " expenses:food:dining $10.00\n"
|
||||||
|
|
||||||
|
sample_transaction2 = " assets:checking\n"
|
||||||
|
|
||||||
|
sample_ledger = "\
|
||||||
|
\\n\
|
||||||
|
\2007/01/27 * joes diner\n\
|
||||||
|
\ expenses:food:dining $10.00\n\
|
||||||
|
\ expenses:gifts $10.00\n\
|
||||||
|
\ assets:checking $-20.00\n\
|
||||||
|
\\n\
|
||||||
|
\\n\
|
||||||
|
\2007/01/28 coopportunity\n\
|
||||||
|
\ expenses:food:groceries $47.18\n\
|
||||||
|
\ assets:checking $-47.18\n\
|
||||||
|
\\n\
|
||||||
|
\" --"
|
||||||
|
|
||||||
|
sample_ledger2 = "\
|
||||||
|
\;comment\n\
|
||||||
|
\2007/01/27 * joes diner\n\
|
||||||
|
\ expenses:food:dining $10.00\n\
|
||||||
|
\ assets:checking $-47.18\n\
|
||||||
|
\\n" --"
|
||||||
|
|
||||||
|
sample_ledger3 = "\
|
||||||
|
\2007/01/27 * joes diner\n\
|
||||||
|
\ expenses:food:dining $10.00\n\
|
||||||
|
\;intra-entry comment\n\
|
||||||
|
\ assets:checking $-47.18\n\
|
||||||
|
\\n" --"
|
||||||
|
|
||||||
|
sample_ledger4 = "\
|
||||||
|
\!include \"somefile\"\n\
|
||||||
|
\2007/01/27 * joes diner\n\
|
||||||
|
\ expenses:food:dining $10.00\n\
|
||||||
|
\ assets:checking $-47.18\n\
|
||||||
|
\\n" --"
|
||||||
|
|
||||||
|
sample_ledger5 = ""
|
||||||
|
|
||||||
|
sample_ledger6 = "\
|
||||||
|
\~ monthly from 2007/1/21\n\
|
||||||
|
\ expenses:entertainment $16.23 ;netflix\n\
|
||||||
|
\ assets:checking\n\
|
||||||
|
\\n\
|
||||||
|
\; 2007/01/01 * opening balance\n\
|
||||||
|
\; assets:saving $200.04\n\
|
||||||
|
\; equity:opening balances \n\
|
||||||
|
\\n" --"
|
||||||
|
|
||||||
|
-- hunit tests
|
||||||
|
|
||||||
|
test1 = TestCase (assertEqual "1==1" 1 1)
|
||||||
|
sometests = TestList [TestLabel "test1" test1]
|
||||||
|
|
||||||
|
tests = Test.HUnit.test [
|
||||||
|
"test1" ~: "1==1" ~: 1 ~=? 1,
|
||||||
|
"test2" ~: assertEqual "2==2" 2 2
|
||||||
|
]
|
||||||
|
|
||||||
|
-- quickcheck tests
|
||||||
|
|
||||||
|
prop_test1 = 1 == 1
|
||||||
|
prop2 = 1 == 1
|
||||||
|
|
||||||
|
-- commands
|
||||||
|
|
||||||
|
test :: IO ()
|
||||||
|
test = do
|
||||||
|
parseTest ledgertransaction sample_transaction
|
||||||
|
parseTest ledgertransaction sample_transaction2
|
||||||
|
parseTest ledgerentry sample_entry
|
||||||
|
parseTest ledgerentry sample_entry2
|
||||||
|
parseTest ledgerentry sample_entry3
|
||||||
|
parseTest ledgerperiodicentry sample_periodic_entry
|
||||||
|
parseTest ledgerperiodicentry sample_periodic_entry2
|
||||||
|
parseTest ledgerperiodicentry sample_periodic_entry3
|
||||||
|
parseTest ledger sample_ledger
|
||||||
|
parseTest ledger sample_ledger2
|
||||||
|
parseTest ledger sample_ledger3
|
||||||
|
parseTest ledger sample_ledger4
|
||||||
|
parseTest ledger sample_ledger5
|
||||||
|
parseTest ledger sample_ledger6
|
||||||
|
parseTest ledger sample_periodic_entry
|
||||||
|
parseTest ledger sample_periodic_entry2
|
||||||
|
parseLedgerFile ledgerFile >>= printParseResult
|
||||||
|
return ()
|
||||||
|
-- assert_ $ amount t1 == 8.50
|
||||||
|
-- putStrLn "ok"
|
||||||
|
-- where assert_ e = assert e return ()
|
||||||
|
|
||||||
83
Types.hs
Normal file
83
Types.hs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
-- a data model
|
||||||
|
module Types where
|
||||||
|
|
||||||
|
import Text.Printf
|
||||||
|
|
||||||
|
data Ledger = Ledger {
|
||||||
|
modifier_entries :: [ModifierEntry],
|
||||||
|
periodic_entries :: [PeriodicEntry],
|
||||||
|
entries :: [Entry]
|
||||||
|
} deriving (Show, Eq)
|
||||||
|
data ModifierEntry = ModifierEntry { -- aka automated entry
|
||||||
|
valueexpr :: String,
|
||||||
|
m_transactions :: [Transaction]
|
||||||
|
} deriving (Eq)
|
||||||
|
data PeriodicEntry = PeriodicEntry {
|
||||||
|
periodexpr :: String,
|
||||||
|
p_transactions :: [Transaction]
|
||||||
|
} deriving (Eq)
|
||||||
|
data Entry = Entry {
|
||||||
|
date :: Date,
|
||||||
|
status :: Bool,
|
||||||
|
code :: String,
|
||||||
|
description :: String,
|
||||||
|
transactions :: [Transaction]
|
||||||
|
} deriving (Eq)
|
||||||
|
data Transaction = Transaction {
|
||||||
|
account :: Account,
|
||||||
|
amount :: Amount
|
||||||
|
} deriving (Eq)
|
||||||
|
data Amount = Amount {
|
||||||
|
currency :: String,
|
||||||
|
quantity :: Float
|
||||||
|
} deriving (Read, Eq)
|
||||||
|
type Date = String
|
||||||
|
type Account = String
|
||||||
|
|
||||||
|
-- show methods
|
||||||
|
|
||||||
|
showLedger :: Ledger -> String
|
||||||
|
showLedger l = "Ledger has\n"
|
||||||
|
++ (showModifierEntries $ modifier_entries l)
|
||||||
|
++ (showPeriodicEntries $ periodic_entries l)
|
||||||
|
++ (showEntries $ entries l)
|
||||||
|
|
||||||
|
showModifierEntries :: [ModifierEntry] -> String
|
||||||
|
showModifierEntries [] = ""
|
||||||
|
showModifierEntries es =
|
||||||
|
(show n) ++ " modifier " ++ (inflectEntries n) ++ ":\n" ++ unlines (map show es)
|
||||||
|
where n = length es
|
||||||
|
|
||||||
|
showPeriodicEntries :: [PeriodicEntry] -> String
|
||||||
|
showPeriodicEntries [] = ""
|
||||||
|
showPeriodicEntries es =
|
||||||
|
(show n) ++ " periodic " ++ (inflectEntries n) ++ ":\n" ++ unlines (map show es)
|
||||||
|
where n = length es
|
||||||
|
|
||||||
|
showEntries :: [Entry] -> String
|
||||||
|
showEntries [] = ""
|
||||||
|
showEntries es =
|
||||||
|
(show n) ++ " " ++ (inflectEntries n) ++ ":\n" ++ unlines (map show es)
|
||||||
|
where n = length es
|
||||||
|
|
||||||
|
inflectEntries 1 = "entry"
|
||||||
|
inflectEntries _ = "entries"
|
||||||
|
|
||||||
|
instance Show ModifierEntry where
|
||||||
|
show e = "= " ++ (valueexpr e) ++ "\n" ++ unlines (map show (m_transactions e))
|
||||||
|
|
||||||
|
instance Show PeriodicEntry where
|
||||||
|
show e = "~ " ++ (periodexpr e) ++ "\n" ++ unlines (map show (p_transactions e))
|
||||||
|
|
||||||
|
instance Show Entry where
|
||||||
|
show e = date e ++ " " ++ s ++ c ++ d ++ "\n" ++ unlines (map show (transactions e))
|
||||||
|
where
|
||||||
|
d = description e
|
||||||
|
s = case (status e) of {True -> "* "; False -> ""}
|
||||||
|
c = case (length(code e) > 0) of {True -> (code e ++ " "); False -> ""}
|
||||||
|
|
||||||
|
instance Show Transaction where
|
||||||
|
show t = printf " %-40s %20.2s" (take 40 $ account t) (show $ amount t)
|
||||||
|
|
||||||
|
instance Show Amount where show a = (currency a) ++ (show $ quantity a)
|
||||||
|
|
||||||
490
hledger.hs
490
hledger.hs
@ -1,497 +1,31 @@
|
|||||||
#!/usr/bin/runhaskell
|
#!/usr/bin/runhaskell
|
||||||
-- hledger - ledger-compatible money management utilities (& haskell study)
|
-- hledger - ledger-compatible money management utilities (& haskell workout)
|
||||||
-- GPLv3, (c) Simon Michael & contributors,
|
-- GPLv3, (c) Simon Michael & contributors,
|
||||||
--
|
|
||||||
-- John Wiegley's ledger is at http://newartisans.com/ledger.html .
|
-- John Wiegley's ledger is at http://newartisans.com/ledger.html .
|
||||||
-- Here's the v2.5 grammar:
|
|
||||||
{-
|
|
||||||
"The ledger file format is quite simple, but also very flexible. It supports
|
|
||||||
many options, though typically the user can ignore most of them. They are
|
|
||||||
summarized below. The initial character of each line determines what the
|
|
||||||
line means, and how it should be interpreted. Allowable initial characters
|
|
||||||
are:
|
|
||||||
|
|
||||||
NUMBER A line beginning with a number denotes an entry. It may be followed by any
|
|
||||||
number of lines, each beginning with whitespace, to denote the entry’s account
|
|
||||||
transactions. The format of the first line is:
|
|
||||||
|
|
||||||
DATE[=EDATE] [*|!] [(CODE)] DESC
|
|
||||||
|
|
||||||
If ‘*’ appears after the date (with optional effective date), it indicates the entry
|
|
||||||
is “cleared”, which can mean whatever the user wants it t omean. If ‘!’ appears
|
|
||||||
after the date, it indicates d the entry is “pending”; i.e., tentatively cleared from
|
|
||||||
the user’s point of view, but not yet actually cleared. If a ‘CODE’ appears in
|
|
||||||
parentheses, it may be used to indicate a check number, or the type of the
|
|
||||||
transaction. Following these is the payee, or a description of the transaction.
|
|
||||||
The format of each following transaction is:
|
|
||||||
|
|
||||||
ACCOUNT AMOUNT [; NOTE]
|
|
||||||
|
|
||||||
The ‘ACCOUNT’ may be surrounded by parentheses if it is a virtual
|
|
||||||
transactions, or square brackets if it is a virtual transactions that must
|
|
||||||
balance. The ‘AMOUNT’ can be followed by a per-unit transaction cost,
|
|
||||||
by specifying ‘ AMOUNT’, or a complete transaction cost with ‘@ AMOUNT’.
|
|
||||||
Lastly, the ‘NOTE’ may specify an actual and/or effective date for the
|
|
||||||
transaction by using the syntax ‘[ACTUAL_DATE]’ or ‘[=EFFECTIVE_DATE]’ or
|
|
||||||
‘[ACTUAL_DATE=EFFECtIVE_DATE]’.
|
|
||||||
|
|
||||||
= An automated entry. A value expression must appear after the equal sign.
|
|
||||||
After this initial line there should be a set of one or more transactions, just as
|
|
||||||
if it were normal entry. If the amounts of the transactions have no commodity,
|
|
||||||
they will be applied as modifiers to whichever real transaction is matched by
|
|
||||||
the value expression.
|
|
||||||
|
|
||||||
~ A period entry. A period expression must appear after the tilde.
|
|
||||||
After this initial line there should be a set of one or more transactions, just as
|
|
||||||
if it were normal entry.
|
|
||||||
|
|
||||||
|
|
||||||
! A line beginning with an exclamation mark denotes a command directive. It
|
|
||||||
must be immediately followed by the command word. The supported commands
|
|
||||||
are:
|
|
||||||
|
|
||||||
‘!include’
|
|
||||||
Include the stated ledger file.
|
|
||||||
‘!account’
|
|
||||||
The account name is given is taken to be the parent of all transac-
|
|
||||||
tions that follow, until ‘!end’ is seen.
|
|
||||||
‘!end’ Ends an account block.
|
|
||||||
|
|
||||||
; A line beginning with a colon indicates a comment, and is ignored.
|
|
||||||
|
|
||||||
Y If a line begins with a capital Y, it denotes the year used for all subsequent
|
|
||||||
entries that give a date without a year. The year should appear immediately
|
|
||||||
after the Y, for example: ‘Y2004’. This is useful at the beginning of a file, to
|
|
||||||
specify the year for that file. If all entries specify a year, however, this command
|
|
||||||
has no effect.
|
|
||||||
|
|
||||||
|
|
||||||
P Specifies a historical price for a commodity. These are usually found in a pricing
|
|
||||||
history file (see the ‘-Q’ option). The syntax is:
|
|
||||||
|
|
||||||
P DATE SYMBOL PRICE
|
|
||||||
|
|
||||||
N SYMBOL Indicates that pricing information is to be ignored for a given symbol, nor will
|
|
||||||
quotes ever be downloaded for that symbol. Useful with a home currency, such
|
|
||||||
as the dollar ($). It is recommended that these pricing options be set in the price
|
|
||||||
database file, which defaults to ‘~/.pricedb’. The syntax for this command is:
|
|
||||||
|
|
||||||
N SYMBOL
|
|
||||||
|
|
||||||
|
|
||||||
D AMOUNT Specifies the default commodity to use, by specifying an amount in the expected
|
|
||||||
format. The entry command will use this commodity as the default when none
|
|
||||||
other can be determined. This command may be used multiple times, to set
|
|
||||||
the default flags for different commodities; whichever is seen last is used as the
|
|
||||||
default commodity. For example, to set US dollars as the default commodity,
|
|
||||||
while also setting the thousands flag and decimal flag for that commodity, use:
|
|
||||||
|
|
||||||
D $1,000.00
|
|
||||||
|
|
||||||
C AMOUNT1 = AMOUNT2
|
|
||||||
Specifies a commodity conversion, where the first amount is given to be equiv-
|
|
||||||
alent to the second amount. The first amount should use the decimal precision
|
|
||||||
desired during reporting:
|
|
||||||
|
|
||||||
C 1.00 Kb = 1024 bytes
|
|
||||||
|
|
||||||
i, o, b, h
|
|
||||||
These four relate to timeclock support, which permits ledger to read timelog
|
|
||||||
files. See the timeclock’s documentation for more info on the syntax of its
|
|
||||||
timelog files."
|
|
||||||
-}
|
|
||||||
-- parsec example: http://pandoc.googlecode.com/svn/trunk/src/Text/Pandoc/Readers/RST.hs
|
|
||||||
|
|
||||||
import Debug.Trace
|
|
||||||
import Test.QuickCheck
|
|
||||||
import Test.HUnit
|
|
||||||
|
|
||||||
--import TildeExpand -- confuses my ghc 6.7
|
|
||||||
import System (getArgs)
|
import System (getArgs)
|
||||||
import System.Directory (getHomeDirectory)
|
|
||||||
import System.Environment (getEnv)
|
|
||||||
import Control.Exception (assert)
|
|
||||||
import Text.ParserCombinators.Parsec
|
|
||||||
import qualified Text.ParserCombinators.Parsec.Token as P
|
|
||||||
import Text.ParserCombinators.Parsec.Language
|
|
||||||
import Text.Printf
|
|
||||||
|
|
||||||
import Options
|
import Options
|
||||||
|
import Types
|
||||||
-- sample data
|
import Parse
|
||||||
|
import Tests
|
||||||
sample_entry = "\
|
|
||||||
\2007/01/27 * joes diner\n\
|
|
||||||
\ expenses:food:dining $10.00\n\
|
|
||||||
\ expenses:gifts $10.00\n\
|
|
||||||
\ assets:checking $-20.00\n\
|
|
||||||
\\n" --"
|
|
||||||
|
|
||||||
sample_entry2 = "\
|
|
||||||
\2007/01/28 coopportunity\n\
|
|
||||||
\ expenses:food:groceries $47.18\n\
|
|
||||||
\ assets:checking\n\
|
|
||||||
\\n" --"
|
|
||||||
|
|
||||||
sample_entry3 = "\
|
|
||||||
\2007/01/01 * opening balance\n\
|
|
||||||
\ assets:cash $4.82\n\
|
|
||||||
\ equity:opening balances\n\
|
|
||||||
\\n\
|
|
||||||
\2007/01/01 * opening balance\n\
|
|
||||||
\ assets:cash $4.82\n\
|
|
||||||
\ equity:opening balances\n\
|
|
||||||
\\n\
|
|
||||||
\2007/01/28 coopportunity\n\
|
|
||||||
\ expenses:food:groceries $47.18\n\
|
|
||||||
\ assets:checking\n\
|
|
||||||
\\n" --"
|
|
||||||
|
|
||||||
sample_periodic_entry = "\
|
|
||||||
\~ monthly from 2007/2/2\n\
|
|
||||||
\ assets:saving $200.00\n\
|
|
||||||
\ assets:checking\n\
|
|
||||||
\\n" --"
|
|
||||||
|
|
||||||
sample_periodic_entry2 = "\
|
|
||||||
\~ monthly from 2007/2/2\n\
|
|
||||||
\ assets:saving $200.00 ;auto savings\n\
|
|
||||||
\ assets:checking\n\
|
|
||||||
\\n" --"
|
|
||||||
|
|
||||||
sample_periodic_entry3 = "\
|
|
||||||
\~ monthly from 2007/01/01\n\
|
|
||||||
\ assets:cash $4.82\n\
|
|
||||||
\ equity:opening balances\n\
|
|
||||||
\\n\
|
|
||||||
\~ monthly from 2007/01/01\n\
|
|
||||||
\ assets:cash $4.82\n\
|
|
||||||
\ equity:opening balances\n\
|
|
||||||
\\n" --"
|
|
||||||
|
|
||||||
sample_transaction = " expenses:food:dining $10.00\n"
|
|
||||||
|
|
||||||
sample_transaction2 = " assets:checking\n"
|
|
||||||
|
|
||||||
sample_ledger = "\
|
|
||||||
\\n\
|
|
||||||
\2007/01/27 * joes diner\n\
|
|
||||||
\ expenses:food:dining $10.00\n\
|
|
||||||
\ expenses:gifts $10.00\n\
|
|
||||||
\ assets:checking $-20.00\n\
|
|
||||||
\\n\
|
|
||||||
\\n\
|
|
||||||
\2007/01/28 coopportunity\n\
|
|
||||||
\ expenses:food:groceries $47.18\n\
|
|
||||||
\ assets:checking $-47.18\n\
|
|
||||||
\\n\
|
|
||||||
\" --"
|
|
||||||
|
|
||||||
sample_ledger2 = "\
|
|
||||||
\;comment\n\
|
|
||||||
\2007/01/27 * joes diner\n\
|
|
||||||
\ expenses:food:dining $10.00\n\
|
|
||||||
\ assets:checking $-47.18\n\
|
|
||||||
\\n" --"
|
|
||||||
|
|
||||||
sample_ledger3 = "\
|
|
||||||
\2007/01/27 * joes diner\n\
|
|
||||||
\ expenses:food:dining $10.00\n\
|
|
||||||
\;intra-entry comment\n\
|
|
||||||
\ assets:checking $-47.18\n\
|
|
||||||
\\n" --"
|
|
||||||
|
|
||||||
sample_ledger4 = "\
|
|
||||||
\!include \"somefile\"\n\
|
|
||||||
\2007/01/27 * joes diner\n\
|
|
||||||
\ expenses:food:dining $10.00\n\
|
|
||||||
\ assets:checking $-47.18\n\
|
|
||||||
\\n" --"
|
|
||||||
|
|
||||||
sample_ledger5 = ""
|
|
||||||
|
|
||||||
sample_ledger6 = "\
|
|
||||||
\~ monthly from 2007/1/21\n\
|
|
||||||
\ expenses:entertainment $16.23 ;netflix\n\
|
|
||||||
\ assets:checking\n\
|
|
||||||
\\n\
|
|
||||||
\; 2007/01/01 * opening balance\n\
|
|
||||||
\; assets:saving $200.04\n\
|
|
||||||
\; equity:opening balances \n\
|
|
||||||
\\n" --"
|
|
||||||
|
|
||||||
-- a data model
|
|
||||||
|
|
||||||
data Ledger = Ledger {
|
|
||||||
modifier_entries :: [ModifierEntry],
|
|
||||||
periodic_entries :: [PeriodicEntry],
|
|
||||||
entries :: [Entry]
|
|
||||||
} deriving (Show, Eq)
|
|
||||||
data ModifierEntry = ModifierEntry { -- aka automated entry
|
|
||||||
valueexpr :: String,
|
|
||||||
m_transactions :: [Transaction]
|
|
||||||
} deriving (Eq)
|
|
||||||
data PeriodicEntry = PeriodicEntry {
|
|
||||||
periodexpr :: String,
|
|
||||||
p_transactions :: [Transaction]
|
|
||||||
} deriving (Eq)
|
|
||||||
data Entry = Entry {
|
|
||||||
date :: Date,
|
|
||||||
status :: Bool,
|
|
||||||
code :: String,
|
|
||||||
description :: String,
|
|
||||||
transactions :: [Transaction]
|
|
||||||
} deriving (Eq)
|
|
||||||
data Transaction = Transaction {
|
|
||||||
account :: Account,
|
|
||||||
amount :: Amount
|
|
||||||
} deriving (Eq)
|
|
||||||
data Amount = Amount {
|
|
||||||
currency :: String,
|
|
||||||
quantity :: Float
|
|
||||||
} deriving (Read, Eq)
|
|
||||||
type Date = String
|
|
||||||
type Account = String
|
|
||||||
|
|
||||||
-- ledger file parsing
|
|
||||||
|
|
||||||
-- set up token parsing, though we're not using it heavily yet
|
|
||||||
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
|
|
||||||
|
|
||||||
-- parsers
|
|
||||||
|
|
||||||
ledger :: Parser Ledger
|
|
||||||
ledger = do
|
|
||||||
ledgernondatalines
|
|
||||||
-- for now these must come first, unlike ledger
|
|
||||||
modifier_entries <- many ledgermodifierentry
|
|
||||||
periodic_entries <- many ledgerperiodicentry
|
|
||||||
--
|
|
||||||
entries <- (many ledgerentry) <?> "entry"
|
|
||||||
eof
|
|
||||||
return (Ledger modifier_entries periodic_entries entries)
|
|
||||||
|
|
||||||
ledgernondatalines :: Parser [String]
|
|
||||||
ledgernondatalines = many (ledgerdirective <|> ledgercomment <|> do {whiteSpace1; return []})
|
|
||||||
|
|
||||||
ledgercomment :: Parser String
|
|
||||||
ledgercomment = char ';' >> restofline <?> "comment"
|
|
||||||
|
|
||||||
ledgerdirective :: Parser String
|
|
||||||
ledgerdirective = char '!' >> restofline <?> "directive"
|
|
||||||
|
|
||||||
ledgermodifierentry :: Parser ModifierEntry
|
|
||||||
ledgermodifierentry = do
|
|
||||||
char '=' <?> "entry"
|
|
||||||
many spacenonewline
|
|
||||||
valueexpr <- restofline
|
|
||||||
transactions <- ledgertransactions
|
|
||||||
ledgernondatalines
|
|
||||||
return (ModifierEntry valueexpr transactions)
|
|
||||||
|
|
||||||
ledgerperiodicentry :: Parser PeriodicEntry
|
|
||||||
ledgerperiodicentry = do
|
|
||||||
char '~' <?> "entry"
|
|
||||||
many spacenonewline
|
|
||||||
periodexpr <- restofline
|
|
||||||
transactions <- ledgertransactions
|
|
||||||
ledgernondatalines
|
|
||||||
return (PeriodicEntry periodexpr transactions)
|
|
||||||
|
|
||||||
ledgerentry :: Parser Entry
|
|
||||||
ledgerentry = do
|
|
||||||
date <- ledgerdate
|
|
||||||
status <- ledgerstatus
|
|
||||||
code <- ledgercode
|
|
||||||
description <- anyChar `manyTill` ledgereol
|
|
||||||
transactions <- ledgertransactions
|
|
||||||
ledgernondatalines
|
|
||||||
return (Entry date status code description transactions)
|
|
||||||
|
|
||||||
ledgerdate :: Parser String
|
|
||||||
ledgerdate = do date <- many1 (digit <|> char '/'); many1 spacenonewline; return date
|
|
||||||
|
|
||||||
ledgerstatus :: Parser Bool
|
|
||||||
ledgerstatus = try (do { char '*'; many1 spacenonewline; return True } ) <|> return False
|
|
||||||
|
|
||||||
ledgercode :: Parser String
|
|
||||||
ledgercode = try (do { char '('; code <- anyChar `manyTill` char ')'; many1 spacenonewline; return code } ) <|> return ""
|
|
||||||
|
|
||||||
ledgertransactions :: Parser [Transaction]
|
|
||||||
ledgertransactions = (ledgertransaction <?> "transaction") `manyTill` (newline <?> "blank line")
|
|
||||||
-- => unlike ledger, we need to end the file with a blank line
|
|
||||||
|
|
||||||
ledgertransaction :: Parser Transaction
|
|
||||||
ledgertransaction = do
|
|
||||||
many1 spacenonewline
|
|
||||||
account <- ledgeraccount <?> "account"
|
|
||||||
amount <- ledgeramount <?> "amount"
|
|
||||||
many spacenonewline
|
|
||||||
ledgereol
|
|
||||||
many ledgercomment
|
|
||||||
return (Transaction account amount)
|
|
||||||
|
|
||||||
-- account names may have single spaces in them, and are terminated by two or more spaces
|
|
||||||
ledgeraccount :: Parser String
|
|
||||||
ledgeraccount = many1 (alphaNum <|> char ':' <|> try (do {spacenonewline; do {notFollowedBy spacenonewline; return ' '}}))
|
|
||||||
|
|
||||||
ledgeramount :: Parser Amount
|
|
||||||
ledgeramount = try (do
|
|
||||||
many1 spacenonewline
|
|
||||||
currency <- many (noneOf "-.0123456789\n") <?> "currency"
|
|
||||||
quantity <- many1 (oneOf "-.0123456789") <?> "quantity"
|
|
||||||
return (Amount currency (read quantity))
|
|
||||||
) <|>
|
|
||||||
return (Amount "" 0)
|
|
||||||
|
|
||||||
ledgereol :: Parser String
|
|
||||||
ledgereol = ledgercomment <|> do {newline; return []}
|
|
||||||
|
|
||||||
spacenonewline :: Parser Char
|
|
||||||
spacenonewline = satisfy (\c -> c `elem` " \v\f\t")
|
|
||||||
|
|
||||||
restofline :: Parser String
|
|
||||||
restofline = anyChar `manyTill` newline
|
|
||||||
|
|
||||||
whiteSpace1 :: Parser ()
|
|
||||||
whiteSpace1 = do space; whiteSpace
|
|
||||||
|
|
||||||
-- tests
|
|
||||||
|
|
||||||
test1 = TestCase (assertEqual "1==1" 1 1)
|
|
||||||
sometests = TestList [TestLabel "test1" test1]
|
|
||||||
|
|
||||||
tests = Test.HUnit.test [
|
|
||||||
"test1" ~: "1==1" ~: 1 ~=? 1,
|
|
||||||
"test2" ~: assertEqual "2==2" 2 2
|
|
||||||
]
|
|
||||||
|
|
||||||
prop_test1 = 1 == 1
|
|
||||||
prop2 = 1 == 1
|
|
||||||
|
|
||||||
test :: IO ()
|
|
||||||
test = do
|
|
||||||
parseTest ledgertransaction sample_transaction
|
|
||||||
parseTest ledgertransaction sample_transaction2
|
|
||||||
parseTest ledgerentry sample_entry
|
|
||||||
parseTest ledgerentry sample_entry2
|
|
||||||
parseTest ledgerentry sample_entry3
|
|
||||||
parseTest ledgerperiodicentry sample_periodic_entry
|
|
||||||
parseTest ledgerperiodicentry sample_periodic_entry2
|
|
||||||
parseTest ledgerperiodicentry sample_periodic_entry3
|
|
||||||
parseTest ledger sample_ledger
|
|
||||||
parseTest ledger sample_ledger2
|
|
||||||
parseTest ledger sample_ledger3
|
|
||||||
parseTest ledger sample_ledger4
|
|
||||||
parseTest ledger sample_ledger5
|
|
||||||
parseTest ledger sample_ledger6
|
|
||||||
parseTest ledger sample_periodic_entry
|
|
||||||
parseTest ledger sample_periodic_entry2
|
|
||||||
parseMyLedgerFile >>= printParseResult
|
|
||||||
return ()
|
|
||||||
-- assert_ $ amount t1 == 8.50
|
|
||||||
-- putStrLn "ok"
|
|
||||||
-- where assert_ e = assert e return ()
|
|
||||||
|
|
||||||
printParseResult r =
|
|
||||||
case r of
|
|
||||||
Left err -> do putStr "ledger parse error at "; print err
|
|
||||||
Right x -> do print x
|
|
||||||
|
|
||||||
-- ok, what can we do with it ?
|
|
||||||
|
|
||||||
showLedger :: Ledger -> String
|
|
||||||
showLedger l = "Ledger has\n"
|
|
||||||
++ (showModifierEntries $ modifier_entries l)
|
|
||||||
++ (showPeriodicEntries $ periodic_entries l)
|
|
||||||
++ (showEntries $ entries l)
|
|
||||||
|
|
||||||
showModifierEntries :: [ModifierEntry] -> String
|
|
||||||
showModifierEntries [] = ""
|
|
||||||
showModifierEntries es =
|
|
||||||
(show n) ++ " modifier " ++ (inflectEntries n) ++ ":\n" ++ unlines (map show es)
|
|
||||||
where n = length es
|
|
||||||
|
|
||||||
showPeriodicEntries :: [PeriodicEntry] -> String
|
|
||||||
showPeriodicEntries [] = ""
|
|
||||||
showPeriodicEntries es =
|
|
||||||
(show n) ++ " periodic " ++ (inflectEntries n) ++ ":\n" ++ unlines (map show es)
|
|
||||||
where n = length es
|
|
||||||
|
|
||||||
showEntries :: [Entry] -> String
|
|
||||||
showEntries [] = ""
|
|
||||||
showEntries es =
|
|
||||||
(show n) ++ " " ++ (inflectEntries n) ++ ":\n" ++ unlines (map show es)
|
|
||||||
where n = length es
|
|
||||||
|
|
||||||
inflectEntries 1 = "entry"
|
|
||||||
inflectEntries _ = "entries"
|
|
||||||
|
|
||||||
instance Show ModifierEntry where
|
|
||||||
show e = "= " ++ (valueexpr e) ++ "\n" ++ unlines (map show (m_transactions e))
|
|
||||||
|
|
||||||
instance Show PeriodicEntry where
|
|
||||||
show e = "~ " ++ (periodexpr e) ++ "\n" ++ unlines (map show (p_transactions e))
|
|
||||||
|
|
||||||
instance Show Entry where
|
|
||||||
show e = date e ++ " " ++ s ++ c ++ d ++ "\n" ++ unlines (map show (transactions e))
|
|
||||||
where
|
|
||||||
d = description e
|
|
||||||
s = case (status e) of {True -> "* "; False -> ""}
|
|
||||||
c = case (length(code e) > 0) of {True -> (code e ++ " "); False -> ""}
|
|
||||||
|
|
||||||
instance Show Transaction where
|
|
||||||
show t = printf " %-40s %20.2s" (take 40 $ account t) (show $ amount t)
|
|
||||||
|
|
||||||
instance Show Amount where show a = (currency a) ++ (show $ quantity a)
|
|
||||||
|
|
||||||
parseMyLedgerFile :: IO (Either ParseError Ledger)
|
|
||||||
parseMyLedgerFile = do
|
|
||||||
ledgerFile >>= parseFromFile ledger >>= return
|
|
||||||
where
|
|
||||||
ledgerFile = do
|
|
||||||
filepath <- getEnv "LEDGER" `catch` \_ -> return "ledger.dat"
|
|
||||||
-- don't know how to accomplish this great feat
|
|
||||||
--ledger_file <- tildeExpand filepath
|
|
||||||
let ledger_file = filepath
|
|
||||||
return ledger_file
|
|
||||||
|
|
||||||
-- commands
|
-- commands
|
||||||
|
|
||||||
register :: IO ()
|
register :: IO ()
|
||||||
register = do
|
register = do
|
||||||
p <- parseMyLedgerFile
|
p <- parseLedgerFile ledgerFile
|
||||||
case p of
|
case p of
|
||||||
Left err -> do putStr "ledger parse error at "; print err
|
Left e -> do putStr "ledger parse error at "; print e
|
||||||
Right l -> putStr $ showLedger l
|
Right l -> putStr $ showLedger l
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
(opts, args) <- getArgs >>= getOptions
|
(opts, args) <- getArgs >>= getOptions
|
||||||
putStr "options: "; print opts
|
--putStr "options: "; print opts
|
||||||
putStr "arguments: "; print args
|
--putStr "arguments: "; print args
|
||||||
if "reg" `elem` args
|
if "reg" `elem` args
|
||||||
then register
|
then register
|
||||||
else return ()
|
else if "test" `elem` args
|
||||||
|
then test
|
||||||
|
else return ()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user