add: add default commodity to commodity-less amounts (#26), misc. defaults fixes

This commit is contained in:
Simon Michael 2010-11-13 15:11:45 +00:00
parent 18fd5fe482
commit 37378d6b97
4 changed files with 71 additions and 25 deletions

View File

@ -390,12 +390,26 @@ The following commands can alter your journal file.
##### add ##### add
The add command prompts interactively for new transactions, and adds them The add command prompts interactively for new transactions, and adds them
to the journal. It is experimental. to the journal, with assistance:
- During data entry, the usual console editing keys should work
- If there are earlier transactions approximately matching the description
you enter, the best match will provide defaults for the other fields.
- If you specify [account pattern(s)](#filter-patterns) on the command
line, only matching transactions will be considered for defaults.
- While entering account names, the tab key will auto-complete up to the
next : separator
- If a [default commodity](#default-commodity) is defined, it will be used
for any commodity-less amounts entered.
Examples: Examples:
$ hledger add $ hledger add
$ hledger add accounts:personal:bob $ hledger -f home.journal add equity:bob
##### web ##### web

View File

@ -27,6 +27,8 @@ import System.Console.Haskeline (
import Control.Monad.Trans (liftIO) import Control.Monad.Trans (liftIO)
import System.Console.Haskeline.Completion import System.Console.Haskeline.Completion
import qualified Data.Set as Set import qualified Data.Set as Set
import Safe (headMay)
-- | Read transactions from the terminal, prompting for each field, -- | Read transactions from the terminal, prompting for each field,
-- and append them to the journal file. If the journal came from stdin, this -- and append them to the journal file. If the journal came from stdin, this
@ -74,7 +76,7 @@ getTransaction j opts args defaultDate = do
else True else True
where (ant,_,_,_) = groupPostings $ journalPostings j where (ant,_,_,_) = groupPostings $ journalPostings j
getpostingsandvalidate = do getpostingsandvalidate = do
ps <- getPostings accept bestmatchpostings [] ps <- getPostings (jContext j) accept bestmatchpostings []
let t = nulltransaction{tdate=date let t = nulltransaction{tdate=date
,tstatus=False ,tstatus=False
,tdescription=description ,tdescription=description
@ -90,37 +92,51 @@ getTransaction j opts args defaultDate = do
hPutStr stderr $ concatMap (\(n,t) -> printf "[%3d%%] %s" (round $ n*100 :: Int) (show t)) $ take 3 historymatches) hPutStr stderr $ concatMap (\(n,t) -> printf "[%3d%%] %s" (round $ n*100 :: Int) (show t)) $ take 3 historymatches)
getpostingsandvalidate getpostingsandvalidate
-- | Read postings from the command line until . is entered, using the -- | Read postings from the command line until . is entered, using any
-- provided historical postings, if any, to guess defaults. -- provided historical postings and the journal context to guess defaults.
getPostings :: (AccountName -> Bool) -> Maybe [Posting] -> [Posting] -> InputT IO [Posting] getPostings :: JournalContext -> (AccountName -> Bool) -> Maybe [Posting] -> [Posting] -> InputT IO [Posting]
getPostings accept historicalps enteredps = do getPostings ctx accept historicalps enteredps = do
account <- askFor (printf "account %d" n) defaultaccount (Just accept) let bestmatch | isNothing historicalps = Nothing
if account=="."
then return enteredps
else do
amountstr <- askFor (printf "amount %d" n) defaultamount validateamount
let amount = fromparse $ parse (someamount <|> return missingamt) "" amountstr
let p = nullposting{paccount=stripbrackets account,
pamount=amount,
ptype=postingtype account}
getPostings accept historicalps $ enteredps ++ [p]
where
n = length enteredps + 1
enteredrealps = filter isReal enteredps
bestmatch | isNothing historicalps = Nothing
| n <= length ps = Just $ ps !! (n-1) | n <= length ps = Just $ ps !! (n-1)
| otherwise = Nothing | otherwise = Nothing
where Just ps = historicalps where Just ps = historicalps
defaultaccount = maybe Nothing (Just . showacctname) bestmatch defaultaccount = maybe Nothing (Just . showacctname) bestmatch
account <- askFor (printf "account %d" n) defaultaccount (Just accept)
if account=="."
then return enteredps
else do
let defaultacctused = Just account == defaultaccount
historicalps' = if defaultacctused then historicalps else Nothing
bestmatch' | isNothing historicalps' = Nothing
| n <= length ps = Just $ ps !! (n-1)
| otherwise = Nothing
where Just ps = historicalps'
defaultamountstr | isJust bestmatch' = Just $ showMixedAmountWithPrecision maxprecision $ pamount $ fromJust bestmatch'
| n > 1 = Just balancingamountstr
| otherwise = Nothing
where balancingamountstr = showMixedAmountWithPrecision maxprecision $ negate $ sumMixedAmountsPreservingHighestPrecision $ map pamount enteredrealps
amountstr <- askFor (printf "amount %d" n) defaultamountstr validateamount
let amount = setMixedAmountPrecision maxprecision $ fromparse $ runParser (someamount <|> return missingamt) ctx "" amountstr
defaultamtused = Just (showMixedAmount amount) == defaultamountstr
historicalps'' = if defaultamtused then historicalps' else Nothing
commodityadded | showMixedAmountWithPrecision maxprecision amount == amountstr = Nothing
| otherwise = maybe Nothing (Just . commodity) $ headMay $ amounts amount
p = nullposting{paccount=stripbrackets account,
pamount=amount,
ptype=postingtype account}
when (isJust commodityadded) $
liftIO $ hPutStrLn stderr $ printf "using default commodity (%s)" (symbol $ fromJust commodityadded)
getPostings ctx accept historicalps'' $ enteredps ++ [p]
where
n = length enteredps + 1
enteredrealps = filter isReal enteredps
showacctname p = showAccountName Nothing (ptype p) $ paccount p showacctname p = showAccountName Nothing (ptype p) $ paccount p
defaultamount = maybe balancingamount (Just . show . pamount) bestmatch
where balancingamount = Just $ show $ negate $ sumMixedAmountsPreservingHighestPrecision $ map pamount enteredrealps
postingtype ('[':_) = BalancedVirtualPosting postingtype ('[':_) = BalancedVirtualPosting
postingtype ('(':_) = VirtualPosting postingtype ('(':_) = VirtualPosting
postingtype _ = RegularPosting postingtype _ = RegularPosting
stripbrackets = dropWhile (`elem` "([") . reverse . dropWhile (`elem` "])") . reverse stripbrackets = dropWhile (`elem` "([") . reverse . dropWhile (`elem` "])") . reverse
validateamount = Just $ \s -> (null s && not (null enteredrealps)) validateamount = Just $ \s -> (null s && not (null enteredrealps))
|| isRight (parse (someamount>>many spacenonewline>>eof) "" s) || isRight (runParser (someamount>>many spacenonewline>>eof) ctx "" s)
-- | Prompt for and read a string value, optionally with a default value -- | Prompt for and read a string value, optionally with a default value
-- and a validator. A validator causes the prompt to repeat until the -- and a validator. A validator causes the prompt to repeat until the

View File

@ -9,6 +9,7 @@ import Hledger.Cli.Version (versionstr)
import Hledger.Data.Types (Journal,AccountName,Transaction(..),Posting(..),PostingType(..)) import Hledger.Data.Types (Journal,AccountName,Transaction(..),Posting(..),PostingType(..))
import Hledger.Data.Utils (strip, spacenonewline, restofline, parseWithCtx, assertParse, assertParseEqual, error') import Hledger.Data.Utils (strip, spacenonewline, restofline, parseWithCtx, assertParse, assertParseEqual, error')
import Hledger.Read.Journal (someamount,ledgeraccountname) import Hledger.Read.Journal (someamount,ledgeraccountname)
import Hledger.Data.Journal (nullctx)
import Hledger.Data.Amount (nullmixedamt) import Hledger.Data.Amount (nullmixedamt)
import Safe (atDef, maximumDef) import Safe (atDef, maximumDef)
import System.IO (stderr) import System.IO (stderr)
@ -281,7 +282,7 @@ transactionFromCsvRecord rules fields =
strnegate s = '-':s strnegate s = '-':s
currency = maybe (fromMaybe "" $ baseCurrency rules) (atDef "" fields) (currencyField rules) currency = maybe (fromMaybe "" $ baseCurrency rules) (atDef "" fields) (currencyField rules)
amountstr'' = currency ++ amountstr' amountstr'' = currency ++ amountstr'
amountparse = parse someamount "" amountstr'' amountparse = runParser someamount nullctx "" amountstr''
amount = either (const nullmixedamt) id amountparse amount = either (const nullmixedamt) id amountparse
unknownacct | (readDef 0 amountstr' :: Double) < 0 = "income:unknown" unknownacct | (readDef 0 amountstr' :: Double) < 0 = "income:unknown"
| otherwise = "expenses:unknown" | otherwise = "expenses:unknown"

View File

@ -0,0 +1,15 @@
# add should use the (final) default commodity if any
# disabled as add currently requires ctrl-c to terminate
#
# printf 'D £1000.00\n' >add-default-commodity-$$.j; hledger -fadd-default-commodity-$$.j add; rm -f add-default-commodity-$$.j
# <<<
# 2010/1/1
# a
# 1000
# b
# >>>
# 2010/01/01 y
# a £1000.00
# b £-1000.00