mirror of
https://github.com/simonmichael/hledger.git
synced 2024-12-26 20:02:27 +03:00
explicit-rules-file-argument-and-converting-stdin
* Adding support for telling which rules file to use with "--rules FILE". * Adding support for using stdin when converting files.
This commit is contained in:
parent
5084280879
commit
170154edfb
16
MANUAL.md
16
MANUAL.md
@ -521,7 +521,13 @@ journal.
|
||||
convert requires a \*.rules file containing data definitions and rules for
|
||||
assigning destination accounts to transactions; it will be auto-created if
|
||||
missing. Typically you will have one csv file and one rules file per bank
|
||||
account. Here's an example rules file for converting csv data from a Wells
|
||||
account.
|
||||
|
||||
If you have many CSV files for each account, have many accounts in the
|
||||
same bank or for any other reason want to re-use the rules file you can
|
||||
state it explicitly with the `--rules` argument.
|
||||
|
||||
Here's an example rules file for converting csv data from a Wells
|
||||
Fargo checking account:
|
||||
|
||||
base-account assets:bank:checking
|
||||
@ -600,6 +606,14 @@ Notes:
|
||||
track the expenses in the currencies there were made, while
|
||||
keeping your base account in single currency
|
||||
|
||||
The convert command also supports converting standard input if you're
|
||||
streaming a CSV file from the web or another tool. Use `-` as the input
|
||||
file and hledger will read from stdin:
|
||||
|
||||
$ cat foo.csv | fixup | hledger convert --rules foo.rules -
|
||||
|
||||
Note that a rules file is required when streaming.
|
||||
|
||||
### histogram
|
||||
|
||||
The histogram command displays a quick bar chart showing transaction
|
||||
|
@ -29,7 +29,6 @@ library
|
||||
-- should set patchlevel here as in Makefile
|
||||
cpp-options: -DPATCHLEVEL=0
|
||||
exposed-modules:
|
||||
Hledger
|
||||
Hledger.Data
|
||||
Hledger.Data.Account
|
||||
Hledger.Data.AccountName
|
||||
|
@ -4,6 +4,7 @@ format, and print it on stdout. See the manual for more details.
|
||||
-}
|
||||
|
||||
module Hledger.Cli.Convert where
|
||||
import Prelude hiding (getContents)
|
||||
import Control.Monad (when, guard, liftM)
|
||||
import Data.Maybe
|
||||
import Data.Time.Format (parseTime)
|
||||
@ -16,18 +17,19 @@ import System.FilePath (takeBaseName, replaceExtension)
|
||||
import System.IO (stderr)
|
||||
import System.Locale (defaultTimeLocale)
|
||||
import Test.HUnit
|
||||
import Text.CSV (parseCSVFromFile, printCSV)
|
||||
import Text.CSV (parseCSV, parseCSVFromFile, printCSV, CSV)
|
||||
import Text.ParserCombinators.Parsec
|
||||
import Text.Printf (hPrintf)
|
||||
import Text.RegexPR (matchRegexPR, gsubRegexPR)
|
||||
|
||||
import Hledger.Cli.Options (Opt(Debug), progname_cli)
|
||||
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.Journal (nullctx)
|
||||
import Hledger.Read.JournalReader (someamount,ledgeraccountname)
|
||||
import Hledger.Utils (strip, spacenonewline, restofline, parseWithCtx, assertParse, assertParseEqual, error')
|
||||
import Hledger.Utils.UTF8 (getContents)
|
||||
|
||||
{- |
|
||||
A set of data definitions and account-matching patterns sufficient to
|
||||
@ -79,12 +81,16 @@ convert :: [Opt] -> [String] -> Journal -> IO ()
|
||||
convert opts args _ = do
|
||||
when (null args) $ error' "please specify a csv data file."
|
||||
let csvfile = head args
|
||||
csvparse <- parseCSVFromFile csvfile
|
||||
let
|
||||
rulesFileSpecified = isNothing $ rulesFileFromOpts opts
|
||||
usingStdin = csvfile == "-"
|
||||
when (usingStdin && (not rulesFileSpecified)) $ error' "please specify a files file when converting stdin"
|
||||
csvparse <- parseCsv csvfile
|
||||
let records = case csvparse of
|
||||
Left e -> error' $ show e
|
||||
Right rs -> reverse $ filter (/= [""]) rs
|
||||
let debug = Debug `elem` opts
|
||||
rulesfile = rulesFileFor csvfile
|
||||
rulesfile = rulesFileFor opts csvfile
|
||||
exists <- doesFileExist rulesfile
|
||||
if (not exists) then do
|
||||
hPrintf stderr "creating conversion rules file %s, edit this file for better results\n" rulesfile
|
||||
@ -105,6 +111,12 @@ convert opts args _ = do
|
||||
]) (show $ head badrecords)
|
||||
exitFailure
|
||||
|
||||
parseCsv :: FilePath -> IO (Either ParseError CSV)
|
||||
parseCsv path =
|
||||
case path of
|
||||
"-" -> liftM (parseCSV "(stdin)") getContents
|
||||
p -> parseCSVFromFile p
|
||||
|
||||
-- | The highest (0-based) field index referenced in the field
|
||||
-- definitions, or -1 if no fields are defined.
|
||||
maxFieldIndex :: CsvRules -> Int
|
||||
@ -119,8 +131,13 @@ maxFieldIndex r = maximumDef (-1) $ catMaybes [
|
||||
,effectiveDateField r
|
||||
]
|
||||
|
||||
rulesFileFor :: FilePath -> FilePath
|
||||
rulesFileFor csvfile = replaceExtension csvfile ".rules"
|
||||
rulesFileFor :: [Opt] -> FilePath -> FilePath
|
||||
rulesFileFor opts csvfile =
|
||||
case opt of
|
||||
Just path -> path
|
||||
Nothing -> replaceExtension csvfile ".rules"
|
||||
where
|
||||
opt = rulesFileFromOpts opts
|
||||
|
||||
initialRulesFileContent :: String
|
||||
initialRulesFileContent =
|
||||
|
@ -84,6 +84,7 @@ options_cli = [
|
||||
,Option "M" ["monthly"] (NoArg MonthlyOpt) "register, stats: report by month"
|
||||
,Option "Q" ["quarterly"] (NoArg QuarterlyOpt) "register, stats: report by quarter"
|
||||
,Option "Y" ["yearly"] (NoArg YearlyOpt) "register, stats: report by year"
|
||||
,Option "r" ["rules"] (ReqArg RulesFile "FILE") "convert, rules file to use"
|
||||
,Option "v" ["verbose"] (NoArg Verbose) "show more verbose output"
|
||||
,Option "" ["debug"] (NoArg Debug) "show extra debug output; implies verbose"
|
||||
,Option "" ["binary-filename"] (NoArg BinaryFilename) "show the download filename for this hledger build"
|
||||
@ -115,6 +116,7 @@ data Opt =
|
||||
| MonthlyOpt
|
||||
| QuarterlyOpt
|
||||
| YearlyOpt
|
||||
| RulesFile {value::String}
|
||||
| Help
|
||||
| Verbose
|
||||
| Version
|
||||
@ -213,6 +215,12 @@ intervalFromOpts opts =
|
||||
periodopts = reverse $ optValuesForConstructor Period opts
|
||||
intervalopts = reverse $ filter (`elem` [DailyOpt,WeeklyOpt,MonthlyOpt,QuarterlyOpt,YearlyOpt]) opts
|
||||
|
||||
rulesFileFromOpts :: [Opt] -> Maybe FilePath
|
||||
rulesFileFromOpts opts = listtomaybe $ optValuesForConstructor RulesFile opts
|
||||
where
|
||||
listtomaybe [] = Nothing
|
||||
listtomaybe vs = Just $ head vs
|
||||
|
||||
-- | Get the value of the (last) depth option, if any.
|
||||
depthFromOpts :: [Opt] -> Maybe Int
|
||||
depthFromOpts opts = listtomaybeint $ optValuesForConstructor Depth opts
|
||||
@ -325,4 +333,4 @@ tests_Hledger_Cli_Options = TestList
|
||||
[Period "quarterly"] `gives` Quarters 1
|
||||
[WeeklyOpt, Period "yearly"] `gives` Years 1
|
||||
|
||||
]
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user