Compare commits

..

No commits in common. "e6a39cb5ea4f45ec389010504d5caa0412ecc5d6" and "1e5f40d7309b455cfbd3950f83672ce2d140d437" have entirely different histories.

12 changed files with 205 additions and 380 deletions

View File

@ -1,8 +1,7 @@
{-# LANGUAGE ImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-}
module Main (main) where module Main (main) where
import Control.Monad.Reader
import qualified Data.Text.IO as TI import qualified Data.Text.IO as TI
import Internal.Config import Internal.Config
import Internal.Database.Ops import Internal.Database.Ops
@ -163,11 +162,11 @@ runSync c = do
case res of case res of
Left es -> throwIO $ InsertException es Left es -> throwIO $ InsertException es
Right s -> do Right s -> do
let run = mapReaderT $ flip runReaderT (s $ takeDirectory c) flip runReaderT (s $ takeDirectory c) $ do
es1 <- concat <$> mapM (run . insertBudget) (budget config) es1 <- concat <$> mapM insertBudget (budget config)
es2 <- run $ insertStatements config es2 <- insertStatements config
let es = es1 ++ es2 let es = es1 ++ es2
unless (null es) $ throwIO $ InsertException es unless (null es) $ throwIO $ InsertException es
where where
err (InsertException es) = do err (InsertException es) = do
liftIO $ mapM_ TI.putStrLn $ concatMap showError es liftIO $ mapM_ TI.putStrLn $ concatMap showError es

View File

@ -1,6 +1,6 @@
cabal-version: 1.12 cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.35.2. -- This file has been generated from package.yaml by hpack version 0.35.1.
-- --
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
@ -36,42 +36,6 @@ library
Paths_budget Paths_budget
hs-source-dirs: hs-source-dirs:
lib/ lib/
default-extensions:
OverloadedStrings
FlexibleContexts
FlexibleInstances
InstanceSigs
MultiParamTypeClasses
EmptyCase
LambdaCase
MultiWayIf
NamedFieldPuns
TupleSections
DeriveFoldable
DeriveFunctor
DeriveGeneric
DeriveLift
DeriveTraversable
DerivingStrategies
DeriveDataTypeable
EmptyDataDecls
PartialTypeSignatures
GeneralizedNewtypeDeriving
StandaloneDeriving
BangPatterns
TypeOperators
ScopedTypeVariables
TypeApplications
ConstraintKinds
RankNTypes
GADTs
DefaultSignatures
NoImplicitPrelude
FunctionalDependencies
DataKinds
TypeFamilies
BinaryLiterals
ViewPatterns
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -Wpartial-fields -Werror -O2 ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -Wpartial-fields -Werror -O2
build-depends: build-depends:
base >=4.12 && <10 base >=4.12 && <10
@ -86,7 +50,6 @@ library
, hashable , hashable
, lens >=5.0.1 , lens >=5.0.1
, monad-logger >=0.3.36 , monad-logger >=0.3.36
, mtl
, optparse-applicative , optparse-applicative
, persistent >=2.13.3.1 , persistent >=2.13.3.1
, persistent-sqlite >=2.13.1.0 , persistent-sqlite >=2.13.1.0
@ -104,42 +67,6 @@ executable pwncash
Paths_budget Paths_budget
hs-source-dirs: hs-source-dirs:
app app
default-extensions:
OverloadedStrings
FlexibleContexts
FlexibleInstances
InstanceSigs
MultiParamTypeClasses
EmptyCase
LambdaCase
MultiWayIf
NamedFieldPuns
TupleSections
DeriveFoldable
DeriveFunctor
DeriveGeneric
DeriveLift
DeriveTraversable
DerivingStrategies
DeriveDataTypeable
EmptyDataDecls
PartialTypeSignatures
GeneralizedNewtypeDeriving
StandaloneDeriving
BangPatterns
TypeOperators
ScopedTypeVariables
TypeApplications
ConstraintKinds
RankNTypes
GADTs
DefaultSignatures
NoImplicitPrelude
FunctionalDependencies
DataKinds
TypeFamilies
BinaryLiterals
ViewPatterns
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -Wpartial-fields -Werror -O2 -threaded ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -Wpartial-fields -Werror -O2 -threaded
build-depends: build-depends:
base >=4.12 && <10 base >=4.12 && <10
@ -155,7 +82,6 @@ executable pwncash
, hashable , hashable
, lens >=5.0.1 , lens >=5.0.1
, monad-logger >=0.3.36 , monad-logger >=0.3.36
, mtl
, optparse-applicative , optparse-applicative
, persistent >=2.13.3.1 , persistent >=2.13.3.1
, persistent-sqlite >=2.13.1.0 , persistent-sqlite >=2.13.1.0

View File

@ -25,14 +25,7 @@ let Weekday = < Mon | Tue | Wed | Thu | Fri | Sat | Sun >
let RepeatPat = let RepeatPat =
{ rpStart : Natural, rpBy : Natural, rpRepeats : Optional Natural } { rpStart : Natural, rpBy : Natural, rpRepeats : Optional Natural }
let MDYPat = let MDYPat = < Single : Natural | Multi : List Natural | Repeat : RepeatPat >
< Single : Natural
| Multi : List Natural
| Repeat : RepeatPat
| After : Natural
| Before : Natural
| Between : { _between1 : Natural, _between2 : Natural }
>
let ModPat = let ModPat =
{ Type = { Type =
@ -187,9 +180,7 @@ let IncomeBucket = < PreTax | IntraTax | PostTax >
let Amount = { amtValue : Decimal, amtDesc : Text } let Amount = { amtValue : Decimal, amtDesc : Text }
let AmountType = < FixedAmt | Percent | Target > let TimeAmount = { taWhen : DatePat, taAmt : Amount }
let TimeAmount = { taWhen : DatePat, taAmt : Amount, taAmtType : AmountType }
let Tax = { taxAcnt : AcntID, taxValue : Decimal } let Tax = { taxAcnt : AcntID, taxValue : Decimal }
@ -266,5 +257,4 @@ in { CurID
, Allocation , Allocation
, Amount , Amount
, TimeAmount , TimeAmount
, AmountType
} }

View File

@ -4,7 +4,7 @@ let List/map =
let T = let T =
./Types.dhall ./Types.dhall
sha256:10af13e592448321c1e298f55a1e924e77b7e64bd35512147e0952de1f3abcfb sha256:27c65b31fe1b0dd58ff03ec0a37648f586914c81b81e5f33d3eac5a465475f2b
let nullSplit = let nullSplit =
\(a : T.SplitAcnt) -> \(a : T.SplitAcnt) ->

View File

@ -1,3 +1,5 @@
{-# LANGUAGE NoImplicitPrelude #-}
module Internal.Config module Internal.Config
( readConfig ( readConfig
-- , readYaml -- , readYaml

View File

@ -1,6 +1,20 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Internal.Database.Model where module Internal.Database.Model where
@ -70,42 +84,13 @@ data DBState = DBState
, kmStatementInterval :: !Bounds , kmStatementInterval :: !Bounds
, kmNewCommits :: ![Int] , kmNewCommits :: ![Int]
, kmConfigDir :: !FilePath , kmConfigDir :: !FilePath
, kmBoundsCache :: !(MVar (M.Map (Bounds, DatePat) [Day]))
} }
type MappingT m = ReaderT DBState (SqlPersistT m) type MappingT m a = ReaderT DBState (SqlPersistT m) a
type KeySplit = Split AccountRId Rational CurrencyRId type KeySplit = Split AccountRId Rational CurrencyRId
type KeyTx = Tx KeySplit type KeyTx = Tx KeySplit
type TreeR = Tree ([T.Text], AccountRId) type TreeR = Tree ([T.Text], AccountRId)
type Balances = M.Map AccountRId Rational
type BalanceM m = ReaderT (MVar Balances) m
class MonadUnliftIO m => MonadFinance m where
askDBState :: (DBState -> a) -> m a
instance MonadUnliftIO m => MonadFinance (ReaderT DBState m) where
askDBState = asks
class MonadUnliftIO m => MonadBalance m where
askBalances :: m (MVar Balances)
withBalances :: (Balances -> m a) -> m a
withBalances f = do
bs <- askBalances
withMVar bs f
modifyBalances :: (Balances -> m (Balances, a)) -> m a
modifyBalances f = do
bs <- askBalances
modifyMVar bs f
lookupBalance :: AccountRId -> m Rational
lookupBalance i = withBalances $ return . fromMaybe 0 . M.lookup i
addBalance :: AccountRId -> Rational -> m ()
addBalance i v =
modifyBalances $ return . (,()) . M.alter (Just . maybe v (v +)) i

View File

@ -1,3 +1,11 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Internal.Database.Ops module Internal.Database.Ops
( migrate_ ( migrate_
, nukeTables , nukeTables
@ -40,7 +48,6 @@ migrate_ c more =
(openConnection c) (openConnection c)
( \backend -> ( \backend ->
flip runSqlConn backend $ do flip runSqlConn backend $ do
_ <- askLoggerIO
runMigration migrateAll runMigration migrateAll
more more
) )
@ -191,8 +198,7 @@ updateCurrencies cs = do
return $ currencyMap curs return $ currencyMap curs
currency2Record :: Currency -> Entity CurrencyR currency2Record :: Currency -> Entity CurrencyR
currency2Record c@Currency {curSymbol, curFullname} = currency2Record c@Currency {..} = Entity (toKey c) $ CurrencyR curSymbol curFullname
Entity (toKey c) $ CurrencyR curSymbol curFullname
currencyMap :: [Entity CurrencyR] -> CurrencyMap currencyMap :: [Entity CurrencyR] -> CurrencyMap
currencyMap = M.fromList . fmap (\e -> (currencyRSymbol $ entityVal e, entityKey e)) currencyMap = M.fromList . fmap (\e -> (currencyRSymbol $ entityVal e, entityKey e))
@ -285,7 +291,7 @@ xs !? n
n n
flattenAcntRoot :: AccountRoot -> [(AcntType, AccountTree)] flattenAcntRoot :: AccountRoot -> [(AcntType, AccountTree)]
flattenAcntRoot AccountRoot_ {arIncome, arExpenses, arLiabilities, arAssets, arEquity} = flattenAcntRoot AccountRoot_ {..} =
((IncomeT,) <$> arIncome) ((IncomeT,) <$> arIncome)
++ ((ExpenseT,) <$> arExpenses) ++ ((ExpenseT,) <$> arExpenses)
++ ((LiabilityT,) <$> arLiabilities) ++ ((LiabilityT,) <$> arLiabilities)
@ -309,6 +315,7 @@ getDBState c = do
am <- updateAccounts $ accounts c am <- updateAccounts $ accounts c
cm <- updateCurrencies $ currencies c cm <- updateCurrencies $ currencies c
hs <- updateHashes c hs <- updateHashes c
v <- newMVar M.empty
-- TODO not sure how I feel about this, probably will change this struct alot -- TODO not sure how I feel about this, probably will change this struct alot
-- in the future so whatever...for now -- in the future so whatever...for now
return $ concatEither2 bi si $ \b s f -> return $ concatEither2 bi si $ \b s f ->
@ -319,6 +326,7 @@ getDBState c = do
, kmStatementInterval = s , kmStatementInterval = s
, kmNewCommits = hs , kmNewCommits = hs
, kmConfigDir = f , kmConfigDir = f
, kmBoundsCache = v
} }
where where
bi = resolveBounds $ budgetInterval $ global c bi = resolveBounds $ budgetInterval $ global c

View File

@ -1,3 +1,8 @@
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Internal.Insert module Internal.Insert
( insertStatements ( insertStatements
, insertBudget , insertBudget
@ -12,8 +17,6 @@ import Internal.Statement
import Internal.Types hiding (sign) import Internal.Types hiding (sign)
import Internal.Utils import Internal.Utils
import RIO hiding (to) import RIO hiding (to)
import qualified RIO.List as L
import qualified RIO.Map as M
import qualified RIO.Text as T import qualified RIO.Text as T
import RIO.Time import RIO.Time
@ -40,27 +43,24 @@ expandModPat ModPat {mpStart = s, mpBy = b, mpUnit = u, mpRepeats = r} bs =
Year -> addGregorianYearsClip Year -> addGregorianYearsClip
expandCronPat :: Bounds -> CronPat -> EitherErrs [Day] expandCronPat :: Bounds -> CronPat -> EitherErrs [Day]
expandCronPat b CronPat {cronYear, cronMonth, cronDay, cronWeekly} = expandCronPat b CronPat {..} = concatEither3 yRes mRes dRes $ \ys ms ds ->
concatEither3 yRes mRes dRes $ \ys ms ds -> filter validWeekday $
filter validWeekday $ mapMaybe (uncurry3 toDay) $
mapMaybe (uncurry3 toDay) $ takeWhile (\((y, _), m, d) -> (y, m, d) <= (yb1, mb1, db1)) $
takeWhile (\((y, _), m, d) -> (y, m, d) <= (yb1, mb1, db1)) $ dropWhile (\((y, _), m, d) -> (y, m, d) < (yb0, mb0, db0)) $
dropWhile (\((y, _), m, d) -> (y, m, d) < (yb0, mb0, db0)) $ [(y, m, d) | y <- (\y -> (y, isLeapYear y)) <$> ys, m <- ms, d <- ds]
[(y, m, d) | y <- (\y -> (y, isLeapYear y)) <$> ys, m <- ms, d <- ds]
where where
yRes = case cronYear of yRes = case cronYear of
Nothing -> return [yb0 .. yb1] Nothing -> return [yb0 .. yb1]
Just pat -> do Just pat -> do
ys <- expandMDYPat (fromIntegral yb0) (fromIntegral yb1) pat ys <- expandMDYPat (fromIntegral yb1) pat
return $ dropWhile (< yb0) $ fromIntegral <$> ys return $ dropWhile (< yb0) $ fromIntegral <$> ys
mRes = expandMD 12 cronMonth mRes = expandMD 12 cronMonth
dRes = expandMD 31 cronDay dRes = expandMD 31 cronDay
(s, e) = expandBounds b (s, e) = expandBounds b
(yb0, mb0, db0) = toGregorian s (yb0, mb0, db0) = toGregorian s
(yb1, mb1, db1) = toGregorian $ addDays (-1) e (yb1, mb1, db1) = toGregorian $ addDays (-1) e
expandMD lim = expandMD lim = fmap (fromIntegral <$>) . maybe (return [1 .. lim]) (expandMDYPat lim)
fmap (fromIntegral <$>)
. maybe (return [1 .. lim]) (expandMDYPat 1 lim)
expandW (OnDay x) = [fromEnum x] expandW (OnDay x) = [fromEnum x]
expandW (OnDays xs) = fromEnum <$> xs expandW (OnDays xs) = fromEnum <$> xs
ws = maybe [] expandW cronWeekly ws = maybe [] expandW cronWeekly
@ -70,59 +70,56 @@ expandCronPat b CronPat {cronYear, cronMonth, cronDay, cronWeekly} =
| m `elem` [4, 6, 9, 11] && d > 30 = Nothing | m `elem` [4, 6, 9, 11] && d > 30 = Nothing
| otherwise = Just $ fromGregorian y m d | otherwise = Just $ fromGregorian y m d
expandMDYPat :: Natural -> Natural -> MDYPat -> EitherErr [Natural] expandMDYPat :: Natural -> MDYPat -> EitherErr [Natural]
expandMDYPat lower upper (Single x) = Right [x | lower <= x && x <= upper] expandMDYPat _ (Single x) = Right [x]
expandMDYPat lower upper (Multi xs) = Right $ dropWhile (<= lower) $ takeWhile (<= upper) xs expandMDYPat _ (Multi xs) = Right xs
expandMDYPat lower upper (After x) = Right [max lower x .. upper] expandMDYPat lim (Repeat RepeatPat {rpStart = s, rpBy = b, rpRepeats = r})
expandMDYPat lower upper (Before x) = Right [lower .. min upper x]
expandMDYPat lower upper (Between x y) = Right [max lower x .. min upper y]
expandMDYPat lower upper (Repeat RepeatPat {rpStart = s, rpBy = b, rpRepeats = r})
| b < 1 = Left $ PatternError s b r ZeroLength | b < 1 = Left $ PatternError s b r ZeroLength
| otherwise = do | otherwise = do
k <- limit r k <- limit r
return $ dropWhile (<= lower) $ takeWhile (<= k) [s + i * b | i <- [0 ..]] return $ takeWhile (<= k) [s + i * b | i <- [0 ..]]
where where
limit Nothing = Right upper limit Nothing = Right lim
limit (Just n) limit (Just n)
-- this guard not only produces the error for the user but also protects -- this guard not only produces the error for the user but also protects
-- from an underflow below it -- from an underflow below it
| n < 1 = Left $ PatternError s b r ZeroRepeats | n < 1 = Left $ PatternError s b r ZeroRepeats
| otherwise = Right $ min (s + b * (n - 1)) upper | otherwise = Right $ min (s + b * (n - 1)) lim
dayToWeekday :: Day -> Int dayToWeekday :: Day -> Int
dayToWeekday (ModifiedJulianDay d) = mod (fromIntegral d + 2) 7 dayToWeekday (ModifiedJulianDay d) = mod (fromIntegral d + 2) 7
withDates withDates
:: MonadFinance m :: MonadUnliftIO m
=> DatePat => DatePat
-> (Day -> SqlPersistT m a) -> (Day -> MappingT m a)
-> SqlPersistT m (EitherErrs [a]) -> MappingT m (EitherErrs [a])
withDates dp f = do withDates dp f = do
bounds <- lift $ askDBState kmBudgetInterval bounds <- asks kmBudgetInterval
let days = expandDatePat bounds dp let days = expandDatePat bounds dp
mapM (mapM f) days mapM (mapM f) days
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- budget -- budget
insertBudget :: MonadFinance m => Budget -> SqlPersistT m [InsertError] insertBudget :: MonadUnliftIO m => Budget -> MappingT m [InsertError]
insertBudget Budget {budgetLabel = name, income = is, transfers = es} = do insertBudget Budget {budgetLabel = name, income = is, transfers = es} = do
es1 <- mapM (insertIncome name) is es1 <- mapM (insertIncome name) is
es2 <- insertTransfers name es es2 <- mapM (insertTransfer name) es
return $ concat es1 ++ es2 return $ concat $ es1 ++ es2
-- TODO this hashes twice (not that it really matters) -- TODO this hashes twice (not that it really matters)
whenHash whenHash
:: (Hashable a, MonadFinance m) :: (Hashable a, MonadUnliftIO m)
=> ConfigType => ConfigType
-> a -> a
-> b -> b
-> (Key CommitR -> SqlPersistT m b) -> (Key CommitR -> MappingT m b)
-> SqlPersistT m b -> MappingT m b
whenHash t o def f = do whenHash t o def f = do
let h = hash o let h = hash o
hs <- lift $ askDBState kmNewCommits hs <- asks kmNewCommits
if h `elem` hs then f =<< insert (CommitR h t) else return def if h `elem` hs then f =<< lift (insert $ CommitR h t) else return def
-- TODO allow currency conversions here -- TODO allow currency conversions here
data BudgetSplit b = BudgetSplit data BudgetSplit b = BudgetSplit
@ -145,28 +142,26 @@ data BudgetTx = BudgetTx
, btDesc :: !T.Text , btDesc :: !T.Text
} }
insertIncome :: MonadFinance m => T.Text -> Income -> SqlPersistT m [InsertError] insertIncome :: MonadUnliftIO m => T.Text -> Income -> MappingT m [InsertError]
insertIncome insertIncome name i@Income {..} =
name whenHash CTIncome i [] $ \c ->
i@Income {incWhen, incCurrency, incFrom, incPretax, incPosttax, incTaxes, incToBal} = unlessLeft (balanceIncome i) $ \balance -> do
whenHash CTIncome i [] $ \c -> res <- withDates incWhen $ \day -> do
unlessLeft (balanceIncome i) $ \balance -> do let meta = BudgetMeta c day incCurrency name
res <- withDates incWhen $ \day -> do let fromAllos b = concatMap (fromAllo meta incFrom (Just b))
let meta = BudgetMeta c day incCurrency name let pre = fromAllos PreTax incPretax
let fromAllos b = concatMap (fromAllo meta incFrom (Just b)) let tax = fmap (fromTax meta incFrom) incTaxes
let pre = fromAllos PreTax incPretax let post = fromAllos PostTax incPosttax
let tax = fmap (fromTax meta incFrom) incTaxes let bal =
let post = fromAllos PostTax incPosttax BudgetTx
let bal = { btMeta = meta
BudgetTx , btFrom = BudgetSplit incFrom $ Just PostTax
{ btMeta = meta , btTo = BudgetSplit incToBal Nothing
, btFrom = BudgetSplit incFrom $ Just PostTax , btValue = balance
, btTo = BudgetSplit incToBal Nothing , btDesc = "balance after deductions"
, btValue = balance }
, btDesc = "balance after deductions" fmap concat $ mapM insertBudgetTx $ bal : (pre ++ tax ++ post)
} unlessLefts res $ return . concat
fmap concat $ mapM insertBudgetTx $ bal : (pre ++ tax ++ post)
unlessLefts res $ return . concat
fromAllo fromAllo
:: BudgetMeta :: BudgetMeta
@ -174,8 +169,7 @@ fromAllo
-> Maybe IncomeBucket -> Maybe IncomeBucket
-> Allocation -> Allocation
-> [BudgetTx] -> [BudgetTx]
fromAllo meta from ib Allocation {alloPath, alloAmts, alloBucket} = fromAllo meta from ib Allocation {..} = fmap (toBT alloPath) alloAmts
fmap (toBT alloPath) alloAmts
where where
toBT to (Amount desc v) = toBT to (Amount desc v) =
BudgetTx BudgetTx
@ -216,98 +210,33 @@ sumAllocation = sum . fmap (dec2Rat . amtValue) . alloAmts
sumTaxes :: [Tax] -> Rational sumTaxes :: [Tax] -> Rational
sumTaxes = sum . fmap (dec2Rat . taxValue) sumTaxes = sum . fmap (dec2Rat . taxValue)
insertTransfers :: MonadFinance m => T.Text -> [Transfer] -> SqlPersistT m [InsertError] insertTransfer :: MonadUnliftIO m => T.Text -> Transfer -> MappingT m [InsertError]
insertTransfers name ts = do insertTransfer name t@Transfer {..} =
res <- expandTransfers name ts fmap concat $ whenHash CTExpense t [] $ \key -> do
unlessLefts res $ \txs -> forM transAmounts $ \(TimeAmount amt pat) -> do
fmap concat <$> mapM insertBudgetTx $ balanceTransfers txs res <- withDates pat $ \day -> insertBudgetTx $ budgetTx amt day key
unlessLefts res $ return . concat
expandTransfers :: MonadFinance m => T.Text -> [Transfer] -> SqlPersistT m (EitherErrs [TransferTx])
expandTransfers name ts = do
txs <- mapM (expandTransfer name) ts
return $ L.sortOn (bmWhen . trxMeta) . concat <$> concatEithersL txs
-- TODO the entire budget needs to have this process applied to it
balanceTransfers :: [TransferTx] -> [BudgetTx]
balanceTransfers ts = snd $ L.mapAccumR go initBals $ L.sortOn (bmWhen . trxMeta) ts
where where
initBals = M.fromList $ fmap (,0) $ L.nub $ (fmap trxTo ts ++ fmap trxTo ts) meta d c =
updateBal x = M.update (Just . (+ x)) BudgetMeta
lookupBal = M.findWithDefault (error "this should not happen") { bmWhen = d
go bals TransferTx {trxMeta, trxFrom, trxTo, trxValue, trxType, trxDesc} = , bmCur = transCurrency
let bal = lookupBal trxTo bals , bmCommit = c
x = amtToMove bal trxType trxValue , bmName = name
t = }
BudgetTx budgetTx (Amount desc v) d c =
{ btMeta = trxMeta BudgetTx
, btFrom = BudgetSplit trxFrom Nothing { btMeta = meta d c
, btTo = BudgetSplit trxTo Nothing , btFrom = BudgetSplit transFrom Nothing
, btValue = x , btTo = BudgetSplit transTo Nothing
, btDesc = trxDesc , btValue = dec2Rat v
} , btDesc = desc
in (updateBal x trxFrom $ updateBal (-x) trxFrom bals, t) }
-- TODO might need to query signs to make this intuitive; as it is this will
-- probably work, but for credit accounts I might need to supply a negative
-- target value
amtToMove _ FixedAmt x = x
amtToMove bal Percent x = -(x / 100 * bal)
amtToMove bal Target x = x - bal
expandTransfer :: MonadFinance m => T.Text -> Transfer -> SqlPersistT m (EitherErrs [TransferTx]) insertBudgetTx :: MonadUnliftIO m => BudgetTx -> MappingT m [InsertError]
expandTransfer name t@Transfer {transAmounts, transTo, transCurrency, transFrom} = insertBudgetTx BudgetTx {..} = do
whenHash CTExpense t (Right []) $ \key -> do
res <- forM transAmounts $ \(TimeAmount (Amount desc v) atype pat) ->
withDates pat $ \day ->
let meta =
BudgetMeta
{ bmWhen = day
, bmCur = transCurrency
, bmCommit = key
, bmName = name
}
in return $
TransferTx
{ trxMeta = meta
, trxFrom = transFrom
, trxTo = transTo
, trxValue = dec2Rat v
, trxType = atype
, trxDesc = desc
}
return $ concat <$> concatEithersL res
data TransferTx = TransferTx
{ trxMeta :: !BudgetMeta
, trxFrom :: !AcntID
, trxTo :: !AcntID
, trxValue :: !Rational
, trxType :: AmountType
, trxDesc :: !T.Text
}
-- amountBalance
-- :: (MonadFinance m, MonadBalance m)
-- => AmountType
-- -> AcntID
-- -> Rational
-- -> SqlPersistT m (EitherErr Rational)
-- amountBalance at i v = do
-- res <- lookupAccountKey i
-- case res of
-- Left e -> return $ Left e
-- Right k -> do
-- b <- lookupBalance k
-- return $ Right $ case at of
-- FixedAmt -> v
-- -- TODO what is the sign for this?
-- Percent -> v / 100 * b
-- -- TODO what is the sign for this?
-- Target -> b - v
insertBudgetTx :: MonadFinance m => BudgetTx -> SqlPersistT m [InsertError]
insertBudgetTx BudgetTx {btFrom, btTo, btMeta, btValue, btDesc} = do
res <- splitPair (bsAcnt btFrom) (bsAcnt btTo) (bmCur btMeta) btValue res <- splitPair (bsAcnt btFrom) (bsAcnt btTo) (bmCur btMeta) btValue
unlessLefts_ res $ \(sFrom, sTo) -> do unlessLefts_ res $ \(sFrom, sTo) -> lift $ do
k <- insert $ TransactionR (bmCommit btMeta) (bmWhen btMeta) btDesc k <- insert $ TransactionR (bmCommit btMeta) (bmWhen btMeta) btDesc
insertBudgetLabel name k IncomeBucketR sFrom btFrom insertBudgetLabel name k IncomeBucketR sFrom btFrom
insertBudgetLabel name k ExpenseBucketR sTo btTo insertBudgetLabel name k ExpenseBucketR sTo btTo
@ -328,12 +257,12 @@ insertBudgetLabel name k bucketType split bs = do
forM_ (bsBucket bs) $ insert_ . bucketType bk forM_ (bsBucket bs) $ insert_ . bucketType bk
splitPair splitPair
:: MonadFinance m :: MonadUnliftIO m
=> AcntID => AcntID
-> AcntID -> AcntID
-> CurID -> CurID
-> Rational -> Rational
-> SqlPersistT m (EitherErrs (KeySplit, KeySplit)) -> MappingT m (EitherErrs (KeySplit, KeySplit))
splitPair from to cur val = do splitPair from to cur val = do
s1 <- split from (-val) s1 <- split from (-val)
s2 <- split to val s2 <- split to val
@ -351,14 +280,14 @@ splitPair from to cur val = do
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- statements -- statements
insertStatements :: MonadFinance m => Config -> SqlPersistT m [InsertError] insertStatements :: MonadUnliftIO m => Config -> MappingT m [InsertError]
insertStatements conf = concat <$> mapM insertStatement (statements conf) insertStatements conf = concat <$> mapM insertStatement (statements conf)
insertStatement :: MonadFinance m => Statement -> SqlPersistT m [InsertError] insertStatement :: MonadUnliftIO m => Statement -> MappingT m [InsertError]
insertStatement (StmtManual m) = insertManual m insertStatement (StmtManual m) = insertManual m
insertStatement (StmtImport i) = insertImport i insertStatement (StmtImport i) = insertImport i
insertManual :: MonadFinance m => Manual -> SqlPersistT m [InsertError] insertManual :: MonadUnliftIO m => Manual -> MappingT m [InsertError]
insertManual insertManual
m@Manual m@Manual
{ manualDate = dp { manualDate = dp
@ -369,23 +298,23 @@ insertManual
, manualDesc = e , manualDesc = e
} = do } = do
whenHash CTManual m [] $ \c -> do whenHash CTManual m [] $ \c -> do
bounds <- lift $ askDBState kmStatementInterval bounds <- asks kmStatementInterval
-- let days = expandDatePat bounds dp -- let days = expandDatePat bounds dp
let dayRes = expandDatePat bounds dp let dayRes = expandDatePat bounds dp
unlessLefts dayRes $ \days -> do unlessLefts dayRes $ \days -> do
txRes <- mapM tx days txRes <- mapM tx days
unlessLefts_ (concatEithersL txRes) $ mapM_ (insertTx c) unlessLefts_ (concatEithersL txRes) $ lift . mapM_ (insertTx c)
where where
tx day = txPair day from to u (dec2Rat v) e tx day = txPair day from to u (dec2Rat v) e
insertImport :: MonadFinance m => Import -> SqlPersistT m [InsertError] insertImport :: MonadUnliftIO m => Import -> MappingT m [InsertError]
insertImport i = whenHash CTImport i [] $ \c -> do insertImport i = whenHash CTImport i [] $ \c -> do
-- TODO this isn't efficient, the whole file will be read and maybe no -- TODO this isn't efficient, the whole file will be read and maybe no
-- transactions will be desired -- transactions will be desired
recoverIO (lift $ readImport i) $ \r -> unlessLefts r $ \bs -> do recoverIO (readImport i) $ \r -> unlessLefts r $ \bs -> do
bounds <- expandBounds <$> lift (askDBState kmStatementInterval) bounds <- expandBounds <$> asks kmStatementInterval
res <- mapM resolveTx $ filter (inBounds bounds . txDate) bs res <- mapM resolveTx $ filter (inBounds bounds . txDate) bs
unlessLefts_ (concatEithersL res) $ mapM_ (insertTx c) unlessLefts_ (concatEithersL res) $ lift . mapM_ (insertTx c)
where where
recoverIO x rest = do recoverIO x rest = do
res <- tryIO x res <- tryIO x
@ -399,14 +328,14 @@ insertImport i = whenHash CTImport i [] $ \c -> do
-- low-level transaction stuff -- low-level transaction stuff
txPair txPair
:: MonadFinance m :: MonadUnliftIO m
=> Day => Day
-> AcntID -> AcntID
-> AcntID -> AcntID
-> CurID -> CurID
-> Rational -> Rational
-> T.Text -> T.Text
-> SqlPersistT m (EitherErrs KeyTx) -> MappingT m (EitherErrs KeyTx)
txPair day from to cur val desc = resolveTx tx txPair day from to cur val desc = resolveTx tx
where where
split a v = Split {sAcnt = a, sValue = v, sComment = "", sCurrency = cur} split a v = Split {sAcnt = a, sValue = v, sComment = "", sCurrency = cur}
@ -418,12 +347,12 @@ txPair day from to cur val desc = resolveTx tx
, txSplits = [split from (-val), split to val] , txSplits = [split from (-val), split to val]
} }
resolveTx :: MonadFinance m => BalTx -> SqlPersistT m (EitherErrs KeyTx) resolveTx :: MonadUnliftIO m => BalTx -> MappingT m (EitherErrs KeyTx)
resolveTx t@Tx {txSplits = ss} = do resolveTx t@Tx {txSplits = ss} = do
res <- concatEithersL <$> mapM resolveSplit ss res <- concatEithersL <$> mapM resolveSplit ss
return $ fmap (\kss -> t {txSplits = kss}) res return $ fmap (\kss -> t {txSplits = kss}) res
resolveSplit :: MonadFinance m => BalSplit -> SqlPersistT m (EitherErrs KeySplit) resolveSplit :: MonadUnliftIO m => BalSplit -> MappingT m (EitherErrs KeySplit)
resolveSplit s@Split {sAcnt = p, sCurrency = c, sValue = v} = do resolveSplit s@Split {sAcnt = p, sCurrency = c, sValue = v} = do
aid <- lookupAccountKey p aid <- lookupAccountKey p
cid <- lookupCurrency c cid <- lookupCurrency c
@ -446,14 +375,14 @@ insertSplit :: MonadUnliftIO m => Key TransactionR -> KeySplit -> SqlPersistT m
insertSplit t Split {sAcnt = aid, sCurrency = cid, sValue = v, sComment = c} = do insertSplit t Split {sAcnt = aid, sCurrency = cid, sValue = v, sComment = c} = do
insert $ SplitR t cid aid c v insert $ SplitR t cid aid c v
lookupAccount :: MonadFinance m => AcntID -> SqlPersistT m (EitherErr (Key AccountR, AcntSign)) lookupAccount :: MonadUnliftIO m => AcntID -> MappingT m (EitherErr (Key AccountR, AcntSign))
lookupAccount p = lookupErr (DBKey AcntField) p <$> lift (askDBState kmAccount) lookupAccount p = lookupErr (DBKey AcntField) p <$> asks kmAccount
lookupAccountKey :: MonadFinance m => AcntID -> SqlPersistT m (EitherErr (Key AccountR)) lookupAccountKey :: MonadUnliftIO m => AcntID -> MappingT m (EitherErr (Key AccountR))
lookupAccountKey = fmap (fmap fst) . lookupAccount lookupAccountKey = fmap (fmap fst) . lookupAccount
lookupAccountSign :: MonadFinance m => AcntID -> SqlPersistT m (EitherErr AcntSign) lookupAccountSign :: MonadUnliftIO m => AcntID -> MappingT m (EitherErr AcntSign)
lookupAccountSign = fmap (fmap snd) . lookupAccount lookupAccountSign = fmap (fmap snd) . lookupAccount
lookupCurrency :: MonadFinance m => T.Text -> SqlPersistT m (EitherErr (Key CurrencyR)) lookupCurrency :: MonadUnliftIO m => T.Text -> MappingT m (EitherErr (Key CurrencyR))
lookupCurrency c = lookupErr (DBKey CurField) c <$> lift (askDBState kmCurrency) lookupCurrency c = lookupErr (DBKey CurField) c <$> asks kmCurrency

View File

@ -1,4 +1,8 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Internal.Statement module Internal.Statement
( readImport ( readImport
@ -20,7 +24,7 @@ import qualified RIO.Vector as V
-- TODO this probably won't scale well (pipes?) -- TODO this probably won't scale well (pipes?)
readImport :: MonadFinance m => Import -> m (EitherErrs [BalTx]) readImport :: MonadUnliftIO m => Import -> MappingT m (EitherErrs [BalTx])
readImport Import {..} = do readImport Import {..} = do
let ores = plural $ compileOptions impTxOpts let ores = plural $ compileOptions impTxOpts
let cres = concatEithersL $ compileMatch <$> impMatches let cres = concatEithersL $ compileMatch <$> impMatches
@ -33,14 +37,14 @@ readImport Import {..} = do
Left es -> return $ Left es Left es -> return $ Left es
readImport_ readImport_
:: MonadFinance m :: MonadUnliftIO m
=> Natural => Natural
-> Word -> Word
-> TxOptsRe -> TxOptsRe
-> FilePath -> FilePath
-> m (EitherErr [TxRecord]) -> MappingT m (EitherErr [TxRecord])
readImport_ n delim tns p = do readImport_ n delim tns p = do
dir <- askDBState kmConfigDir dir <- asks kmConfigDir
bs <- liftIO $ BL.readFile $ dir </> p bs <- liftIO $ BL.readFile $ dir </> p
case decodeByNameWithP (parseTxRecord p tns) opts $ skip bs of case decodeByNameWithP (parseTxRecord p tns) opts $ skip bs of
Left m -> return $ Left $ ParseError $ T.pack m Left m -> return $ Left $ ParseError $ T.pack m

View File

@ -1,5 +1,17 @@
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Internal.Types where module Internal.Types where
@ -33,7 +45,6 @@ makeHaskellTypesWith
, MultipleConstructors "SplitNum" "(./dhall/Types.dhall).SplitNum" , MultipleConstructors "SplitNum" "(./dhall/Types.dhall).SplitNum"
, MultipleConstructors "ExpenseBucket" "(./dhall/Types.dhall).ExpenseBucket" , MultipleConstructors "ExpenseBucket" "(./dhall/Types.dhall).ExpenseBucket"
, MultipleConstructors "IncomeBucket" "(./dhall/Types.dhall).IncomeBucket" , MultipleConstructors "IncomeBucket" "(./dhall/Types.dhall).IncomeBucket"
, MultipleConstructors "AmountType" "(./dhall/Types.dhall).AmountType"
, SingleConstructor "Currency" "Currency" "(./dhall/Types.dhall).Currency" , SingleConstructor "Currency" "Currency" "(./dhall/Types.dhall).Currency"
, SingleConstructor "Gregorian" "Gregorian" "(./dhall/Types.dhall).Gregorian" , SingleConstructor "Gregorian" "Gregorian" "(./dhall/Types.dhall).Gregorian"
, SingleConstructor "GregorianM" "GregorianM" "(./dhall/Types.dhall).GregorianM" , SingleConstructor "GregorianM" "GregorianM" "(./dhall/Types.dhall).GregorianM"
@ -292,10 +303,6 @@ instance PersistField ExpenseBucket where
instance PersistFieldSql ExpenseBucket where instance PersistFieldSql ExpenseBucket where
sqlType _ = SqlString sqlType _ = SqlString
deriving instance Eq AmountType
deriving instance Hashable AmountType
deriving instance Eq TimeAmount deriving instance Eq TimeAmount
deriving instance Hashable TimeAmount deriving instance Hashable TimeAmount

View File

@ -1,3 +1,8 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Internal.Utils module Internal.Utils
( compareDate ( compareDate
, inBounds , inBounds
@ -73,14 +78,14 @@ xGregToDay :: XGregorian -> Day
xGregToDay XGregorian {xgYear = y, xgMonth = m, xgDay = d} = fromGregorian (fromIntegral y) m d xGregToDay XGregorian {xgYear = y, xgMonth = m, xgDay = d} = fromGregorian (fromIntegral y) m d
gregTup :: Gregorian -> (Integer, Int, Int) gregTup :: Gregorian -> (Integer, Int, Int)
gregTup Gregorian {gYear, gMonth, gDay} = gregTup Gregorian {..} =
( fromIntegral gYear ( fromIntegral gYear
, fromIntegral gMonth , fromIntegral gMonth
, fromIntegral gDay , fromIntegral gDay
) )
gregMTup :: GregorianM -> (Integer, Int) gregMTup :: GregorianM -> (Integer, Int)
gregMTup GregorianM {gmYear, gmMonth} = gregMTup GregorianM {..} =
( fromIntegral gmYear ( fromIntegral gmYear
, fromIntegral gmMonth , fromIntegral gmMonth
) )
@ -140,22 +145,20 @@ expandBounds (d, n) = (d, addDays (fromIntegral n + 1) d)
-- matching -- matching
matches :: MatchRe -> TxRecord -> EitherErrs (MatchRes RawTx) matches :: MatchRe -> TxRecord -> EitherErrs (MatchRes RawTx)
matches matches Match {..} r@TxRecord {..} = do
Match {mTx, mOther, mVal, mDate, mDesc} res <- concatEither3 val other desc $ \x y z -> x && y && z
r@TxRecord {trDate, trAmount, trDesc, trOther} = do if date && res
res <- concatEither3 val other desc $ \x y z -> x && y && z then maybe (Right MatchSkip) (fmap MatchPass . convert) mTx
if date && res else Right MatchFail
then maybe (Right MatchSkip) (fmap MatchPass . convert) mTx where
else Right MatchFail val = valMatches mVal trAmount
where date = maybe True (`dateMatches` trDate) mDate
val = valMatches mVal trAmount other = foldM (\a o -> (a &&) <$> otherMatches trOther o) True mOther
date = maybe True (`dateMatches` trDate) mDate desc = maybe (return True) (matchMaybe trDesc . snd) mDesc
other = foldM (\a o -> (a &&) <$> otherMatches trOther o) True mOther convert (ToTx cur a ss) = toTx cur a ss r
desc = maybe (return True) (matchMaybe trDesc . snd) mDesc
convert (ToTx cur a ss) = toTx cur a ss r
toTx :: SplitCur -> SplitAcnt -> [ExpSplit] -> TxRecord -> EitherErrs RawTx toTx :: SplitCur -> SplitAcnt -> [ExpSplit] -> TxRecord -> EitherErrs RawTx
toTx sc sa toSplits r@TxRecord {trAmount, trDate, trDesc} = toTx sc sa toSplits r@TxRecord {..} =
concatEithers2 acRes ssRes $ \(a_, c_) ss_ -> concatEithers2 acRes ssRes $ \(a_, c_) ss_ ->
let fromSplit = let fromSplit =
Split Split
@ -175,7 +178,7 @@ toTx sc sa toSplits r@TxRecord {trAmount, trDate, trDesc} =
ssRes = concatEithersL $ fmap (resolveSplit r) toSplits ssRes = concatEithersL $ fmap (resolveSplit r) toSplits
valMatches :: MatchVal -> Rational -> EitherErr Bool valMatches :: MatchVal -> Rational -> EitherErr Bool
valMatches MatchVal {mvDen, mvSign, mvNum, mvPrec} x valMatches MatchVal {..} x
| Just d_ <- mvDen, d_ >= p = Left $ MatchValPrecisionError d_ p | Just d_ <- mvDen, d_ >= p = Left $ MatchValPrecisionError d_ p
| otherwise = | otherwise =
Right $ Right $
@ -248,9 +251,8 @@ parseRational (pat, re) s = case matchGroupsMaybe s re of
_ -> msg "malformed decimal" _ -> msg "malformed decimal"
where where
readT what t = case readMaybe $ T.unpack t of readT what t = case readMaybe $ T.unpack t of
Just d -> return d Just d -> return $ fromInteger d
_ -> msg $ T.unwords ["could not parse", what, t] _ -> msg $ T.unwords ["could not parse", what, t]
msg :: MonadFail m => T.Text -> m a
msg m = msg m =
fail $ fail $
T.unpack $ T.unpack $
@ -294,7 +296,7 @@ fmtRational precision x = T.concat [s, txt n', ".", pad 2 "0" $ txt d']
pad i c z = T.append (T.replicate (i - T.length z) c) z pad i c z = T.append (T.replicate (i - T.length z) c) z
dec2Rat :: Decimal -> Rational dec2Rat :: Decimal -> Rational
dec2Rat D {sign, whole, decimal, precision} = dec2Rat D {..} =
k * (fromIntegral whole + (fromIntegral decimal % (10 ^ precision))) k * (fromIntegral whole + (fromIntegral decimal % (10 ^ precision)))
where where
k = if sign then 1 else -1 k = if sign then 1 else -1
@ -362,7 +364,7 @@ showError other = (: []) $ case other of
splits = T.intercalate ", " $ fmap (singleQuote . showSplit) rss splits = T.intercalate ", " $ fmap (singleQuote . showSplit) rss
showGregorian_ :: Gregorian -> T.Text showGregorian_ :: Gregorian -> T.Text
showGregorian_ Gregorian {gYear, gMonth, gDay} = T.intercalate "-" $ showT <$> [gYear, gMonth, gDay] showGregorian_ Gregorian {..} = T.intercalate "-" $ showT <$> [gYear, gMonth, gDay]
showTx :: TxRecord -> T.Text showTx :: TxRecord -> T.Text
showTx TxRecord {trDate = d, trAmount = v, trDesc = e, trFile = f} = showTx TxRecord {trDate = d, trAmount = v, trDesc = e, trFile = f} =
@ -426,8 +428,7 @@ showYMD_ md =
showMatchVal :: MatchVal -> Maybe T.Text showMatchVal :: MatchVal -> Maybe T.Text
showMatchVal MatchVal {mvSign = Nothing, mvNum = Nothing, mvDen = Nothing} = Nothing showMatchVal MatchVal {mvSign = Nothing, mvNum = Nothing, mvDen = Nothing} = Nothing
showMatchVal MatchVal {mvNum, mvDen, mvSign, mvPrec} = showMatchVal MatchVal {..} = Just $ singleQuote $ keyVals [(k, v) | (k, Just v) <- kvs]
Just $ singleQuote $ keyVals [(k, v) | (k, Just v) <- kvs]
where where
kvs = kvs =
[ ("sign", (\s -> if s then "+" else "-") <$> mvSign) [ ("sign", (\s -> if s then "+" else "-") <$> mvSign)

View File

@ -10,59 +10,15 @@ extra-source-files:
- README.md - README.md
- ChangeLog.md - ChangeLog.md
# Metadata used when publishing your package
# synopsis: Short description of your package # synopsis: Short description of your package
# category: Web # category: Web
# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: Please see the README on GitHub at <https://github.com/ndwarshuis/budget#readme> description: Please see the README on GitHub at <https://github.com/ndwarshuis/budget#readme>
default-extensions:
- OverloadedStrings
- FlexibleContexts
- FlexibleInstances
- InstanceSigs
- MultiParamTypeClasses
- EmptyCase
- LambdaCase
- MultiWayIf
- NamedFieldPuns
- TupleSections
- DeriveFoldable
- DeriveFunctor
- DeriveGeneric
- DeriveLift
- DeriveTraversable
- DerivingStrategies
- DeriveDataTypeable
- EmptyDataDecls
- PartialTypeSignatures
- GeneralizedNewtypeDeriving
- StandaloneDeriving
- BangPatterns
- TypeOperators
- ScopedTypeVariables
- TypeApplications
- ConstraintKinds
- RankNTypes
- GADTs
- DefaultSignatures
- NoImplicitPrelude
- FunctionalDependencies
- DataKinds
- TypeFamilies
- BinaryLiterals
- ViewPatterns
ghc-options:
- -Wall
- -Wcompat
- -Widentities
- -Wincomplete-record-updates
- -Wincomplete-uni-patterns
- -Wredundant-constraints
- -Wpartial-fields
- -Werror
- -O2
dependencies: dependencies:
- base >= 4.12 && < 10 - base >= 4.12 && < 10
- rio >= 0.1.21.0 - rio >= 0.1.21.0
@ -85,16 +41,34 @@ dependencies:
- recursion-schemes - recursion-schemes
- data-fix - data-fix
- filepath - filepath
- mtl
library: library:
source-dirs: lib/ source-dirs: lib/
ghc-options:
- -Wall
- -Wcompat
- -Widentities
- -Wincomplete-record-updates
- -Wincomplete-uni-patterns
- -Wredundant-constraints
- -Wpartial-fields
- -Werror
- -O2
executables: executables:
pwncash: pwncash:
main: Main.hs main: Main.hs
source-dirs: app source-dirs: app
ghc-options: ghc-options:
- -Wall
- -Wcompat
- -Widentities
- -Wincomplete-record-updates
- -Wincomplete-uni-patterns
- -Wredundant-constraints
- -Wpartial-fields
- -Werror
- -O2
- -threaded - -threaded
dependencies: dependencies:
- budget - budget