hledger-lib 1.1 → 1.2
raw patch · 38 files changed
+1122/−1038 lines, 38 filesdep −parsersdep −system-filepathdep −trifectadep ~megaparsecPVP ok
version bump matches the API change (PVP)
Dependencies removed: parsers, system-filepath, trifecta
Dependency ranges changed: megaparsec
API changes (from Hackage documentation)
- Hledger.Data.RawOptions: optserror :: String -> a
- Hledger.Read.LedgerReader: reader :: Reader
- Hledger.Read.LedgerReader: tests_Hledger_Read_LedgerReader :: Test
+ Hledger.Data.AccountName: escapeName :: AccountName -> Regexp
+ Hledger.Data.AutoTransaction: jdatespan :: Journal -> DateSpan
+ Hledger.Data.AutoTransaction: mtvaluequery :: ModifierTransaction -> (Day -> Query)
+ Hledger.Data.AutoTransaction: runModifierTransaction :: Query -> ModifierTransaction -> (Transaction -> Transaction)
+ Hledger.Data.AutoTransaction: runPeriodicTransaction :: PeriodicTransaction -> (DateSpan -> [Transaction])
+ Hledger.Data.Journal: overJournalAmounts :: (Amount -> Amount) -> Journal -> Journal
+ Hledger.Data.Journal: traverseJournalAmounts :: Applicative f => (Amount -> f Amount) -> Journal -> f Journal
+ Hledger.Data.MarketPrice: showMarketPrice :: MarketPrice -> String
+ Hledger.Data.MarketPrice: tests_Hledger_Data_MarketPrice :: Test
+ Hledger.Data.Posting: originalPosting :: Posting -> Posting
+ Hledger.Data.Posting: payeeAndNoteFromDescription :: Text -> (Text, Text)
+ Hledger.Data.Posting: postingAllImplicitTags :: Posting -> [Tag]
+ Hledger.Data.Posting: transactionNote :: Transaction -> Text
+ Hledger.Data.Posting: transactionPayee :: Transaction -> Text
+ Hledger.Data.Transaction: showGenericSourcePos :: GenericSourcePos -> String
+ Hledger.Data.Transaction: showPostingLines :: Posting -> [String]
+ Hledger.Data.Transaction: sourceFilePath :: GenericSourcePos -> FilePath
+ Hledger.Data.Transaction: sourceFirstLine :: GenericSourcePos -> Int
+ Hledger.Data.Types: JournalSourcePos :: FilePath -> (Int, Int) -> GenericSourcePos
+ Hledger.Data.Types: [porigin] :: Posting -> Maybe Posting
+ Hledger.Read.Common: journalSourcePos :: SourcePos -> SourcePos -> GenericSourcePos
+ Hledger.Read.CsvReader: expandIncludes :: FilePath -> Text -> IO Text
+ Hledger.Read.CsvReader: parseAndValidateCsvRules :: FilePath -> Text -> ExceptT String IO CsvRules
+ Hledger.Reports.BalanceReport: amountValue :: Journal -> Day -> Amount -> Amount
+ Hledger.Reports.ReportOptions: [pretty_tables_] :: ReportOpts -> Bool
+ Hledger.Utils: mapM' :: Monad f => (a -> f b) -> [a] -> f [b]
+ Hledger.Utils: maximumStrict :: Ord a => [a] -> a
+ Hledger.Utils: minimumStrict :: Ord a => [a] -> a
+ Hledger.Utils: sequence' :: Monad f => [f a] -> f [a]
+ Hledger.Utils: sumStrict :: Num a => [a] -> a
+ Hledger.Utils: usageError :: String -> a
+ Hledger.Utils.UTF8IOCompat: usageError :: String -> a
- Hledger.Data.Types: AmountStyle :: Side -> Bool -> Int -> Maybe Char -> Maybe DigitGroupStyle -> AmountStyle
+ Hledger.Data.Types: AmountStyle :: Side -> Bool -> !Int -> Maybe Char -> Maybe DigitGroupStyle -> AmountStyle
- Hledger.Data.Types: Posting :: Maybe Day -> Maybe Day -> ClearedStatus -> AccountName -> MixedAmount -> Text -> PostingType -> [Tag] -> Maybe Amount -> Maybe Transaction -> Posting
+ Hledger.Data.Types: Posting :: Maybe Day -> Maybe Day -> ClearedStatus -> AccountName -> MixedAmount -> Text -> PostingType -> [Tag] -> Maybe Amount -> Maybe Transaction -> Maybe Posting -> Posting
- Hledger.Data.Types: [asprecision] :: AmountStyle -> Int
+ Hledger.Data.Types: [asprecision] :: AmountStyle -> !Int
- Hledger.Reports.ReportOptions: ReportOpts :: Period -> Interval -> Maybe ClearedStatus -> Bool -> Maybe Int -> Maybe DisplayExp -> Bool -> Bool -> Bool -> Bool -> Maybe FormatStr -> String -> Bool -> Bool -> BalanceType -> AccountListMode -> Int -> Bool -> Bool -> Bool -> ReportOpts
+ Hledger.Reports.ReportOptions: ReportOpts :: Period -> Interval -> Maybe ClearedStatus -> Bool -> Maybe Int -> Maybe DisplayExp -> Bool -> Bool -> Bool -> Bool -> Maybe FormatStr -> String -> Bool -> Bool -> BalanceType -> AccountListMode -> Int -> Bool -> Bool -> Bool -> Bool -> ReportOpts
Files
- CHANGES +31/−0
- Hledger/Data.hs +3/−0
- Hledger/Data/Account.hs +1/−1
- Hledger/Data/AccountName.hs +9/−2
- Hledger/Data/Amount.hs +1/−1
- Hledger/Data/AutoTransaction.hs +156/−0
- Hledger/Data/Journal.hs +35/−16
- Hledger/Data/MarketPrice.hs +32/−0
- Hledger/Data/Posting.hs +39/−1
- Hledger/Data/RawOptions.hs +2/−7
- Hledger/Data/Transaction.hs +86/−22
- Hledger/Data/Types.hs +7/−5
- Hledger/Query.hs +4/−1
- Hledger/Read.hs +3/−3
- Hledger/Read/Common.hs +7/−0
- Hledger/Read/CsvReader.hs +44/−35
- Hledger/Read/JournalReader.hs +9/−1
- Hledger/Read/LedgerReader.hs +0/−177
- Hledger/Reports/BalanceReport.hs +1/−0
- Hledger/Reports/MultiBalanceReports.hs +3/−3
- Hledger/Reports/ReportOptions.hs +13/−9
- Hledger/Utils.hs +39/−4
- Hledger/Utils/UTF8IOCompat.hs +7/−0
- doc/hledger_csv.5 +1/−1
- doc/hledger_csv.5.info +57/−73
- doc/hledger_csv.5.txt +1/−1
- doc/hledger_journal.5 +70/−16
- doc/hledger_journal.5.info +299/−292
- doc/hledger_journal.5.txt +97/−58
- doc/hledger_timeclock.5 +1/−1
- doc/hledger_timeclock.5.info +11/−16
- doc/hledger_timeclock.5.txt +1/−1
- doc/hledger_timedot.5 +1/−1
- doc/hledger_timedot.5.info +23/−32
- doc/hledger_timedot.5.txt +1/−1
- hledger-lib.cabal +25/−37
- other/ledger-parse/Ledger/Parser/Text.hs +0/−218
- tests/doctests.hs +2/−2
CHANGES view
@@ -2,6 +2,37 @@ See also the hledger and project change logs (for user-visible changes). +# 1.2 (2016/3/31)++## journal format++A pipe character can optionally be used to delimit payee names in+transaction descriptions, for more accurate querying and pivoting by+payee. Eg, for a description like `payee name | additional notes`,+the two parts will be accessible as pseudo-fields/tags named `payee`+and `note`.++Some journal parse errors now show the range of lines involved, not just the first.++## ledger format++The experimental `ledger:` reader based on the WIP ledger4 project has+been disabled, reducing build dependencies.++## Misc++Fix a bug when tying the knot between postings and their parent transaction, reducing memory usage by about 10% (#483) (Mykola Orliuk)++Fix a few spaceleaks (#413) (Moritz Kiefer)++Add Ledger.Parse.Text to package.yaml, fixing a potential build failure.++Allow megaparsec 5.2 (#503)++Rename optserror -> usageError, consolidate with other error functions+++ # 1.1 (2016/12/31) ## journal format
Hledger/Data.hs view
@@ -15,6 +15,7 @@ module Hledger.Data.Dates, module Hledger.Data.Journal, module Hledger.Data.Ledger,+ module Hledger.Data.MarketPrice, module Hledger.Data.Period, module Hledger.Data.Posting, module Hledger.Data.RawOptions,@@ -34,6 +35,7 @@ import Hledger.Data.Dates import Hledger.Data.Journal import Hledger.Data.Ledger+import Hledger.Data.MarketPrice import Hledger.Data.Period import Hledger.Data.Posting import Hledger.Data.RawOptions@@ -50,6 +52,7 @@ ,tests_Hledger_Data_Amount ,tests_Hledger_Data_Commodity ,tests_Hledger_Data_Journal+ ,tests_Hledger_Data_MarketPrice ,tests_Hledger_Data_Ledger ,tests_Hledger_Data_Posting -- ,tests_Hledger_Data_RawOptions
Hledger/Data/Account.hs view
@@ -64,7 +64,7 @@ acctamts = [(paccount p,pamount p) | p <- ps] grouped = groupBy (\a b -> fst a == fst b) $ sort $ acctamts counted = [(a, length acctamts) | acctamts@((a,_):_) <- grouped]- summed = map (\as@((aname,_):_) -> (aname, sum $ map snd as)) grouped -- always non-empty+ summed = map (\as@((aname,_):_) -> (aname, sumStrict $ map snd as)) grouped -- always non-empty nametree = treeFromPaths $ map (expandAccountName . fst) summed acctswithnames = nameTreeToAccount "root" nametree acctswithnumps = mapAccounts setnumps acctswithnames where setnumps a = a{anumpostings=fromMaybe 0 $ lookup (aname a) counted}
Hledger/Data/AccountName.hs view
@@ -149,15 +149,22 @@ clipOrEllipsifyAccountName 0 = const "..." clipOrEllipsifyAccountName n = accountNameFromComponents . take n . accountNameComponents +-- | Escape an AccountName for use within a regular expression.+-- >>> putStr $ escapeName "First?!#$*?$(*) !@^#*? %)*!@#"+-- First\?!#\$\*\?\$\(\*\) !@\^#\*\? %\)\*!@#+escapeName :: AccountName -> Regexp+escapeName = regexReplaceBy "[[?+|()*\\\\^$]" ("\\" <>)+ . T.unpack+ -- | Convert an account name to a regular expression matching it and its subaccounts. accountNameToAccountRegex :: AccountName -> Regexp accountNameToAccountRegex "" = ""-accountNameToAccountRegex a = printf "^%s(:|$)" (T.unpack a)+accountNameToAccountRegex a = printf "^%s(:|$)" (escapeName a) -- | Convert an account name to a regular expression matching it but not its subaccounts. accountNameToAccountOnlyRegex :: AccountName -> Regexp accountNameToAccountOnlyRegex "" = ""-accountNameToAccountOnlyRegex a = printf "^%s$" $ T.unpack a -- XXX pack+accountNameToAccountOnlyRegex a = printf "^%s$" $ escapeName a -- XXX pack -- | Convert an exact account-matching regular expression to a plain account name. accountRegexToAccountName :: Regexp -> AccountName
Hledger/Data/Amount.hs view
@@ -435,7 +435,7 @@ -- rendering helper. sumSimilarAmountsUsingFirstPrice :: [Amount] -> Amount sumSimilarAmountsUsingFirstPrice [] = nullamt-sumSimilarAmountsUsingFirstPrice as = (sum as){aprice=aprice $ head as}+sumSimilarAmountsUsingFirstPrice as = (sumStrict as){aprice=aprice $ head as} -- -- | Sum same-commodity amounts. If there were different prices, set -- -- the price to a special marker indicating "various". Only used as a
+ Hledger/Data/AutoTransaction.hs view
@@ -0,0 +1,156 @@+{-# LANGUAGE OverloadedStrings, ViewPatterns #-}+{-|++This module provides utilities for applying automated transactions like+'ModifierTransaction' and 'PeriodicTransaction'.++-}+module Hledger.Data.AutoTransaction+ (+ -- * Transaction processors+ runModifierTransaction+ , runPeriodicTransaction++ -- * Accessors+ , mtvaluequery+ , jdatespan+ )+where++import Data.Maybe+import Data.Monoid ((<>))+import Data.Time.Calendar+import qualified Data.Text as T+import Hledger.Data.Types+import Hledger.Data.Dates+import Hledger.Data.Amount+import Hledger.Data.Transaction+import Hledger.Utils.Parse+import Hledger.Utils.UTF8IOCompat (error')+import Hledger.Query++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import Hledger.Data.Posting+-- >>> import Hledger.Data.Journal++-- | Builds a 'Transaction' transformer based on 'ModifierTransaction'.+--+-- 'Query' parameter allows injection of additional restriction on posting+-- match. Don't forget to call 'txnTieKnot'.+--+-- >>> runModifierTransaction Any (ModifierTransaction "" ["pong" `post` usd 2]) nulltransaction{tpostings=["ping" `post` usd 1]}+-- 0000/01/01+-- ping $1.00+-- pong $2.00+-- <BLANKLINE>+-- <BLANKLINE>+-- >>> runModifierTransaction Any (ModifierTransaction "miss" ["pong" `post` usd 2]) nulltransaction{tpostings=["ping" `post` usd 1]}+-- 0000/01/01+-- ping $1.00+-- <BLANKLINE>+-- <BLANKLINE>+-- >>> runModifierTransaction None (ModifierTransaction "" ["pong" `post` usd 2]) nulltransaction{tpostings=["ping" `post` usd 1]}+-- 0000/01/01+-- ping $1.00+-- <BLANKLINE>+-- <BLANKLINE>+-- >>> runModifierTransaction Any (ModifierTransaction "ping" ["pong" `post` amount{acommodity="*", aquantity=3}]) nulltransaction{tpostings=["ping" `post` usd 2]}+-- 0000/01/01+-- ping $2.00+-- pong $6.00+-- <BLANKLINE>+-- <BLANKLINE>+runModifierTransaction :: Query -> ModifierTransaction -> (Transaction -> Transaction)+runModifierTransaction q mt = modifier where+ q' = simplifyQuery $ And [q, mtvaluequery mt (error "query cannot depend on current time")]+ mods = map runModifierPosting $ mtpostings mt+ generatePostings ps = [m p | p <- ps, q' `matchesPosting` p, m <- mods]+ modifier t@(tpostings -> ps) = t { tpostings = ps ++ generatePostings ps }++-- | Extract 'Query' equivalent of 'mtvalueexpr' from 'ModifierTransaction'+--+-- >>> mtvaluequery (ModifierTransaction "" []) undefined+-- Any+-- >>> mtvaluequery (ModifierTransaction "ping" []) undefined+-- Acct "ping"+-- >>> mtvaluequery (ModifierTransaction "date:2016" []) undefined+-- Date (DateSpan 2016)+-- >>> mtvaluequery (ModifierTransaction "date:today" []) (read "2017-01-01")+-- Date (DateSpan 2017/01/01)+mtvaluequery :: ModifierTransaction -> (Day -> Query)+mtvaluequery mt = fst . flip parseQuery (mtvalueexpr mt)++-- | 'DateSpan' of all dates mentioned in 'Journal'+--+-- >>> jdatespan nulljournal+-- DateSpan -+-- >>> jdatespan nulljournal{jtxns=[nulltransaction{tdate=read "2016-01-01"}] }+-- DateSpan 2016/01/01+-- >>> jdatespan nulljournal{jtxns=[nulltransaction{tdate=read "2016-01-01", tpostings=[nullposting{pdate=Just $ read "2016-02-01"}]}] }+-- DateSpan 2016/01/01-2016/02/01+jdatespan :: Journal -> DateSpan+jdatespan j+ | null dates = nulldatespan+ | otherwise = DateSpan (Just $ minimum dates) (Just $ 1 `addDays` maximum dates)+ where+ dates = concatMap tdates $ jtxns j++-- | 'DateSpan' of all dates mentioned in 'Transaction'+--+-- >>> tdates nulltransaction+-- [0000-01-01]+tdates :: Transaction -> [Day]+tdates t = tdate t : concatMap pdates (tpostings t) ++ maybeToList (tdate2 t) where+ pdates p = catMaybes [pdate p, pdate2 p]++postingScale :: Posting -> Maybe Quantity+postingScale p =+ case amounts $ pamount p of+ [a] | acommodity a == "*" -> Just $ aquantity a+ _ -> Nothing++runModifierPosting :: Posting -> (Posting -> Posting)+runModifierPosting p' = modifier where+ modifier p = renderPostingCommentDates $ p'+ { pdate = pdate p+ , pdate2 = pdate2 p+ , pamount = amount' p+ }+ amount' =+ case postingScale p' of+ Nothing -> const $ pamount p'+ Just n -> \p -> pamount p `divideMixedAmount` (1/n)++renderPostingCommentDates :: Posting -> Posting+renderPostingCommentDates p = p { pcomment = comment' }+ where+ datesComment = T.concat $ catMaybes [T.pack . showDate <$> pdate p, ("=" <>) . T.pack . showDate <$> pdate2 p]+ comment'+ | T.null datesComment = pcomment p+ | otherwise = T.intercalate "\n" $ filter (not . T.null) [T.strip $ pcomment p, "[" <> datesComment <> "]"]++-- | Generate transactions from 'PeriodicTransaction' within a 'DateSpan'+--+-- Note that new transactions require 'txnTieKnot' post-processing.+--+-- >>> mapM_ (putStr . show) $ runPeriodicTransaction (PeriodicTransaction "monthly from 2017/1 to 2017/4" ["hi" `post` usd 1]) nulldatespan+-- 2017/01/01+-- hi $1.00+-- <BLANKLINE>+-- 2017/02/01+-- hi $1.00+-- <BLANKLINE>+-- 2017/03/01+-- hi $1.00+-- <BLANKLINE>+runPeriodicTransaction :: PeriodicTransaction -> (DateSpan -> [Transaction])+runPeriodicTransaction pt = generate where+ base = nulltransaction { tpostings = ptpostings pt }+ periodExpr = ptperiodicexpr pt+ errCurrent = error' $ "Current date cannot be referenced in " ++ show (T.unpack periodExpr)+ (interval, effectspan) =+ case parsePeriodExpr errCurrent periodExpr of+ Left e -> error' $ "Failed to parse " ++ show (T.unpack periodExpr) ++ ": " ++ showDateParseError e+ Right x -> x+ generate jspan = [base {tdate=date} | span <- interval `splitSpan` spanIntersect effectspan jspan, let Just date = spanStart span]
Hledger/Data/Journal.hs view
@@ -32,6 +32,8 @@ journalAccountNamesUsed, -- journalAmountAndPriceCommodities, journalAmounts,+ overJournalAmounts,+ traverseJournalAmounts, -- journalCanonicalCommodities, journalDateSpan, journalDescriptions,@@ -62,12 +64,14 @@ tests_Hledger_Data_Journal, ) where+import Control.Applicative (Const(..)) import Control.Arrow import Control.Monad import Control.Monad.Except import qualified Control.Monad.Reader as R import Control.Monad.ST import Data.Array.ST+import Data.Functor.Identity (Identity(..)) import qualified Data.HashTable.ST.Cuckoo as HT import Data.List -- import Data.Map (findWithDefault)@@ -529,9 +533,8 @@ ]) (case ptransaction p of Nothing -> ":" -- shouldn't happen- Just t -> printf " in \"%s\" (line %d, column %d):\nin transaction:\n%s"- f l c (chomp $ show t) :: String- where GenericSourcePos f l c = tsourcepos t)+ Just t -> printf " in %s:\nin transaction:\n%s"+ (showGenericSourcePos $ tsourcepos t) (chomp $ show t) :: String) (showPostingLine p) (showDate $ postingDate p) (T.unpack $ paccount p) -- XXX pack@@ -584,8 +587,8 @@ flip R.runReaderT (Env bals (storeIn txStore) assrt $ Just $ jinferredcommodities j) $ do dated <- fmap snd . sortBy (comparing fst) . concat- <$> mapM discriminateByDate (jtxns j)- mapM checkInferAndRegisterAmounts dated+ <$> mapM' discriminateByDate (jtxns j)+ mapM' checkInferAndRegisterAmounts dated lift $ extract txStore where size = genericLength $ journalPostings j @@ -657,7 +660,7 @@ where inferFromAssignment :: Posting -> CurrentBalancesModifier s Posting inferFromAssignment p = maybe (return p)- (fmap (\a -> p { pamount = a }) . setBalance (paccount p))+ (fmap (\a -> p { pamount = a, porigin = Just $ originalPosting p }) . setBalance (paccount p)) $ pbalanceassertion p -- | Adds a posting's amonut to the posting's account balance and@@ -759,7 +762,7 @@ where mgrps = maybe Nothing Just $ headMay $ catMaybes $ map asdigitgroups ss -- precision is maximum of all precisions- prec = maximum $ map asprecision ss+ prec = maximumStrict $ map asprecision ss mdec = Just $ headDef '.' $ catMaybes $ map asdecimalpoint ss -- precision is that of first amount with a decimal point -- (mdec, prec) =@@ -827,13 +830,29 @@ -- Amounts in posting prices are not used for canonicalisation. -- journalAmounts :: Journal -> [Amount]-journalAmounts j =- concat- [map mpamount $ jmarketprices j- ,concatMap flatten $ map pamount $ journalPostings j- ]- where flatten (Mixed as) = as+journalAmounts = getConst . traverseJournalAmounts (Const . (:[])) +-- | Maps over all of the amounts in the journal+overJournalAmounts :: (Amount -> Amount) -> Journal -> Journal+overJournalAmounts f = runIdentity . traverseJournalAmounts (Identity . f)++-- | Traverses over all ofthe amounts in the journal, in the order+-- indicated by 'journalAmounts'.+traverseJournalAmounts+ :: Applicative f+ => (Amount -> f Amount)+ -> Journal -> f Journal+traverseJournalAmounts f j =+ recombine <$> (traverse . mpa) f (jmarketprices j)+ <*> (traverse . tp . traverse . pamt . maa . traverse) f (jtxns j)+ where+ recombine mps txns = j { jmarketprices = mps, jtxns = txns }+ -- a bunch of traversals+ mpa g mp = (\amt -> mp { mpamount = amt }) <$> g (mpamount mp)+ tp g t = (\ps -> t { tpostings = ps }) <$> g (tpostings t)+ pamt g p = (\amt -> p { pamount = amt }) <$> g (pamount p)+ maa g (Mixed as) = Mixed <$> g as+ -- | The fully specified date span enclosing the dates (primary or secondary) -- of all this journal's transactions and postings, or DateSpan Nothing Nothing -- if there are none.@@ -842,8 +861,8 @@ | null ts = DateSpan Nothing Nothing | otherwise = DateSpan (Just earliest) (Just $ addDays 1 latest) where- earliest = minimum dates- latest = maximum dates+ earliest = minimumStrict dates+ latest = maximumStrict dates dates = pdates ++ tdates tdates = map (if secondary then transactionDate2 else tdate) ts pdates = concatMap (catMaybes . map (if secondary then (Just . postingDate2) else pdate) . tpostings) ts@@ -889,7 +908,7 @@ -- tests --- A sample journal for testing, similar to data/sample.journal:+-- A sample journal for testing, similar to examples/sample.journal: -- -- 2008/01/01 income -- assets:bank:checking $1
+ Hledger/Data/MarketPrice.hs view
@@ -0,0 +1,32 @@+{-|++A 'MarketPrice' represents a historical exchange rate between two+commodities. (Ledger calls them historical prices.) For example, prices+published by a stock exchange or the foreign exchange market. Some+commands (balance, currently) can use this information to show the market+value of things at a given date.++-}++{-# LANGUAGE OverloadedStrings, LambdaCase #-}++module Hledger.Data.MarketPrice+where+import qualified Data.Text as T+import Test.HUnit++import Hledger.Data.Amount+import Hledger.Data.Dates+import Hledger.Data.Types++-- | Get the string representation of an market price, based on its+-- commodity's display settings.+showMarketPrice :: MarketPrice -> String+showMarketPrice mp = unwords+ [ "P"+ , showDate (mpdate mp)+ , T.unpack (mpcommodity mp)+ , (showAmount . setAmountPrecision maxprecision) (mpamount mp)+ ]++tests_Hledger_Data_MarketPrice = TestList []
Hledger/Data/Posting.hs view
@@ -15,6 +15,7 @@ posting, post, -- * operations+ originalPosting, postingStatus, isReal, isVirtual,@@ -24,6 +25,7 @@ hasAmount, postingAllTags, transactionAllTags,+ postingAllImplicitTags, relatedPostings, removePrices, -- * date operations@@ -42,6 +44,10 @@ concatAccountNames, accountNameApplyAliases, accountNameApplyAliasesMemo,+ -- * transaction description operations+ transactionPayee,+ transactionNote,+ payeeAndNoteFromDescription, -- * arithmetic sumPostings, -- * rendering@@ -83,12 +89,16 @@ ,ptags=[] ,pbalanceassertion=Nothing ,ptransaction=Nothing+ ,porigin=Nothing } posting = nullposting post :: AccountName -> Amount -> Posting post acct amt = posting {paccount=acct, pamount=Mixed [amt]} +originalPosting :: Posting -> Posting+originalPosting p = fromMaybe p $ porigin p+ -- XXX once rendered user output, but just for debugging now; clean up showPosting :: Posting -> String showPosting p@Posting{paccount=a,pamount=amt,ptype=t} =@@ -126,7 +136,7 @@ accountNamesFromPostings = nub . map paccount sumPostings :: [Posting] -> MixedAmount-sumPostings = sum . map pamount+sumPostings = sumStrict . map pamount -- | Remove all prices of a posting removePrices :: Posting -> Posting@@ -162,6 +172,34 @@ | s == Uncleared = case mt of Just t -> tstatus t Nothing -> Uncleared | otherwise = s++-- | Implicit tags for this transaction.+transactionImplicitTags :: Transaction -> [Tag]+transactionImplicitTags t = filter (not . T.null . snd) [("code", tcode t)+ ,("description", tdescription t)+ ,("payee", transactionPayee t)+ ,("note", transactionNote t)+ ]++transactionPayee :: Transaction -> Text+transactionPayee = fst . payeeAndNoteFromDescription . tdescription++transactionNote :: Transaction -> Text+transactionNote = fst . payeeAndNoteFromDescription . tdescription++-- | Parse a transaction's description into payee and note (aka narration) fields,+-- assuming a convention of separating these with | (like Beancount).+-- Ie, everything up to the first | is the payee, everything after it is the note.+-- When there's no |, payee == note == description.+payeeAndNoteFromDescription :: Text -> (Text,Text)+payeeAndNoteFromDescription t = (textstrip p, textstrip $ T.tail n)+ where+ (p,n) = T.breakOn "|" t++-- | Tags for this posting including implicit and any inherited from its parent transaction.+postingAllImplicitTags :: Posting -> [Tag]+postingAllImplicitTags p = ptags p ++ maybe [] transactionTags (ptransaction p)+ where transactionTags t = ttags t ++ transactionImplicitTags t -- | Tags for this posting including any inherited from its parent transaction. postingAllTags :: Posting -> [Tag]
Hledger/Data/RawOptions.hs view
@@ -17,8 +17,7 @@ maybestringopt, listofstringopt, intopt,- maybeintopt,- optserror+ maybeintopt ) where @@ -58,12 +57,8 @@ maybeintopt name rawopts = let ms = maybestringopt name rawopts in case ms of Nothing -> Nothing- Just s -> Just $ readDef (optserror $ "could not parse "++name++" number: "++s) s+ Just s -> Just $ readDef (usageError $ "could not parse "++name++" number: "++s) s intopt :: String -> RawOpts -> Int intopt name = fromMaybe 0 . maybeintopt name---- | Raise an error, showing the specified message plus a hint about --help.-optserror :: String -> a-optserror = error' . (++ " (run with --help for usage)")
Hledger/Data/Transaction.hs view
@@ -8,7 +8,7 @@ -} -{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, LambdaCase #-} module Hledger.Data.Transaction ( -- * Transaction@@ -37,6 +37,11 @@ showTransactionUnelided, showTransactionUnelidedOneLineAmounts, showPostingLine,+ showPostingLines,+ -- * GenericSourcePos+ sourceFilePath,+ sourceFirstLine,+ showGenericSourcePos, -- * misc. tests_Hledger_Data_Transaction )@@ -66,6 +71,21 @@ instance Show PeriodicTransaction where show t = "~ " ++ T.unpack (ptperiodicexpr t) ++ "\n" ++ unlines (map show (ptpostings t)) +sourceFilePath :: GenericSourcePos -> FilePath+sourceFilePath = \case+ GenericSourcePos fp _ _ -> fp+ JournalSourcePos fp _ -> fp++sourceFirstLine :: GenericSourcePos -> Int+sourceFirstLine = \case+ GenericSourcePos _ line _ -> line+ JournalSourcePos _ (line, _) -> line++showGenericSourcePos :: GenericSourcePos -> String+showGenericSourcePos = \case+ GenericSourcePos fp line column -> show fp ++ " (line " ++ show line ++ ", column " ++ show column ++ ")"+ JournalSourcePos fp (line, line') -> show fp ++ " (lines " ++ show line ++ "-" ++ show line' ++ ")"+ nullsourcepos :: GenericSourcePos nullsourcepos = GenericSourcePos "" 1 1 @@ -132,7 +152,8 @@ `gives` unlines [ "2012/05/14=2012/05/15 (code) desc ; tcomment1", " ; tcomment2",- " $1.00",+ " * a $1.00",+ " ; pcomment2", " * a 2.00h", " ; pcomment2", ""@@ -186,12 +207,13 @@ | otherwise = concatMap (postingAsLines False onelineamounts ps) ps postingAsLines :: Bool -> Bool -> [Posting] -> Posting -> [String]-postingAsLines elideamount onelineamounts ps p =- postinglines+postingAsLines elideamount onelineamounts ps p = concat [+ postingblock ++ newlinecomments+ | postingblock <- postingblocks] where- postinglines = map rstrip $ lines $ concatTopPadded [account, " ", amount, assertion, samelinecomment]- assertion = maybe "" ((" = " ++) . showAmount) $ pbalanceassertion p+ postingblocks = [map rstrip $ lines $ concatTopPadded [account, " ", amount, assertion, samelinecomment] | amount <- shownAmounts]+ assertion = maybe "" ((" = " ++) . showAmountWithZeroCommodity) $ pbalanceassertion p account = indent $ showstatus p ++ fitString (Just acctwidth) Nothing False True (showAccountName Nothing (ptype p) (paccount p))@@ -200,10 +222,10 @@ acctwidth = maximum $ map (textWidth . paccount) ps -- currently prices are considered part of the amount string when right-aligning amounts- amount- | elideamount = ""- | onelineamounts = fitString (Just amtwidth) Nothing False False $ showMixedAmountOneLine $ pamount p- | otherwise = fitStringMulti (Just amtwidth) Nothing False False $ showMixedAmount $ pamount p+ shownAmounts+ | elideamount = [""]+ | onelineamounts = [fitString (Just amtwidth) Nothing False False $ showMixedAmountOneLine $ pamount p]+ | otherwise = map (fitStringMulti (Just amtwidth) Nothing False False . showAmount ) . amounts $ pamount p where amtwidth = maximum $ 12 : map (strWidth . showMixedAmount . pamount) ps -- min. 12 for backwards compatibility @@ -220,10 +242,16 @@ " " ++ showMixedAmountOneLine (pamount p) +-- | Produce posting line with all comment lines associated with it+showPostingLines :: Posting -> [String]+showPostingLines p = postingAsLines False False ps p where+ ps | Just t <- ptransaction p = tpostings t+ | otherwise = [p]+ tests_postingAsLines = [ "postingAsLines" ~: do- let p `gives` ls = assertEqual "" ls (postingAsLines False False [p] p)- posting `gives` [" 0"]+ let p `gives` ls = assertEqual (show p) ls (postingAsLines False False [p] p)+ posting `gives` [] posting{ pstatus=Cleared, paccount="a",@@ -233,13 +261,47 @@ ptags=[("ptag1","val1"),("ptag2","val2")] } `gives` [- " $1.00",+ " * a $1.00 ; pcomment1",+ " ; pcomment2",+ " ; tag3: val3 ", " * a 2.00h ; pcomment1", " ; pcomment2", " ; tag3: val3 " ] ] +tests_inference = [+ "inferBalancingAmount" ~: do+ let p `gives` p' = assertEqual (show p) (Right p') $ inferTransaction p+ inferTransaction :: Transaction -> Either String Transaction+ inferTransaction = runIdentity . runExceptT . inferBalancingAmount (\_ _ -> return ())+ nulltransaction `gives` nulltransaction+ nulltransaction{+ tpostings=[+ "a" `post` usd (-5),+ "b" `post` missingamt+ ]}+ `gives`+ nulltransaction{+ tpostings=[+ "a" `post` usd (-5),+ "b" `post` usd 5+ ]}+ nulltransaction{+ tpostings=[+ "a" `post` usd (-5),+ "b" `post` (eur 3 @@ usd 4),+ "c" `post` missingamt+ ]}+ `gives`+ nulltransaction{+ tpostings=[+ "a" `post` usd (-5),+ "b" `post` (eur 3 @@ usd 4),+ "c" `post` usd 1+ ]}+ ]+ indent :: String -> String indent = (" "++) @@ -357,17 +419,17 @@ return t{tpostings=postings} where printerr s = intercalate "\n" [s, showTransactionUnelided t]- ((amountfulrealps, amountlessrealps), realsum) =- (partition hasAmount (realPostings t), sum $ map pamount amountfulrealps)- ((amountfulbvps, amountlessbvps), bvsum) =- (partition hasAmount (balancedVirtualPostings t), sum $ map pamount amountfulbvps)+ (amountfulrealps, amountlessrealps) = partition hasAmount (realPostings t)+ realsum = sumStrict $ map pamount amountfulrealps+ (amountfulbvps, amountlessbvps) = partition hasAmount (balancedVirtualPostings t)+ bvsum = sumStrict $ map pamount amountfulbvps inferamount p@Posting{ptype=RegularPosting} | not (hasAmount p) = updateAmount p realsum inferamount p@Posting{ptype=BalancedVirtualPosting} | not (hasAmount p) = updateAmount p bvsum inferamount p = return p- updateAmount p amt = update (paccount p) amt' >> return p { pamount=amt' }- where amt' = costOfMixedAmount (-amt)+ updateAmount p amt = update (paccount p) amt' >> return p { pamount=amt', porigin=Just $ originalPosting p }+ where amt' = normaliseMixedAmount $ costOfMixedAmount (-amt) -- | Infer prices for this transaction's posting amounts, if needed to make -- the postings balance, and if possible. This is done once for the real@@ -424,14 +486,14 @@ pmixedamounts = map pamount postings pamounts = concatMap amounts pmixedamounts pcommodities = map acommodity pamounts- sumamounts = amounts $ sum pmixedamounts -- sum normalises to one amount per commodity & price+ sumamounts = amounts $ sumStrict pmixedamounts -- sum normalises to one amount per commodity & price sumcommodities = map acommodity sumamounts sumprices = filter (/=NoPrice) $ map aprice sumamounts caninferprices = length sumcommodities == 2 && null sumprices inferprice p@Posting{pamount=Mixed [a]} | caninferprices && ptype p == pt && acommodity a == fromcommodity- = p{pamount=Mixed [a{aprice=conversionprice}]}+ = p{pamount=Mixed [a{aprice=conversionprice}], porigin=Just $ originalPosting p} where fromcommodity = head $ filter (`elem` sumcommodities) pcommodities -- these heads are ugly but should be safe conversionprice@@ -453,7 +515,8 @@ -- | Ensure a transaction's postings refer back to it, so that eg -- relatedPostings works right. txnTieKnot :: Transaction -> Transaction-txnTieKnot t@Transaction{tpostings=ps} = t{tpostings=map (postingSetTransaction t) ps}+txnTieKnot t@Transaction{tpostings=ps} = t' where+ t' = t{tpostings=map (postingSetTransaction t') ps} -- | Ensure a transaction's postings do not refer back to it, so that eg -- recursiveSize and GHCI's :sprint work right.@@ -467,6 +530,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [ tests_postingAsLines, tests_showTransactionUnelided,+ tests_inference, [ "showTransaction" ~: do assertEqual "show a balanced transaction, eliding last amount"
Hledger/Data/Types.hs view
@@ -130,7 +130,7 @@ data AmountStyle = AmountStyle { ascommodityside :: Side, -- ^ does the symbol appear on the left or the right ? ascommodityspaced :: Bool, -- ^ space between symbol and quantity ?- asprecision :: Int, -- ^ number of digits displayed after the decimal point+ asprecision :: !Int, -- ^ number of digits displayed after the decimal point asdecimalpoint :: Maybe Char, -- ^ character used as decimal point: period or comma. Nothing means "unspecified, use default" asdigitgroups :: Maybe DigitGroupStyle -- ^ style for displaying digit groups, if any } deriving (Eq,Ord,Read,Show,Typeable,Data,Generic)@@ -199,8 +199,9 @@ ptype :: PostingType, ptags :: [Tag], -- ^ tag names and values, extracted from the comment pbalanceassertion :: Maybe Amount, -- ^ optional: the expected balance in this commodity in the account after this posting- ptransaction :: Maybe Transaction -- ^ this posting's parent transaction (co-recursive types).+ ptransaction :: Maybe Transaction, -- ^ this posting's parent transaction (co-recursive types). -- Tying this knot gets tedious, Maybe makes it easier/optional.+ porigin :: Maybe Posting -- ^ original posting if this one is result of any transformations (one level only) } deriving (Typeable,Data,Generic) instance NFData Posting@@ -208,11 +209,12 @@ -- 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 g1 h1 i1 _) (Posting a2 b2 c2 d2 e2 f2 g2 h2 i2 _) = a1==a2 && b1==b2 && c1==c2 && d1==d2 && e1==e2 && f1==f2 && g1==g2 && h1==h2 && i1==i2+ (==) (Posting a1 b1 c1 d1 e1 f1 g1 h1 i1 _ _) (Posting a2 b2 c2 d2 e2 f2 g2 h2 i2 _ _) = a1==a2 && b1==b2 && c1==c2 && d1==d2 && e1==e2 && f1==f2 && g1==g2 && h1==h2 && i1==i2 +-- TODO: needs renaming, or removal if no longer needed. See also TextPosition in Hledger.UI.Editor -- | The position of parse errors (eg), like parsec's SourcePos but generic.--- File name, 1-based line number and 1-based column number.-data GenericSourcePos = GenericSourcePos FilePath Int Int+data GenericSourcePos = GenericSourcePos FilePath Int Int -- ^ name, 1-based line number and 1-based column number.+ | JournalSourcePos FilePath (Int, Int) -- ^ file name, inclusive range of 1-based line numbers (first, last). deriving (Eq, Read, Show, Ord, Data, Generic, Typeable) instance NFData GenericSourcePos
Hledger/Query.hs view
@@ -659,6 +659,8 @@ AbsEq -> abs aq == abs q -- | Does the match expression match this posting ?+--+-- Note that for account match we try both original and effective account matchesPosting :: Query -> Posting -> Bool matchesPosting (Not q) p = not $ q `matchesPosting` p matchesPosting (Any) _ = True@@ -667,7 +669,8 @@ matchesPosting (And qs) p = all (`matchesPosting` p) qs matchesPosting (Code r) p = regexMatchesCI r $ maybe "" (T.unpack . tcode) $ ptransaction p matchesPosting (Desc r) p = regexMatchesCI r $ maybe "" (T.unpack . tdescription) $ ptransaction p-matchesPosting (Acct r) p = regexMatchesCI r $ T.unpack $ paccount p -- XXX pack+matchesPosting (Acct r) p = matchesPosting p || matchesPosting (originalPosting p)+ where matchesPosting p = regexMatchesCI r $ T.unpack $ paccount p -- XXX pack matchesPosting (Date span) p = span `spanContainsDate` postingDate p matchesPosting (Date2 span) p = span `spanContainsDate` postingDate2 p matchesPosting (Status Uncleared) p = postingStatus p /= Cleared
Hledger/Read.hs view
@@ -56,7 +56,7 @@ import Hledger.Data.Types import Hledger.Read.Common import qualified Hledger.Read.JournalReader as JournalReader-import qualified Hledger.Read.LedgerReader as LedgerReader+-- import qualified Hledger.Read.LedgerReader as LedgerReader import qualified Hledger.Read.TimedotReader as TimedotReader import qualified Hledger.Read.TimeclockReader as TimeclockReader import qualified Hledger.Read.CsvReader as CsvReader@@ -76,7 +76,7 @@ ,TimeclockReader.reader ,TimedotReader.reader ,CsvReader.reader- ,LedgerReader.reader+-- ,LedgerReader.reader ] readerNames :: [String]@@ -293,7 +293,7 @@ tests_readJournal' ++ [ JournalReader.tests_Hledger_Read_JournalReader,- LedgerReader.tests_Hledger_Read_LedgerReader,+-- LedgerReader.tests_Hledger_Read_LedgerReader, TimeclockReader.tests_Hledger_Read_TimeclockReader, TimedotReader.tests_Hledger_Read_TimedotReader, CsvReader.tests_Hledger_Read_CsvReader,
Hledger/Read/Common.hs view
@@ -68,6 +68,13 @@ genericSourcePos :: SourcePos -> GenericSourcePos genericSourcePos p = GenericSourcePos (sourceName p) (fromIntegral . unPos $ sourceLine p) (fromIntegral . unPos $ sourceColumn p) +journalSourcePos :: SourcePos -> SourcePos -> GenericSourcePos+journalSourcePos p p' = JournalSourcePos (sourceName p) (fromIntegral . unPos $ sourceLine p, fromIntegral $ line')+ where line'+ | (unPos $ sourceColumn p') == 1 = unPos (sourceLine p') - 1+ | otherwise = unPos $ sourceLine p' -- might be at end of file withat last new-line++ -- | Given a megaparsec ParsedJournal parser, balance assertion flag, file -- path and file content: parse and post-process a Journal, or give an error. parseAndFinaliseJournal :: ErroringJournalParser IO ParsedJournal -> Bool
Hledger/Read/CsvReader.hs view
@@ -20,6 +20,8 @@ -- rules, rulesFileFor, parseRulesFile,+ parseAndValidateCsvRules,+ expandIncludes, transactionFromCsvRecord, -- * Tests tests_Hledger_Read_CsvReader@@ -83,13 +85,13 @@ -- | Read a Journal from the given CSV data (and filename, used for error -- messages), or return an error. Proceed as follows: -- @--- 1. parse the CSV data--- 2. identify the name of a file specifying conversion rules: either use--- the name provided, derive it from the CSV filename, or raise an error--- if the CSV filename is -.--- 3. auto-create the rules file with default rules if it doesn't exist--- 4. parse the rules file--- 5. convert the CSV records to a journal using the rules+-- 1. parse CSV conversion rules from the specified rules file, or from+-- the default rules file for the specified CSV file, if it exists,+-- or throw a parse error; if it doesn't exist, use built-in default rules+-- 2. parse the CSV data, or throw a parse error+-- 3. convert the CSV records to transactions using the rules+-- 4. if the rules file didn't exist, create it with the default rules and filename+-- 5. return the transactions as a Journal -- @ readJournalFromCsv :: Maybe FilePath -> FilePath -> Text -> IO (Either String Journal) readJournalFromCsv Nothing "-" _ = return $ Left "please use --rules-file when reading CSV from stdin"@@ -100,11 +102,13 @@ -- parse rules let rulesfile = fromMaybe (rulesFileFor csvfile) mrulesfile rulesfileexists <- doesFileExist rulesfile- when rulesfileexists $ hPrintf stderr "using conversion rules file %s\n" rulesfile- rules <-+ rulestext <- if rulesfileexists- then liftIO (runExceptT $ parseRulesFile rulesfile) >>= either throwerr return- else return defaultRules+ then do+ hPrintf stderr "using conversion rules file %s\n" rulesfile+ liftIO $ (readFile' rulesfile >>= expandIncludes (takeDirectory rulesfile))+ else return $ defaultRulesText rulesfile+ rules <- liftIO (runExceptT $ parseAndValidateCsvRules rulesfile rulestext) >>= either throwerr return dbg2IO "rules" rules -- apply skip directive@@ -114,7 +118,7 @@ oneorerror s = readDef (throwerr $ "could not parse skip value: " ++ show s) s -- parse csv- -- parsec seems to fail if you pass it "-" here+ -- parsec seems to fail if you pass it "-" here XXX try again with megaparsec let parsecfilename = if csvfile == "-" then "(stdin)" else csvfile records <- (either throwerr id . dbg2 "validateCsv" . validateCsv skip .@@ -144,7 +148,7 @@ when (not rulesfileexists) $ do hPrintf stderr "created default conversion rules file %s, edit this for better results\n" rulesfile- writeFile rulesfile $ T.unpack $ defaultRulesText rulesfile+ writeFile rulesfile $ T.unpack rulestext return $ Right nulljournal{jtxns=sortBy (comparing tdate) txns'} @@ -219,13 +223,6 @@ ," account2 assets:bank:savings\n" ] -defaultRules :: CsvRules-defaultRules =- either- (error' "Could not parse the default CSV rules, this should not happen")- id- $ parseCsvRules "" $ defaultRulesText ""- -------------------------------------------------------------------------------- -- Conversion rules parsing @@ -351,23 +348,18 @@ instance ShowErrorComponent String where showErrorComponent = id +-- | An error-throwing action that parses this file's content +-- as CSV conversion rules, interpolating any included files first, +-- and runs some extra validation checks. parseRulesFile :: FilePath -> ExceptT String IO CsvRules-parseRulesFile f = do- s <- liftIO $ (readFile' f >>= expandIncludes (takeDirectory f))- let rules = parseCsvRules f s- case rules of- Left e -> ExceptT $ return $ Left $ parseErrorPretty e- Right r -> do- r_ <- liftIO $ runExceptT $ validateRules r- ExceptT $ case r_ of- Left e -> return $ Left $ parseErrorPretty $ toParseError e- Right r -> return $ Right r- where- toParseError :: forall s. Ord s => s -> ParseError Char s- toParseError s = (mempty :: ParseError Char s) { errorCustom = S.singleton s}+parseRulesFile f = + liftIO (readFile' f >>= expandIncludes (takeDirectory f)) >>= parseAndValidateCsvRules f --- | Pre-parse csv rules to interpolate included files, recursively.--- This is a cheap hack to avoid rewriting the existing parser.+-- | Look for hledger rules file-style include directives in this text,+-- and interpolate the included files, recursively.+-- Included file paths may be relative to the directory of the+-- provided file path.+-- This is a cheap hack to avoid rewriting the CSV rules parser. expandIncludes :: FilePath -> T.Text -> IO T.Text expandIncludes basedir content = do let (ls,rest) = break (T.isPrefixOf "include") $ T.lines content@@ -380,6 +372,23 @@ return $ T.unlines [T.unlines ls, included, T.unlines ls'] ls' -> return $ T.unlines $ ls ++ ls' -- should never get here +-- | An error-throwing action that parses this text as CSV conversion rules +-- and runs some extra validation checks. The file path is for error messages.+parseAndValidateCsvRules :: FilePath -> T.Text -> ExceptT String IO CsvRules+parseAndValidateCsvRules rulesfile s = do+ let rules = parseCsvRules rulesfile s+ case rules of+ Left e -> ExceptT $ return $ Left $ parseErrorPretty e+ Right r -> do+ r_ <- liftIO $ runExceptT $ validateRules r+ ExceptT $ case r_ of+ Left e -> return $ Left $ parseErrorPretty $ toParseError e+ Right r -> return $ Right r+ where+ toParseError :: forall s. Ord s => s -> ParseError Char s+ toParseError s = (mempty :: ParseError Char s) { errorCustom = S.singleton s}++-- | Parse this text as CSV conversion rules. The file path is for error messages. parseCsvRules :: FilePath -> T.Text -> Either (ParseError Char Dec) CsvRules -- parseCsvRules rulesfile s = runParser csvrulesfile nullrules{baseAccount=takeBaseName rulesfile} rulesfile s parseCsvRules rulesfile s =
Hledger/Read/JournalReader.hs view
@@ -426,7 +426,7 @@ transactionp :: MonadIO m => ErroringJournalParser m Transaction transactionp = do -- ptrace "transactionp"- sourcepos <- genericSourcePos <$> getPosition+ pos <- getPosition date <- datep <?> "transaction" edate <- optional (secondarydatep date) <?> "secondary date" lookAhead (lift spacenonewline <|> newline) <?> "whitespace or newline"@@ -436,6 +436,8 @@ comment <- try followingcommentp <|> (newline >> return "") let tags = commentTags comment postings <- postingsp (Just date)+ pos' <- getPosition+ let sourcepos = journalSourcePos pos pos' return $ txnTieKnot $ Transaction 0 sourcepos date edate status code description comment tags postings "" #ifdef TESTS@@ -622,6 +624,12 @@ tests_Hledger_Read_JournalReader = TestList $ concat [ -- test_numberp+ [+ "showParsedMarketPrice" ~: do+ let mp = parseWithState mempty marketpricedirectivep "P 2017/01/30 BTC $922.83\n"+ mpString = (fmap . fmap) showMarketPrice mp+ mpString `is` (Just (Right "P 2017/01/30 BTC $922.83"))+ ] ] {- old hunit tests
− Hledger/Read/LedgerReader.hs
@@ -1,177 +0,0 @@-{-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns, NoMonoLocalBinds, ScopedTypeVariables, FlexibleContexts, TupleSections, OverloadedStrings #-}--module Hledger.Read.LedgerReader (---- * exports-- -- * Reader- reader-- -- * Tests- ,tests_Hledger_Read_LedgerReader--)-where---- * imports-import Prelude ()-import Prelude.Compat hiding (readFile)-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Except (ExceptT(..), throwError)-import Data.Maybe-import Data.Text (Text, pack)-import Data.Text.Encoding (encodeUtf8)--- import Safe-import Test.HUnit--- #ifdef TESTS--- import Test.Framework--- import Text.Megaparsec.Error--- #endif-import Text.Megaparsec (eof)--- import Text.Printf-import System.Time-import qualified Filesystem.Path.CurrentOS as F--import Hledger.Data-import Hledger.Read.Common-import Hledger.Utils-import Ledger.Parser.Text-import Text.Trifecta.Result (Result(..))---- $setup--- >>> :set -XOverloadedStrings----- * reader--reader :: Reader-reader = Reader- {rFormat = "ledger"- ,rExtensions = []- ,rParser = parse- ,rExperimental = True- }---- | Generate an action that parses and post-processes a "Journal" from a--- C++ Ledger journal, or raises an error.-parse :: Maybe FilePath -> Bool -> FilePath -> Text -> ExceptT String IO Journal-parse _mrulespath assrt path txt = do- let- path' = F.decodeString path -- XXX can fail, according to docs- bs = encodeUtf8 txt- r = parseJournalFile path' bs- case r of- Failure ei -> throwError $ show ei- Success res -> do- -- dbg7IO "raw entities" res- ejus <- liftIO $ sequence <$> mapM rawEntityInSituToJournalUpdate res- case ejus of- Left err -> throwError err- Right jus -> do- let pj = foldr (flip (.)) id jus nulljournal- t <- liftIO getClockTime- either throwError return $ journalFinalise t path txt assrt pj---- (I'm not too fond of journal update functions, but ok..)--- | Convert a ledger4 RawEntityInSitu - representing a parsed transaction,--- directive, comment etc. - into either a journal update function or an error.--- Currently converts only transactions, and ignores some transaction fields.--- Runs in IO because it uses some hledger parsers that have some need for that.-rawEntityInSituToJournalUpdate :: RawEntityInSitu -> IO (Either String (ParsedJournal -> ParsedJournal))-rawEntityInSituToJournalUpdate RawEntityInSitu{rawEntity=RawTransactionEntity (rt@RawTransaction{- rawTxnDate = date -- :: !String- , rawTxnDateAux = mdate2 -- :: Maybe String- , rawTxnState = _mstatus -- :: Maybe Char- , rawTxnCode = _mcode -- :: Maybe String- , rawTxnDesc = desc -- :: !String- , rawTxnNote = _mnote -- :: Maybe String- , rawTxnPosts = rps -- :: ![RawPosting]- })}- = do- let md = parsedateM date- md2 = mdate2 >>= parsedateM- dateerr = return . Left . ("could not parse date "++)- case (md, mdate2, md2) of- (Nothing, _, _) -> dateerr date- (_, Just date2, Nothing) -> dateerr date2- (Just d, _, _) -> do- eps <- sequence . catMaybes <$> mapM rawPostingToPosting rps- case eps of- Left err -> return $ Left err- Right ps -> do- let t = nulltransaction{- -- XXX TODO more complete transaction parsing- -- tindex -- :: Integer, -- ^ this transaction's 1-based position in the input stream, or 0 when not available- -- tsourcepos -- :: GenericSourcePos,- tdate = d -- :: Day- , tdate2 = md2 -- :: Maybe Day- -- tstatus -- :: ClearedStatus,- -- tcode -- :: Text,- , tdescription = pack desc -- :: Text,- -- tcomment -- :: Text, -- ^ this transaction's comment lines, as a single non-indented multi-line string- -- ttags -- :: [Tag], -- ^ tag names and values, extracted from the comment- , tpostings = ps -- :: [Posting], -- ^ this transaction's postings- -- tpreceding_comment_lines -- :: Text -- ^ any comment lines immediately preceding this transaction- }- dbg7IO "raw transaction" rt- dbg7IO "cooked transaction" t- return $ Right $ addTransaction t--- TODO convert other entities-rawEntityInSituToJournalUpdate _ = return $ Right id---- | Convert a ledger4 RawPosting to a hledger Posting or an error message.--- Currently ignores some posting fields, and the RawPostingNote variant--- (which represents a comment line, not a posting; returns Nothing for these).-rawPostingToPosting :: RawPosting -> IO (Maybe (Either String Posting))-rawPostingToPosting RawPosting{- -- TODO- rawPostState = _mstatus -- :: Maybe Char- , rawPostAccount = acct -- :: !String- , rawPostAmount = mamtstr -- :: Maybe String- , rawPostNote = _mnote -- :: Maybe String- } = do- eamt <- runErroringJournalParser (spaceandamountormissingp <* eof) $ pack $ maybe "" (' ':) mamtstr- case eamt of- Left err -> return $ Just $ Left err- Right (amt :: MixedAmount) -> do- return $ Just $ Right nullposting{- -- pdate -- :: Maybe Day, -- ^ this posting's date, if different from the transaction's- -- , pdate2 -- :: Maybe Day, -- ^ this posting's secondary date, if different from the transaction's- -- , pstatus -- :: ClearedStatus,- paccount = pack acct -- :: AccountName,- , pamount = amt -- :: MixedAmount,- -- , pcomment -- :: Text, -- ^ this posting's comment lines, as a single non-indented multi-line string- -- , ptype -- :: PostingType,- -- , ptags -- :: [Tag], -- ^ tag names and values, extracted from the comment- -- , pbalanceassertion -- :: Maybe MixedAmount, -- ^ optional: the expected balance in the account after this posting- -- , ptransaction -- :: Maybe Transaction -- ^ this posting's parent transaction (co-recursive types).- }-rawPostingToPosting (RawPostingNote _) = return Nothing----- A raw parse example:------ 2010/01/01 * T1--- Accounts:Hub 30.00 USD--- Accounts:A1--- & comments...------ [--- RawTransactionEntity (RawTransaction {rawTxnDate = "2010/01/01",--- rawTxnDateAux = Nothing, rawTxnState = Just '*', rawTxnCode = Nothing,--- rawTxnDesc = "T1", rawTxnNote = Nothing, rawTxnPosts = [--- RawPosting {rawPostState = Nothing, rawPostAccount = "Accounts:Hub",--- rawPostAmount = Just "30.00 USD", rawPostNote = Nothing},--- RawPosting {rawPostState = Nothing, rawPostAccount = "Accounts:A1",--- rawPostAmount = Nothing, rawPostNote = Nothing}--- ]})--- ,Whitespace "\n"--- ,FileComment "2010/01/01 * T2\n Accounts:Hub 40.00 USD\n Accounts:A2\n\n2010/01/01 * T3\n Accounts:Hub 10.00 USD\n Accounts:A1\n"--- ,Whitespace "\n"--- ,FileComment " Now, I wish to list all transactions that pay into Accounts:Hub ONLY from \n Accounts:A1. How can I write a query like that? My cursory filtering \n attempts didn't work.\n"--- ,Whitespace "\n"--- ,FileComment " 2. The register displays all transactions that put a commodity into or take \n a commodity out of an account. Can I display where the money comes \n from/goes to as well? In other words, can I get the complete transaction \n detail in the register?\n"--- ]------ * hunit tests--tests_Hledger_Read_LedgerReader = TestList $ concat [- ]
Hledger/Reports/BalanceReport.hs view
@@ -19,6 +19,7 @@ balanceReport, balanceReportValue, mixedAmountValue,+ amountValue, flatShowsExclusiveBalance, -- * Tests
Hledger/Reports/MultiBalanceReports.hs view
@@ -73,14 +73,14 @@ type ClippedAccountName = AccountName -- | Generates a single column BalanceReport like balanceReport, but uses--- multiBalanceReport, so supports --historical. Does not support--- boring parent eliding yet.+-- multiBalanceReport, so supports --historical. +-- TODO Does not support boring parent eliding or --flat yet. singleBalanceReport :: ReportOpts -> Query -> Journal -> BalanceReport singleBalanceReport opts q j = (rows', total) where MultiBalanceReport (_, rows, (totals, _, _)) = multiBalanceReport opts q j rows' = [(a- ,if tree_ opts then a' else a -- BalanceReport expects full account name here with --flat+ ,if flat_ opts then a else a' -- BalanceReport expects full account name here with --flat ,if tree_ opts then d-1 else 0 -- BalanceReport uses 0-based account depths , headDef nullmixedamt amts -- 0 columns is illegal, should not happen, return zeroes if it does ) | (a,a',d, amts, _, _) <- rows]
Hledger/Reports/ReportOptions.hs view
@@ -41,6 +41,7 @@ import Data.Default import Safe import Test.HUnit+import Text.Megaparsec.Error import Hledger.Data import Hledger.Query@@ -90,6 +91,7 @@ ,row_total_ :: Bool ,no_total_ :: Bool ,value_ :: Bool+ ,pretty_tables_ :: Bool } deriving (Show, Data, Typeable) instance Default ReportOpts where def = defreportopts@@ -117,6 +119,7 @@ def def def+ def rawOptsToReportOpts :: RawOpts -> IO ReportOpts rawOptsToReportOpts rawopts = checkReportOpts <$> do@@ -143,6 +146,7 @@ ,row_total_ = boolopt "row-total" rawopts' ,no_total_ = boolopt "no-total" rawopts' ,value_ = boolopt "value" rawopts'+ ,pretty_tables_ = boolopt "pretty-tables" rawopts' } -- | Do extra validation of raw option values, raising an error if there's a problem.@@ -151,11 +155,11 @@ -- our standard behaviour is to accept conflicting options actually, -- using the last one - more forgiving for overriding command-line aliases -- | countopts ["change","cumulative","historical"] > 1--- = optserror "please specify at most one of --change, --cumulative, --historical"+-- = usageError "please specify at most one of --change, --cumulative, --historical" -- | countopts ["flat","tree"] > 1--- = optserror "please specify at most one of --flat, --tree"+-- = usageError "please specify at most one of --flat, --tree" -- | countopts ["daily","weekly","monthly","quarterly","yearly"] > 1--- = optserror "please specify at most one of --daily, "+-- = usageError "please specify at most one of --daily, " | otherwise = rawopts -- where -- countopts = length . filter (`boolopt` rawopts)@@ -163,7 +167,7 @@ -- | Do extra validation of report options, raising an error if there's a problem. checkReportOpts :: ReportOpts -> ReportOpts checkReportOpts ropts@ReportOpts{..} =- either optserror (const ropts) $ do+ either usageError (const ropts) $ do case depth_ of Just d | d < 0 -> Left "--depth should have a positive number" _ -> Right ()@@ -207,11 +211,11 @@ where begindatefromrawopt d (n,v) | n == "begin" =- either (\e -> optserror $ "could not parse "++n++" date: "++show e) Just $+ either (\e -> usageError $ "could not parse "++n++" date: "++parseErrorPretty e) Just $ fixSmartDateStrEither' d (T.pack v) | n == "period" = case- either (\e -> optserror $ "could not parse period option: "++show e) id $+ either (\e -> usageError $ "could not parse period option: "++parseErrorPretty e) id $ parsePeriodExpr d (stripquotes $ T.pack v) of (_, DateSpan (Just b) _) -> Just b@@ -225,11 +229,11 @@ where enddatefromrawopt d (n,v) | n == "end" =- either (\e -> optserror $ "could not parse "++n++" date: "++show e) Just $+ either (\e -> usageError $ "could not parse "++n++" date: "++parseErrorPretty e) Just $ fixSmartDateStrEither' d (T.pack v) | n == "period" = case- either (\e -> optserror $ "could not parse period option: "++show e) id $+ either (\e -> usageError $ "could not parse period option: "++parseErrorPretty e) id $ parsePeriodExpr d (stripquotes $ T.pack v) of (_, DateSpan _ (Just e)) -> Just e@@ -243,7 +247,7 @@ where intervalfromrawopt (n,v) | n == "period" =- either (\e -> optserror $ "could not parse period option: "++show e) (Just . fst) $+ either (\e -> usageError $ "could not parse period option: "++parseErrorPretty e) (Just . fst) $ parsePeriodExpr nulldate (stripquotes $ T.pack v) -- reference date does not affect the interval | n == "daily" = Just $ Days 1 | n == "weekly" = Just $ Weeks 1
Hledger/Utils.hs view
@@ -28,13 +28,13 @@ -- Debug.Trace.trace, -- module Data.PPrint, -- module Hledger.Utils.UTF8IOCompat- SystemString,fromSystemString,toSystemString,error',userError',+ SystemString,fromSystemString,toSystemString,error',userError',usageError, -- the rest need to be done in each module I think ) where import Control.Monad (liftM) -- import Data.Char--- import Data.List+import Data.List -- import Data.Maybe -- import Data.PPrint import Data.Text (Text)@@ -58,7 +58,7 @@ import Hledger.Utils.Tree -- import Prelude hiding (readFile,writeFile,appendFile,getContents,putStr,putStrLn) -- import Hledger.Utils.UTF8IOCompat (readFile,writeFile,appendFile,getContents,putStr,putStrLn)-import Hledger.Utils.UTF8IOCompat (SystemString,fromSystemString,toSystemString,error',userError')+import Hledger.Utils.UTF8IOCompat (SystemString,fromSystemString,toSystemString,error',userError',usageError) -- tuples@@ -173,4 +173,39 @@ -- | Total version of maximum, for integral types, giving 0 for an empty list. maximum' :: Integral a => [a] -> a maximum' [] = 0-maximum' xs = maximum xs+maximum' xs = maximumStrict xs++-- | Strict version of sum that doesn’t leak space+{-# INLINABLE sumStrict #-}+sumStrict :: Num a => [a] -> a+sumStrict = foldl' (+) 0++-- | Strict version of maximum that doesn’t leak space+{-# INLINABLE maximumStrict #-}+maximumStrict :: Ord a => [a] -> a+maximumStrict = foldl1' max++-- | Strict version of minimum that doesn’t leak space+{-# INLINABLE minimumStrict #-}+minimumStrict :: Ord a => [a] -> a+minimumStrict = foldl1' min++-- | This is a version of sequence based on difference lists. It is+-- slightly faster but we mostly use it because it uses the heap+-- instead of the stack. This has the advantage that Neil Mitchell’s+-- trick of limiting the stack size to discover space leaks doesn’t+-- show this as a false positive.+{-# INLINABLE sequence' #-}+sequence' :: Monad f => [f a] -> f [a]+sequence' ms = do+ h <- go id ms+ return (h [])+ where+ go h [] = return h+ go h (m:ms) = do+ x <- m+ go (h . (x :)) ms++{-# INLINABLE mapM' #-}+mapM' :: Monad f => (a -> f b) -> [a] -> f [b]+mapM' f = sequence' . map f
Hledger/Utils/UTF8IOCompat.hs view
@@ -16,6 +16,7 @@ Not carefully tested. -}+-- TODO obsolete ? module Hledger.Utils.UTF8IOCompat ( readFile,@@ -33,6 +34,7 @@ toSystemString, error', userError',+ usageError, ) where @@ -130,3 +132,8 @@ -- | A SystemString-aware version of userError. userError' :: String -> IOError userError' = userError . toSystemString++-- | A SystemString-aware version of error that adds a usage hint.+usageError :: String -> a+usageError = error' . (++ " (use -h to see usage)") +
doc/hledger_csv.5 view
@@ -1,5 +1,5 @@ -.TH "hledger_csv" "5" "December 2016" "hledger 1.1" "hledger User Manuals"+.TH "hledger_csv" "5" "March 2017" "hledger 1.2" "hledger User Manuals"
doc/hledger_csv.5.info view
@@ -1,23 +1,21 @@-This is hledger-lib/doc/hledger_csv.5.info, produced by makeinfo-version 4.8 from stdin.+This is hledger_csv.5.info, produced by makeinfo version 6.0 from stdin. -File: hledger_csv.5.info, Node: Top, Up: (dir)+File: hledger_csv.5.info, Node: Top, Next: CSV RULES, Up: (dir) -hledger_csv(5) hledger 1.1+hledger_csv(5) hledger 1.2 ************************** hledger can read CSV files, converting each CSV record into a journal entry (transaction), if you provide some conversion hints in a "rules-file". This file should be named like the CSV file with an additional-`.rules' suffix (eg: `mybank.csv.rules'); or, you can specify the file-with `--rules-file PATH'. hledger will create it if necessary, with-some default rules which you'll need to adjust. At minimum, the rules-file must specify the `date' and `amount' fields. For an example, see+file". This file should be named like the CSV file with an additional+'.rules' suffix (eg: 'mybank.csv.rules'); or, you can specify the file+with '--rules-file PATH'. hledger will create it if necessary, with+some default rules which you'll need to adjust. At minimum, the rules+file must specify the 'date' and 'amount' fields. For an example, see How to read CSV files. To learn about _exporting_ CSV, see CSV output.- * Menu: * CSV RULES::@@ -30,8 +28,7 @@ *********** The following six kinds of rule can appear in the rules file, in any-order. Blank lines and lines beginning with `#' or `;' are ignored.-+order. Blank lines and lines beginning with '#' or ';' are ignored. * Menu: * skip::@@ -47,11 +44,10 @@ 1.1 skip ======== -`skip'_`N'_-- Skip this number of CSV records at the beginning. You'll need this-whenever your CSV data contains header lines. Eg:+'skip'_'N'_ + Skip this number of CSV records at the beginning. You'll need this+whenever your CSV data contains header lines. Eg: # ignore the first CSV line skip 1@@ -62,26 +58,22 @@ 1.2 date-format =============== -`date-format'_`DATEFMT'_+'date-format'_'DATEFMT'_ - When your CSV date fields are not formatted like `YYYY/MM/DD' (or-`YYYY-MM-DD' or `YYYY.MM.DD'), you'll need to specify the format.+ When your CSV date fields are not formatted like 'YYYY/MM/DD' (or+'YYYY-MM-DD' or 'YYYY.MM.DD'), you'll need to specify the format. DATEFMT is a strptime-like date parsing pattern, which must parse the-date field values completely. Examples:-+date field values completely. Examples: # for dates like "6/11/2013": date-format %-d/%-m/%Y - # for dates like "11/06/2013": date-format %m/%d/%Y - # for dates like "2013-Nov-06": date-format %Y-%h-%d - # for dates like "11/6/2013 11:32 PM": date-format %-m/%-d/%Y %l:%M %p @@ -91,15 +83,13 @@ 1.3 field list ============== -`fields'_`FIELDNAME1'_, _`FIELDNAME2'_...+'fields'_'FIELDNAME1'_, _'FIELDNAME2'_... This (a) names the CSV fields, in order (names may not contain whitespace; uninteresting names may be left blank), and (b) assigns them to journal entry fields if you use any of these standard field names:-`date', `date2', `status', `code', `description', `comment',-`account1', `account2', `amount', `amount-in', `amount-out',-`currency'. Eg:-+'date', 'date2', 'status', 'code', 'description', 'comment', 'account1',+'account2', 'amount', 'amount-in', 'amount-out', 'currency'. Eg: # use the 1st, 2nd and 4th CSV fields as the entry's date, description and amount, # and give the 7th and 8th fields meaningful names for later reference:@@ -115,17 +105,15 @@ 1.4 field assignment ==================== -_`ENTRYFIELDNAME'_ _`FIELDVALUE'_+_'ENTRYFIELDNAME'_ _'FIELDVALUE'_ This sets a journal entry field (one of the standard names above) to the given text value, which can include CSV field values interpolated by-name (`%CSVFIELDNAME') or 1-based position (`%N'). Eg:-+name ('%CSVFIELDNAME') or 1-based position ('%N'). Eg: # set the amount to the 4th CSV field with "USD " prepended amount USD %4 - # combine three fields to make a comment (containing two tags) comment note: %somefield - %anotherfield, date: %1 @@ -138,28 +126,26 @@ 1.5 conditional block ===================== -`if' _`PATTERN'_-_`FIELDASSIGNMENTS'_...+'if' _'PATTERN'_+ _'FIELDASSIGNMENTS'_... - `if'-_`PATTERN'_-_`PATTERN'_...-_`FIELDASSIGNMENTS'_...+ 'if'+_'PATTERN'_+_'PATTERN'_...+ _'FIELDASSIGNMENTS'_... This applies one or more field assignments, only to those CSV records-matched by one of the PATTERNs. The patterns are case-insensitive+matched by one of the PATTERNs. The patterns are case-insensitive regular expressions which match anywhere within the whole CSV record-(it's not yet possible to match within a specific field). When there are-multiple patterns they can be written on separate lines, unindented. The-field assignments are on separate lines indented by at least one space.-Examples:-+(it's not yet possible to match within a specific field). When there+are multiple patterns they can be written on separate lines, unindented.+The field assignments are on separate lines indented by at least one+space. Examples: # if the CSV record contains "groceries", set account2 to "expenses:groceries" if groceries account2 expenses:groceries - # if the CSV record contains any of these patterns, set account2 and comment as shown if monthly service fee@@ -174,13 +160,12 @@ 1.6 include =========== -`include'_`RULESFILE'_+'include'_'RULESFILE'_ - Include another rules file at this point. `RULESFILE' is either an+ Include another rules file at this point. 'RULESFILE' is either an absolute file path or a path relative to the current file's directory. Eg: - # rules reused with several CSV files include common.rules @@ -190,42 +175,41 @@ 2 TIPS ****** -Each generated journal entry will have two postings, to `account1' and-`account2' respectively. Currently it's not possible to generate+Each generated journal entry will have two postings, to 'account1' and+'account2' respectively. Currently it's not possible to generate entries with more than two postings. If the CSV has debit/credit amounts in separate fields, assign to the-`amount-in' and `amount-out' pseudo fields instead of `amount'.+'amount-in' and 'amount-out' pseudo fields instead of 'amount'. If the CSV has the currency in a separate field, assign that to the-`currency' pseudo field which will be automatically prepended to the-amount. (Or you can do the same thing with a field assignment.)+'currency' pseudo field which will be automatically prepended to the+amount. (Or you can do the same thing with a field assignment.) If an amount value is parenthesised, it will be de-parenthesised and sign-flipped automatically. - The generated journal entries will be sorted by date. The original+ The generated journal entries will be sorted by date. The original order of same-day entries will be preserved, usually. - Tag Table:-Node: Top90-Node: CSV RULES795-Ref: #csv-rules901-Node: skip1144-Ref: #skip1240-Node: date-format1411-Ref: #date-format1540-Node: field list2049-Ref: #field-list2188-Node: field assignment2883-Ref: #field-assignment3040-Node: conditional block3545-Ref: #conditional-block3701-Node: include4588-Ref: #include4699-Node: TIPS4930-Ref: #tips5014+Node: Top74+Node: CSV RULES800+Ref: #csv-rules906+Node: skip1149+Ref: #skip1245+Node: date-format1417+Ref: #date-format1546+Node: field list2052+Ref: #field-list2191+Node: field assignment2886+Ref: #field-assignment3043+Node: conditional block3547+Ref: #conditional-block3703+Node: include4599+Ref: #include4710+Node: TIPS4941+Ref: #tips5025 End Tag Table
doc/hledger_csv.5.txt view
@@ -166,4 +166,4 @@ -hledger 1.1 December 2016 hledger_csv(5)+hledger 1.2 March 2017 hledger_csv(5)
doc/hledger_journal.5 view
@@ -1,6 +1,6 @@ .\"t -.TH "hledger_journal" "5" "December 2016" "hledger 1.1" "hledger User Manuals"+.TH "hledger_journal" "5" "March 2017" "hledger 1.2" "hledger User Manuals" @@ -368,6 +368,7 @@ This order dependence does bring an advantage: precise control over the order of postings and assertions within a day, so you can assert intra\-day balances.+.SS Assertions and included files .PP With included files, things are a little more complicated. Including preserves the ordering of postings and assertions.@@ -375,6 +376,11 @@ across different files, and you also want to assert the account\[aq]s balance on the same day, you\[aq]ll have to put the assertion in the right file.+.SS Assertions and multiple \-f options+.PP+Balance assertions don\[aq]t work well across files specified with+multiple \-f options.+Use include or concatenate the files instead. .SS Assertions and commodities .PP The asserted balance must be a simple single\-commodity amount, and in@@ -548,8 +554,7 @@ .fi .PP DATE is a simple date as usual.-COMMODITYBEINGPRICED is the symbol of the commodity being priced (just-the symbol, no quantity).+COMMODITYBEINGPRICED is the symbol of the commodity being priced. UNITPRICE is an ordinary amount (symbol and quantity) in a second commodity, specifying the unit price or conversion rate for the first commodity in terms of the second, on the given date.@@ -607,32 +612,81 @@ .fi .SS Tags .PP-A \f[I]tag\f[] is a word followed by a full colon inside a transaction-or posting comment.-You can write multiple tags, comma separated.-Eg: \f[C];\ a\ comment\ containing\ sometag:,\ anothertag:\f[].-You can search for tags with the \f[C]tag:\f[] query.+Tags are a way to add extra labels or labelled data to postings and+transactions, which you can then search or pivot on. .PP-A tag can also have a value, which is any text between the colon and the-next comma or newline, excluding leading/trailing whitespace.-(So hledger tag values can not contain commas or newlines).+A simple tag is a word (which may contain hyphens) followed by a full+colon, written inside a transaction or posting comment line:+.IP+.nf+\f[C]+2017/1/16\ bought\ groceries\ \ \ \ ;\ sometag:+\f[]+.fi .PP+Tags can have a value, which is the text after the colon, up to the next+comma or end of line, with leading/trailing whitespace removed:+.IP+.nf+\f[C]+\ \ \ \ expenses:food\ \ \ \ $10\ \ \ ;\ a\-posting\-tag:\ the\ tag\ value+\f[]+.fi+.PP+Note this means hledger\[aq]s tag values can not contain commas or+newlines.+Ending at commas means you can write multiple short tags on one line,+comma separated:+.IP+.nf+\f[C]+\ \ \ \ assets:checking\ \ \ \ \ \ \ ;\ a\ comment\ containing\ tag1:,\ tag2:\ some\ value\ ...+\f[]+.fi+.PP+Here,+.IP \[bu] 2+"\f[C]a\ comment\ containing\f[]" is just comment text, not a tag+.IP \[bu] 2+"\f[C]tag1\f[]" is a tag with no value+.IP \[bu] 2+"\f[C]tag2\f[]" is another tag, whose value is+"\f[C]some\ value\ ...\f[]"+.PP Tags in a transaction comment affect the transaction and all of its postings, while tags in a posting comment affect only that posting.-For example, the following transaction has three tags (A, TAG2,-third\-tag) and the posting has four (A, TAG2, third\-tag,-posting\-tag):+For example, the following transaction has three tags (\f[C]A\f[],+\f[C]TAG2\f[], \f[C]third\-tag\f[]) and the posting has four (those plus+\f[C]posting\-tag\f[]): .IP .nf \f[C] 1/1\ a\ transaction\ \ ;\ A:,\ TAG2:-\ \ \ \ ;\ third\-tag:\ a\ third\ transaction\ tag,\ this\ time\ with\ a\ value+\ \ \ \ ;\ third\-tag:\ a\ third\ transaction\ tag,\ <\-\ with\ a\ value \ \ \ \ (a)\ \ $1\ \ ;\ posting\-tag: \f[] .fi .PP Tags are like Ledger\[aq]s metadata feature, except hledger\[aq]s tag values are simple strings.+.SS Implicit tags+.PP+Some predefined "implicit" tags are also provided:+.IP \[bu] 2+\f[C]code\f[] \- the transaction\[aq]s code field+.IP \[bu] 2+\f[C]description\f[] \- the transaction\[aq]s description+.IP \[bu] 2+\f[C]payee\f[] \- the part of description before \f[C]|\f[], or all of+it+.IP \[bu] 2+\f[C]note\f[] \- the part of description after \f[C]|\f[], or all of it+.PP+\f[C]payee\f[] and \f[C]note\f[] support descriptions written in a+special \f[C]PAYEE\ |\ NOTE\f[] format, accessing the parts before and+after the pipe character respectively.+For descriptions not containing a pipe character they are the same as+\f[C]description\f[]. .SS Directives .SS Account aliases .PP@@ -650,7 +704,7 @@ .IP \[bu] 2 customising reports .PP-See also How to use account aliases.+See also Cookbook: rewrite account names. .SS Basic aliases .PP To set an account alias, use the \f[C]alias\f[] directive in your
doc/hledger_journal.5.info view
@@ -1,33 +1,32 @@-This is hledger-lib/doc/hledger_journal.5.info, produced by makeinfo-version 4.8 from stdin.+This is hledger_journal.5.info, produced by makeinfo version 6.0 from+stdin. -File: hledger_journal.5.info, Node: Top, Up: (dir)+File: hledger_journal.5.info, Node: Top, Next: FILE FORMAT, Up: (dir) -hledger_journal(5) hledger 1.1+hledger_journal(5) hledger 1.2 ****************************** hledger's usual data source is a plain text file containing journal-entries in hledger journal format. This file represents a standard-accounting general journal. I use file names ending in `.journal', but-that's not required. The journal file contains a number of transaction+entries in hledger journal format. This file represents a standard+accounting general journal. I use file names ending in '.journal', but+that's not required. The journal file contains a number of transaction entries, each describing a transfer of money (or any commodity) between two or more named accounts, in a simple format readable by both hledger and humans. hledger's journal format is a compatible subset, mostly, of ledger's journal format, so hledger can work with compatible ledger journal files-as well. It's safe, and encouraged, to run both hledger and ledger on+as well. It's safe, and encouraged, to run both hledger and ledger on the same journal file, eg to validate the results you're getting. You can use hledger without learning any more about this file; just-use the add or web commands to create and update it. Many users, though,-also edit the journal file directly with a text editor, perhaps assisted-by the helper modes for emacs or vim.+use the add or web commands to create and update it. Many users,+though, also edit the journal file directly with a text editor, perhaps+assisted by the helper modes for emacs or vim. Here's an example: - ; A sample journal file. This is a comment. 2008/01/01 income ; <- transaction's first line starts in column 0, contains date and description@@ -82,31 +81,26 @@ 1.1 Transactions ================ -Transactions are represented by journal entries. Each begins with a+Transactions are represented by journal entries. Each begins with a simple date in column 0, followed by three optional fields with spaces between them: - * a status flag, which can be empty or `!' or `*' (meaning+ * a status flag, which can be empty or '!' or '*' (meaning "uncleared", "pending" and "cleared", or whatever you want)- * a transaction code (eg a check number),- * and/or a description - then some number of postings, of some amount to some account. Each+ then some number of postings, of some amount to some account. Each posting is on its own line, consisting of: * indentation of one or more spaces (or tabs)-- * optionally, a `!' or `*' status flag followed by a space-+ * optionally, a '!' or '*' status flag followed by a space * an account name, optionally containing single spaces- * optionally, two or more spaces or tabs followed by an amount Usually there are two or more postings, though one or none is also-possible. The posting amounts within a transaction must always balance,-ie add up to 0. Optionally one amount can be left blank, in which case+possible. The posting amounts within a transaction must always balance,+ie add up to 0. Optionally one amount can be left blank, in which case it will be inferred. @@ -128,11 +122,11 @@ ------------------ Within a journal file, transaction dates use Y/M/D (or Y-M-D or Y.M.D)-Leading zeros are optional. The year may be omitted, in which case it+Leading zeros are optional. The year may be omitted, in which case it will be inferred from the context - the current transaction, the default year set with a default year directive, or the current date when the-command is run. Some examples: `2010/01/31', `1/31', `2010-01-31',-`2010.1.31'.+command is run. Some examples: '2010/01/31', '1/31', '2010-01-31',+'2010.1.31'. File: hledger_journal.5.info, Node: Secondary dates, Next: Posting dates, Prev: Simple dates, Up: Dates@@ -141,40 +135,37 @@ --------------------- Real-life transactions sometimes involve more than one date - eg the-date you write a cheque, and the date it clears in your bank. When you+date you write a cheque, and the date it clears in your bank. When you want to model this, eg for more accurate balances, you can specify-individual posting dates, which I recommend. Or, you can use the+individual posting dates, which I recommend. Or, you can use the secondary dates (aka auxiliary/effective dates) feature, supported for compatibility with Ledger. A secondary date can be written after the primary date, separated by-an equals sign. The primary date, on the left, is used by default; the-secondary date, on the right, is used when the `--date2' flag is-specified (`--aux-date' or `--effective' also work).+an equals sign. The primary date, on the left, is used by default; the+secondary date, on the right, is used when the '--date2' flag is+specified ('--aux-date' or '--effective' also work). The meaning of secondary dates is up to you, but it's best to follow-a consistent rule. Eg write the bank's clearing date as primary, and+a consistent rule. Eg write the bank's clearing date as primary, and when needed, the date the transaction was initiated as secondary. - Here's an example. Note that a secondary date will use the year of+ Here's an example. Note that a secondary date will use the year of the primary date if unspecified. - 2010/2/23=2/19 movie ticket expenses:cinema $10 assets:checking - $ hledger register checking 2010/02/23 movie ticket assets:checking $-10 $-10 - $ hledger register checking --date2 2010/02/19 movie ticket assets:checking $-10 $-10 Secondary dates require some effort; you must use them consistently in your journal entries and remember whether to use or not use the-`--date2' flag for your reports. They are included in hledger for+'--date2' flag for your reports. They are included in hledger for Ledger compatibility, but posting dates are a more powerful and less confusing alternative. @@ -186,34 +177,31 @@ You can give individual postings a different date from their parent transaction, by adding a posting comment containing a tag (see below)-like `date:DATE'. This is probably the best way to control posting-dates precisely. Eg in this example the expense should appear in May+like 'date:DATE'. This is probably the best way to control posting+dates precisely. Eg in this example the expense should appear in May reports, and the deduction from checking should be reported on 6/1 for easy bank reconciliation: - 2015/5/30 expenses:food $10 ; food purchased on saturday 5/30 assets:checking ; bank cleared it on monday, date:6/1 - $ hledger -f t.j register food 2015/05/30 expenses:food $10 $10 - $ hledger -f t.j register checking 2015/06/01 assets:checking $-10 $-10 DATE should be a simple date; if the year is not specified it will-use the year of the transaction's date. You can set the secondary date-similarly, with `date2:DATE2'. The `date:' or `date2:' tags must have a-valid simple date value if they are present, eg a `date:' tag with no+use the year of the transaction's date. You can set the secondary date+similarly, with 'date2:DATE2'. The 'date:' or 'date2:' tags must have a+valid simple date value if they are present, eg a 'date:' tag with no value is not allowed. Ledger's earlier, more compact bracketed date syntax is also-supported: `[DATE]', `[DATE=DATE2]' or `[=DATE2]'. hledger will attempt-to parse any square-bracketed sequence of the `0123456789/-.='-characters in this way. With this syntax, DATE infers its year from the+supported: '[DATE]', '[DATE=DATE2]' or '[=DATE2]'. hledger will attempt+to parse any square-bracketed sequence of the '0123456789/-.='+characters in this way. With this syntax, DATE infers its year from the transaction and DATE2 infers its year from DATE. @@ -223,12 +211,13 @@ ================= Account names typically have several parts separated by a full colon,-from which hledger derives a hierarchical chart of accounts. They can be-anything you like, but in finance there are traditionally five top-level-accounts: `assets', `liabilities', `income', `expenses', and `equity'.+from which hledger derives a hierarchical chart of accounts. They can+be anything you like, but in finance there are traditionally five+top-level accounts: 'assets', 'liabilities', 'income', 'expenses', and+'equity'. - Account names may contain single spaces, eg: `assets:accounts-receivable'. Because of this, they must always be followed by *two or+ Account names may contain single spaces, eg: 'assets:accounts+receivable'. Because of this, they must always be followed by *two or more spaces* (or newline). Account names can be aliased.@@ -239,58 +228,53 @@ 1.4 Amounts =========== -After the account name, there is usually an amount. Important: between+After the account name, there is usually an amount. Important: between account name and amount, there must be *two or more spaces*. Amounts consist of a number and (usually) a currency symbol or-commodity name. Some examples:+commodity name. Some examples: - `2.00001'-`$1'-`4000 AAPL'-`3 "green apples"'-`-$1,000,000.00'-`INR 9,99,99,999.00'-`EUR -2.000.000,00'+ '2.00001'+'$1'+'4000 AAPL'+'3 "green apples"'+'-$1,000,000.00'+'INR 9,99,99,999.00'+'EUR -2.000.000,00' As you can see, the amount format is somewhat flexible: * amounts are a number (the "quantity") and optionally a currency symbol/commodity name (the "commodity").- * the commodity is a symbol, word, or phrase, on the left or right,- with or without a separating space. If the commodity contains+ with or without a separating space. If the commodity contains numbers, spaces or non-word punctuation it must be enclosed in double quotes.- * negative amounts with a commodity on the left can have the minus sign before or after it- * digit groups (thousands, or any other grouping) can be separated by commas (in which case period is used for decimal point) or periods (in which case comma is used for decimal point) You can use any of these variations when recording data, but when hledger displays amounts, it will choose a consistent format for each-commodity. (Except for price amounts, which are always formatted as-written). The display format is chosen as follows:+commodity. (Except for price amounts, which are always formatted as+written). The display format is chosen as follows: * if there is a commodity directive specifying the format, that is used- * otherwise the format is inferred from the first posting amount in- that commodity in the journal, and the precision (number of- decimal places) will be the maximum from all posting amounts in- that commmodity-- * or if there are no such amounts in the journal, a default format- is used (like `$1000.00').+ that commodity in the journal, and the precision (number of decimal+ places) will be the maximum from all posting amounts in that+ commmodity+ * or if there are no such amounts in the journal, a default format is+ used (like '$1000.00'). Price amounts and amounts in D directives usually don't affect amount-format inference, but in some situations they can do so indirectly. (Eg+format inference, but in some situations they can do so indirectly. (Eg when D's default commodity is applied to a commodity-less amount, or when an amountless posting is balanced using a price's commodity, or-when -V is used.) If you find this causing problems, set the desired+when -V is used.) If you find this causing problems, set the desired format with a commodity directive. @@ -303,23 +287,20 @@ _virtual posting_, which means: * it is ignored when checking that the transaction is balanced-- * it is excluded from reports when the `--real/-R' flag is used, or- the `real:1' query.+ * it is excluded from reports when the '--real/-R' flag is used, or+ the 'real:1' query. You could use this, eg, to set an account's opening balance without-needing to use the `equity:opening balances' account:-+needing to use the 'equity:opening balances' account: 1/1 special unbalanced posting to set initial balance (assets:checking) $1000 When the account name is bracketed, we call it a _balanced virtual-posting_. This is like an ordinary virtual posting except the balanced+posting_. This is like an ordinary virtual posting except the balanced virtual postings in a transaction must balance to 0, like the real-postings (but separately from them). Balanced virtual postings are also-excluded by `--real/-R' or `real:1'.-+postings (but separately from them). Balanced virtual postings are also+excluded by '--real/-R' or 'real:1'. 1/1 buy food with cash, and update some budget-tracking subaccounts elsewhere expenses:food $10@@ -327,7 +308,7 @@ [assets:checking:available] $10 [assets:checking:budget:food] $-10 - Virtual postings have some legitimate uses, but those are few. You+ Virtual postings have some legitimate uses, but those are few. You can usually find an equivalent journal entry using real postings, which is more correct and provides better error checking. @@ -337,11 +318,10 @@ 1.6 Balance Assertions ====================== -hledger supports Ledger-style balance assertions in journal files. These-look like `=EXPECTEDBALANCE' following a posting's amount. Eg in this-example we assert the expected dollar balance in accounts a and b after-each posting:-+hledger supports Ledger-style balance assertions in journal files.+These look like '=EXPECTEDBALANCE' following a posting's amount. Eg in+this example we assert the expected dollar balance in accounts a and b+after each posting: 2013/1/1 a $1 =$1@@ -352,71 +332,87 @@ b $-1 =$-2 After reading a journal file, hledger will check all balance-assertions and report an error if any of them fail. Balance assertions+assertions and report an error if any of them fail. Balance assertions can protect you from, eg, inadvertently disrupting reconciled balances-while cleaning up old entries. You can disable them temporarily with the-`--ignore-assertions' flag, which can be useful for troubleshooting or-for reading Ledger files.-+while cleaning up old entries. You can disable them temporarily with+the '--ignore-assertions' flag, which can be useful for troubleshooting+or for reading Ledger files. * Menu: * Assertions and ordering::+* Assertions and included files::+* Assertions and multiple -f options:: * Assertions and commodities:: * Assertions and subaccounts:: * Assertions and virtual postings:: -File: hledger_journal.5.info, Node: Assertions and ordering, Next: Assertions and commodities, Up: Balance Assertions+File: hledger_journal.5.info, Node: Assertions and ordering, Next: Assertions and included files, Up: Balance Assertions 1.6.1 Assertions and ordering ----------------------------- hledger sorts an account's postings and assertions first by date and-then (for postings on the same day) by parse order. Note this is+then (for postings on the same day) by parse order. Note this is different from Ledger, which sorts assertions only by parse order. (Also, Ledger assertions do not see the accumulated effect of repeated postings to the same account within a transaction.) So, hledger balance assertions keep working if you reorder-differently-dated transactions within the journal. But if you reorder+differently-dated transactions within the journal. But if you reorder same-dated transactions or postings, assertions might break and require-updating. This order dependence does bring an advantage: precise control-over the order of postings and assertions within a day, so you can-assert intra-day balances.+updating. This order dependence does bring an advantage: precise+control over the order of postings and assertions within a day, so you+can assert intra-day balances. - With included files, things are a little more complicated. Including-preserves the ordering of postings and assertions. If you have multiple++File: hledger_journal.5.info, Node: Assertions and included files, Next: Assertions and multiple -f options, Prev: Assertions and ordering, Up: Balance Assertions++1.6.2 Assertions and included files+-----------------------------------++With included files, things are a little more complicated. Including+preserves the ordering of postings and assertions. If you have multiple postings to an account on the same day, split across different files, and you also want to assert the account's balance on the same day, you'll have to put the assertion in the right file. -File: hledger_journal.5.info, Node: Assertions and commodities, Next: Assertions and subaccounts, Prev: Assertions and ordering, Up: Balance Assertions+File: hledger_journal.5.info, Node: Assertions and multiple -f options, Next: Assertions and commodities, Prev: Assertions and included files, Up: Balance Assertions -1.6.2 Assertions and commodities+1.6.3 Assertions and multiple -f options+----------------------------------------++Balance assertions don't work well across files specified with multiple+-f options. Use include or concatenate the files instead.+++File: hledger_journal.5.info, Node: Assertions and commodities, Next: Assertions and subaccounts, Prev: Assertions and multiple -f options, Up: Balance Assertions++1.6.4 Assertions and commodities -------------------------------- The asserted balance must be a simple single-commodity amount, and in fact the assertion checks only this commodity's balance within the-(possibly multi-commodity) account balance. We could call this a partial-balance assertion. This is compatible with Ledger, and makes it possible-to make assertions about accounts containing multiple commodities.+(possibly multi-commodity) account balance. We could call this a+partial balance assertion. This is compatible with Ledger, and makes it+possible to make assertions about accounts containing multiple+commodities. To assert each commodity's balance in such a multi-commodity account,-you can add multiple postings (with amount 0 if necessary). But note+you can add multiple postings (with amount 0 if necessary). But note that no matter how many assertions you add, you can't be sure the-account does not contain some unexpected commodity. (We'll add support+account does not contain some unexpected commodity. (We'll add support for this kind of total balance assertion if there's demand.) File: hledger_journal.5.info, Node: Assertions and subaccounts, Next: Assertions and virtual postings, Prev: Assertions and commodities, Up: Balance Assertions -1.6.3 Assertions and subaccounts+1.6.5 Assertions and subaccounts -------------------------------- Balance assertions do not count the balance from subaccounts; they check-the posted account's exclusive balance. For example:-+the posted account's exclusive balance. For example: 1/1 checking:fund 1 = 1 ; post to this subaccount, its balance is now 1@@ -426,7 +422,6 @@ The balance report's flat mode shows these exclusive balances more clearly: - $ hledger bal checking --flat 1 checking 1 checking:fund@@ -436,11 +431,12 @@ File: hledger_journal.5.info, Node: Assertions and virtual postings, Prev: Assertions and subaccounts, Up: Balance Assertions -1.6.4 Assertions and virtual postings+1.6.6 Assertions and virtual postings ------------------------------------- Balance assertions are checked against all postings, both real and-virtual. They are not affected by the `--real/-R' flag or `real:' query.+virtual. They are not affected by the '--real/-R' flag or 'real:'+query. File: hledger_journal.5.info, Node: Balance Assignments, Next: Prices, Prev: Balance Assertions, Up: FILE FORMAT@@ -448,13 +444,12 @@ 1.7 Balance Assignments ======================= -Ledger-style balance assignments are also supported. These are like+Ledger-style balance assignments are also supported. These are like balance assertions, but with no posting amount on the left side of the equals sign; instead it is calculated automatically so as to satisfy the-assertion. This can be a convenience during data entry, eg when setting+assertion. This can be a convenience during data entry, eg when setting opening balances: - ; starting a new journal, set asset account balances 2016/1/1 opening balances assets:checking = $409.32@@ -464,7 +459,6 @@ or when adjusting a balance to reality: - ; no cash left; update balance, record any untracked spending as a generic expense 2016/1/15 assets:cash = $0@@ -473,7 +467,7 @@ The calculated amount depends on the account's balance in the commodity at that point (which depends on the previously-dated postings of the commodity to that account since the last balance assertion or-assignment). Note that using balance assignments makes your journal a+assignment). Note that using balance assignments makes your journal a little less explicit; to know the exact amount posted, you have to run hledger or do the calculations yourself, instead of just reading it. @@ -495,45 +489,40 @@ ------------------------ Within a transaction posting, you can record an amount's price in-another commodity. This can be used to document the cost (for a+another commodity. This can be used to document the cost (for a purchase), or selling price (for a sale), or the exchange rate that was-used, for this transaction. These transaction prices are fixed, and do+used, for this transaction. These transaction prices are fixed, and do not change over time. Amounts with transaction prices can be displayed in the transaction-price's commodity, by using the `--cost/-B' flag supported by most+price's commodity, by using the '--cost/-B' flag supported by most hledger commands (mnemonic: "cost Basis"). There are several ways to record a transaction price: - 1. Write the unit price (aka exchange rate), as `@ UNITPRICE' after+ 1. Write the unit price (aka exchange rate), as '@ UNITPRICE' after the amount: - 2009/1/1 assets:foreign currency €100 @ $1.35 ; one hundred euros at $1.35 each assets:cash - 2. Or write the total price, as `@@ TOTALPRICE' after the amount:-+ 2. Or write the total price, as '@@ TOTALPRICE' after the amount: 2009/1/1 assets:foreign currency €100 @@ $135 ; one hundred euros at $135 for the lot assets:cash - 3. Or let hledger infer the price so as to balance the transaction. To- permit this, you must fully specify all posting amounts, and their- sum must have a non-zero amount in exactly two commodities:-+ 3. Or let hledger infer the price so as to balance the transaction.+ To permit this, you must fully specify all posting amounts, and+ their sum must have a non-zero amount in exactly two commodities: 2009/1/1 assets:foreign currency €100 ; one hundred euros assets:cash $-135 ; exchanged for $135 - With any of the above examples we get: - $ hledger print -B 2009/01/01 assets:foreign currency $135.00@@ -549,28 +538,26 @@ ------------------- Market prices are not tied to a particular transaction; they represent-historical exchange rates between two commodities. (Ledger calls them-historical prices.) For example, the prices published by a stock-exchange or the foreign exchange market. Some commands (balance,+historical exchange rates between two commodities. (Ledger calls them+historical prices.) For example, the prices published by a stock+exchange or the foreign exchange market. Some commands (balance, currently) can use this information to show the market value of things at a given date. To record market prices, use P directives in the main journal or in-an included file. Their format is:-+an included file. Their format is: P DATE COMMODITYBEINGPRICED UNITPRICE - DATE is a simple date as usual. COMMODITYBEINGPRICED is the symbol of-the commodity being priced (just the symbol, no quantity). UNITPRICE is-an ordinary amount (symbol and quantity) in a second commodity,-specifying the unit price or conversion rate for the first commodity in-terms of the second, on the given date.+ DATE is a simple date as usual. COMMODITYBEINGPRICED is the symbol+of the commodity being priced. UNITPRICE is an ordinary amount (symbol+and quantity) in a second commodity, specifying the unit price or+conversion rate for the first commodity in terms of the second, on the+given date. For example, the following directives say that one euro was worth 1.35 US dollars during 2009, and $1.40 from 2010 onward: - P 2009/1/1 € $1.35 P 2010/1/1 € $1.40 @@ -580,22 +567,21 @@ 1.9 Comments ============ -Lines in the journal beginning with a semicolon (`;') or hash (`#') or-asterisk (`*') are comments, and will be ignored. (Asterisk comments+Lines in the journal beginning with a semicolon (';') or hash ('#') or+asterisk ('*') are comments, and will be ignored. (Asterisk comments make it easy to treat your journal like an org-mode outline in emacs.) - Also, anything between `comment' and `end comment' directives is a-(multi-line) comment. If there is no `end comment', the comment extends+ Also, anything between 'comment' and 'end comment' directives is a+(multi-line) comment. If there is no 'end comment', the comment extends to the end of the file. You can attach comments to a transaction by writing them after the description and/or indented on the following lines (before the-postings). Similarly, you can attach comments to an individual posting+postings). Similarly, you can attach comments to an individual posting by writing them after the amount and/or indented on the following lines. Some examples: - # a journal comment ; also a journal comment@@ -621,29 +607,65 @@ 1.10 Tags ========= -A _tag_ is a word followed by a full colon inside a transaction or-posting comment. You can write multiple tags, comma separated. Eg: `; a-comment containing sometag:, anothertag:'. You can search for tags with-the `tag:' query.+Tags are a way to add extra labels or labelled data to postings and+transactions, which you can then search or pivot on. - A tag can also have a value, which is any text between the colon and-the next comma or newline, excluding leading/trailing whitespace. (So-hledger tag values can not contain commas or newlines).+ A simple tag is a word (which may contain hyphens) followed by a full+colon, written inside a transaction or posting comment line: - Tags in a transaction comment affect the transaction and all of its-postings, while tags in a posting comment affect only that posting. For-example, the following transaction has three tags (A, TAG2, third-tag)-and the posting has four (A, TAG2, third-tag, posting-tag):+2017/1/16 bought groceries ; sometag: + Tags can have a value, which is the text after the colon, up to the+next comma or end of line, with leading/trailing whitespace removed: + expenses:food $10 ; a-posting-tag: the tag value++ Note this means hledger's tag values can not contain commas or+newlines. Ending at commas means you can write multiple short tags on+one line, comma separated:++ assets:checking ; a comment containing tag1:, tag2: some value ...++ Here,++ * "'a comment containing'" is just comment text, not a tag+ * "'tag1'" is a tag with no value+ * "'tag2'" is another tag, whose value is "'some value ...'"++ Tags in a transaction comment affect the transaction and all of its+postings, while tags in a posting comment affect only that posting. For+example, the following transaction has three tags ('A', 'TAG2',+'third-tag') and the posting has four (those plus 'posting-tag'):+ 1/1 a transaction ; A:, TAG2:- ; third-tag: a third transaction tag, this time with a value+ ; third-tag: a third transaction tag, <- with a value (a) $1 ; posting-tag: Tags are like Ledger's metadata feature, except hledger's tag values are simple strings.+* Menu: +* Implicit tags::+ +File: hledger_journal.5.info, Node: Implicit tags, Up: Tags++1.10.1 Implicit tags+--------------------++Some predefined "implicit" tags are also provided:++ * 'code' - the transaction's code field+ * 'description' - the transaction's description+ * 'payee' - the part of description before '|', or all of it+ * 'note' - the part of description after '|', or all of it++ 'payee' and 'note' support descriptions written in a special 'PAYEE |+NOTE' format, accessing the parts before and after the pipe character+respectively. For descriptions not containing a pipe character they are+the same as 'description'.++ File: hledger_journal.5.info, Node: Directives, Prev: Tags, Up: FILE FORMAT 1.11 Directives@@ -667,21 +689,17 @@ ---------------------- You can define aliases which rewrite your account names (after reading-the journal, before generating reports). hledger's account aliases can+the journal, before generating reports). hledger's account aliases can be useful for: * expanding shorthand account names to their full form, allowing easier data entry and a less verbose journal- * adapting old journals to your current chart of accounts- * experimenting with new account organisations, like a new hierarchy or combining two accounts into one- * customising reports - See also How to use account aliases.-+ See also Cookbook: rewrite account names. * Menu: * Basic aliases::@@ -695,22 +713,20 @@ 1.11.1.1 Basic aliases ...................... -To set an account alias, use the `alias' directive in your journal-file. This affects all subsequent journal entries in the current file or-its included files. The spaces around the = are optional:-+To set an account alias, use the 'alias' directive in your journal file.+This affects all subsequent journal entries in the current file or its+included files. The spaces around the = are optional: alias OLD = NEW - Or, you can use the `--alias 'OLD=NEW'' option on the command line.-This affects all entries. It's useful for trying out aliases+ Or, you can use the '--alias 'OLD=NEW'' option on the command line.+This affects all entries. It's useful for trying out aliases interactively. - OLD and NEW are full account names. hledger will replace any-occurrence of the old account name with the new one. Subaccounts are+ OLD and NEW are full account names. hledger will replace any+occurrence of the old account name with the new one. Subaccounts are also affected. Eg: - alias checking = assets:bank:wells fargo:checking # rewrites "checking" to "assets:bank:wells fargo:checking", or "checking:a" to "assets:bank:wells fargo:checking:a" @@ -721,21 +737,19 @@ ...................... There is also a more powerful variant that uses a regular expression,-indicated by the forward slashes. (This was the default behaviour in+indicated by the forward slashes. (This was the default behaviour in hledger 0.24-0.25): - alias /REGEX/ = REPLACEMENT - or `--alias '/REGEX/=REPLACEMENT''.+ or '--alias '/REGEX/=REPLACEMENT''. - REGEX is a case-insensitive regular expression. Anywhere it matches+ REGEX is a case-insensitive regular expression. Anywhere it matches inside an account name, the matched part will be replaced by REPLACEMENT. If REGEX contains parenthesised match groups, these can be referenced by the usual numeric backreferences in REPLACEMENT. Note, currently regular expression aliases may cause noticeable slow-downs.-(And if you use Ledger on your hledger file, they will be ignored.) Eg:-+(And if you use Ledger on your hledger file, they will be ignored.) Eg: alias /^(.+):bank:([^:]+)(.*)/ = \1:\2 \3 # rewrites "assets:bank:wells fargo:checking" to "assets:wells fargo checking"@@ -747,14 +761,13 @@ ......................... You can define as many aliases as you like using directives or-command-line options. Aliases are recursive - each alias sees the result-of applying previous ones. (This is different from Ledger, where aliases-are non-recursive by default). Aliases are applied in the following-order:+command-line options. Aliases are recursive - each alias sees the+result of applying previous ones. (This is different from Ledger, where+aliases are non-recursive by default). Aliases are applied in the+following order: 1. alias directives, most recently seen first (recent directives take precedence over earlier ones; directives not yet seen are ignored)- 2. alias options, in the order they appear on the command line @@ -763,10 +776,9 @@ 1.11.1.4 end aliases .................... -You can clear (forget) all currently defined aliases with the `end+You can clear (forget) all currently defined aliases with the 'end aliases' directive: - end aliases @@ -775,11 +787,10 @@ 1.11.2 account directive ------------------------ -The `account' directive predefines account names, as in Ledger and-Beancount. This may be useful for your own documentation; hledger+The 'account' directive predefines account names, as in Ledger and+Beancount. This may be useful for your own documentation; hledger doesn't make use of it yet. - ; account ACCT ; OPTIONAL COMMENTS/TAGS... @@ -798,10 +809,9 @@ ------------------------------ You can specify a parent account which will be prepended to all accounts-within a section of the journal. Use the `apply account' and `end apply+within a section of the journal. Use the 'apply account' and 'end apply account' directives like so: - apply account home 2010/1/1@@ -812,14 +822,12 @@ which is equivalent to: - 2010/01/01 home:food $10 home:cash $-10 - If `end apply account' is omitted, the effect lasts to the end of-the file. Included files are also affected, eg:-+ If 'end apply account' is omitted, the effect lasts to the end of the+file. Included files are also affected, eg: apply account business include biz.journal@@ -827,7 +835,7 @@ apply account personal include personal.journal - Prior to hledger 1.0, legacy `account' and `end' spellings were also+ Prior to hledger 1.0, legacy 'account' and 'end' spellings were also supported. @@ -836,8 +844,8 @@ 1.11.4 Multi-line comments -------------------------- -A line containing just `comment' starts a multi-line comment, and a-line containing just `end comment' ends it. See comments.+A line containing just 'comment' starts a multi-line comment, and a line+containing just 'end comment' ends it. See comments. File: hledger_journal.5.info, Node: commodity directive, Next: Default commodity, Prev: Multi-line comments, Up: Directives@@ -845,14 +853,12 @@ 1.11.5 commodity directive -------------------------- -The `commodity' directive predefines commodities (currently this is-just informational), and also it may define the display format for-amounts in this commodity (overriding the automatically inferred-format).+The 'commodity' directive predefines commodities (currently this is just+informational), and also it may define the display format for amounts in+this commodity (overriding the automatically inferred format). It may be written on a single line, like this: - ; commodity EXAMPLEAMOUNT ; display AAAA amounts with the symbol on the right, space-separated,@@ -860,11 +866,10 @@ ; separating thousands with comma. commodity 1,000.0000 AAAA - or on multiple lines, using the "format" subdirective. In this case+ or on multiple lines, using the "format" subdirective. In this case the commodity symbol appears twice and should be the same in both places: - ; commodity SYMBOL ; format EXAMPLEAMOUNT @@ -881,12 +886,11 @@ ------------------------ The D directive sets a default commodity (and display format), to be-used for amounts without a commodity symbol (ie, plain numbers). (Note-this differs from Ledger's default commodity directive.) The commodity+used for amounts without a commodity symbol (ie, plain numbers). (Note+this differs from Ledger's default commodity directive.) The commodity and display format will be applied to all subsequent commodity-less amounts, or until the next D directive. - # commodity-less amounts should be treated as dollars # (and displayed with symbol on the left, thousands separators and two decimal places) D $1,000.00@@ -902,10 +906,9 @@ ------------------- You can set a default year to be used for subsequent dates which don't-specify a year. This is a line beginning with `Y' followed by the year.+specify a year. This is a line beginning with 'Y' followed by the year. Eg: - Y2009 ; set default year to 2009 12/15 ; equivalent to 2009/12/15@@ -931,13 +934,12 @@ You can pull in the content of additional journal files by writing an include directive, like this: - include path/to/file.journal If the path does not begin with a slash, it is relative to the-current file. Glob patterns (`*') are not currently supported.+current file. Glob patterns ('*') are not currently supported. - The `include' directive can only be used in journal files. It can+ The 'include' directive can only be used in journal files. It can include journal, timeclock or timedot files, but not CSV files. @@ -947,8 +949,8 @@ **************** Add-on modes exist for various text editors, to make working with-journal files easier. They add colour, navigation aids and helpful-commands. For hledger users who edit the journal file directly (the+journal files easier. They add colour, navigation aids and helpful+commands. For hledger users who edit the journal file directly (the majority), using one of these modes is quite recommended. These were written with Ledger in mind, but also work with hledger@@ -960,77 +962,82 @@ Textmate https://github.com/ledger/ledger/wiki/Using-TextMate-2 Text Wrangler https://github.com/ledger/ledger/wiki/Editing-Ledger-files-with-TextWrangler - Tag Table:-Node: Top94-Node: FILE FORMAT2284-Ref: #file-format2410-Node: Transactions2593-Ref: #transactions2713-Node: Dates3656-Ref: #dates3784-Node: Simple dates3849-Ref: #simple-dates3977-Node: Secondary dates4341-Ref: #secondary-dates4497-Node: Posting dates6057-Ref: #posting-dates6188-Node: Account names7559-Ref: #account-names7698-Node: Amounts8183-Ref: #amounts8321-Node: Virtual Postings10420-Ref: #virtual-postings10581-Node: Balance Assertions11801-Ref: #balance-assertions11978-Node: Assertions and ordering12800-Ref: #assertions-and-ordering12985-Node: Assertions and commodities14016-Ref: #assertions-and-commodities14242-Node: Assertions and subaccounts14934-Ref: #assertions-and-subaccounts15168-Node: Assertions and virtual postings15690-Ref: #assertions-and-virtual-postings15899-Node: Balance Assignments16040-Ref: #balance-assignments16209-Node: Prices17327-Ref: #prices17460-Node: Transaction prices17511-Ref: #transaction-prices17656-Node: Market prices19236-Ref: #market-prices19371-Node: Comments20371-Ref: #comments20493-Node: Tags21605-Ref: #tags21725-Node: Directives22648-Ref: #directives22763-Node: Account aliases22956-Ref: #account-aliases23102-Node: Basic aliases23704-Ref: #basic-aliases23849-Node: Regex aliases24537-Ref: #regex-aliases24707-Node: Multiple aliases25477-Ref: #multiple-aliases25651-Node: end aliases26147-Ref: #end-aliases26289-Node: account directive26391-Ref: #account-directive26573-Node: apply account directive26869-Ref: #apply-account-directive27067-Node: Multi-line comments27727-Ref: #multi-line-comments27919-Node: commodity directive28046-Ref: #commodity-directive28232-Node: Default commodity29105-Ref: #default-commodity29280-Node: Default year29816-Ref: #default-year29983-Node: Including other files30406-Ref: #including-other-files30565-Node: EDITOR SUPPORT30961-Ref: #editor-support31081+Node: Top78+Node: FILE FORMAT2292+Ref: #file-format2418+Node: Transactions2601+Ref: #transactions2721+Node: Dates3663+Ref: #dates3791+Node: Simple dates3856+Ref: #simple-dates3984+Node: Secondary dates4350+Ref: #secondary-dates4506+Node: Posting dates6069+Ref: #posting-dates6200+Node: Account names7574+Ref: #account-names7713+Node: Amounts8200+Ref: #amounts8338+Node: Virtual Postings10439+Ref: #virtual-postings10600+Node: Balance Assertions11820+Ref: #balance-assertions11997+Node: Assertions and ordering12893+Ref: #assertions-and-ordering13081+Node: Assertions and included files13781+Ref: #assertions-and-included-files14024+Node: Assertions and multiple -f options14357+Ref: #assertions-and-multiple--f-options14613+Node: Assertions and commodities14745+Ref: #assertions-and-commodities14982+Node: Assertions and subaccounts15678+Ref: #assertions-and-subaccounts15912+Node: Assertions and virtual postings16433+Ref: #assertions-and-virtual-postings16642+Node: Balance Assignments16784+Ref: #balance-assignments16953+Node: Prices18072+Ref: #prices18205+Node: Transaction prices18256+Ref: #transaction-prices18401+Node: Market prices19978+Ref: #market-prices20113+Node: Comments21086+Ref: #comments21208+Node: Tags22321+Ref: #tags22441+Node: Implicit tags23870+Ref: #implicit-tags23978+Node: Directives24495+Ref: #directives24610+Node: Account aliases24803+Ref: #account-aliases24949+Node: Basic aliases25553+Ref: #basic-aliases25698+Node: Regex aliases26388+Ref: #regex-aliases26558+Node: Multiple aliases27329+Ref: #multiple-aliases27503+Node: end aliases28001+Ref: #end-aliases28143+Node: account directive28244+Ref: #account-directive28426+Node: apply account directive28722+Ref: #apply-account-directive28920+Node: Multi-line comments29579+Ref: #multi-line-comments29771+Node: commodity directive29899+Ref: #commodity-directive30085+Node: Default commodity30957+Ref: #default-commodity31132+Node: Default year31669+Ref: #default-year31836+Node: Including other files32259+Ref: #including-other-files32418+Node: EDITOR SUPPORT32815+Ref: #editor-support32935 End Tag Table
doc/hledger_journal.5.txt view
@@ -287,28 +287,33 @@ trol over the order of postings and assertions within a day, so you can assert intra-day balances. + Assertions and included files With included files, things are a little more complicated. Including preserves the ordering of postings and assertions. If you have multi- ple postings to an account on the same day, split across different files, and you also want to assert the account's balance on the same day, you'll have to put the assertion in the right file. + Assertions and multiple -f options+ Balance assertions don't work well across files specified with multiple+ -f options. Use include or concatenate the files instead.+ Assertions and commodities- The asserted balance must be a simple single-commodity amount, and in- fact the assertion checks only this commodity's balance within the- (possibly multi-commodity) account balance. We could call this a par-- tial balance assertion. This is compatible with Ledger, and makes it+ The asserted balance must be a simple single-commodity amount, and in+ fact the assertion checks only this commodity's balance within the+ (possibly multi-commodity) account balance. We could call this a par-+ tial balance assertion. This is compatible with Ledger, and makes it possible to make assertions about accounts containing multiple commodi- ties. - To assert each commodity's balance in such a multi-commodity account,- you can add multiple postings (with amount 0 if necessary). But note- that no matter how many assertions you add, you can't be sure the+ To assert each commodity's balance in such a multi-commodity account,+ you can add multiple postings (with amount 0 if necessary). But note+ that no matter how many assertions you add, you can't be sure the account does not contain some unexpected commodity. (We'll add support for this kind of total balance assertion if there's demand.) Assertions and subaccounts- Balance assertions do not count the balance from subaccounts; they+ Balance assertions do not count the balance from subaccounts; they check the posted account's exclusive balance. For example: 1/1@@ -316,7 +321,7 @@ checking 1 = 1 ; post to the parent account, its exclusive balance is now 1 equity - The balance report's flat mode shows these exclusive balances more+ The balance report's flat mode shows these exclusive balances more clearly: $ hledger bal checking --flat@@ -330,10 +335,10 @@ tual. They are not affected by the --real/-R flag or real: query. Balance Assignments- Ledger-style balance assignments are also supported. These are like- balance assertions, but with no posting amount on the left side of the- equals sign; instead it is calculated automatically so as to satisfy- the assertion. This can be a convenience during data entry, eg when+ Ledger-style balance assignments are also supported. These are like+ balance assertions, but with no posting amount on the left side of the+ equals sign; instead it is calculated automatically so as to satisfy+ the assertion. This can be a convenience during data entry, eg when setting opening balances: ; starting a new journal, set asset account balances@@ -351,27 +356,27 @@ expenses:misc The calculated amount depends on the account's balance in the commodity- at that point (which depends on the previously-dated postings of the- commodity to that account since the last balance assertion or assign-+ at that point (which depends on the previously-dated postings of the+ commodity to that account since the last balance assertion or assign- ment). Note that using balance assignments makes your journal a little less explicit; to know the exact amount posted, you have to run hledger or do the calculations yourself, instead of just reading it. Prices Transaction prices- Within a transaction posting, you can record an amount's price in- another commodity. This can be used to document the cost (for a pur-- chase), or selling price (for a sale), or the exchange rate that was+ Within a transaction posting, you can record an amount's price in+ another commodity. This can be used to document the cost (for a pur-+ chase), or selling price (for a sale), or the exchange rate that was used, for this transaction. These transaction prices are fixed, and do not change over time. - Amounts with transaction prices can be displayed in the transaction- price's commodity, by using the --cost/-B flag supported by most+ Amounts with transaction prices can be displayed in the transaction+ price's commodity, by using the --cost/-B flag supported by most hledger commands (mnemonic: "cost Basis"). There are several ways to record a transaction price: - 1. Write the unit price (aka exchange rate), as @ UNITPRICE after the+ 1. Write the unit price (aka exchange rate), as @ UNITPRICE after the amount: 2009/1/1@@ -385,7 +390,7 @@ assets:cash 3. Or let hledger infer the price so as to balance the transaction. To- permit this, you must fully specify all posting amounts, and their+ permit this, you must fully specify all posting amounts, and their sum must have a non-zero amount in exactly two commodities: 2009/1/1@@ -399,46 +404,46 @@ assets:foreign currency $135.00 assets:cash $-135.00 - Example use for transaction prices: recording the effective conversion+ Example use for transaction prices: recording the effective conversion rate of purchases made in a foreign currency. Market prices- Market prices are not tied to a particular transaction; they represent- historical exchange rates between two commodities. (Ledger calls them- historical prices.) For example, the prices published by a stock- exchange or the foreign exchange market. Some commands (balance, cur-- rently) can use this information to show the market value of things at+ Market prices are not tied to a particular transaction; they represent+ historical exchange rates between two commodities. (Ledger calls them+ historical prices.) For example, the prices published by a stock+ exchange or the foreign exchange market. Some commands (balance, cur-+ rently) can use this information to show the market value of things at a given date. - To record market prices, use P directives in the main journal or in an+ To record market prices, use P directives in the main journal or in an included file. Their format is: P DATE COMMODITYBEINGPRICED UNITPRICE - DATE is a simple date as usual. COMMODITYBEINGPRICED is the symbol of- the commodity being priced (just the symbol, no quantity). UNITPRICE- is an ordinary amount (symbol and quantity) in a second commodity,- specifying the unit price or conversion rate for the first commodity in- terms of the second, on the given date.+ DATE is a simple date as usual. COMMODITYBEINGPRICED is the symbol of+ the commodity being priced. UNITPRICE is an ordinary amount (symbol+ and quantity) in a second commodity, specifying the unit price or con-+ version rate for the first commodity in terms of the second, on the+ given date. - For example, the following directives say that one euro was worth 1.35+ For example, the following directives say that one euro was worth 1.35 US dollars during 2009, and $1.40 from 2010 onward: P 2009/1/1 $1.35 P 2010/1/1 $1.40 Comments- Lines in the journal beginning with a semicolon (;) or hash (#) or- asterisk (*) are comments, and will be ignored. (Asterisk comments- make it easy to treat your journal like an org-mode outline in emacs.)+ Lines in the journal beginning with a semicolon (;) or hash (#) or+ asterisk (*) are comments, and will be ignored. (Asterisk comments+ make it easy to treat your journal like an org-mode outline in emacs.) - Also, anything between comment and end comment directives is a- (multi-line) comment. If there is no end comment, the comment extends+ Also, anything between comment and end comment directives is a+ (multi-line) comment. If there is no end comment, the comment extends to the end of the file. - You can attach comments to a transaction by writing them after the- description and/or indented on the following lines (before the post-- ings). Similarly, you can attach comments to an individual posting by+ You can attach comments to a transaction by writing them after the+ description and/or indented on the following lines (before the post-+ ings). Similarly, you can attach comments to an individual posting by writing them after the amount and/or indented on the following lines. Some examples:@@ -463,27 +468,61 @@ ; a journal comment (because not indented) Tags- A tag is a word followed by a full colon inside a transaction or post-- ing comment. You can write multiple tags, comma separated. Eg:- ; a comment containing sometag:, anothertag:. You can search for tags- with the tag: query.+ Tags are a way to add extra labels or labelled data to postings and+ transactions, which you can then search or pivot on. - A tag can also have a value, which is any text between the colon and- the next comma or newline, excluding leading/trailing whitespace. (So- hledger tag values can not contain commas or newlines).+ A simple tag is a word (which may contain hyphens) followed by a full+ colon, written inside a transaction or posting comment line: - Tags in a transaction comment affect the transaction and all of its- postings, while tags in a posting comment affect only that posting.- For example, the following transaction has three tags (A, TAG2,- third-tag) and the posting has four (A, TAG2, third-tag, posting-tag):+ 2017/1/16 bought groceries ; sometag: + Tags can have a value, which is the text after the colon, up to the+ next comma or end of line, with leading/trailing whitespace removed:++ expenses:food $10 ; a-posting-tag: the tag value++ Note this means hledger's tag values can not contain commas or new-+ lines. Ending at commas means you can write multiple short tags on one+ line, comma separated:++ assets:checking ; a comment containing tag1:, tag2: some value ...++ Here,++ o "a comment containing" is just comment text, not a tag++ o "tag1" is a tag with no value++ o "tag2" is another tag, whose value is "some value ..."++ Tags in a transaction comment affect the transaction and all of its+ postings, while tags in a posting comment affect only that posting.+ For example, the following transaction has three tags (A, TAG2,+ third-tag) and the posting has four (those plus posting-tag):+ 1/1 a transaction ; A:, TAG2:- ; third-tag: a third transaction tag, this time with a value+ ; third-tag: a third transaction tag, <- with a value (a) $1 ; posting-tag: - Tags are like Ledger's metadata feature, except hledger's tag values+ Tags are like Ledger's metadata feature, except hledger's tag values are simple strings. + Implicit tags+ Some predefined "implicit" tags are also provided:++ o code - the transaction's code field++ o description - the transaction's description++ o payee - the part of description before |, or all of it++ o note - the part of description after |, or all of it++ payee and note support descriptions written in a special PAYEE | NOTE+ format, accessing the parts before and after the pipe character respec-+ tively. For descriptions not containing a pipe character they are the+ same as description.+ Directives Account aliases You can define aliases which rewrite your account names (after reading@@ -500,7 +539,7 @@ o customising reports - See also How to use account aliases.+ See also Cookbook: rewrite account names. Basic aliases To set an account alias, use the alias directive in your journal file.@@ -727,4 +766,4 @@ -hledger 1.1 December 2016 hledger_journal(5)+hledger 1.2 March 2017 hledger_journal(5)
doc/hledger_timeclock.5 view
@@ -1,5 +1,5 @@ -.TH "hledger_timeclock" "5" "December 2016" "hledger 1.1" "hledger User Manuals"+.TH "hledger_timeclock" "5" "March 2017" "hledger 1.2" "hledger User Manuals"
doc/hledger_timeclock.5.info view
@@ -1,30 +1,28 @@-This is hledger-lib/doc/hledger_timeclock.5.info, produced by makeinfo-version 4.8 from stdin.+This is hledger_timeclock.5.info, produced by makeinfo version 6.0 from+stdin. File: hledger_timeclock.5.info, Node: Top, Up: (dir) -hledger_timeclock(5) hledger 1.1+hledger_timeclock(5) hledger 1.2 ******************************** -hledger can read timeclock files. As with Ledger, these are (a subset+hledger can read timeclock files. As with Ledger, these are (a subset of) timeclock.el's format, containing clock-in and clock-out entries as-in the example below. The date is a simple date. The time format is-HH:MM[:SS][+-ZZZZ]. Seconds and timezone are optional. The timezone, if+in the example below. The date is a simple date. The time format is+HH:MM[:SS][+-ZZZZ]. Seconds and timezone are optional. The timezone, if present, must be four digits and is ignored (currently the time is always interpreted as a local time). - i 2015/03/30 09:00:00 some:account name optional description after two spaces o 2015/03/30 09:20:00 i 2015/03/31 22:21:45 another account o 2015/04/01 02:00:34 hledger treats each clock-in/clock-out pair as a transaction posting-some number of hours to an account. Or if the session spans more than-one day, it is split into several transactions, one for each day. For-the above time log, `hledger print' generates these journal entries:-+some number of hours to an account. Or if the session spans more than+one day, it is split into several transactions, one for each day. For+the above time log, 'hledger print' generates these journal entries: $ hledger -f t.timeclock print 2015/03/30 * optional description after two spaces@@ -38,7 +36,6 @@ Here is a sample.timeclock to download and some queries to try: - $ hledger -f sample.timeclock balance # current time balances $ hledger -f sample.timeclock register -p 2009/3 # sessions in march 2009 $ hledger -f sample.timeclock register -p weekly --depth 1 --empty # time summary by week@@ -50,18 +47,16 @@ * at the command line, use these bash aliases: - alias ti="echo i `date '+%Y-%m-%d %H:%M:%S'` \$* >>$TIMELOG" alias to="echo o `date '+%Y-%m-%d %H:%M:%S'` >>$TIMELOG" - * or use the old `ti' and `to' scripts in the ledger 2.x repository.+ * or use the old 'ti' and 'to' scripts in the ledger 2.x repository. These rely on a "timeclock" executable which I think is just the ledger 2 executable renamed. - Tag Table:-Node: Top96+Node: Top80 End Tag Table
doc/hledger_timeclock.5.txt view
@@ -79,4 +79,4 @@ -hledger 1.1 December 2016 hledger_timeclock(5)+hledger 1.2 March 2017 hledger_timeclock(5)
doc/hledger_timedot.5 view
@@ -1,5 +1,5 @@ -.TH "hledger_timedot" "5" "December 2016" "hledger 1.1" "hledger User Manuals"+.TH "hledger_timedot" "5" "March 2017" "hledger 1.2" "hledger User Manuals"
doc/hledger_timedot.5.info view
@@ -1,24 +1,23 @@-This is hledger-lib/doc/hledger_timedot.5.info, produced by makeinfo-version 4.8 from stdin.+This is hledger_timedot.5.info, produced by makeinfo version 6.0 from+stdin. -File: hledger_timedot.5.info, Node: Top, Up: (dir)+File: hledger_timedot.5.info, Node: Top, Next: FILE FORMAT, Up: (dir) -hledger_timedot(5) hledger 1.1+hledger_timedot(5) hledger 1.2 ****************************** Timedot is a plain text format for logging dated, categorised quantities-(eg time), supported by hledger. It is convenient for approximate and+(eg time), supported by hledger. It is convenient for approximate and retroactive time logging, eg when the real-time clock-in/out required-with a timeclock file is too precise or too interruptive. It can be+with a timeclock file is too precise or too interruptive. It can be formatted like a bar chart, making clear at a glance where time was spent. Though called "timedot", the format does not specify the commodity-being logged, so could represent other dated, quantifiable things. Eg+being logged, so could represent other dated, quantifiable things. Eg you could record a single-entry journal of financial transactions, perhaps slightly more conveniently than with hledger_journal(5) format.- * Menu: * FILE FORMAT::@@ -29,26 +28,24 @@ 1 FILE FORMAT ************* -A timedot file contains a series of day entries. A day entry begins with-a date, and is followed by category/quantity pairs, one per line. Dates-are hledger-style simple dates (see hledger_journal(5)). Categories are-hledger-style account names, optionally indented. There must be at least-two spaces between the category and the quantity. Quantities can be-written in two ways:+A timedot file contains a series of day entries. A day entry begins+with a date, and is followed by category/quantity pairs, one per line.+Dates are hledger-style simple dates (see hledger_journal(5)).+Categories are hledger-style account names, optionally indented. There+must be at least two spaces between the category and the quantity.+Quantities can be written in two ways: - 1. a series of dots (period characters). Each dot represents "a- quarter" - eg, a quarter hour. Spaces can be used to group dots+ 1. a series of dots (period characters). Each dot represents "a+ quarter" - eg, a quarter hour. Spaces can be used to group dots into hours, for easier counting. - 2. a number (integer or decimal), representing "units" - eg, hours. A- good alternative when dots are cumbersome. (A number also can+ 2. a number (integer or decimal), representing "units" - eg, hours. A+ good alternative when dots are cumbersome. (A number also can record negative quantities.) -- Blank lines and lines beginning with #, ; or * are ignored. An+ Blank lines and lines beginning with #, ; or * are ignored. An example: - # on this day, 6h was spent on client work, 1.5h on haskell FOSS work, etc. 2016/2/1 inc:client1 .... .... .... .... .... ....@@ -61,7 +58,6 @@ Or with numbers: - 2016/2/3 inc:client1 4 fos:hledger 3@@ -69,7 +65,6 @@ Reporting: - $ hledger -f t.timedot print date:2016/2/2 2016/02/02 * (inc:client1) 2.00@@ -77,7 +72,6 @@ 2016/02/02 * (biz:research) 0.25 - $ hledger -f t.timedot bal --daily --tree Balance changes in 2016/02/01-2016/02/03: @@ -93,15 +87,13 @@ ------------++---------------------------------------- || 7.75 2.25 8.00 - I prefer to use period for separating account components. We can make-this work with an account alias:-+ I prefer to use period for separating account components. We can+make this work with an account alias: 2016/2/4 fos.hledger.timedot 4 fos.ledger .. - $ hledger -f t.timedot --alias /\\./=: bal date:2016/2/4 4.50 fos 4.00 hledger:timedot@@ -111,11 +103,10 @@ Here is a sample.timedot. - Tag Table:-Node: Top94-Node: FILE FORMAT876-Ref: #file-format979+Node: Top78+Node: FILE FORMAT882+Ref: #file-format985 End Tag Table
doc/hledger_timedot.5.txt view
@@ -120,4 +120,4 @@ -hledger 1.1 December 2016 hledger_timedot(5)+hledger 1.2 March 2017 hledger_timedot(5)
hledger-lib.cabal view
@@ -1,11 +1,9 @@--- This file has been generated from package.yaml by hpack version 0.15.0.+-- This file has been generated from package.yaml by hpack version 0.17.0. -- -- see: https://github.com/sol/hpack name: hledger-lib-version: 1.1-stability: stable-category: Finance+version: 1.2 synopsis: Core data types, parsers and functionality for the hledger accounting tools description: This is a reusable library containing hledger's core functionality. .@@ -15,15 +13,17 @@ with ledger(1). hledger provides command-line, curses and web interfaces, and aims to be a reliable, practical tool for daily use.-license: GPL-license-file: LICENSE-author: Simon Michael <simon@joyful.com>-maintainer: Simon Michael <simon@joyful.com>+category: Finance+stability: stable homepage: http://hledger.org bug-reports: http://bugs.hledger.org-cabal-version: >= 1.10-build-type: Simple+author: Simon Michael <simon@joyful.com>+maintainer: Simon Michael <simon@joyful.com>+license: GPL-3+license-file: LICENSE tested-with: GHC==7.10.3, GHC==8.0+build-type: Simple+cabal-version: >= 1.10 extra-source-files: CHANGES@@ -54,8 +54,7 @@ library hs-source-dirs:- other/ledger-parse- , .+ ./. ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans build-depends: base >=4.8 && <5@@ -72,7 +71,7 @@ , directory , filepath , hashtables >= 1.2- , megaparsec >=5.0 && < 5.2+ , megaparsec >=5.0 && < 5.3 , mtl , mtl-compat , old-time@@ -85,9 +84,6 @@ , uglymemo , utf8-string >=0.3.5 && <1.1 , HUnit- , parsers >= 0.5- , system-filepath- , trifecta >= 0.91 , parsec , semigroups if impl(ghc <7.6)@@ -110,19 +106,20 @@ Hledger.Data.Dates Hledger.Data.Journal Hledger.Data.Ledger+ Hledger.Data.MarketPrice Hledger.Data.Period Hledger.Data.StringFormat Hledger.Data.Posting Hledger.Data.RawOptions Hledger.Data.Timeclock Hledger.Data.Transaction+ Hledger.Data.AutoTransaction Hledger.Data.Types Hledger.Query Hledger.Read Hledger.Read.Common Hledger.Read.CsvReader Hledger.Read.JournalReader- Hledger.Read.LedgerReader Hledger.Read.TimedotReader Hledger.Read.TimeclockReader Hledger.Reports@@ -143,18 +140,16 @@ Hledger.Utils.Tree Hledger.Utils.UTF8IOCompat other-modules:- Ledger.Parser.Text Paths_hledger_lib default-language: Haskell2010 test-suite doctests type: exitcode-stdio-1.0+ main-is: doctests.hs hs-source-dirs:- other/ledger-parse- , .- , tests+ ./.+ tests ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans- main-is: doctests.hs build-depends: base >=4.8 && <5 , base-compat >=0.8.1@@ -170,7 +165,7 @@ , directory , filepath , hashtables >= 1.2- , megaparsec >=5.0 && < 5.2+ , megaparsec >=5.0 && < 5.3 , mtl , mtl-compat , old-time@@ -183,25 +178,23 @@ , uglymemo , utf8-string >=0.3.5 && <1.1 , HUnit- , parsers >= 0.5- , system-filepath- , trifecta >= 0.91 , doctest >=0.8 , Glob >=0.7 if impl(ghc <7.6) build-depends: ghc-prim other-modules:- Ledger.Parser.Text Hledger Hledger.Data Hledger.Data.Account Hledger.Data.AccountName Hledger.Data.Amount+ Hledger.Data.AutoTransaction Hledger.Data.Commodity Hledger.Data.Dates Hledger.Data.Journal Hledger.Data.Ledger+ Hledger.Data.MarketPrice Hledger.Data.Period Hledger.Data.Posting Hledger.Data.RawOptions@@ -214,7 +207,6 @@ Hledger.Read.Common Hledger.Read.CsvReader Hledger.Read.JournalReader- Hledger.Read.LedgerReader Hledger.Read.TimeclockReader Hledger.Read.TimedotReader Hledger.Reports@@ -240,9 +232,8 @@ type: exitcode-stdio-1.0 main-is: hunittests.hs hs-source-dirs:- other/ledger-parse- , .- , tests+ ./.+ tests ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans build-depends: base >=4.8 && <5@@ -259,7 +250,7 @@ , directory , filepath , hashtables >= 1.2- , megaparsec >=5.0 && < 5.2+ , megaparsec >=5.0 && < 5.3 , mtl , mtl-compat , old-time@@ -272,9 +263,6 @@ , uglymemo , utf8-string >=0.3.5 && <1.1 , HUnit- , parsers >= 0.5- , system-filepath- , trifecta >= 0.91 , hledger-lib , parsec , test-framework@@ -290,16 +278,17 @@ build-depends: time >=1.5 other-modules:- Ledger.Parser.Text Hledger Hledger.Data Hledger.Data.Account Hledger.Data.AccountName Hledger.Data.Amount+ Hledger.Data.AutoTransaction Hledger.Data.Commodity Hledger.Data.Dates Hledger.Data.Journal Hledger.Data.Ledger+ Hledger.Data.MarketPrice Hledger.Data.Period Hledger.Data.Posting Hledger.Data.RawOptions@@ -312,7 +301,6 @@ Hledger.Read.Common Hledger.Read.CsvReader Hledger.Read.JournalReader- Hledger.Read.LedgerReader Hledger.Read.TimeclockReader Hledger.Read.TimedotReader Hledger.Reports
− other/ledger-parse/Ledger/Parser/Text.hs
@@ -1,218 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Ledger.Parser.Text- ( parseJournalFile- , RawJournal(..)- , RawEntity(..)- , RawEntityInSitu(..)- , RawPosting(..)- , RawTransaction(..)- , RawAutoTxn(..)- , RawPeriodTxn(..)- -- , main- ) where--import Control.Applicative-import Data.ByteString (ByteString)-import Data.Maybe-import qualified Data.Text.Encoding as E-import Filesystem.Path.CurrentOS hiding (concat)-import Prelude hiding (FilePath, readFile, until)-import Text.Parser.Combinators-import Text.Parser.LookAhead-import Text.Parser.Token-import Text.Trifecta-import Text.Trifecta.Delta--- import Control.DeepSeq--- import Criterion--- import Criterion.Main--infixl 4 <$!>--(<$!>) :: TokenParsing m => (a -> b) -> m a -> m b-f <$!> ma = ($!) <$> pure f <*> ma--data RawJournal = RawJournal [RawEntity]- deriving (Show, Eq)--data RawEntity = Whitespace String- | FileComment String- | Directive { directiveChar :: Maybe Char- , directiveName :: !String- , directiveArg :: Maybe String }- | RawTransactionEntity RawTransaction- | RawAutoTxnEntity RawAutoTxn- | RawPeriodTxnEntity RawPeriodTxn- | EndOfFile- deriving (Show, Eq)--data RawEntityInSitu = RawEntityInSitu { rawEntityIndex :: !Int- , rawEntityStartPos :: !Rendering- , rawEntity :: !RawEntity- , rawEntityEndPos :: !Rendering }--instance Show RawEntityInSitu where- show x = show (rawEntity x) ++ "\n"--data RawPosting = RawPosting { rawPostState :: Maybe Char- , rawPostAccount :: !String- , rawPostAmount :: Maybe String- , rawPostNote :: Maybe String }- | RawPostingNote !String- deriving (Show, Eq)--data RawTransaction = RawTransaction { rawTxnDate :: !String- , rawTxnDateAux :: Maybe String- , rawTxnState :: Maybe Char- , rawTxnCode :: Maybe String- , rawTxnDesc :: !String- , rawTxnNote :: Maybe String- , rawTxnPosts :: ![RawPosting] }- deriving (Show, Eq)--data RawAutoTxn = RawAutoTxn { rawATxnQuery :: !String- , rawATxnPosts :: ![RawPosting] }- deriving (Show, Eq)--data RawPeriodTxn = RawPeriodTxn { rawPTxnPeriod :: !String- , rawPTxnPosts :: ![RawPosting] }- deriving (Show, Eq)--txnDateParser :: TokenParsing m => m String-txnDateParser = some (digit <|> oneOf "/-." <|> letter)- <?> "transaction date"--longSep :: CharParsing m => m ()-longSep = () <$ (try (char ' ' *> char ' ') <|> tab)--noteParser :: (LookAheadParsing m, CharParsing m) => m String-noteParser = char ';' *> manyTill anyChar (try (lookAhead endOfLine))- <?> "note"--longSepOrEOL :: (LookAheadParsing m, CharParsing m) => m ()-longSepOrEOL = try (lookAhead (longSep <|> endOfLine))--longSepOrEOLIf :: (LookAheadParsing m, CharParsing m) => m p -> m ()-longSepOrEOLIf p = try (lookAhead ((() <$ longSep <* p) <|> endOfLine))--until :: CharParsing m => m () -> m String-until end = (:) <$> noneOf "\r\n" <*> manyTill anyChar end--tokenP :: TokenParsing m => m p -> m p-tokenP p = p <* skipMany spaceChars--postingParser :: (LookAheadParsing m, TokenParsing m) => m RawPosting-postingParser =- (RawPosting <$!> (some spaceChars *>- optional (tokenP (char '*' <|> char '!')))- <*> tokenP (until longSepOrEOL)- <*> optional (tokenP (until (longSepOrEOLIf (char ';'))))- <*> (optional noteParser <* endOfLine)- <?> "posting")- <|>- (RawPostingNote <$!> (concat <$!>- some ((++) <$!> (some spaceChars *> noteParser)- <*> ((:[]) <$> endOfLineChar)))- <?> "posting note")--spaceChars :: CharParsing m => m ()-spaceChars = () <$ oneOf " \t"--regularTxnParser :: (LookAheadParsing m, TokenParsing m) => m RawEntity-regularTxnParser = RawTransactionEntity <$!> go- where go = RawTransaction- <$!> txnDateParser- <*> optional (char '=' *> txnDateParser)- <*> (many spaceChars *>- optional (tokenP (char '*' <|> char '!')))- <*> optional- (tokenP (parens (many (noneOf ")\r\n"))))- <*> tokenP (until (longSepOrEOLIf (char ';')))- <*> optional noteParser- <*> (endOfLine *> some postingParser)- <?> "regular transaction"--automatedTxnParser :: (LookAheadParsing m, TokenParsing m) => m RawEntity-automatedTxnParser = RawAutoTxnEntity <$!> go- where go = RawAutoTxn- <$!> (tokenP (char '=') *>- manyTill anyChar (try (lookAhead endOfLine)))- <*> (endOfLine *> some postingParser)- <?> "automated transaction"--periodicTxnParser :: (LookAheadParsing m, TokenParsing m) => m RawEntity-periodicTxnParser = RawPeriodTxnEntity <$!> go- where go = RawPeriodTxn- <$!> (tokenP (char '~') *>- manyTill anyChar (try (lookAhead endOfLine)))- <*> (endOfLine *> some postingParser)- <?> "periodic transaction"--transactionParser :: (LookAheadParsing m, TokenParsing m) => m RawEntity-transactionParser = regularTxnParser- <|> automatedTxnParser- <|> periodicTxnParser- <?> "transaction"--directiveParser :: (LookAheadParsing m, TokenParsing m) => m RawEntity-directiveParser =- Directive <$!> optional (oneOf "@!")- <*> ((:) <$!> letter <*> tokenP (many alphaNum))- <*> (optional- ((:) <$!> noneOf "\r\n"- <*> manyTill anyChar (try (lookAhead endOfLine)))- <* endOfLine)- <?> "directive"--endOfLine :: CharParsing m => m ()-endOfLine = () <$ endOfLineChar--endOfLineChar :: CharParsing m => m Char-endOfLineChar = skipOptional (char '\r') *> char '\n'--commentParser :: (LookAheadParsing m, TokenParsing m) => m RawEntity-commentParser = FileComment- <$!> (concat <$!>- some ((++) <$!> noteParser- <*> ((:[]) <$> endOfLineChar)))- <?> "comment"--whitespaceParser :: TokenParsing m => m RawEntity-whitespaceParser = Whitespace <$!> some space <?> "whitespace"--entityParser :: (LookAheadParsing m, TokenParsing m) => m RawEntity-entityParser = directiveParser- <|> commentParser- <|> whitespaceParser- <|> transactionParser- <?> "journal"--rendCaret :: DeltaParsing m => m Rendering-rendCaret = addCaret <$!> position <*> rend--journalParser :: (LookAheadParsing m, DeltaParsing m) => m [RawEntityInSitu]-journalParser =- many (RawEntityInSitu <$!> pure 0 <*> rendCaret <*> entityParser <*> rendCaret)--parseJournalFile :: FilePath -> ByteString -> Result [RawEntityInSitu]-parseJournalFile file contents =- let filepath = either id id $ toText file- start = Directed (E.encodeUtf8 filepath) 0 0 0 0- in zipWith (\e i -> e { rawEntityIndex = i})- <$> parseByteString journalParser start contents- <*> pure [1..]---- testme :: IO (Result [RawEntityInSitu])--- testme =--- let file = "/Users/johnw/Documents/Finances/ledger.dat"--- in parseJournalFile (fromText (T.pack file)) <$> B.readFile file---- instance NFData RawEntityInSitu--- instance NFData (Result a)---- main = do let file = "/Users/johnw/Documents/Finances/ledger.dat"--- bs <- B.readFile file--- defaultMain [--- bench "main" $ nf (parseJournalFile (fromText (T.pack file))) bs ]---- Text.hs ends here
tests/doctests.hs view
@@ -6,5 +6,5 @@ main = do fs1 <- filter (not . isInfixOf "/.") <$> glob "Hledger/**/*.hs"- fs2 <- filter (not . isInfixOf "/.") <$> glob "other/ledger-parse/**/*.hs"- doctest $ ["Hledger.hs"] ++ fs1 ++ fs2+ -- fs2 <- filter (not . isInfixOf "/.") <$> glob "other/ledger-parse/**/*.hs"+ doctest $ ["Hledger.hs"] ++ fs1 -- ++ fs2