packages feed

hledger-lib 0.15.2 → 0.16

raw patch · 6 files changed

+107/−66 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Hledger.Data.Matching: transactionEffectiveDate :: Transaction -> Day
- Hledger.Data.Transaction: showTransactionForPrint :: Bool -> Transaction -> String
- Hledger.Read: myTimelog :: IO Journal
- Hledger.Read: myTimelogPath :: IO String
+ Hledger.Data.Transaction: transactionActualDate :: Transaction -> Day
+ Hledger.Data.Transaction: transactionEffectiveDate :: Transaction -> Day
+ Hledger.Read: ensureJournalFile :: FilePath -> IO ()
+ Hledger.Read: requireJournalFile :: FilePath -> IO ()
- Hledger.Data.Transaction: showTransaction' :: Bool -> Bool -> Transaction -> String
+ Hledger.Data.Transaction: showTransaction' :: Bool -> Transaction -> String

Files

Hledger/Data/Dates.hs view
@@ -116,6 +116,20 @@     where a = if isJust a1 then a1 else a2           b = if isJust b1 then b1 else b2 +-- | Calculate the intersection of two datespans.+spanIntersect (DateSpan b1 e1) (DateSpan b2 e2) = DateSpan b e+    where+      b = latest b1 b2+      e = earliest e1 e2++latest d Nothing = d+latest Nothing d = d+latest (Just d1) (Just d2) = Just $ max d1 d2++earliest d Nothing = d+earliest Nothing d = d+earliest (Just d1) (Just d2) = Just $ min d1 d2+ -- | Parse a period expression to an Interval and overall DateSpan using -- the provided reference date, or return a parse error. parsePeriodExpr :: Day -> String -> Either ParseError (Interval, DateSpan)
Hledger/Data/Matching.hs view
@@ -112,10 +112,13 @@     where       maybeprefixedquotedphrases = choice' [prefixedQuotedPattern, quotedPattern, pattern] `sepBy` many1 spacenonewline       prefixedQuotedPattern = do-        not' <- optionMaybe $ string "not:"-        prefix <- choice' $ map string prefixes+        not' <- fromMaybe "" `fmap` (optionMaybe $ string "not:")+        let allowednexts | null not' = prefixes+                         | otherwise = prefixes ++ [""]+        next <- choice' $ map string allowednexts+        let prefix = not' ++ next         p <- quotedPattern-        return $ fromMaybe "" not' ++ prefix ++ stripquotes p+        return $ prefix ++ stripquotes p       quotedPattern = do         p <- between (oneOf "'\"") (oneOf "'\"") $ many $ noneOf "'\""         return $ stripquotes p@@ -216,10 +219,6 @@ postingEffectiveDate :: Posting -> Maybe Day postingEffectiveDate p = maybe Nothing (Just . transactionEffectiveDate) $ ptransaction p -transactionEffectiveDate :: Transaction -> Day-transactionEffectiveDate t = case teffectivedate t of Just d  -> d-                                                      Nothing -> tdate t- -- | Does the match expression match this account ? -- A matching in: clause is also considered a match. matchesAccount :: Matcher -> AccountName -> Bool@@ -313,5 +312,13 @@     assertBool "real:1 on real posting" $ (MatchReal True) `matchesPosting` nullposting{ptype=RegularPosting}     assertBool "real:1 on virtual posting fails" $ not $ (MatchReal True) `matchesPosting` nullposting{ptype=VirtualPosting}     assertBool "real:1 on balanced virtual posting fails" $ not $ (MatchReal True) `matchesPosting` nullposting{ptype=BalancedVirtualPosting}++  ,"words''" ~: do+    assertEqual "1" ["a","b"]        (words'' [] "a b")+    assertEqual "2" ["a b"]          (words'' [] "'a b'")+    assertEqual "3" ["not:a","b"]    (words'' [] "not:a b")+    assertEqual "4" ["not:a b"]    (words'' [] "not:'a b'")+    assertEqual "5" ["not:a b"]    (words'' [] "'not:a b'")+    assertEqual "6" ["not:desc:a b"]    (words'' ["desc:"] "not:desc:'a b'")   ]
Hledger/Data/Transaction.hs view
@@ -10,6 +10,7 @@ where import Data.List import Data.Maybe+import Data.Time.Calendar import Test.HUnit import Text.Printf import qualified Data.Map as Map@@ -59,27 +60,23 @@ @ -} showTransaction :: Transaction -> String-showTransaction = showTransaction' True False+showTransaction = showTransaction' True  showTransactionUnelided :: Transaction -> String-showTransactionUnelided = showTransaction' False False--showTransactionForPrint :: Bool -> Transaction -> String-showTransactionForPrint effective = showTransaction' False effective+showTransactionUnelided = showTransaction' False -showTransaction' :: Bool -> Bool -> Transaction -> String-showTransaction' elide effective t =+showTransaction' :: Bool -> Transaction -> String+showTransaction' elide t =     unlines $ [description] ++ showpostings (tpostings t) ++ [""]     where       description = concat [date, status, code, desc, comment]-      date | effective = showdate $ fromMaybe (tdate t) $ teffectivedate t-           | otherwise = showdate (tdate t) ++ maybe "" showedate (teffectivedate t)+      date = showdate (tdate t) ++ maybe "" showedate (teffectivedate t)+      showdate = printf "%-10s" . showDate+      showedate = printf "=%s" . showdate       status = if tstatus t then " *" else ""       code = if length (tcode t) > 0 then printf " (%s)" $ tcode t else ""       desc = if null d then "" else " " ++ d where d = tdescription t       comment = if null c then "" else "  ; " ++ c where c = tcomment t-      showdate = printf "%-10s" . showDate-      showedate = printf "=%s" . showdate       showpostings ps           | elide && length ps > 1 && isTransactionBalanced Nothing t -- imprecise balanced check               = map showposting (init ps) ++ [showpostingnoamt (last ps)]@@ -237,10 +234,18 @@             | otherwise = "balanced virtual postings are off by " ++ show (costOfMixedAmount bvsum)       sep = if not (null rmsg) && not (null bvmsg) then "; " else "" --- | Convert the primary date to either the actual or effective date.+transactionActualDate :: Transaction -> Day+transactionActualDate = tdate++-- Get a transaction's effective date, defaulting to the actual date.+transactionEffectiveDate :: Transaction -> Day+transactionEffectiveDate t = fromMaybe (tdate t) $ teffectivedate t++-- | Once we no longer need both, set the main transaction date to either+-- the actual or effective date. A bit hacky. journalTransactionWithDate :: WhichDate -> Transaction -> Transaction journalTransactionWithDate ActualDate t = t-journalTransactionWithDate EffectiveDate t = txnTieKnot t{tdate=fromMaybe (tdate t) (teffectivedate t)}+journalTransactionWithDate EffectiveDate t = txnTieKnot t{tdate=transactionEffectiveDate t}  -- | Ensure a transaction's postings refer back to it. txnTieKnot :: Transaction -> Transaction
Hledger/Read.hs view
@@ -11,12 +11,12 @@        journalFromPathAndString,        ledgeraccountname,        myJournalPath,-       myTimelogPath,        myJournal,-       myTimelog,        someamount,        journalenvvar,-       journaldefaultfilename+       journaldefaultfilename,+       requireJournalFile,+       ensureJournalFile, ) where import Control.Monad.Error@@ -25,6 +25,7 @@ import Safe (headDef) import System.Directory (doesFileExist, getHomeDirectory) import System.Environment (getEnv)+import System.Exit (exitFailure) import System.FilePath ((</>)) import System.IO (IOMode(..), withFile, stderr) import Test.HUnit@@ -36,15 +37,13 @@ import Hledger.Read.JournalReader as JournalReader import Hledger.Read.TimelogReader as TimelogReader import Hledger.Utils-import Prelude hiding (getContents)-import Hledger.Utils.UTF8 (getContents, hGetContents)+import Prelude hiding (getContents, writeFile)+import Hledger.Utils.UTF8 (getContents, hGetContents, writeFile)   journalenvvar           = "LEDGER_FILE" journalenvvar2          = "LEDGER"-timelogenvvar           = "TIMELOG" journaldefaultfilename  = ".hledger.journal"-timelogdefaultfilename = ".hledger.timelog"  -- Here are the available readers. The first is the default, used for unknown data formats. readers :: [Reader]@@ -91,23 +90,34 @@ readJournalFile :: Maybe String -> FilePath -> IO (Either String Journal) readJournalFile format "-" = getContents >>= journalFromPathAndString format "(stdin)" readJournalFile format f = do-  ensureJournalFile f+  requireJournalFile f   withFile f ReadMode $ \h -> hGetContents h >>= journalFromPathAndString format f --- | Ensure there is a journal at the given file path, creating an empty one if needed.+-- | If the specified journal file does not exist, give a helpful error and quit.+requireJournalFile :: FilePath -> IO ()+requireJournalFile f = do+  exists <- doesFileExist f+  when (not exists) $ do+    hPrintf stderr "The hledger journal file \"%s\" was not found.\n" f+    hPrintf stderr "Please create it first, eg with hledger add, hledger web, or a text editor.\n"+    hPrintf stderr "Or, specify an existing journal file with -f or LEDGER_FILE.\n"+    exitFailure++-- | Ensure there is a journal file at the given path, creating an empty one if needed. ensureJournalFile :: FilePath -> IO () ensureJournalFile f = do   exists <- doesFileExist f   when (not exists) $ do-    hPrintf stderr "No journal file \"%s\", creating it.\n" f-    hPrintf stderr "Edit this file or use \"hledger add\" or \"hledger web\" to add transactions.\n"-    emptyJournal >>= writeFile f+    hPrintf stderr "Creating hledger journal file \"%s\".\n" f+    -- note Hledger.Utils.UTF8.* do no line ending conversion on windows,+    -- we currently require unix line endings on all platforms.+    newJournalContent >>= writeFile f  -- | Give the content for a new auto-created journal file.-emptyJournal :: IO String-emptyJournal = do+newJournalContent :: IO String+newJournalContent = do   d <- getCurrentDay-  return $ printf "; journal created %s by hledger\n\n" (show d)+  return $ printf "; journal created %s by hledger\n" (show d)  -- | Read a Journal from this string, using the specified data format or -- trying all known formats, or give an error string.@@ -129,22 +139,10 @@       defaultJournalPath = do                   home <- getHomeDirectory `catch` (\_ -> return "")                   return $ home </> journaldefaultfilename-  --- | Get the user's default timelog file path.-myTimelogPath :: IO String-myTimelogPath =-    getEnv timelogenvvar `catch`-               (\_ -> do-                  home <- getHomeDirectory-                  return $ home </> timelogdefaultfilename)  -- | Read the user's default journal file, or give an error. myJournal :: IO Journal myJournal = myJournalPath >>= readJournalFile Nothing >>= either error' return---- | Read the user's default timelog file, or give an error.-myTimelog :: IO Journal-myTimelog = myTimelogPath >>= readJournalFile Nothing >>= either error' return  tests_Hledger_Read = TestList   [
Hledger/Reports.hs view
@@ -146,10 +146,14 @@                                     | uncleared_ = Just False                                     | otherwise  = Nothing --- | Detect which date we will report on, based on --effective.+-- | Report which date we will report on based on --effective. whichDateFromOpts :: ReportOpts -> WhichDate whichDateFromOpts ReportOpts{..} = if effective_ then EffectiveDate else ActualDate +-- | Select a Transaction date accessor based on --effective.+transactionDateFn :: ReportOpts -> (Transaction -> Day)+transactionDateFn ReportOpts{..} = if effective_ then transactionEffectiveDate else transactionActualDate+ -- | Convert this journal's transactions' primary date to either the -- actual or effective date, as per options. journalSelectingDateFromOpts :: ReportOpts -> Journal -> Journal@@ -197,9 +201,10 @@  -- | Select transactions for an entries report. entriesReport :: ReportOpts -> FilterSpec -> Journal -> EntriesReport-entriesReport opts fspec j = sortBy (comparing tdate) $ jtxns $ filterJournalTransactions fspec j'+entriesReport opts fspec j = sortBy (comparing f) $ jtxns $ filterJournalTransactions fspec j'     where-      j' = journalSelectingDateFromOpts opts $ journalSelectingAmountFromOpts opts j+      f = transactionDateFn opts+      j' = journalSelectingAmountFromOpts opts j  ------------------------------------------------------------------------------- @@ -219,17 +224,28 @@ postingsReport opts fspec j = (totallabel, postingsReportItems ps nullposting startbal (+))     where       ps | interval == NoInterval = displayableps-         | otherwise              = summarisePostingsByInterval interval depth empty filterspan displayableps-      (precedingps, displayableps, _) = postingsMatchingDisplayExpr (display_ opts)+         | otherwise              = summarisePostingsByInterval interval depth empty reportspan displayableps+      j' =                                journalSelectingDateFromOpts opts+                                        $ journalSelectingAmountFromOpts opts+                                        j+      (precedingps, displayableps, _) =   postingsMatchingDisplayExpr (display_ opts)                                         $ depthClipPostings depth                                         $ journalPostings                                         $ filterJournalPostings fspec{depth=Nothing}-                                        $ journalSelectingDateFromOpts opts-                                        $ journalSelectingAmountFromOpts opts-                                        j+                                        j'+      (interval, depth, empty, displayexpr) = (intervalFromOpts opts, depth_ opts, empty_ opts, display_ opts)+      journalspan = journalDateSpan j'+      -- requestedspan should be the intersection of any span specified+      -- with period options and any span specified with display option.+      -- The latter is not easily available, fake it for now.+      requestedspan = periodspan `spanIntersect` displayspan+      periodspan = datespan fspec+      displayspan = postingsDateSpan ps+          where (_,ps,_) = postingsMatchingDisplayExpr displayexpr $ journalPostings j'+      matchedspan = postingsDateSpan displayableps+      reportspan | empty     = requestedspan `orDatesFrom` journalspan+                 | otherwise = requestedspan `spanIntersect` matchedspan       startbal = sumPostings precedingps-      filterspan = datespan fspec-      (interval, depth, empty) = (intervalFromOpts opts, depth_ opts, empty_ opts)  totallabel = "Total" balancelabel = "Balance"@@ -304,13 +320,10 @@ -- are one per account per interval and aggregated to the specified depth -- if any. summarisePostingsByInterval :: Interval -> Maybe Int -> Bool -> DateSpan -> [Posting] -> [Posting]-summarisePostingsByInterval interval depth empty filterspan ps = concatMap summarisespan $ splitSpan interval reportspan+summarisePostingsByInterval interval depth empty reportspan ps = concatMap summarisespan $ splitSpan interval reportspan     where       summarisespan s = summarisePostingsInDateSpan s depth empty (postingsinspan s)       postingsinspan s = filter (isPostingInDateSpan s) ps-      dataspan = postingsDateSpan ps-      reportspan | empty = filterspan `orDatesFrom` dataspan-                 | otherwise = dataspan  -- | Given a date span (representing a reporting interval) and a list of -- postings within it: aggregate the postings so there is only one per@@ -526,6 +539,7 @@ isInteresting opts l a | flat_ opts = isInterestingFlat opts l a                        | otherwise = isInterestingIndented opts l a +-- | Determine whether an account should get its own line in the --flat balance report. isInterestingFlat :: ReportOpts -> Ledger -> AccountName -> Bool isInterestingFlat opts l a = notempty || emptyflag     where@@ -533,16 +547,19 @@       notempty = not $ isZeroMixedAmount $ exclusiveBalance acct       emptyflag = empty_ opts +-- | Determine whether an account should get its own line in the indented+-- balance report.  Cf Balance module doc. isInterestingIndented :: ReportOpts -> Ledger -> AccountName -> Bool isInterestingIndented opts l a-    | numinterestingsubs==1 && not atmaxdepth = notlikesub-    | otherwise = notzero || emptyflag+    | numinterestingsubs == 1 && samebalanceassub && not atmaxdepth = False+    | numinterestingsubs < 2 && zerobalance && not emptyflag = False+    | otherwise = True     where       atmaxdepth = isJust d && Just (accountNameLevel a) == d where d = depth_ opts       emptyflag = empty_ opts       acct = ledgerAccount l a-      notzero = not $ isZeroMixedAmount inclbalance where inclbalance = abalance acct-      notlikesub = not $ isZeroMixedAmount exclbalance where exclbalance = sumPostings $ apostings acct+      zerobalance = isZeroMixedAmount inclbalance where inclbalance = abalance acct+      samebalanceassub = isZeroMixedAmount exclbalance where exclbalance = sumPostings $ apostings acct       numinterestingsubs = length $ filter isInterestingTree subtrees           where             isInterestingTree = treeany (isInteresting opts l . aname)
hledger-lib.cabal view
@@ -1,5 +1,5 @@ name:           hledger-lib-version: 0.15.2+version: 0.16 category:       Finance synopsis:       Core data types, parsers and utilities for the hledger accounting tool. description: