diff --git a/MANUAL.markdown b/MANUAL.markdown index 1669545fb..005c76764 100644 --- a/MANUAL.markdown +++ b/MANUAL.markdown @@ -390,12 +390,26 @@ The following commands can alter your journal file. ##### add 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: $ hledger add - $ hledger add accounts:personal:bob + $ hledger -f home.journal add equity:bob ##### web diff --git a/hledger/Hledger/Cli/Commands/Add.hs b/hledger/Hledger/Cli/Commands/Add.hs index 880f66276..83d037df0 100644 --- a/hledger/Hledger/Cli/Commands/Add.hs +++ b/hledger/Hledger/Cli/Commands/Add.hs @@ -27,6 +27,8 @@ import System.Console.Haskeline ( import Control.Monad.Trans (liftIO) import System.Console.Haskeline.Completion import qualified Data.Set as Set +import Safe (headMay) + -- | Read transactions from the terminal, prompting for each field, -- 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 where (ant,_,_,_) = groupPostings $ journalPostings j getpostingsandvalidate = do - ps <- getPostings accept bestmatchpostings [] + ps <- getPostings (jContext j) accept bestmatchpostings [] let t = nulltransaction{tdate=date ,tstatus=False ,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) getpostingsandvalidate --- | Read postings from the command line until . is entered, using the --- provided historical postings, if any, to guess defaults. -getPostings :: (AccountName -> Bool) -> Maybe [Posting] -> [Posting] -> InputT IO [Posting] -getPostings accept historicalps enteredps = do - account <- askFor (printf "account %d" n) defaultaccount (Just accept) - 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 +-- | Read postings from the command line until . is entered, using any +-- provided historical postings and the journal context to guess defaults. +getPostings :: JournalContext -> (AccountName -> Bool) -> Maybe [Posting] -> [Posting] -> InputT IO [Posting] +getPostings ctx accept historicalps enteredps = do + let bestmatch | isNothing historicalps = Nothing | n <= length ps = Just $ ps !! (n-1) | otherwise = Nothing where Just ps = historicalps 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 - defaultamount = maybe balancingamount (Just . show . pamount) bestmatch - where balancingamount = Just $ show $ negate $ sumMixedAmountsPreservingHighestPrecision $ map pamount enteredrealps postingtype ('[':_) = BalancedVirtualPosting postingtype ('(':_) = VirtualPosting 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) ctx "" 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 diff --git a/hledger/Hledger/Cli/Commands/Convert.hs b/hledger/Hledger/Cli/Commands/Convert.hs index f8b45d63a..5c892f256 100644 --- a/hledger/Hledger/Cli/Commands/Convert.hs +++ b/hledger/Hledger/Cli/Commands/Convert.hs @@ -9,6 +9,7 @@ import Hledger.Cli.Version (versionstr) import Hledger.Data.Types (Journal,AccountName,Transaction(..),Posting(..),PostingType(..)) import Hledger.Data.Utils (strip, spacenonewline, restofline, parseWithCtx, assertParse, assertParseEqual, error') import Hledger.Read.Journal (someamount,ledgeraccountname) +import Hledger.Data.Journal (nullctx) import Hledger.Data.Amount (nullmixedamt) import Safe (atDef, maximumDef) import System.IO (stderr) @@ -281,7 +282,7 @@ transactionFromCsvRecord rules fields = strnegate s = '-':s currency = maybe (fromMaybe "" $ baseCurrency rules) (atDef "" fields) (currencyField rules) amountstr'' = currency ++ amountstr' - amountparse = parse someamount "" amountstr'' + amountparse = runParser someamount nullctx "" amountstr'' amount = either (const nullmixedamt) id amountparse unknownacct | (readDef 0 amountstr' :: Double) < 0 = "income:unknown" | otherwise = "expenses:unknown" diff --git a/tests/add-default-commodity.test b/tests/add-default-commodity.test new file mode 100644 index 000000000..0735c77f1 --- /dev/null +++ b/tests/add-default-commodity.test @@ -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 +