Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -1066,9 +1066,9 @@ ghrel-upload:
@devver:
#!/usr/bin/env bash
set -euo pipefail
RELVER=$(just relver)
RELVER=$(just majorver)
DEVVER=$RELVER.99
just _on-master-branch
just _on-trunk
echo "Setting versions to $DEVVER.."
./Shake setversion "$DEVVER" -c

Expand Down Expand Up @@ -1473,13 +1473,13 @@ _on-release-branch:
exit 1
fi

# Check that we're on the master branch.
_on-master-branch:
# Check that we're on the hledger1 or main branch.
_on-trunk:
#!/usr/bin/env bash
set -euo pipefail
BRANCH=$(git branch --show-current)
if [[ ! $BRANCH =~ master ]]; then
echo "You are currently on $BRANCH branch. Please switch to the master branch."
if [[ ! $BRANCH =~ hledger1 && ! $BRANCH =~ main ]]; then
echo "You are currently on $BRANCH branch. Please switch to the hledger1 or main branch."
exit 1
fi

Expand Down
3 changes: 3 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ write-ghc-environment-files: always
-- package *
-- -- Speeds up a bit
-- library-vanilla: False

-- avoid https://github.com/yesodweb/yesod/issues/1917
constraints: ram < 0
15 changes: 5 additions & 10 deletions doc/ANNOUNCE
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
I'm pleased to announce hledger-1.52. Highlights include:
I'm pleased to announce hledger-1.52.1.
This is a small release adjusting the G account type and adding the U account type,
for better compatibility between hledger 1 and 2.

Preserve cost basis syntax,
cost basis export examples,
faster valuation,
commodity tags,
more robust paging,
fixes.

- https://github.com/simonmichael/hledger/releases/1.52
- https://hledger.org/relnotes.html#2026-03-20-hledger-152
- https://github.com/simonmichael/hledger/releases/1.52.1
- https://hledger.org/relnotes.html#2026-04-28-hledger-1521
- https://hledger.org/install

#hledger is free, robust, friendly, fast, cross-platform, double-entry, multi-currency, multi-interface, GNU GPL-licensed, #plaintextaccounting software.
Expand Down
2 changes: 2 additions & 0 deletions doc/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ General changes in the hledger project.
For package-specific changes, see the hledger package changelogs.


# 1.52.1 2026-04-28

# 1.52 2026-03-20

Doc updates
Expand Down
2 changes: 1 addition & 1 deletion doc/ghrelnotes
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ In a powershell window (press `WINDOWS-R`, `powershell`, `ENTER`):

```
cd ~
curl https://github.com/simonmichael/hledger/releases/download/1.52/hledger-windows-x64.zip -OutFile hledger-windows-x64.zip
curl https://github.com/simonmichael/hledger/releases/download/1.52.1/hledger-windows-x64.zip -OutFile hledger-windows-x64.zip
Expand-Archive hledger-windows-x64.zip -Force -DestinationPath AppData\Roaming\local\bin
hledger --version; hledger-ui --version; hledger-web --version # should show $REL
```
Expand Down
2 changes: 1 addition & 1 deletion doc/ghtestbinnotes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Here you can find unreleased test binaries, which are updated occasionally. By using these and providing [feedback](https://hledger.org/support.html), you can help to make the next release better.

These binaries have the following changes since the 1.52 release: https://github.com/simonmichael/hledger/compare/1.52...testlatest
These binaries have the following changes since the 1.52.1 release: https://github.com/simonmichael/hledger/compare/1.52.1...testlatest

<details>
<summary>
Expand Down
49 changes: 48 additions & 1 deletion doc/relnotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,53 @@ Major releases and user-visible changes.




## 2026-04-28 hledger-1.52.1

### hledger 1.52.1


Breaking changes

- The `Gain` (`G`) account type is no longer auto-detected from account names like
`revenue:gains` or `income:capital gains`; it must now be declared explicitly with `; type: G`.
Also, the `UnrealisedGain` (`U`) account type, a subtype of Equity, has been added
(spellings `U`, `UnrealisedGain`, and `UnrealizedGain` are all accepted).
These improve compatibility between hledger 1 and 2.

Docs

- The "Cost basis / lot syntax" section has been renamed to "Cost basis",
and updated to mention hledger 2.


### hledger-ui 1.52.1


Improvements

- Uses hledger 1.52.1.

- Allow vty-crossplatform 0.5.


### hledger-web 1.52.1


Improvements

- Uses hledger 1.52.1.


### credits 1.52.1


Simon Michael.



## 2026-03-20 hledger-1.52

**Preserve cost basis syntax, cost basis export examples, faster valuation, commodity tags, more robust paging, fixes.**

### hledger 1.52

Expand Down Expand Up @@ -289,6 +333,7 @@ n0vdd.




## 2026-01-08 hledger-1.51.2
### hledger 1.51.2

Expand Down Expand Up @@ -11046,3 +11091,5 @@ Release stats:





2 changes: 2 additions & 0 deletions hledger-lib/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ API/developer-ish changes in hledger-lib.
For user-visible changes, see the hledger package changelog.


# 1.52.1 2026-04-28

# 1.52 2026-03-20

Breaking changes
Expand Down
16 changes: 8 additions & 8 deletions hledger-lib/Hledger/Data/AccountName.hs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ accountNameInferType a
| regexMatchText liabilityAccountRegex a = Just Liability
| regexMatchText conversionAccountRegex a = Just Conversion
| regexMatchText equityAccountRegex a = Just Equity
| regexMatchText gainAccountRegex a = Just Gain
| regexMatchText revenueAccountRegex a = Just Revenue
| regexMatchText expenseAccountRegex a = Just Expense
| otherwise = Nothing
Expand Down Expand Up @@ -450,13 +449,14 @@ tests_AccountName = testGroup "AccountName" [
accountNameInferType "revenues" @?= Just Revenue
accountNameInferType "revenue" @?= Just Revenue
accountNameInferType "income" @?= Just Revenue
accountNameInferType "income:gains" @?= Just Gain
accountNameInferType "revenue:gain" @?= Just Gain
accountNameInferType "revenues:capital-gains" @?= Just Gain
accountNameInferType "income:capitalgain" @?= Just Gain
accountNameInferType "income:losses" @?= Just Gain
accountNameInferType "revenue:capital-loss" @?= Just Gain
accountNameInferType "income:gains:realized" @?= Just Gain
-- Gain type is no longer inferred from names; these are now Revenue
accountNameInferType "income:gains" @?= Just Revenue
accountNameInferType "revenue:gain" @?= Just Revenue
accountNameInferType "revenues:capital-gains" @?= Just Revenue
accountNameInferType "income:capitalgain" @?= Just Revenue
accountNameInferType "income:losses" @?= Just Revenue
accountNameInferType "revenue:capital-loss" @?= Just Revenue
accountNameInferType "income:gains:realized" @?= Just Revenue
,testCase "joinAccountNames" $ do
joinAccountNames "assets" "cash" @?= "assets:cash"
joinAccountNames "assets:cash" "a" @?= "assets:cash:a"
Expand Down
13 changes: 8 additions & 5 deletions hledger-lib/Hledger/Data/Balancing.hs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ data BalancingOpts = BalancingOpts
-- Distinct from InputOpts{infer_costs_}.
, commodity_styles_ :: Maybe (M.Map CommoditySymbol AmountStyle) -- ^ commodity display styles
, txn_balancing_ :: TransactionBalancingPrecision
, account_types_ :: M.Map AccountName AccountType -- ^ account type map, passed through for any balancing helpers that need it
} deriving (Eq, Ord, Show)

defbalancingopts :: BalancingOpts
Expand All @@ -74,6 +75,7 @@ defbalancingopts = BalancingOpts
, infer_balancing_costs_ = True
, commodity_styles_ = Nothing
, txn_balancing_ = TBPExact
, account_types_ = M.empty
}

-- | Check that this transaction would appear balanced to a human when displayed.
Expand Down Expand Up @@ -193,7 +195,7 @@ balanceTransactionHelper bopts t = do
(t', inferredamtsandaccts) <- t
& (if infer_balancing_costs_ bopts then transactionInferBalancingCosts else id)
& dbg9With (lbl "amounts after balancing-cost-inferring".show.map showMixedAmountOneLine.transactionAmounts)
& transactionInferBalancingAmount (fromMaybe M.empty $ commodity_styles_ bopts)
& transactionInferBalancingAmount (fromMaybe M.empty $ commodity_styles_ bopts) (account_types_ bopts)
<&> dbg9With (lbl "balancing amounts inferred".show.map (second showMixedAmountOneLine).snd)
case transactionCheckBalanced bopts t' of
[] -> Right (txnTieKnot t', inferredamtsandaccts)
Expand Down Expand Up @@ -242,9 +244,10 @@ transactionBalanceError t errs = printf "%s:\n%s\n\nThis %stransaction is unbala
-- have the same price(s), and will be converted to the price commodity.
transactionInferBalancingAmount ::
M.Map CommoditySymbol AmountStyle -- ^ commodity display styles
-> M.Map AccountName AccountType -- ^ account type map (passed through; reserved for future use)
-> Transaction
-> Either String (Transaction, [(AccountName, MixedAmount)])
transactionInferBalancingAmount styles t@Transaction{tpostings=ps}
transactionInferBalancingAmount styles _atypes t@Transaction{tpostings=ps}
| length amountlessrealps > 1
= Left $ transactionBalanceError t
["There can't be more than one real posting with no amount."
Expand Down Expand Up @@ -793,10 +796,10 @@ tests_Balancing =
testGroup "Balancing" [

testCase "transactionInferBalancingAmount" $ do
(fst <$> transactionInferBalancingAmount M.empty nulltransaction) @?= Right nulltransaction
(fst <$> transactionInferBalancingAmount M.empty nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` missingamt]}) @?=
(fst <$> transactionInferBalancingAmount M.empty M.empty nulltransaction) @?= Right nulltransaction
(fst <$> transactionInferBalancingAmount M.empty M.empty nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` missingamt]}) @?=
Right nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` usd 5]}
(fst <$> transactionInferBalancingAmount M.empty nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` (eur 3 @@ usd 4), "c" `post` missingamt]}) @?=
(fst <$> transactionInferBalancingAmount M.empty M.empty nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` (eur 3 @@ usd 4), "c" `post` missingamt]}) @?=
Right nulltransaction{tpostings = ["a" `post` usd (-5), "b" `post` (eur 3 @@ usd 4), "c" `post` usd 1]}

, testGroup "balanceSingleTransaction" [
Expand Down
49 changes: 24 additions & 25 deletions hledger-lib/Hledger/Data/Journal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -128,20 +128,23 @@ import Control.Monad.State.Strict (StateT)
import Data.Char (toUpper, isDigit)
import Data.Default (Default(..))
import Data.Foldable (toList)
import Data.List ((\\), find, sortBy, union, intercalate)
import Data.Function ((&))
import Data.List (find, intercalate, sortBy, union, (\\))
#if !MIN_VERSION_base(4,20,0)
import Data.List (foldl')
#endif
import Data.List.Extra (nubSort)
import Data.Map.Strict qualified as M
import Data.Maybe (catMaybes, fromMaybe, mapMaybe, maybeToList)
import Data.Ord (comparing)
import Data.Set qualified as S
import Data.Text (Text)
import Data.Text qualified as T
import Safe (headMay, headDef, maximumMay, minimumMay, lastDef)
import Data.Time.Calendar (Day, addDays, fromGregorian, diffDays)
import Data.Time.Calendar (Day, addDays, diffDays, fromGregorian)
import Data.Time.Clock.POSIX (POSIXTime)
import Data.Tree (Tree(..), flatten)
import Data.Tree (Tree (..), flatten)
import Safe (headMay, headDef, maximumMay, minimumMay)
import System.FilePath (takeFileName)
import Text.Printf (printf)
import Text.Megaparsec (ParsecT)

Expand All @@ -154,12 +157,6 @@ import Hledger.Data.Transaction
import Hledger.Data.TransactionModifier
import Hledger.Data.Valuation
import Hledger.Query
import System.FilePath (takeFileName)
import Data.Ord (comparing)
import Hledger.Data.Dates (nulldate)
import Data.List (sort)
import Data.Function ((&))
-- import Data.Function ((&))


-- | A parser of text that runs in some monad, keeping a Journal as state.
Expand Down Expand Up @@ -530,36 +527,38 @@ journalInheritedAccountTags j a =

type DateWeightedSimilarityScore = Double
type SimilarityScore = Double
type Age = Integer
type TimeDistance = Integer

-- | Find up to N most similar and most recent transactions matching
-- | Find up to N most similar and nearest-dated transactions matching
-- the given transaction description and query and exceeding the given
-- description similarity score (0 to 1, see compareDescriptions).
-- The provided Day (today) is used as the reference date;
-- transactions are penalised by their absolute distance from it in days
-- (both past and future).
-- Returns transactions along with
-- their age in days compared to the latest transaction date,
-- their distance in days from today,
-- their description similarity score,
-- and a heuristically date-weighted variant of this that favours more recent transactions.
journalTransactionsSimilarTo :: Journal -> Text -> Query -> SimilarityScore -> Int
-> [(DateWeightedSimilarityScore, Age, SimilarityScore, Transaction)]
journalTransactionsSimilarTo Journal{jtxns} desc q similaritythreshold n =
-- and a heuristically date-weighted variant of this that favours nearby transactions.
journalTransactionsSimilarTo :: Journal -> Day -> Text -> Query -> SimilarityScore -> Int
-> [(DateWeightedSimilarityScore, TimeDistance, SimilarityScore, Transaction)]
journalTransactionsSimilarTo Journal{jtxns} today desc q similaritythreshold n =
take n $
dbg1With (
unlines .
("up to 30 transactions above description similarity threshold "<>show similaritythreshold<>" ordered by recency-weighted similarity:":) .
unlines .
("up to 30 transactions above description similarity threshold "<>show similaritythreshold<>" ordered by proximity-weighted similarity:":) .
take 30 .
map ( \(w,a,s,Transaction{..}) -> printf "weighted:%8.3f age:%4d similarity:%5.3f %s %s" w a s (show tdate) tdescription )) $
map ( \(w,d,s,Transaction{..}) -> printf "weighted:%8.3f distance:%4d similarity:%5.3f %s %s" w d s (show tdate) tdescription )) $
sortBy (comparing (negate.first4)) $
map (\(s,t) -> (weightedScore (s,t), age t, s, t)) $
map (\(s,t) -> (weightedScore (s,t), timedistance t, s, t)) $
filter ((> similaritythreshold).fst)
[(compareDescriptions desc $ tdescription t, t) | t <- jtxns, q `matchesTransaction` t]
where
latest = lastDef nulldate $ sort $ map tdate jtxns
age = diffDays latest . tdate
-- Combine similarity and recency heuristically. This gave decent results
timedistance t = abs $ diffDays today (tdate t)
-- Combine similarity and time distance heuristically. This gave decent results
-- in my "find most recent invoice" use case in 2023-03,
-- but will probably need more attention.
weightedScore :: (Double, Transaction) -> Double
weightedScore (s, t) = 100 * s - fromIntegral (age t) / 4
weightedScore (s, t) = 100 * s - fromIntegral (timedistance t) / 4

-- | Return a similarity score from 0 to 1.5 for two transaction descriptions.
-- This is based on compareStrings, with the following modifications:
Expand Down
Loading