mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-08 15:14:49 +03:00
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.
This commit is contained in:
parent
d4545966b5
commit
8412225ba5
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user