module Penny.Copper (
C.Comment(Comment),
Q.RadGroup,
Q.periodComma, Q.periodSpace, Q.commaPeriod, Q.commaSpace,
Q.GroupingSpec(..),
DT.DefaultTimeZone(DefaultTimeZone),
DT.utcDefault,
FileContents(FileContents, unFileContents),
ErrorMsg (ErrorMsg, unErrorMsg),
I.Item(Transaction, Price, CommentItem, BlankLine),
I.Line(unLine),
Ledger(Ledger, unLedger),
parse,
I.render
) where
import Control.Applicative ((<$>))
import qualified Control.Monad.Exception.Synchronous as Ex
import qualified Data.Text as X
import Text.Parsec ( manyTill, eof )
import qualified Text.Parsec as P
import qualified Penny.Copper.Comments as C
import qualified Penny.Copper.Qty as Q
import qualified Penny.Copper.DateTime as DT
import qualified Penny.Copper.Item as I
import qualified Penny.Lincoln as L
data Ledger =
Ledger { unLedger :: [(I.Line, I.Item)] }
deriving Show
newtype FileContents = FileContents { unFileContents :: X.Text }
deriving (Eq, Show)
newtype ErrorMsg = ErrorMsg { unErrorMsg :: X.Text }
deriving (Eq, Show)
parseFile ::
DT.DefaultTimeZone
-> Q.RadGroup
-> (L.Filename, FileContents)
-> Ex.Exceptional ErrorMsg
[(I.Line, I.Item)]
parseFile dtz rg (fn, (FileContents c)) =
let p = addFileMetadata fn
<$> manyTill (I.itemWithLineNumber dtz rg) eof
fnStr = X.unpack . L.unFilename $ fn
in case P.parse p fnStr c of
Left err -> Ex.throw (ErrorMsg . X.pack . show $ err)
Right g -> return g
addFileMetadata ::
L.Filename
-> [(I.Line, I.Item)]
-> [(I.Line, I.Item)]
addFileMetadata fn ls =
let (lns, is) = (map fst ls, map snd ls)
eis = map toEiItem is
procTop s m =
m { L.fileTransaction = Just (L.FileTransaction s)
, L.filename = Just fn }
procPstg s m =
m { L.filePosting = Just (L.FilePosting s) }
eis' = L.addSerialsToEithers procTop procPstg eis
is' = map fromEiItem eis'
in zip lns is'
addGlobalMetadata ::
[[(I.Line, I.Item)]]
-> [(I.Line, I.Item)]
addGlobalMetadata lss =
let ls = concat lss
procTop s m =
m { L.globalTransaction = Just (L.GlobalTransaction s) }
procPstg s m =
m { L.globalPosting = Just (L.GlobalPosting s) }
(lns, is) = (map fst ls, map snd ls)
eis = map toEiItem is
eis' = L.addSerialsToEithers procTop procPstg eis
is' = map fromEiItem eis'
in zip lns is'
parse ::
DT.DefaultTimeZone
-> Q.RadGroup
-> [(L.Filename, FileContents)]
-> Ex.Exceptional ErrorMsg Ledger
parse dtz rg ps =
mapM (parseFile dtz rg) ps
>>= (return . Ledger . addGlobalMetadata)
data Other = OPrice L.PricePoint
| OCommentItem C.Comment
| OBlankLine
deriving Show
type EiItem = Either Other L.Transaction
toEiItem :: I.Item -> EiItem
toEiItem i = case i of
I.Transaction t -> Right t
I.Price p -> Left (OPrice p)
I.CommentItem c -> Left (OCommentItem c)
I.BlankLine -> Left OBlankLine
fromEiItem :: EiItem -> I.Item
fromEiItem i = case i of
Left l -> case l of
OPrice p -> I.Price p
OCommentItem c -> I.CommentItem c
OBlankLine -> I.BlankLine
Right t -> I.Transaction t