hledger 1.51.1 → 1.51.2
raw patch · 16 files changed
+120/−60 lines, 16 filesdep ~basedep ~hledger-libPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base, hledger-lib
API changes (from Hackage documentation)
+ Hledger.Cli.Utils: withPossibleJournal :: CliOpts -> (Journal -> IO a) -> IO a
Files
- CHANGES.md +39/−0
- Hledger/Cli.hs +3/−5
- Hledger/Cli/CliOptions.hs +2/−2
- Hledger/Cli/Commands/Add.hs +8/−11
- Hledger/Cli/Commands/Roi.hs +22/−9
- Hledger/Cli/Utils.hs +14/−1
- embeddedfiles/hledger-ui.1 +2/−2
- embeddedfiles/hledger-ui.info +1/−1
- embeddedfiles/hledger-ui.txt +2/−2
- embeddedfiles/hledger-web.1 +2/−2
- embeddedfiles/hledger-web.info +1/−1
- embeddedfiles/hledger-web.txt +2/−2
- embeddedfiles/hledger.1 +2/−2
- embeddedfiles/hledger.info +1/−1
- embeddedfiles/hledger.txt +2/−2
- hledger.cabal +17/−17
CHANGES.md view
@@ -26,6 +26,45 @@ User-visible changes in the hledger command line tool and library. +# 1.51.2 2026-01-08++Fixes++- The `add` and `import` commands now once again auto-create the journal file+ if it does not exist yet, fixing a regression in 1.50.3.+ Also they now create it lazily, only when they have data to write,+ not unconditionally at the start.+ [#2514]++- The `roi` command has some more sanity checks, and some error messages+ have been clarified.+ (Dmitry Astapov, [#2505])++Improvements++- The `-f` option now reports an error if you give it a glob pattern+ (a path containing `[`, `{`, `*`, or `?`) that matches nothing.+ This makes it consistent with `LEDGER_FILE`.++- Journal format's `include` directive no longer unnecessarily reads+ the attributes of all files in a directory. This works better with+ build tools like tup which detect filesystem operations.++- Journal format's `include` directive has been optimised,+ repairing a slight slowdown introduced in 1.50.3.+ It no longer calls `canonicalizePath` unnecessarily.+ This might be noticeable with many includes on a slow filesystem.++- Allow base 4.22 / ghc 9.14.++API++- Hledger.Cli.Utils:+ withPossibleJournal++[#2505]: https://github.com/simonmichael/hledger/issues/2505+[#2514]: https://github.com/simonmichael/hledger/issues/2514+ # 1.51.1 2025-12-08 Fixes
Hledger/Cli.hs view
@@ -107,7 +107,6 @@ import Data.Function ((&)) import Data.Functor ((<&>)) import Data.List-import Data.List.NonEmpty qualified as NE import Data.Maybe (isJust, fromMaybe, fromJust) import Data.Text (pack, Text) import Data.Time.Clock.POSIX (getPOSIXTime)@@ -426,10 +425,9 @@ | cmdname `elem` ["commands","demo","help","setup","test"] -> cmdaction opts (ignoredjournal cmdname) - -- 6.4.3. builtin command which should create the journal if missing - do that and run it- | cmdname `elem` ["add","import"] -> do- ensureJournalFileExists . NE.head =<< journalFilePathFromOpts opts- withJournal opts (cmdaction opts)+ -- 6.4.3. builtin command which can work with a non-existent journal+ | cmdname `elem` ["add","import"] ->+ withPossibleJournal opts (cmdaction opts) -- 6.4.4. "run" and "repl" need findBuiltinCommands passed to it to avoid circular dependency in the code | cmdname == "run" -> Hledger.Cli.Commands.Run.run Nothing findBuiltinCommand addons opts
Hledger/Cli/CliOptions.hs view
@@ -741,7 +741,7 @@ case mbpaths of Just paths -> return paths Nothing -> do- f <- defaultJournalPath+ f <- defaultExistingJournalPath return $ NE.fromList [f] -- | Like journalFilePathFromOpts, but does not use defaultJournalPath@@ -755,7 +755,7 @@ expandPathPreservingPrefix :: FilePath -> PrefixedFilePath -> IO PrefixedFilePath expandPathPreservingPrefix d prefixedf = do let (p,f) = splitReaderPrefix prefixedf- f' <- expandPath d f+ f' <- expandPathOrGlob d f return $ case p of Just p' -> (show p') ++ ":" ++ f' Nothing -> f'
Hledger/Cli/Commands/Add.hs view
@@ -284,7 +284,7 @@ retryMsg "Please enter y or n." $ parser ((fmap (\c -> if c == '<' then Nothing else Just c)) . headMay . map toLower . strip) $ defaultTo' def $ nonEmpty $- line' $ green' $ printf "Save this transaction to the journal ?%s: " (showDefault def)+ line $ green' $ printf "Save this transaction to the journal ?%s: " (showDefault def) case y of Just 'y' -> return t Just _ -> throw RestartTransactionException@@ -303,7 +303,7 @@ defaultTo' def $ nonEmpty $ maybeExit $ -- maybeShowHelp $- linePrewritten' (green' $ printf "Date%s: " (showDefault def)) (fromMaybe "" prevDateAndCode) ""+ linePrewritten (green' $ printf "Date%s: " (showDefault def)) (fromMaybe "" prevDateAndCode) "" where parseSmartDateAndCode refdate s = if s == "<" then return Nothing else either (const Nothing) (\(d,c) -> return $ Just (fixSmartDate refdate d, c)) edc where@@ -325,7 +325,7 @@ let def = headDef "" asArgs s <- withCompletion (descriptionCompleter asJournal def) $ defaultTo' def $ nonEmpty $- linePrewritten' (green' $ printf "Description%s: " (showDefault def)) (fromMaybe "" prevDescAndCmnt) ""+ linePrewritten (green' $ printf "Description%s: " (showDefault def)) (fromMaybe "" prevDescAndCmnt) "" if s == "<" then return Nothing else do@@ -348,7 +348,7 @@ parser (parseAccountOrDotOrNull def canfinish) $ withCompletion (accountCompleter asJournal def) $ defaultTo' def $ -- nonEmpty $- linePrewritten' (green' $ printf "Account %d%s%s: " pnum (endmsg::String) (showDefault def)) (fromMaybe "" $ prevAccount `atMay` length asPostings) ""+ linePrewritten (green' $ printf "Account %d%s%s: " pnum (endmsg::String) (showDefault def)) (fromMaybe "" $ prevAccount `atMay` length asPostings) "" where canfinish = not (null asPostings) && postingsAreBalanced asPostings parseAccountOrDotOrNull :: String -> Bool -> String -> Maybe (Maybe String)@@ -388,7 +388,7 @@ withCompletion (amountCompleter def) $ defaultTo' def $ nonEmpty $- linePrewritten' (green' $ printf "Amount %d%s: " pnum (showDefault def)) (fromMaybe "" $ prevAmountAndCmnt `atMay` length asPostings) ""+ linePrewritten (green' $ printf "Amount %d%s: " pnum (showDefault def)) (fromMaybe "" $ prevAmountAndCmnt `atMay` length asPostings) "" where -- Custom parser that combines with Wizard to use IO via outputLn parser' f a = a >>= \input ->@@ -475,11 +475,6 @@ -- maybeShowHelp wizard = maybe (liftIO showHelp >> wizard) return $ -- parser (\s -> if s=="?" then Nothing else Just s) wizard --- | A workaround we seem to need for #2410 right now: wizards' input-reading functions disrupt ANSI codes--- somehow, so these variants first print the ANSI coded prompt as ordinary output, then do the input with no prompt.-line' prompt = output prompt >> line ""-linePrewritten' prompt beforetxt aftertxt = output prompt >> linePrewritten "" beforetxt aftertxt- defaultTo' = flip defaultTo withCompletion f = withSettings (setComplete f defaultSettings)@@ -528,7 +523,9 @@ appendToJournalFileOrStdout :: FilePath -> Text -> IO () appendToJournalFileOrStdout f s | f == "-" = T.putStr s'- | otherwise = appendFile f $ T.unpack s'+ | otherwise = do+ ensureJournalFileExists f+ appendFile f $ T.unpack s' where s' = "\n" <> ensureOneNewlineTerminated s -- | Replace a string's 0 or more terminating newlines with exactly one.
Hledger/Cli/Commands/Roi.hs view
@@ -23,6 +23,7 @@ import Data.List import Numeric.RootFinding import Data.Decimal+import Data.Set qualified as S import Data.Text qualified as T import Data.Text.Lazy.IO qualified as TL import Safe (headDef)@@ -83,10 +84,14 @@ makeQuery flag = do q <- either usageError (return . fst) . parseQuery today . T.pack $ stringopt flag rawopts return . simplifyQuery $ And [queryFromFlags ropts{period_=PeriodAll}, q]+ cantCompute msg = error' $ msg ++ " - will be unable to compute the rates of return" investmentsQuery <- makeQuery "investment" pnlQuery <- makeQuery "pnl" + when (pnlQuery == Any) $+ cantCompute "Need some transactions classed as investment and not pnl, but the pnl query matches any transaction"+ let filteredj = filterJournalTransactions investmentsQuery j trans = dbg3 "investments" $ jtxns filteredj@@ -96,7 +101,7 @@ let (fullPeriodDateSpan, mspans) = reportSpan filteredj rspec - let err = error' "Undefined start or end of the period - will be unable to compute the rates of return"+ let err = cantCompute "Undefined start or end of the period" spans = maybe err (map (second (addDays 1)) . dayPartitionToList) mspans fullPeriod = case fullPeriodDateSpan of DateSpan (Just b) (Just e) -> (fromEFDay b, fromEFDay e)@@ -125,15 +130,21 @@ , Not pnlQuery , Date spn ] ) - pnl =+ pnl = dbg3 "pnl" $ cashFlowApplyCostValue $ calculateCashFlow wd trans (And [ Not investmentsQuery , pnlQuery , Date spn ] ) + spanCommodities = dbg3 "spanCommodities" $+ S.unions $ (maCommodities valueBefore) : (maCommodities valueAfter) : map (maCommodities.snd) (cashFlow ++ pnl)+ thisSpan = dbg3 "processing span" $ OneSpan b e valueBefore valueAfter cashFlow pnl + when (S.size spanCommodities > 1) $+ multiCommodityError $ "Period " ++ show (b,e) ++ " has multiple commodities: " ++ (T.unpack $ T.intercalate ", " $ map showCommoditySymbol (S.toList spanCommodities))+ irr <- internalRateOfReturn styles showCashFlow prettyTables thisSpan (periodTwr, annualizedTwr) <- timeWeightedReturn styles showCashFlow prettyTables investmentsQuery trans mixedAmountValue thisSpan let cashFlowAmt = maNegate . maSum $ map snd cashFlow@@ -194,7 +205,7 @@ valueBefore = dbg3 ("value at the start of the interval, "++show begin) $ unMix valueBeforeAmt valueAfter = dbg3 ("value at the end of the interval, "++show end) $ unMix valueAfterAmt- + investmentPostings = concatMap (filter (matchesPosting investmentsQuery) . realPostings) trans totalInvestmentPostingsTill date = sumPostings $ filter (matchesPosting (Date (DateSpan Nothing (Just $ Exact date)))) investmentPostings@@ -207,7 +218,7 @@ -- We are dividing the period [begin, end) into subperiods on each cashflow, and then compute -- the rate of return for each subperiod. For this we need to know the value of the investment -- at the beginning and end of each subperiod, adjusted for cashflow.- -- + -- -- Subperiods are going to be [valueBefore ... (c_0,v_0)][... (c_1, v_1)][... (c_2,v_2)] ... [... (c_n,v_n)][... valueAfter] -- , where v_i is the value of investment computed immediately after cashflow c_i --@@ -295,9 +306,9 @@ (0.000000000001,10000) (interestSum end totalCF) of Root rate -> return ((rate-1)*100)- NotBracketed -> error' $ "Error (NotBracketed): No solution for Internal Rate of Return (IRR).\n"+ NotBracketed -> error' $ "Equation for Internal Rate of Return (IRR) can not be solved.\n" ++ " Possible causes: IRR is huge (>1000000%), balance of investment becomes negative at some point in time."- SearchFailed -> error' $ "Error (SearchFailed): Failed to find solution for Internal Rate of Return (IRR).\n"+ SearchFailed -> error' $ "Equation for Internal Rate of Return (IRR) can not be solved.\n" ++ " Either search does not converge to a solution, or converges too slowly." type CashFlow = [(Day, MixedAmount)]@@ -318,9 +329,11 @@ unMix a = case (unifyMixedAmount a) of Just a' -> aquantity a'- Nothing -> error' $ "Amounts could not be converted to a single commodity: " ++ show (map showAmount $ amounts a) ++- "\nConsider using --value to force all costs to be in a single commodity." ++- "\nFor example, \"--value=end,<commodity> --infer-market-prices\", where commodity is the one that was used for investment valuations."+ Nothing -> multiCommodityError $ "Amounts could not be converted to a single commodity: " ++ show (map showAmount $ amounts a)++multiCommodityError msg = error' $ msg +++ "\nConsider using --value to force all costs to be in a single commodity." +++ "\nFor example, \"--value=end,<commodity> --infer-market-prices\", where commodity is the one that you want to value the investment in." -- Show Decimal rounded to two decimal places, unless it has less places already. This ensures that "2" won't be shown as "2.00" showDecimal :: Decimal -> String
Hledger/Cli/Utils.hs view
@@ -12,6 +12,7 @@ unsupportedOutputFormatError, withJournal, withJournalDo,+ withPossibleJournal, writeOutput, writeOutputLazyText, journalTransform,@@ -30,7 +31,7 @@ import Control.Monad.Except (ExceptT) import Control.Monad.IO.Class (liftIO) import Data.List-import Data.List.NonEmpty qualified as NE (toList)+import Data.List.NonEmpty qualified as NE (head, toList) import Data.Maybe import Data.Text qualified as T import Data.Text.IO qualified as T@@ -77,6 +78,18 @@ {-# DEPRECATED withJournalDo "renamed, please use withJournal instead" #-} withJournalDo = withJournal++-- | Like withJournal, but if the journal file does not exist, provides an empty+-- journal with the file path set. This is useful for commands like add and import+-- that need to work with a potentially non-existent journal file.+withPossibleJournal :: CliOpts -> (Journal -> IO a) -> IO a+withPossibleJournal opts cmd = do+ journalpaths <- journalFilePathFromOptsNoDefault opts+ f <- case journalpaths of+ Just paths -> return $ NE.head paths+ Nothing -> defaultJournalPath+ j <- runExceptT $ journalTransform opts <$> readPossibleJournalFile (inputopts_ opts) f+ either error' cmd j -- PARTIAL: -- | Apply some journal transformations, if enabled by options, that should happen late. -- These happen after parsing, finalising the journal, strict checks, and .latest filtering/updating,
embeddedfiles/hledger-ui.1 view
@@ -1,5 +1,5 @@ -.TH "HLEDGER\-UI" "1" "December 2025" "hledger-ui-1.51.1 " "hledger User Manuals"+.TH "HLEDGER\-UI" "1" "December 2025" "hledger-ui-1.51.2 " "hledger User Manuals" @@ -17,7 +17,7 @@ .PD \f[CR]hledger ui [OPTS] [QUERYARGS]\f[R] .SH DESCRIPTION-This manual is for hledger\[aq]s terminal interface, version 1.51.1.+This manual is for hledger\[aq]s terminal interface, version 1.51.2. See also the hledger manual for common concepts and file formats. .PP hledger is a robust, user\-friendly, cross\-platform set of programs for
embeddedfiles/hledger-ui.info view
@@ -18,7 +18,7 @@ or 'hledger ui [OPTS] [QUERYARGS]' - This manual is for hledger's terminal interface, version 1.51.1. See+ This manual is for hledger's terminal interface, version 1.51.2. See also the hledger manual for common concepts and file formats. hledger is a robust, user-friendly, cross-platform set of programs
embeddedfiles/hledger-ui.txt view
@@ -11,7 +11,7 @@ hledger ui [OPTS] [QUERYARGS] DESCRIPTION- This manual is for hledger's terminal interface, version 1.51.1. See+ This manual is for hledger's terminal interface, version 1.51.2. See also the hledger manual for common concepts and file formats. hledger is a robust, user-friendly, cross-platform set of programs for@@ -460,4 +460,4 @@ SEE ALSO hledger(1), hledger-ui(1), hledger-web(1), ledger(1) -hledger-ui-1.51.1 December 2025 HLEDGER-UI(1)+hledger-ui-1.51.2 December 2025 HLEDGER-UI(1)
embeddedfiles/hledger-web.1 view
@@ -1,5 +1,5 @@ -.TH "HLEDGER\-WEB" "1" "December 2025" "hledger-web-1.51.1 " "hledger User Manuals"+.TH "HLEDGER\-WEB" "1" "December 2025" "hledger-web-1.51.2 " "hledger User Manuals" @@ -17,7 +17,7 @@ .PD \f[CR]hledger web [OPTS] [QUERY]\f[R] .SH DESCRIPTION-This manual is for hledger\[aq]s web interface, version 1.51.1.+This manual is for hledger\[aq]s web interface, version 1.51.2. See also the hledger manual for common concepts and file formats. .PP hledger is a robust, user\-friendly, cross\-platform set of programs for
embeddedfiles/hledger-web.info view
@@ -18,7 +18,7 @@ or 'hledger web [OPTS] [QUERY]' - This manual is for hledger's web interface, version 1.51.1. See also+ This manual is for hledger's web interface, version 1.51.2. See also the hledger manual for common concepts and file formats. hledger is a robust, user-friendly, cross-platform set of programs
embeddedfiles/hledger-web.txt view
@@ -11,7 +11,7 @@ hledger web [OPTS] [QUERY] DESCRIPTION- This manual is for hledger's web interface, version 1.51.1. See also+ This manual is for hledger's web interface, version 1.51.2. See also the hledger manual for common concepts and file formats. hledger is a robust, user-friendly, cross-platform set of programs for@@ -480,4 +480,4 @@ SEE ALSO hledger(1), hledger-ui(1), hledger-web(1), ledger(1) -hledger-web-1.51.1 December 2025 HLEDGER-WEB(1)+hledger-web-1.51.2 December 2025 HLEDGER-WEB(1)
embeddedfiles/hledger.1 view
@@ -1,6 +1,6 @@ .\"t -.TH "HLEDGER" "1" "December 2025" "hledger-1.51.1 " "hledger User Manuals"+.TH "HLEDGER" "1" "December 2025" "hledger-1.51.2 " "hledger User Manuals" @@ -27,7 +27,7 @@ hledger is inspired by and largely compatible with ledger(1), and largely interconvertible with beancount(1). .PP-This manual is for hledger\[aq]s command line interface, version 1.51.1.+This manual is for hledger\[aq]s command line interface, version 1.51.2. It also describes the common options, file formats and concepts used by all hledger programs. It might accidentally teach you some bookkeeping/accounting as well!
embeddedfiles/hledger.info view
@@ -24,7 +24,7 @@ and largely compatible with ledger(1), and largely interconvertible with beancount(1). - This manual is for hledger's command line interface, version 1.51.1.+ This manual is for hledger's command line interface, version 1.51.2. It also describes the common options, file formats and concepts used by all hledger programs. It might accidentally teach you some bookkeeping/accounting as well! You don't need to know everything in
embeddedfiles/hledger.txt view
@@ -16,7 +16,7 @@ and largely compatible with ledger(1), and largely interconvertible with beancount(1). - This manual is for hledger's command line interface, version 1.51.1.+ This manual is for hledger's command line interface, version 1.51.2. It also describes the common options, file formats and concepts used by all hledger programs. It might accidentally teach you some bookkeep- ing/accounting as well! You don't need to know everything in here to@@ -10950,4 +10950,4 @@ SEE ALSO hledger(1), hledger-ui(1), hledger-web(1), ledger(1) -hledger-1.51.1 December 2025 HLEDGER(1)+hledger-1.51.2 December 2025 HLEDGER(1)
hledger.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: hledger-version: 1.51.1+version: 1.51.2 synopsis: Command-line interface for the hledger accounting system description: The command-line interface for the hledger accounting system. Its basic function is to read a plain text file describing@@ -151,13 +151,13 @@ hs-source-dirs: ./ ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind -optP-Wno-nonportable-include-path- cpp-options: -DVERSION="1.51.1"+ cpp-options: -DVERSION="1.51.2" build-depends: Decimal >=0.5.1 , Diff >=0.2 , aeson >=1 && <2.3 , ansi-terminal >=0.9- , base >=4.18 && <4.22+ , base >=4.18 && <4.23 , bytestring , cmdargs >=0.10 , containers >=0.5.9@@ -167,8 +167,8 @@ , filepath , githash >=0.1.6.2 , hashable >=1.2.4- , haskeline >=0.6- , hledger-lib >=1.51.1 && <1.52+ , haskeline >=0.8.4.0+ , hledger-lib >=1.51.2 && <1.52 , http-client , http-types , lucid@@ -208,12 +208,12 @@ hs-source-dirs: app ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind -optP-Wno-nonportable-include-path -threaded -with-rtsopts=-T- cpp-options: -DVERSION="1.51.1"+ cpp-options: -DVERSION="1.51.2" build-depends: Decimal >=0.5.1 , aeson >=1 && <2.3 , ansi-terminal >=0.9- , base >=4.18 && <4.22+ , base >=4.18 && <4.23 , bytestring , cmdargs >=0.10 , containers >=0.5.9@@ -222,9 +222,9 @@ , extra >=1.7.11 , filepath , githash >=0.1.6.2- , haskeline >=0.6+ , haskeline >=0.8.4.0 , hledger- , hledger-lib >=1.51.1 && <1.52+ , hledger-lib >=1.51.2 && <1.52 , http-client , http-types , math-functions >=0.3.3.0@@ -259,12 +259,12 @@ hs-source-dirs: test ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind -optP-Wno-nonportable-include-path- cpp-options: -DVERSION="1.51.1"+ cpp-options: -DVERSION="1.51.2" build-depends: Decimal >=0.5.1 , aeson >=1 && <2.3 , ansi-terminal >=0.9- , base >=4.18 && <4.22+ , base >=4.18 && <4.23 , bytestring , cmdargs >=0.10 , containers >=0.5.9@@ -273,9 +273,9 @@ , extra >=1.7.11 , filepath , githash >=0.1.6.2- , haskeline >=0.6+ , haskeline >=0.8.4.0 , hledger- , hledger-lib >=1.51.1 && <1.52+ , hledger-lib >=1.51.2 && <1.52 , http-client , http-types , math-functions >=0.3.3.0@@ -310,12 +310,12 @@ hs-source-dirs: bench ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind -optP-Wno-nonportable-include-path- cpp-options: -DVERSION="1.51.1"+ cpp-options: -DVERSION="1.51.2" build-depends: Decimal >=0.5.1 , aeson >=1 && <2.3 , ansi-terminal >=0.9- , base >=4.18 && <4.22+ , base >=4.18 && <4.23 , bytestring , cmdargs >=0.10 , containers >=0.5.9@@ -325,9 +325,9 @@ , extra >=1.7.11 , filepath , githash >=0.1.6.2- , haskeline >=0.6+ , haskeline >=0.8.4.0 , hledger- , hledger-lib >=1.51.1 && <1.52+ , hledger-lib >=1.51.2 && <1.52 , html , http-client , http-types