From 8412225ba51efb77c673fa325dc3718f3cc5bf87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Laugst=C3=B8l?= Date: Sun, 29 May 2011 21:50:59 +0000 Subject: [PATCH] add-in-and-out-field-rules-for-convert * Support in-field and out-field for CSV files that use two columns for each kind of movement. --- MANUAL.md | 7 +++++ hledger/Hledger/Cli/Convert.hs | 54 ++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/MANUAL.md b/MANUAL.md index 3f8d3a564..2ab25c256 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -563,6 +563,13 @@ This says: - if description contains TO SAVINGS or FROM SAVINGS, the transaction is a savings transfer +If your bank or external system uses two different columns for in and +out movements, use the `in-field` and `out-field` rules instead of +`amount-field`. + +Note that the numbers are assumed to be positive, implying that an "out" +movement gets recorded as a transaction with a negative amount. + Notes: - Lines beginning with ; or \# are ignored (but avoid using inside an diff --git a/hledger/Hledger/Cli/Convert.hs b/hledger/Hledger/Cli/Convert.hs index d457f6798..2f6c82800 100644 --- a/hledger/Hledger/Cli/Convert.hs +++ b/hledger/Hledger/Cli/Convert.hs @@ -8,7 +8,6 @@ import Prelude hiding (getContents) import Control.Monad (when, guard, liftM) import Data.Maybe import Data.Time.Format (parseTime) -import Hledger.Data.Dates (firstJust, showDate, parsedate) import Safe (atDef, maximumDef) import Safe (readDef, readMay) import System.Directory (doesFileExist) @@ -23,8 +22,9 @@ import Text.Printf (hPrintf) import Hledger.Cli.Options (Opt(Debug), progname_cli, rulesFileFromOpts) import Hledger.Cli.Version (progversionstr) -import Hledger.Data (Journal,AccountName,Transaction(..),Posting(..),PostingType(..)) import Hledger.Data.Amount (nullmixedamt, costOfMixedAmount) +import Hledger.Data.Dates (firstJust, showDate, parsedate) +import Hledger.Data (Journal,AccountName,Transaction(..),Posting(..),PostingType(..)) import Hledger.Data.Journal (nullctx) import Hledger.Read.JournalReader (someamount,ledgeraccountname) import Hledger.Utils (strip, spacenonewline, restofline, parseWithCtx, assertParse, assertParseEqual, error', regexMatchesCI, regexReplaceCI) @@ -41,6 +41,8 @@ data CsvRules = CsvRules { codeField :: Maybe FieldPosition, descriptionField :: Maybe FieldPosition, amountField :: Maybe FieldPosition, + inField :: Maybe FieldPosition, + outField :: Maybe FieldPosition, currencyField :: Maybe FieldPosition, baseCurrency :: Maybe String, accountField :: Maybe FieldPosition, @@ -56,6 +58,8 @@ nullrules = CsvRules { codeField=Nothing, descriptionField=Nothing, amountField=Nothing, + inField=Nothing, + outField=Nothing, currencyField=Nothing, baseCurrency=Nothing, accountField=Nothing, @@ -97,7 +101,9 @@ convert opts args _ = do else hPrintf stderr "using conversion rules file %s\n" rulesfile rules <- liftM (either (error'.show) id) $ parseCsvRulesFile rulesfile + let invalid = validateRules rules when debug $ hPrintf stderr "rules: %s\n" (show rules) + when (isJust invalid) $ error (fromJust invalid) let requiredfields = max 2 (maxFieldIndex rules + 1) badrecords = take 1 $ filter ((< requiredfields).length) records if null badrecords @@ -125,6 +131,8 @@ maxFieldIndex r = maximumDef (-1) $ catMaybes [ ,codeField r ,descriptionField r ,amountField r + ,inField r + ,outField r ,currencyField r ,accountField r ,effectiveDateField r @@ -162,6 +170,18 @@ initialRulesFileContent = "(TO|FROM) SAVINGS\n" ++ "assets:bank:savings\n" +validateRules :: CsvRules -> Maybe String +validateRules rules = let + hasAccount = isJust $ accountField rules + hasIn = isJust $ inField rules + hasOut = isJust $ outField rules + in case (hasAccount, hasIn, hasOut) of + (True, True, _) -> Just "Don't specify in-field when specifying amount-field" + (True, _, True) -> Just "Don't specify out-field when specifying amount-field" + (_, False, True) -> Just "You have to specify in-field when specifying out-field" + (_, True, False) -> Just "You have to specify out-field when specifying in-field" + _ -> Nothing + -- rules file parser parseCsvRulesFile :: FilePath -> IO (Either ParseError CsvRules) @@ -194,6 +214,8 @@ definitions = do ,codefield ,descriptionfield ,amountfield + ,infield + ,outfield ,currencyfield ,accountfield ,effectivedatefield @@ -252,6 +274,20 @@ amountfield = do r <- getState setState r{amountField=readMay v} +infield = do + string "in-field" + many1 spacenonewline + v <- restofline + r <- getState + setState r{inField=readMay v} + +outfield = do + string "out-field" + many1 spacenonewline + v <- restofline + r <- getState + setState r{outField=readMay v} + currencyfield = do string "currency-field" many1 spacenonewline @@ -329,7 +365,7 @@ transactionFromCsvRecord rules fields = comment = "" precomment = "" baseacc = maybe (baseAccount rules) (atDef "" fields) (accountField rules) - amountstr = maybe "" (atDef "" fields) (amountField rules) + amountstr = getAmount rules fields amountstr' = strnegate amountstr where strnegate ('-':s) = s strnegate s = '-':s currency = maybe (fromMaybe "" $ baseCurrency rules) (atDef "" fields) (currencyField rules) @@ -407,6 +443,18 @@ identify rules defacct desc | null matchingrules = (defacct,desc) caseinsensitive = ("(?i)"++) +getAmount :: CsvRules -> CsvRecord -> String +getAmount rules fields = case (accountField rules) of + Just f -> maybe "" (atDef "" fields) $ Just f + Nothing -> + case (c, d) of + (x, "") -> x + ("", x) -> "-"++x + _ -> "" + where + c = maybe "" (atDef "" fields) (inField rules) + d = maybe "" (atDef "" fields) (outField rules) + tests_Hledger_Cli_Convert = TestList [ "convert rules parsing: empty file" ~: do