hledger-lib-0.18: Hledger/Data/Types.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-|
Most data types are defined here to avoid import cycles.
Here is an overview of the hledger data model:
> Journal -- a journal is read from one or more data files. It contains..
> [Transaction] -- journal transactions (aka entries), which have date, status, code, description and..
> [Posting] -- multiple account postings, which have account name and amount
> [HistoricalPrice] -- historical commodity prices
>
> Ledger -- a ledger is derived from a journal, by applying a filter specification and doing some further processing. It contains..
> Journal -- a filtered copy of the original journal, containing only the transactions and postings we are interested in
> Tree AccountName -- all accounts named by the journal's transactions, as a hierarchy
> Map AccountName Account -- the postings, and resulting balances, in each account
For more detailed documentation on each type, see the corresponding modules.
Evolution of transaction\/entry\/posting terminology:
- ledger 2: entries contain transactions
- hledger 0.4: Entrys contain RawTransactions (which are flattened to Transactions)
- ledger 3: transactions contain postings
- hledger 0.5: LedgerTransactions contain Postings (which are flattened to Transactions)
- hledger 0.8: Transactions contain Postings (referencing Transactions..)
-}
module Hledger.Data.Types
where
import Control.Monad.Error (ErrorT)
import Data.Time.Calendar
import Data.Time.LocalTime
import Data.Tree
import Data.Typeable
import qualified Data.Map as Map
import System.Time (ClockTime)
type SmartDate = (String,String,String)
data WhichDate = ActualDate | EffectiveDate deriving (Eq,Show)
data DateSpan = DateSpan (Maybe Day) (Maybe Day) deriving (Eq,Show,Ord)
data Interval = NoInterval
| Days Int | Weeks Int | Months Int | Quarters Int | Years Int
| DayOfMonth Int | DayOfWeek Int
-- WeekOfYear Int | MonthOfYear Int | QuarterOfYear Int
deriving (Eq,Show,Ord)
type AccountName = String
data Side = L | R deriving (Eq,Show,Read,Ord)
data Commodity = Commodity {
symbol :: String, -- ^ the commodity's symbol
-- display preferences for amounts of this commodity
side :: Side, -- ^ should the symbol appear on the left or the right
spaced :: Bool, -- ^ should there be a space between symbol and quantity
precision :: Int, -- ^ number of decimal places to display
-- XXX these three might be better belonging to Journal
decimalpoint :: Char, -- ^ character to use as decimal point
separator :: Char, -- ^ character to use for separating digit groups (eg thousands)
separatorpositions :: [Int] -- ^ positions of separators, counting leftward from decimal point
} deriving (Eq,Ord,Show,Read)
type Quantity = Double
-- | An amount's price in another commodity may be written as \@ unit
-- price or \@\@ total price. Note although a MixedAmount is used, it
-- should be in a single commodity, also the amount should be positive;
-- these are not enforced currently.
data Price = UnitPrice MixedAmount | TotalPrice MixedAmount
deriving (Eq,Ord)
data Amount = Amount {
commodity :: Commodity,
quantity :: Quantity,
price :: Maybe Price -- ^ the price for this amount at posting time
} deriving (Eq,Ord)
newtype MixedAmount = Mixed [Amount] deriving (Eq,Ord)
data PostingType = RegularPosting | VirtualPosting | BalancedVirtualPosting
deriving (Eq,Show)
type Tag = (String, String)
data Posting = Posting {
pstatus :: Bool,
paccount :: AccountName,
pamount :: MixedAmount,
pcomment :: String, -- ^ this posting's non-tag comment lines, as a single non-indented string
ptype :: PostingType,
ptags :: [Tag],
ptransaction :: Maybe Transaction -- ^ this posting's parent transaction (co-recursive types).
-- Tying this knot gets tedious, Maybe makes it easier/optional.
}
-- The equality test for postings ignores the parent transaction's
-- identity, to avoid infinite loops.
instance Eq Posting where
(==) (Posting a1 b1 c1 d1 e1 f1 _) (Posting a2 b2 c2 d2 e2 f2 _) = a1==a2 && b1==b2 && c1==c2 && d1==d2 && e1==e2 && f1==f2
data Transaction = Transaction {
tdate :: Day,
teffectivedate :: Maybe Day,
tstatus :: Bool, -- XXX tcleared ?
tcode :: String,
tdescription :: String,
tcomment :: String, -- ^ this transaction's non-tag comment lines, as a single non-indented string
ttags :: [Tag],
tpostings :: [Posting], -- ^ this transaction's postings (co-recursive types).
tpreceding_comment_lines :: String
} deriving (Eq)
data ModifierTransaction = ModifierTransaction {
mtvalueexpr :: String,
mtpostings :: [Posting]
} deriving (Eq)
data PeriodicTransaction = PeriodicTransaction {
ptperiodicexpr :: String,
ptpostings :: [Posting]
} deriving (Eq)
data TimeLogCode = SetBalance | SetRequiredHours | In | Out | FinalOut deriving (Eq,Ord)
data TimeLogEntry = TimeLogEntry {
tlcode :: TimeLogCode,
tldatetime :: LocalTime,
tlcomment :: String
} deriving (Eq,Ord)
data HistoricalPrice = HistoricalPrice {
hdate :: Day,
hsymbol :: String,
hamount :: MixedAmount
} deriving (Eq) -- & Show (in Amount.hs)
type Year = Integer
-- | A journal "context" is some data which can change in the course of
-- parsing a journal. An example is the default year, which changes when a
-- Y directive is encountered. At the end of parsing, the final context
-- is saved for later use by eg the add command.
data JournalContext = Ctx {
ctxYear :: !(Maybe Year) -- ^ the default year most recently specified with Y
, ctxCommodity :: !(Maybe Commodity) -- ^ the default commodity most recently specified with D
, ctxAccount :: ![AccountName] -- ^ the current stack of parent accounts/account name components
-- specified with "account" directive(s). Concatenated, these
-- are the account prefix prepended to parsed account names.
, ctxAliases :: ![(AccountName,AccountName)] -- ^ the current list of account name aliases in effect
} deriving (Read, Show, Eq)
data Journal = Journal {
jmodifiertxns :: [ModifierTransaction],
jperiodictxns :: [PeriodicTransaction],
jtxns :: [Transaction],
open_timelog_entries :: [TimeLogEntry],
historical_prices :: [HistoricalPrice],
final_comment_lines :: String, -- ^ any trailing comments from the journal file
jContext :: JournalContext, -- ^ the context (parse state) at the end of parsing
files :: [(FilePath, String)], -- ^ the file path and raw text of the main and
-- any included journal files. The main file is
-- first followed by any included files in the
-- order encountered.
filereadtime :: ClockTime -- ^ when this journal was last read from its file(s)
} deriving (Eq, Typeable)
-- | A JournalUpdate is some transformation of a Journal. It can do I/O or
-- raise an error.
type JournalUpdate = ErrorT String IO (Journal -> Journal)
-- | The id of a data format understood by hledger, eg @journal@ or @csv@.
type Format = String
-- | A hledger journal reader is a triple of format name, format-detecting
-- predicate, and a parser to Journal.
data Reader = Reader {
-- name of the format this reader handles
rFormat :: Format
-- quickly check if this reader can probably handle the given file path and file content
,rDetector :: FilePath -> String -> Bool
-- parse the given string, using the given parse rules file if any, returning a journal or error aware of the given file path
,rParser :: Maybe FilePath -> FilePath -> String -> ErrorT String IO Journal
}
instance Show Reader where show r = "Reader for "++rFormat r
-- data format parse/conversion rules
-- currently the only parse (conversion) rules are those for the CSV format
type ParseRules = CsvRules
-- XXX copied from Convert.hs
{- |
A set of data definitions and account-matching patterns sufficient to
convert a particular CSV data file into meaningful journal transactions. See above.
-}
data CsvRules = CsvRules {
dateField :: Maybe FieldPosition,
dateFormat :: Maybe String,
statusField :: Maybe FieldPosition,
codeField :: Maybe FieldPosition,
descriptionField :: [FormatString],
amountField :: Maybe FieldPosition,
amountInField :: Maybe FieldPosition,
amountOutField :: Maybe FieldPosition,
currencyField :: Maybe FieldPosition,
baseCurrency :: Maybe String,
accountField :: Maybe FieldPosition,
account2Field :: Maybe FieldPosition,
effectiveDateField :: Maybe FieldPosition,
baseAccount :: AccountName,
accountRules :: [AccountRule]
} deriving (Show, Eq)
type FieldPosition = Int
type AccountRule = (
[(String, Maybe String)] -- list of regex match patterns with optional replacements
,AccountName -- account name to use for a transaction matching this rule
)
-- format strings
data HledgerFormatField =
AccountField
| DefaultDateField
| DescriptionField
| TotalField
| DepthSpacerField
| FieldNo Int
deriving (Show, Eq)
data FormatString =
FormatLiteral String
| FormatField Bool -- Left justified ?
(Maybe Int) -- Min width
(Maybe Int) -- Max width
HledgerFormatField -- Field
deriving (Show, Eq)
data Ledger = Ledger {
ledgerJournal :: Journal,
ledgerAccountNameTree :: Tree AccountName,
ledgerAccountMap :: Map.Map AccountName Account
}
data Account = Account {
aname :: AccountName,
apostings :: [Posting], -- ^ postings in this account
abalance :: MixedAmount -- ^ sum of postings in this account and subaccounts
} -- deriving (Eq) XXX