mirror of
https://github.com/simonmichael/hledger.git
synced 2024-12-26 20:02:27 +03:00
parsing: support D default commodity directive
This commit is contained in:
parent
8429df0f32
commit
78db98366f
@ -797,6 +797,20 @@ You can pull in the content of additional journal files, by writing lines like t
|
||||
The `!include` directive may only be used in journal files, and currently
|
||||
it may only include other journal files (eg, not timelog files.)
|
||||
|
||||
##### Default commodity
|
||||
|
||||
You can set a default commodity with a `D` directive in the journal. This
|
||||
will be used for any subsequent amounts with no commodity symbol,
|
||||
including the commodity display settings (left or right symbol, spacing,
|
||||
thousands separator, and precision.)
|
||||
|
||||
; default commodity: british pound, comma thousands separator, two decimal places
|
||||
D £1,000.00
|
||||
|
||||
2010/1/1
|
||||
a 2340.11 ; <- no commodity symbol, so will use the above
|
||||
b
|
||||
|
||||
#### Default parent account
|
||||
|
||||
You can specify a default parent account within a section of the journal with
|
||||
|
@ -49,7 +49,7 @@ data Interval = NoInterval | Daily | Weekly | Monthly | Quarterly | Yearly
|
||||
|
||||
type AccountName = String
|
||||
|
||||
data Side = L | R deriving (Eq,Show,Ord)
|
||||
data Side = L | R deriving (Eq,Show,Read,Ord)
|
||||
|
||||
data Commodity = Commodity {
|
||||
symbol :: String, -- ^ the commodity's symbol
|
||||
@ -58,7 +58,7 @@ data Commodity = Commodity {
|
||||
spaced :: Bool, -- ^ should there be a space between symbol and quantity
|
||||
comma :: Bool, -- ^ should thousands be comma-separated
|
||||
precision :: Int -- ^ number of decimal places to display
|
||||
} deriving (Eq,Show,Ord)
|
||||
} deriving (Eq,Show,Read,Ord)
|
||||
|
||||
data Amount = Amount {
|
||||
commodity :: Commodity,
|
||||
|
@ -10,7 +10,7 @@ where
|
||||
|
||||
import Control.Monad.Error
|
||||
import Hledger.Data.Utils
|
||||
import Hledger.Data.Types (Journal)
|
||||
import Hledger.Data.Types (Journal, Commodity)
|
||||
import Hledger.Data.Journal
|
||||
import System.Directory (getHomeDirectory)
|
||||
import System.FilePath(takeDirectory,combine)
|
||||
@ -43,13 +43,13 @@ parseJournalWith p f s = do
|
||||
|
||||
-- | Some state kept while parsing a journal file.
|
||||
data JournalContext = 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
|
||||
ctxYear :: !(Maybe Integer) -- ^ the default year most recently specified with Y
|
||||
, ctxCommodity :: !(Maybe Commodity) -- ^ the default commodity recently specified with D
|
||||
, ctxAccount :: ![String] -- ^ the current stack of parent accounts specified by !account
|
||||
} deriving (Read, Show)
|
||||
|
||||
emptyCtx :: JournalContext
|
||||
emptyCtx = Ctx { ctxYear = Nothing, ctxCommod = Nothing, ctxAccount = [] }
|
||||
emptyCtx = Ctx { ctxYear = Nothing, ctxCommodity = Nothing, ctxAccount = [] }
|
||||
|
||||
setYear :: Integer -> GenParser tok JournalContext ()
|
||||
setYear y = updateState (\ctx -> ctx{ctxYear=Just y})
|
||||
@ -57,6 +57,12 @@ setYear y = updateState (\ctx -> ctx{ctxYear=Just y})
|
||||
getYear :: GenParser tok JournalContext (Maybe Integer)
|
||||
getYear = liftM ctxYear getState
|
||||
|
||||
setCommodity :: Commodity -> GenParser tok JournalContext ()
|
||||
setCommodity c = updateState (\ctx -> ctx{ctxCommodity=Just c})
|
||||
|
||||
getCommodity :: GenParser tok JournalContext (Maybe Commodity)
|
||||
getCommodity = liftM ctxCommodity getState
|
||||
|
||||
pushParentAccount :: String -> GenParser tok JournalContext ()
|
||||
pushParentAccount parent = updateState addParentAccount
|
||||
where addParentAccount ctx0 = ctx0 { ctxAccount = normalize parent : ctxAccount ctx0 }
|
||||
|
@ -169,6 +169,7 @@ journalFile = do journalupdates <- many journalItem
|
||||
, liftM (return . addPeriodicTransaction) ledgerPeriodicTransaction
|
||||
, liftM (return . addHistoricalPrice) ledgerHistoricalPrice
|
||||
, ledgerDefaultYear
|
||||
, ledgerDefaultCommodity
|
||||
, ledgerIgnoredPriceCommodity
|
||||
, ledgerTagDirective
|
||||
, ledgerEndTagDirective
|
||||
@ -178,20 +179,20 @@ journalFile = do journalupdates <- many journalItem
|
||||
journalAddFilePath :: FilePath -> Journal -> Journal
|
||||
journalAddFilePath f j@Journal{allfilepaths=fs} = j{allfilepaths=fs++[f]}
|
||||
|
||||
emptyLine :: GenParser Char st ()
|
||||
emptyLine :: GenParser Char JournalContext ()
|
||||
emptyLine = do many spacenonewline
|
||||
optional $ (char ';' <?> "comment") >> many (noneOf "\n")
|
||||
newline
|
||||
return ()
|
||||
|
||||
ledgercomment :: GenParser Char st String
|
||||
ledgercomment :: GenParser Char JournalContext String
|
||||
ledgercomment = do
|
||||
many1 $ char ';'
|
||||
many spacenonewline
|
||||
many (noneOf "\n")
|
||||
<?> "comment"
|
||||
|
||||
ledgercommentline :: GenParser Char st String
|
||||
ledgercommentline :: GenParser Char JournalContext String
|
||||
ledgercommentline = do
|
||||
many spacenonewline
|
||||
s <- ledgercomment
|
||||
@ -272,14 +273,6 @@ ledgerIgnoredPriceCommodity = do
|
||||
restofline
|
||||
return $ return id
|
||||
|
||||
ledgerDefaultCommodity :: GenParser Char JournalContext JournalUpdate
|
||||
ledgerDefaultCommodity = do
|
||||
char 'D' <?> "default commodity"
|
||||
many1 spacenonewline
|
||||
someamount
|
||||
restofline
|
||||
return $ return id
|
||||
|
||||
ledgerCommodityConversion :: GenParser Char JournalContext JournalUpdate
|
||||
ledgerCommodityConversion = do
|
||||
char 'C' <?> "commodity conversion"
|
||||
@ -317,6 +310,17 @@ ledgerDefaultYear = do
|
||||
setYear y'
|
||||
return $ return id
|
||||
|
||||
ledgerDefaultCommodity :: GenParser Char JournalContext JournalUpdate
|
||||
ledgerDefaultCommodity = do
|
||||
char 'D' <?> "default commodity"
|
||||
many1 spacenonewline
|
||||
a <- someamount
|
||||
-- someamount always returns a MixedAmount containing one Amount, but let's be safe
|
||||
let as = amounts a
|
||||
when (not $ null as) $ setCommodity $ commodity $ head as
|
||||
restofline
|
||||
return $ return id
|
||||
|
||||
-- | Try to parse a ledger entry. If we successfully parse an entry,
|
||||
-- check it can be balanced, and fail if not.
|
||||
ledgerTransaction :: GenParser Char JournalContext Transaction
|
||||
@ -384,10 +388,10 @@ ledgereffectivedate actualdate = do
|
||||
edate <- withDefaultYear actualdate ledgerdate
|
||||
return edate
|
||||
|
||||
ledgerstatus :: GenParser Char st Bool
|
||||
ledgerstatus :: GenParser Char JournalContext Bool
|
||||
ledgerstatus = try (do { many1 spacenonewline; char '*' <?> "status"; return True } ) <|> return False
|
||||
|
||||
ledgercode :: GenParser Char st String
|
||||
ledgercode :: GenParser Char JournalContext String
|
||||
ledgercode = try (do { many1 spacenonewline; char '(' <?> "code"; code <- anyChar `manyTill` char ')'; return code } ) <|> return ""
|
||||
|
||||
ledgerpostings :: GenParser Char JournalContext [Posting]
|
||||
@ -404,7 +408,7 @@ ledgerpostings = do
|
||||
return $ map (fromparse . parseWithCtx ctx (setPosition pos >> ledgerposting)) ls'
|
||||
<?> "postings"
|
||||
|
||||
linebeginningwithspaces :: GenParser Char st String
|
||||
linebeginningwithspaces :: GenParser Char JournalContext String
|
||||
linebeginningwithspaces = do
|
||||
sp <- many1 spacenonewline
|
||||
c <- nonspace
|
||||
@ -448,17 +452,17 @@ ledgeraccountname = do
|
||||
|
||||
-- | Parse an amount, with an optional left or right currency symbol and
|
||||
-- optional price.
|
||||
postingamount :: GenParser Char st MixedAmount
|
||||
postingamount :: GenParser Char JournalContext MixedAmount
|
||||
postingamount =
|
||||
try (do
|
||||
many1 spacenonewline
|
||||
someamount <|> return missingamt
|
||||
) <|> return missingamt
|
||||
|
||||
someamount :: GenParser Char st MixedAmount
|
||||
someamount :: GenParser Char JournalContext MixedAmount
|
||||
someamount = try leftsymbolamount <|> try rightsymbolamount <|> nosymbolamount
|
||||
|
||||
leftsymbolamount :: GenParser Char st MixedAmount
|
||||
leftsymbolamount :: GenParser Char JournalContext MixedAmount
|
||||
leftsymbolamount = do
|
||||
sign <- optionMaybe $ string "-"
|
||||
let applysign = if isJust sign then negate else id
|
||||
@ -470,7 +474,7 @@ leftsymbolamount = do
|
||||
return $ applysign $ Mixed [Amount c q pri]
|
||||
<?> "left-symbol amount"
|
||||
|
||||
rightsymbolamount :: GenParser Char st MixedAmount
|
||||
rightsymbolamount :: GenParser Char JournalContext MixedAmount
|
||||
rightsymbolamount = do
|
||||
(q,p,comma) <- amountquantity
|
||||
sp <- many spacenonewline
|
||||
@ -480,28 +484,29 @@ rightsymbolamount = do
|
||||
return $ Mixed [Amount c q pri]
|
||||
<?> "right-symbol amount"
|
||||
|
||||
nosymbolamount :: GenParser Char st MixedAmount
|
||||
nosymbolamount :: GenParser Char JournalContext MixedAmount
|
||||
nosymbolamount = do
|
||||
(q,p,comma) <- amountquantity
|
||||
pri <- priceamount
|
||||
let c = Commodity {symbol="",side=L,spaced=False,comma=comma,precision=p}
|
||||
defc <- getCommodity
|
||||
let c = fromMaybe Commodity{symbol="",side=L,spaced=False,comma=comma,precision=p} defc
|
||||
return $ Mixed [Amount c q pri]
|
||||
<?> "no-symbol amount"
|
||||
|
||||
commoditysymbol :: GenParser Char st String
|
||||
commoditysymbol :: GenParser Char JournalContext String
|
||||
commoditysymbol = (quotedcommoditysymbol <|> simplecommoditysymbol) <?> "commodity symbol"
|
||||
|
||||
quotedcommoditysymbol :: GenParser Char st String
|
||||
quotedcommoditysymbol :: GenParser Char JournalContext String
|
||||
quotedcommoditysymbol = do
|
||||
char '"'
|
||||
s <- many1 $ noneOf ";\n\""
|
||||
char '"'
|
||||
return s
|
||||
|
||||
simplecommoditysymbol :: GenParser Char st String
|
||||
simplecommoditysymbol :: GenParser Char JournalContext String
|
||||
simplecommoditysymbol = many1 (noneOf nonsimplecommoditychars)
|
||||
|
||||
priceamount :: GenParser Char st (Maybe MixedAmount)
|
||||
priceamount :: GenParser Char JournalContext (Maybe MixedAmount)
|
||||
priceamount =
|
||||
try (do
|
||||
many spacenonewline
|
||||
@ -516,7 +521,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 :: GenParser Char st (Double, Int, Bool)
|
||||
amountquantity :: GenParser Char JournalContext (Double, Int, Bool)
|
||||
amountquantity = do
|
||||
sign <- optionMaybe $ string "-"
|
||||
(intwithcommas,frac) <- numberparts
|
||||
@ -534,10 +539,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 :: GenParser Char st (String,String)
|
||||
numberparts :: GenParser Char JournalContext (String,String)
|
||||
numberparts = numberpartsstartingwithdigit <|> numberpartsstartingwithpoint
|
||||
|
||||
numberpartsstartingwithdigit :: GenParser Char st (String,String)
|
||||
numberpartsstartingwithdigit :: GenParser Char JournalContext (String,String)
|
||||
numberpartsstartingwithdigit = do
|
||||
let digitorcomma = digit <|> char ','
|
||||
first <- digit
|
||||
@ -545,7 +550,7 @@ numberpartsstartingwithdigit = do
|
||||
frac <- try (do {char '.'; many digit}) <|> return ""
|
||||
return (first:rest,frac)
|
||||
|
||||
numberpartsstartingwithpoint :: GenParser Char st (String,String)
|
||||
numberpartsstartingwithpoint :: GenParser Char JournalContext (String,String)
|
||||
numberpartsstartingwithpoint = do
|
||||
char '.'
|
||||
frac <- many1 digit
|
||||
@ -618,7 +623,7 @@ tests_Journal = TestList [
|
||||
let -- | compare a parse result with a MixedAmount, showing the debug representation for clarity
|
||||
assertMixedAmountParse parseresult mixedamount =
|
||||
(either (const "parse error") showMixedAmountDebug parseresult) ~?= (showMixedAmountDebug mixedamount)
|
||||
assertMixedAmountParse (parsewith someamount "1 @ $2")
|
||||
assertMixedAmountParse (parseWithCtx emptyCtx someamount "1 @ $2")
|
||||
(Mixed [Amount unknown 1 (Just $ Mixed [Amount dollar{precision=0} 2 Nothing])])
|
||||
|
||||
,"postingamount" ~: do
|
||||
|
@ -9,6 +9,7 @@ module Hledger.Cli.Commands.Add
|
||||
where
|
||||
import Hledger.Data
|
||||
import Hledger.Read.Journal (someamount)
|
||||
import Hledger.Read.Common (emptyCtx)
|
||||
import Hledger.Cli.Options
|
||||
import Hledger.Cli.Commands.Register (registerReport, registerReportAsText)
|
||||
#if __GLASGOW_HASKELL__ <= 610
|
||||
@ -92,7 +93,7 @@ getPostings accept historicalps enteredps = do
|
||||
then return enteredps
|
||||
else do
|
||||
amountstr <- askFor (printf "amount %d" n) defaultamount validateamount
|
||||
let amount = fromparse $ parse (someamount <|> return missingamt) "" amountstr
|
||||
let amount = fromparse $ runParser (someamount <|> return missingamt) emptyCtx "" amountstr
|
||||
let p = nullposting{paccount=stripbrackets account,
|
||||
pamount=amount,
|
||||
ptype=postingtype account}
|
||||
@ -113,7 +114,7 @@ getPostings accept historicalps enteredps = do
|
||||
postingtype _ = RegularPosting
|
||||
stripbrackets = dropWhile (`elem` "([") . reverse . dropWhile (`elem` "])") . reverse
|
||||
validateamount = Just $ \s -> (null s && not (null enteredrealps))
|
||||
|| isRight (parse (someamount>>many spacenonewline>>eof) "" s)
|
||||
|| isRight (runParser (someamount>>many spacenonewline>>eof) emptyCtx "" s)
|
||||
|
||||
-- | Prompt for and read a string value, optionally with a default value
|
||||
-- and a validator. A validator causes the prompt to repeat until the
|
||||
|
38
tests/default-commodity.test
Normal file
38
tests/default-commodity.test
Normal file
@ -0,0 +1,38 @@
|
||||
# a default commodity defined with the D directive will be used for any
|
||||
# commodity-less amounts in subsequent transactions.
|
||||
#
|
||||
bin/hledger -f- print
|
||||
<<<
|
||||
|
||||
; no default commodity
|
||||
2010/1/1 x
|
||||
a 1000
|
||||
b
|
||||
|
||||
; pound, two decimal places, no thousands separator
|
||||
D £1000.00
|
||||
|
||||
2010/1/1 y
|
||||
a 1000
|
||||
b
|
||||
|
||||
; dollar, no decimal places, comma thousands separator
|
||||
D $1,000
|
||||
|
||||
2010/1/1 z
|
||||
a 1000
|
||||
b
|
||||
|
||||
>>>
|
||||
2010/01/01 x
|
||||
a 1000
|
||||
b -1000
|
||||
|
||||
2010/01/01 y
|
||||
a £1000.00
|
||||
b £-1000.00
|
||||
|
||||
2010/01/01 z
|
||||
a $1,000
|
||||
b $-1,000
|
||||
|
Loading…
Reference in New Issue
Block a user