rabocsv2qif 1.1.7 → 2.0.0
raw patch · 3 files changed
+55/−42 lines, 3 filesdep +bytestringdep +bytestring-conversionPVP ok
version bump matches the API change (PVP)
Dependencies added: bytestring, bytestring-conversion
API changes (from Hackage documentation)
- Rabobank: baseName :: IO String
+ Rabobank: baseName :: IO FilePath
- Rabobank: toQif :: String -> String
+ Rabobank: toQif :: ByteString -> ByteString
Files
- Main.hs +4/−2
- Rabobank.hs +41/−32
- rabocsv2qif.cabal +10/−8
Main.hs view
@@ -17,6 +17,8 @@ module Main where import Rabobank (toQif, baseName)++import qualified Data.ByteString.Lazy.Char8 as BL import System.Environment (getArgs, getProgName) import System.Exit (exitFailure) import System.IO (hPutStrLn, stderr)@@ -26,9 +28,9 @@ input <- getArgs case input of [fileName] -> do- file <- readFile fileName+ file <- BL.readFile fileName outfile <- baseName -- creates a timestamped basename, with "Rabobank_" prefix- writeFile outfile (toQif file)+ BL.writeFile outfile (toQif file) _ -> do name <- getProgName
Rabobank.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE OverloadedStrings #-}+ {- Rabobank - Convert Rabobank CSV exports to QIF files Copyright (C) 2012 Sander Venema <sander.venema@gmail.com> @@ -24,16 +26,18 @@ -- debited, account number, and the transaction date. -- -- This module exports two functions namely 'toQif', which is the main function--- that will take a String of Rabobank CSV data and returns a String with QIF+-- that will take a ByteString of Rabobank CSV data and returns a ByteString with QIF -- data, and 'baseName', which will generate a filename for the export file -- based on current date and time. module Rabobank (toQif, baseName) where import ListUtils (takeWhileIndex) +import qualified Data.ByteString.Lazy.Char8 as BL+import qualified Data.ByteString.Conversion as B import Data.Char (chr) import Data.List (intercalate)-import Data.List.Split (splitOn, splitPlaces)+import Data.List.Split (splitPlaces) import Data.Maybe (fromJust, isJust) import Data.Time.Clock (getCurrentTime) import Data.Time.Format (formatTime, defaultTimeLocale)@@ -42,17 +46,17 @@ -- |'Transaction' is the internal data structure used to represent a -- transaction. CSV data is turned into Transactions, and then, using the -- accessor methods converted to a QIF representation.-data Transaction = Transaction { date :: String -- ^ The transaction date- , amount :: Double -- ^ The amount of the transaction- , description :: String -- ^ The transaction description- , payee :: String -- ^ The payee- , accountNumber :: String -- ^ The account number+data Transaction = Transaction { date :: BL.ByteString -- ^ The transaction date+ , amount :: Double -- ^ The amount of the transaction+ , description :: BL.ByteString -- ^ The transaction description+ , payee :: BL.ByteString -- ^ The payee+ , accountNumber :: BL.ByteString -- ^ The account number } deriving (Show) -- |'TransactionType' records whether a 'Transaction' has to be debited or -- credited. We use this data structure in the 'creditDebit' function to -- determine whether a transaction amount needs to be positive or negative. The--- 'toTransactionType' function turns a String containing "D" or "C" into a+-- 'toTransactionType' function turns a ByteString containing "D" or "C" into a -- 'TransactionType'. data TransactionType = D -- ^ Debit | C -- ^ Credit@@ -60,44 +64,49 @@ -- |This function turns a string containing Rabobank CSV data to a string -- containing QIF data, ready for writing to stdout or to a file.-toQif :: String -> String-toQif file = qifHeader ++ unlines (map transactionToQif (fileToTransactions records))- where rows = lines $ filter (/= '"') file- rawRecords = map (splitOn ",") rows+toQif :: BL.ByteString -> BL.ByteString+toQif file = BL.append qifHeader $ BL.unlines (map transactionToQif (fileToTransactions records))+ where rows = BL.lines $ BL.filter (/= '"') file+ rawRecords = map (BL.split ',') rows records = map (takeWhileIndex [2,3,4,5,6,10,11,12,13,14,15]) (filter (\x -> length x == 19) rawRecords) --- |Turns a list of a list of list of a list of 'String's into a list of+-- |Turns a list of a list of list of a list of 'ByteString's into a list of -- 'Transaction's.-fileToTransactions :: [[String]] -> [Transaction]+fileToTransactions :: [[BL.ByteString]] -> [Transaction] fileToTransactions = map fromJust . filter isJust . map rowToTransaction --- |'transactionToQif' will turn a 'Transaction' into a 'String' suitable+-- |'transactionToQif' will turn a 'Transaction' into a 'ByteString' suitable -- for QIF files. We simply call the accessor methods of the 'Transaction' -- type, prefix them with the appropriate prefix letters as required by the QIF -- specification, and then intercalate then with newlines, thereby returning a--- 'String' with this data, separated by newlines, just what we need.-transactionToQif :: Transaction -> String-transactionToQif t = intercalate "\n" ['P':payee t, 'M': description t, 'N':accountNumber t, - 'D':date t, 'T':printf "%.2f" (amount t)] ++ "\n^"+-- 'ByteString' with this data, separated by newlines, just what we need.+transactionToQif :: Transaction -> BL.ByteString+transactionToQif t = BL.intercalate "\n" [+ BL.cons 'P' (payee t), BL.cons 'M' (description t), BL.cons 'N' (accountNumber t), + BL.cons 'D' (date t), BL.cons 'T' (BL.pack (printf "%.2f" (amount t)))] `BL.append` "\n^" --- |'rowToTransaction' turns a list of 'String's to a 'Transaction'. It will+-- |'rowToTransaction' turns a list of 'ByteString's to a 'Transaction'. It will -- apply all the necessary corrections and reformatting necessary to turn a row -- into a correct 'Transaction' for use with 'transactionToQif'.-rowToTransaction :: [String] -> Maybe Transaction+rowToTransaction :: [BL.ByteString] -> Maybe Transaction rowToTransaction (date:debitcredit:amount:accountNumber:payee:descriptionfields) =- Just (Transaction correctDate correctAmount correctDescription payee accountNumber)- where correctDate = intercalate "/" $ splitPlaces [4,2,2] date- correctAmount = creditDebit (read amount :: Double) (toTransactionType debitcredit)- correctDescription = intercalate ", " descriptionfields+ maybe Nothing + (\correctAmount -> Just (Transaction correctDate correctAmount correctDescription payee accountNumber))+ maybeAmount+ where correctDate = BL.pack $ intercalate "/" $ splitPlaces [4,2,2] $ BL.unpack date+ maybeAmount = maybe Nothing (\amount -> + Just (creditDebit amount (toTransactionType debitcredit)))+ (B.fromByteString' amount :: Maybe Double)+ correctDescription = BL.intercalate ", " descriptionfields rowToTransaction _ = Nothing --- |Helper function to turn a 'String' containing "D" or something else into a+-- |Helper function to turn a 'ByteString' containing "D" or something else into a -- 'TransactionType'. This is used for 'creditDebit' to determine if an amount -- should be positive (added to the account) or negative (subtracted from the -- account.-toTransactionType :: String -> TransactionType+toTransactionType :: BL.ByteString -> TransactionType toTransactionType c = if c == "D" then D else C -- |'creditDebit' will make the given amount negative if 'transactiontype' == D@@ -106,15 +115,15 @@ creditDebit amount transactiontype = if transactiontype == D then -1 * amount else amount --- |'baseName' gets the current date and time and returns an 'IO String' with the+-- |'baseName' gets the current date and time and returns an 'IO FilePath' with the -- timestamp and 'Rabobank_' as the prefix. This will be used as the filename -- (minus extension) for the exported QIF file.-baseName :: IO String+baseName :: IO FilePath baseName = do now <- getCurrentTime- return ("Rabobank_" ++ formatTime defaultTimeLocale "%Y%m%d%H%M%S" now ++ ".qif")+ return $ "Rabobank_" ++ (formatTime defaultTimeLocale "%Y%m%d%H%M%S" now) ++ ".qif" -- |'qifHeader' returns the header at the start of the QIF file.-qifHeader :: String-qifHeader = "!Type:Bank\n"+qifHeader :: BL.ByteString+qifHeader = BL.pack "!Type:Bank\n"
rabocsv2qif.cabal view
@@ -1,5 +1,5 @@ Name: rabocsv2qif-Version: 1.1.7+Version: 2.0.0 Synopsis : A library and program to create QIF files from Rabobank CSV exports. License: GPL License-file: LICENSE@@ -13,18 +13,20 @@ executable, rabocsv2qif. With the executable you can quickly create a timestamped QIF file from a Rabobank CSV file with transactions. The library exposes a method, toQif, that can be- used to create QIF data in code. It simply takes a String and- returns a String.+ used to create QIF data in code. It simply takes a ByteString and+ returns a ByteString. library- Build-Depends: base >= 3 && < 5,- time,- split >= 0.2.1.1+ Build-Depends: base >= 3 && < 5+ , bytestring+ , bytestring-conversion+ , time+ , split >= 0.2.1.1 Exposed-modules: Rabobank Other-modules: ListUtils Executable rabocsv2qif Main-is: Main.hs Build-Depends: base >= 3 && < 5- Other-modules: Rabobank,- ListUtils+ Other-modules: Rabobank+ , ListUtils