mirror of
https://github.com/simonmichael/hledger.git
synced 2024-09-21 19:27:23 +03:00
029b59093b
CSV rules files can now be read directly, eg you have the option of writing `hledger -f foo.csv.rules CMD`. By default this will read data from foo.csv in the same directory. But you can also specify a different data file with a new `source FILE` rule. This has some convenience features: - If the data file does not exist, it is treated as empty, not an error. - If FILE is a relative path, it is relative to the rules file's directory. If it is just a file name with no path, it is relative to ~/Downloads/. - If FILE is a glob pattern, the most recently modified matched file is used. This helps remove some of the busywork of managing CSV downloads. Most of your financial institutions's default CSV filenames are different and can be recognised by a glob pattern. So you can put a rule like `source Checking1*.csv` in foo-checking.csv.rules, periodically download CSV from Foo's website accepting your browser's defaults, and then run `hledger import checking.csv.rules` to import any new transactions. The next time, if you have done no cleanup, your browser will probably save it as something like Checking1-2.csv, and hledger will still see that because of the * wild card. You can choose whether to delete CSVs after import, or keep them for a while as temporary backups, or archive them somewhere.
75 lines
2.3 KiB
Haskell
75 lines
2.3 KiB
Haskell
--- * -*- outline-regexp:"--- \\*"; -*-
|
|
--- ** doc
|
|
-- In Emacs, use TAB on lines beginning with "-- *" to collapse/expand sections.
|
|
{-|
|
|
|
|
A reader for CSV (character-separated) data.
|
|
This also reads a rules file to help interpret the CSV data.
|
|
|
|
-}
|
|
|
|
--- ** language
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
--- ** exports
|
|
module Hledger.Read.CsvReader (
|
|
-- * Reader
|
|
reader,
|
|
-- * Tests
|
|
tests_CsvReader,
|
|
)
|
|
where
|
|
|
|
--- ** imports
|
|
import Prelude hiding (Applicative(..))
|
|
import Control.Monad.Except (ExceptT(..), liftEither)
|
|
import Control.Monad.IO.Class (MonadIO)
|
|
import Data.Text (Text)
|
|
|
|
import Hledger.Data
|
|
import Hledger.Utils
|
|
import Hledger.Read.Common (aliasesFromOpts, Reader(..), InputOpts(..), journalFinalise)
|
|
import Hledger.Read.RulesReader (readJournalFromCsv)
|
|
|
|
--- ** doctest setup
|
|
-- $setup
|
|
-- >>> :set -XOverloadedStrings
|
|
|
|
--- ** reader
|
|
|
|
reader :: MonadIO m => Reader m
|
|
reader = Reader
|
|
{rFormat = "csv"
|
|
,rExtensions = ["csv","tsv","ssv"]
|
|
,rReadFn = parse
|
|
,rParser = error' "sorry, CSV files can't be included yet" -- PARTIAL:
|
|
}
|
|
|
|
-- | Parse and post-process a "Journal" from CSV data, or give an error.
|
|
-- This currently ignores the provided data, and reads it from the file path instead.
|
|
-- This file path is normally the CSV(/SSV/TSV) data file, and a corresponding rules file is inferred.
|
|
-- But it can also be the rules file, in which case the corresponding data file is inferred.
|
|
-- This does not check balance assertions.
|
|
parse :: InputOpts -> FilePath -> Text -> ExceptT String IO Journal
|
|
parse iopts f t = do
|
|
let mrulesfile = mrules_file_ iopts
|
|
readJournalFromCsv (Right <$> mrulesfile) f t
|
|
-- apply any command line account aliases. Can fail with a bad replacement pattern.
|
|
>>= liftEither . journalApplyAliases (aliasesFromOpts iopts)
|
|
-- journalFinalise assumes the journal's items are
|
|
-- reversed, as produced by JournalReader's parser.
|
|
-- But here they are already properly ordered. So we'd
|
|
-- better preemptively reverse them once more. XXX inefficient
|
|
. journalReverse
|
|
>>= journalFinalise iopts{balancingopts_=(balancingopts_ iopts){ignore_assertions_=True}} f t
|
|
|
|
--- ** tests
|
|
|
|
tests_CsvReader = testGroup "CsvReader" [
|
|
]
|
|
|