module Penny.Brenner.Import (mode) where
import Control.Monad.Exception.Synchronous as Ex
import Data.Maybe (mapMaybe)
import qualified System.Console.MultiArg as MA
import qualified Penny.Brenner.Types as Y
import qualified Penny.Brenner.Util as U
import qualified System.IO as IO
import qualified System.Exit as E
data Arg
= AHelp
| AFitFile String
| AAllowNew
deriving (Eq, Show)
toFitFile :: Arg -> Maybe String
toFitFile a = case a of
AFitFile s -> Just s
_ -> Nothing
data ImportOpts = ImportOpts
{ fitFile :: Y.FitFileLocation
, allowNew :: Y.AllowNew
, parser :: Y.FitFileLocation
-> IO (Ex.Exceptional String [Y.Posting])
}
mode
:: Maybe Y.FitAcct
-> MA.Mode (Ex.Exceptional String (IO ()))
mode mayFa = MA.Mode
{ MA.mName = "import"
, MA.mIntersperse = MA.Intersperse
, MA.mOpts =
[ MA.OptSpec ["help"] "h" (MA.NoArg AHelp)
, MA.OptSpec ["new"] "n" (MA.NoArg AAllowNew)
]
, MA.mPosArgs = AFitFile
, MA.mProcess = processor mayFa
}
processor
:: Maybe Y.FitAcct
-> [Arg]
-> Ex.Exceptional String (IO ())
processor mayFa as = do
let err s = Ex.throw $ "import: " ++ s
if any (== AHelp) as
then return $ putStrLn help
else do
(dbLoc, prsr) <- case mayFa of
Nothing -> err $ "no financial institution account provided"
++ " on command line, and no default financial institution"
++ " account is configured."
Just fa -> return (Y.dbLocation fa, snd . Y.parser $ fa)
loc <- case mapMaybe toFitFile as of
[] -> err "you must provide a postings file to read"
x:[] -> return (Y.FitFileLocation x)
_ -> err "you cannot provide more than one postings file to read"
let aNew = Y.AllowNew $ any (== AAllowNew) as
return $ doImport dbLoc (ImportOpts loc aNew prsr)
appendNew
:: [(Y.UNumber, Y.Posting)]
-> [Y.Posting]
-> ([(Y.UNumber, Y.Posting)], Int)
appendNew db new = (db ++ newWithU, length newWithU)
where
nextUNum = if null db
then 0
else (Y.unUNumber . maximum . map fst $ db) + 1
currFitIds = map (Y.fitId . snd) db
isNew p = not (any (== Y.fitId p) currFitIds)
newPstgs = filter isNew new
mkPair i p = (Y.UNumber i, p)
newWithU = zipWith mkPair [nextUNum..] newPstgs
doImport :: Y.DbLocation -> ImportOpts -> IO ()
doImport dbLoc os = do
txnsOld <- U.quitOnError $ U.loadDb (allowNew os) dbLoc
parseResult <- parser os (fitFile os)
ins <- case parseResult of
Ex.Exception e -> do
IO.hPutStrLn IO.stderr $ "penny-fit: error: " ++ e
E.exitFailure
Ex.Success g -> return g
let (new, len) = appendNew txnsOld ins
U.saveDb dbLoc new
putStrLn $ "imported " ++ show len ++ " new transactions."
help :: String
help = unlines
[ "penny-fit [global-options] import [local-options] FIT_FILE"
, "where FIT_FILE is the file downloaded from the financial"
, "institution."
, ""
, "Local Options:"
, ""
, "-n, --new - Allows creation of new database. Without this option,"
, "if the database file is not found, quits with an error."
, ""
, "-h, --help - Show this help."
, ""
]