hledger-forecast 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 914475b3b188c0bf0950e6d2e0a88eb374a4486d41c901603c8cb41332e268f7
4
- data.tar.gz: 241b415b6b95b42e757fd5302700cb4b6395bdce105f00782a49b0149be6b2d5
3
+ metadata.gz: 5acfd1bbab287a58bfe5ce7237576535b6b927d034da9e3651a4c240653e0f33
4
+ data.tar.gz: 23eb8d2646996b422473440b8f90797215a7390f4d6e2862b470bd361b6d55d5
5
5
  SHA512:
6
- metadata.gz: cc7ee657e34ec05931165642761b797ec783d1c5c370d6085777d494503782efd32d140aea61e1e9f0d005ce5a6b69f5e28250a3c4150225ce70e5fbcc840a22
7
- data.tar.gz: 5e6de9e3a6da7531b7d6a4f47b6126e6c34f92ba387cbdcb21584053530f46447d74c9a472bd34e89b566b731c6f71b56a826d17e2613632ae46f435bdbe9d65
6
+ metadata.gz: 62a027f56e0faf7641769b13928ed61c91e3851fe6b1623df00efd233ec244be03c57d7b94ea2605f01580ba18e0a916807a59c6ef848118d9b80fa9a3e8be28
7
+ data.tar.gz: 470b89a138eff0a2f5da24d4a4fe31ffd7e9584646094665020d19a40a9303877073bfcbc95eb35eae1498149fbfa96b899bd44c5ee4eab4225f26acec9b2e11
@@ -13,9 +13,36 @@ jobs:
13
13
  contents: write
14
14
  pull-requests: write
15
15
 
16
+ outputs:
17
+ release_created: ${{ steps.release.outputs.release_created }}
18
+
16
19
  steps:
17
- - name: Release 📦
20
+ - name: Release
21
+ id: release
18
22
  uses: googleapis/release-please-action@v4
19
23
  with:
20
24
  release-type: ruby
21
25
 
26
+ publish:
27
+ runs-on: ubuntu-latest
28
+ needs: release
29
+ if: needs.release.outputs.release_created == 'true'
30
+
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+
34
+ - name: Set up Ruby
35
+ uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: "3.3"
38
+
39
+ - name: Publish to RubyGems
40
+ run: |
41
+ mkdir -p $HOME/.gem
42
+ touch $HOME/.gem/credentials
43
+ chmod 0600 $HOME/.gem/credentials
44
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
45
+ gem build *.gemspec
46
+ gem push *.gem
47
+ env:
48
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.2.0](https://github.com/olimorris/hledger-forecast/compare/v3.1.0...v3.2.0) (2026-04-13)
4
+
5
+
6
+ ### Features
7
+
8
+ * can exclude tags in summarizer ([#16](https://github.com/olimorris/hledger-forecast/issues/16)) ([faee83e](https://github.com/olimorris/hledger-forecast/commit/faee83ee8eb554d146177832c307b87f5ecd4616))
9
+ * from date can have calculations ([#18](https://github.com/olimorris/hledger-forecast/issues/18)) ([e62a693](https://github.com/olimorris/hledger-forecast/commit/e62a69385bf6c34f2d446593d5046fe22a1e7f5f))
10
+
3
11
  ## [3.1.0](https://github.com/olimorris/hledger-forecast/compare/v3.0.0...v3.1.0) (2026-04-02)
4
12
 
5
13
 
data/README.md CHANGED
@@ -147,7 +147,7 @@ The CSV file should have a header row with these columns:
147
147
  | `type` | string | yes | One of: `monthly`, `quarterly`, `half-yearly`, `yearly`, `once`, `custom` |
148
148
  | `frequency` | string | `custom` only | Repeating frequency, using hledger's [periodic rule syntax](https://hledger.org/dev/hledger.html#periodic-transactions) |
149
149
  | `account` | string | yes | The account the transaction applies to, e.g. `Assets:Bank` |
150
- | `from` | date | yes | Start date, e.g. `01/03/2023` |
150
+ | `from` | date | yes | Start date, e.g. `01/03/2023`. Supports `=` prefix for calculated values, e.g. `=01/03/2023+(5*12)` |
151
151
  | `to` | date | no | End date, e.g. `01/01/2025`. Supports `+` prefix for calculated values, e.g. `+12` for 12 months |
152
152
  | `description` | string | yes | A description of the transaction |
153
153
  | `category` | string | yes | The category account, e.g. `Expenses:Food` |
@@ -189,7 +189,17 @@ monthly,,Assets:Bank,01/03/2023,,New Kitchen,Expenses:House,=5000/24,,
189
189
 
190
190
  ### Calculated dates
191
191
 
192
- The `to` column supports calculated values. Use `+` followed by a number to mean "N months from the `from` date":
192
+ Both `from` and `to` support calculated values.
193
+
194
+ **`from` column** — prefix with `=` to compute a start date. The formula is `=BASE_DATE+OFFSET` where the offset is evaluated as months:
195
+
196
+ ```csv
197
+ monthly,,Assets:Bank,=01/09/2025+(5*12),,Salary,Income:Salary,-3500,,
198
+ ```
199
+
200
+ That sets the start date to 60 months (5 years) after `01/09/2025`, giving `01/09/2030`.
201
+
202
+ **`to` column** — use `+` followed by a number to mean "N months from the `from` date":
193
203
 
194
204
  ```csv
195
205
  monthly,,Assets:Bank,01/03/2026,+12,Holiday,Expenses:Holiday,125,,
@@ -235,6 +245,9 @@ hledger-forecast summarize -f forecast.csv --tags=essential
235
245
 
236
246
  # Multiple tags use OR logic — matches any
237
247
  hledger-forecast summarize -f forecast.csv --tags=fixed,living
248
+
249
+ # Can also exclude tags with a `-` prefix
250
+ hledger-forecast summarize -f forecast.csv --tags=fixed,-essential
238
251
  ```
239
252
 
240
253
  **Querying in hledger** - because the tags are native hledger format, you can query them directly:
@@ -8,6 +8,17 @@ module HledgerForecast
8
8
  @calc.evaluate(amount.slice(1..-1))
9
9
  end
10
10
 
11
+ def self.evaluate_from_date(value)
12
+ value = value.to_s
13
+ return Date.parse(value) unless value.start_with?("=")
14
+
15
+ date_str, offset_expr = value[1..].split("+", 2)
16
+ date = Date.parse(date_str)
17
+ return date unless offset_expr
18
+
19
+ date >> @calc.evaluate(offset_expr).to_i
20
+ end
21
+
11
22
  def self.evaluate_date(from, to)
12
23
  return (from >> to) - 1 if to.is_a?(Numeric)
13
24
  return Date.parse(to) unless to.start_with?("=") || to.start_with?("+")
@@ -24,7 +24,7 @@ module HledgerForecast
24
24
  keyword_init: true
25
25
  ) do
26
26
  def self.from_row(row)
27
- from = Date.parse(row[:from].to_s)
27
+ from = Calculator.evaluate_from_date(row[:from])
28
28
  new(
29
29
  type: row[:type],
30
30
  frequency: row[:frequency],
@@ -42,7 +42,14 @@ module HledgerForecast
42
42
 
43
43
  def matches_tags?(filter_tags)
44
44
  return true if filter_tags.nil? || filter_tags.empty?
45
- (tags & filter_tags).any?
45
+
46
+ exclude_tags = filter_tags.select { |t| t.start_with?("-") }.map { |t| t[1..] }
47
+ include_tags = filter_tags.reject { |t| t.start_with?("-") }
48
+
49
+ return false if exclude_tags.any? && (tags & exclude_tags).any?
50
+ return (tags & include_tags).any? if include_tags.any?
51
+
52
+ true
46
53
  end
47
54
 
48
55
  def annualised_amount
@@ -1,3 +1,3 @@
1
1
  module HledgerForecast
2
- VERSION = "3.1.0"
2
+ VERSION = "3.2.0"
3
3
  end
@@ -23,6 +23,24 @@ RSpec.describe HledgerForecast::Calculator do
23
23
  end
24
24
  end
25
25
 
26
+ describe ".evaluate_from_date" do
27
+ it "parses a plain date string" do
28
+ expect(described_class.evaluate_from_date("01/09/2022")).to(eq(Date.parse("2022-09-01")))
29
+ end
30
+
31
+ it "evaluates a date with a month offset" do
32
+ expect(described_class.evaluate_from_date("=01/09/2022+6")).to(eq(Date.parse("2023-03-01")))
33
+ end
34
+
35
+ it "evaluates a date with a multiplied offset" do
36
+ expect(described_class.evaluate_from_date("=01/09/2022+(5*12)")).to(eq(Date.parse("2027-09-01")))
37
+ end
38
+
39
+ it "evaluates a formula with no offset" do
40
+ expect(described_class.evaluate_from_date("=01/09/2022")).to(eq(Date.parse("2022-09-01")))
41
+ end
42
+ end
43
+
26
44
  describe ".evaluate_date" do
27
45
  let(:from) { Date.parse("2023-03-01") }
28
46
 
data/spec/tags_spec.rb CHANGED
@@ -72,6 +72,32 @@ RSpec.describe "tags" do
72
72
  expect(descriptions).to(eq(["Food", "Netflix"]))
73
73
  end
74
74
 
75
+ it "excludes transactions by tag with - prefix" do
76
+ result = HledgerForecast::Summarizer.summarize(config, {tags: ["-fixed"]})
77
+ descriptions = result[:output].map { |r| r[:description] }
78
+
79
+ expect(descriptions).to(eq(["Food", "Netflix"]))
80
+ end
81
+
82
+ it "excludes tags in generator output" do
83
+ expected = <<~JOURNAL
84
+ ~ monthly from 2023-03-01 * Food, Netflix
85
+ Expenses:Food £500.00; living:, essential:
86
+ Expenses:Subscriptions £15.00 ; living:
87
+ Assets:Bank
88
+
89
+ JOURNAL
90
+
91
+ expect(HledgerForecast::Generator.generate(config, {tags: ["-fixed"]})).to(eq(expected))
92
+ end
93
+
94
+ it "combines include and exclude tags" do
95
+ result = HledgerForecast::Summarizer.summarize(config, {tags: ["essential", "-fixed"]})
96
+ descriptions = result[:output].map { |r| r[:description] }
97
+
98
+ expect(descriptions).to(eq(["Food"]))
99
+ end
100
+
75
101
  it "raises an error when --tags is used on a CSV without a tag column" do
76
102
  csv_without_tag_column = <<~CSV
77
103
  type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hledger-forecast
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oli Morris
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-02 00:00:00.000000000 Z
11
+ date: 2026-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: abbrev
@@ -132,7 +132,6 @@ extensions: []
132
132
  extra_rdoc_files: []
133
133
  files:
134
134
  - ".github/workflows/ci.yml"
135
- - ".github/workflows/publish_ruby_gem.yml"
136
135
  - ".github/workflows/release.yml"
137
136
  - ".gitignore"
138
137
  - ".mise.toml"
@@ -201,7 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
201
200
  - !ruby/object:Gem::Version
202
201
  version: '0'
203
202
  requirements: []
204
- rubygems_version: 3.4.20
203
+ rubygems_version: 3.5.22
205
204
  signing_key:
206
205
  specification_version: 4
207
206
  summary: Generate hledger journal entries and income statements from a CSV forecast
@@ -1,24 +0,0 @@
1
- name: Publish Ruby Gem
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*'
7
- workflow_dispatch:
8
-
9
- jobs:
10
- release:
11
- runs-on: ubuntu-latest
12
-
13
- steps:
14
- - uses: actions/checkout@v4
15
- - name: Publish Gem 💎️
16
- run: |
17
- mkdir -p $HOME/.gem
18
- touch $HOME/.gem/credentials
19
- chmod 0600 $HOME/.gem/credentials
20
- printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
21
- gem build *.gemspec
22
- gem push *.gem
23
- env:
24
- GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"