hledger-forecast 0.3.0 → 1.0.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 +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/.gitignore +1 -0
- data/.rubocop.yml +3 -0
- data/README.md +68 -33
- data/example.journal +6 -1
- data/example.yml +3 -0
- data/hledger-forecast.gemspec +2 -0
- data/lib/hledger_forecast/calculator.rb +21 -0
- data/lib/hledger_forecast/cli.rb +2 -2
- data/lib/hledger_forecast/formatter.rb +24 -0
- data/lib/hledger_forecast/generator.rb +41 -249
- data/lib/hledger_forecast/settings.rb +41 -0
- data/lib/hledger_forecast/{summarize.rb → summarizer.rb} +41 -43
- data/lib/hledger_forecast/transactions/default.rb +88 -0
- data/lib/hledger_forecast/transactions/modifiers.rb +90 -0
- data/lib/hledger_forecast/transactions/trackers.rb +87 -0
- data/lib/hledger_forecast/version.rb +1 -1
- data/lib/hledger_forecast.rb +11 -4
- data/spec/computed_amounts_spec.rb +35 -0
- data/spec/custom_spec.rb +31 -4
- data/spec/modifier_spec.rb +21 -6
- data/spec/monthly_end_date_spec.rb +18 -33
- data/spec/monthly_end_date_transaction_spec.rb +44 -7
- data/spec/track_spec.rb +5 -67
- metadata +28 -7
- data/lib/hledger_forecast/tracker.rb +0 -37
|
@@ -1,47 +1,32 @@
|
|
|
1
1
|
require_relative '../lib/hledger_forecast'
|
|
2
2
|
|
|
3
3
|
config = <<~YAML
|
|
4
|
-
settings:
|
|
5
|
-
|
|
4
|
+
settings:
|
|
5
|
+
currency: GBP
|
|
6
6
|
|
|
7
|
-
monthly:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
amount: 200.00
|
|
19
|
-
- description: Food
|
|
20
|
-
category: "Expenses:Food"
|
|
21
|
-
amount: 100.00
|
|
22
|
-
- description: Party time
|
|
23
|
-
category: "Expenses:Going Out"
|
|
24
|
-
amount: 50.00
|
|
7
|
+
monthly:
|
|
8
|
+
- from: "2023-03-01"
|
|
9
|
+
to: "2023-06-01"
|
|
10
|
+
account: "Assets:Bank"
|
|
11
|
+
transactions:
|
|
12
|
+
- description: Mortgage
|
|
13
|
+
category: "Expenses:Mortgage"
|
|
14
|
+
amount: 2000.00
|
|
15
|
+
- description: Food
|
|
16
|
+
category: "Expenses:Food"
|
|
17
|
+
amount: 100.00
|
|
25
18
|
YAML
|
|
26
19
|
|
|
27
20
|
output = <<~JOURNAL
|
|
28
|
-
~ monthly from 2023-03-01 *
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
~ monthly from 2023-03-01 to 2023-06-01 * Mortgage
|
|
34
|
-
Expenses:Mortgage £2,000.00; Mortgage
|
|
35
|
-
Assets:Bank
|
|
36
|
-
|
|
37
|
-
~ monthly from 2023-03-01 to 2023-06-01 * Mortgage top up
|
|
38
|
-
Expenses:Mortgage Top Up £200.00 ; Mortgage top up
|
|
39
|
-
Assets:Bank
|
|
21
|
+
~ monthly from 2023-03-01 to 2023-06-01 * Mortgage, Food
|
|
22
|
+
Expenses:Mortgage £2,000.00; Mortgage
|
|
23
|
+
Expenses:Food £100.00 ; Food
|
|
24
|
+
Assets:Bank
|
|
40
25
|
|
|
41
26
|
JOURNAL
|
|
42
27
|
|
|
43
28
|
RSpec.describe 'generate' do
|
|
44
|
-
it 'generates a forecast with correct MONTHLY transactions that have an end date' do
|
|
29
|
+
it 'generates a forecast with correct MONTHLY transactions that have an end date, at the top level' do
|
|
45
30
|
expect(HledgerForecast::Generator.generate(config)).to eq(output)
|
|
46
31
|
end
|
|
47
32
|
end
|
|
@@ -1,32 +1,69 @@
|
|
|
1
1
|
require_relative '../lib/hledger_forecast'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
base_config = <<~YAML
|
|
4
4
|
settings:
|
|
5
5
|
currency: GBP
|
|
6
6
|
|
|
7
7
|
monthly:
|
|
8
8
|
- from: "2023-03-01"
|
|
9
|
-
to: "2023-06-01"
|
|
10
9
|
account: "Assets:Bank"
|
|
11
10
|
transactions:
|
|
12
11
|
- description: Mortgage
|
|
12
|
+
to: "2023-06-01"
|
|
13
13
|
category: "Expenses:Mortgage"
|
|
14
14
|
amount: 2000.00
|
|
15
|
+
- description: Mortgage top up
|
|
16
|
+
to: "2023-06-01"
|
|
17
|
+
category: "Expenses:Mortgage Top Up"
|
|
18
|
+
amount: 200.00
|
|
15
19
|
- description: Food
|
|
16
20
|
category: "Expenses:Food"
|
|
17
21
|
amount: 100.00
|
|
22
|
+
- description: Party time
|
|
23
|
+
category: "Expenses:Going Out"
|
|
24
|
+
amount: 50.00
|
|
18
25
|
YAML
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
~ monthly from 2023-03-01 to 2023-06-01 * Mortgage,
|
|
27
|
+
base_output = <<~JOURNAL
|
|
28
|
+
~ monthly from 2023-03-01 to 2023-06-01 * Mortgage, Mortgage top up
|
|
29
|
+
Expenses:Mortgage £2,000.00; Mortgage
|
|
30
|
+
Expenses:Mortgage Top Up £200.00 ; Mortgage top up
|
|
31
|
+
Assets:Bank
|
|
32
|
+
|
|
33
|
+
~ monthly from 2023-03-01 * Food, Party time
|
|
34
|
+
Expenses:Food £100.00 ; Food
|
|
35
|
+
Expenses:Going Out £50.00 ; Party time
|
|
36
|
+
Assets:Bank
|
|
37
|
+
|
|
38
|
+
JOURNAL
|
|
39
|
+
|
|
40
|
+
computed_config = <<~YAML
|
|
41
|
+
settings:
|
|
42
|
+
currency: GBP
|
|
43
|
+
|
|
44
|
+
monthly:
|
|
45
|
+
- from: "2023-03-01"
|
|
46
|
+
account: "Assets:Bank"
|
|
47
|
+
transactions:
|
|
48
|
+
- description: Mortgage
|
|
49
|
+
category: "Expenses:Mortgage"
|
|
50
|
+
to: "=12"
|
|
51
|
+
amount: 2000.00
|
|
52
|
+
YAML
|
|
53
|
+
|
|
54
|
+
computed_output = <<~JOURNAL
|
|
55
|
+
~ monthly from 2023-03-01 to 2024-02-29 * Mortgage
|
|
22
56
|
Expenses:Mortgage £2,000.00; Mortgage
|
|
23
|
-
Expenses:Food £100.00 ; Food
|
|
24
57
|
Assets:Bank
|
|
25
58
|
|
|
26
59
|
JOURNAL
|
|
27
60
|
|
|
28
61
|
RSpec.describe 'generate' do
|
|
29
|
-
it 'generates a forecast with correct MONTHLY transactions that have an end date
|
|
30
|
-
expect(HledgerForecast::Generator.generate(
|
|
62
|
+
it 'generates a forecast with correct MONTHLY transactions that have an end date' do
|
|
63
|
+
expect(HledgerForecast::Generator.generate(base_config)).to eq(base_output)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'generates a forecast with correct MONTHLY transactions that have a COMPUTED end date' do
|
|
67
|
+
expect(HledgerForecast::Generator.generate(computed_config)).to eq(computed_output)
|
|
31
68
|
end
|
|
32
69
|
end
|
data/spec/track_spec.rb
CHANGED
|
@@ -42,70 +42,13 @@ base_output = <<~JOURNAL
|
|
|
42
42
|
JOURNAL
|
|
43
43
|
|
|
44
44
|
RSpec.describe 'Tracking transactions -' do
|
|
45
|
-
it 'Determines which transactions should be tracked' do
|
|
46
|
-
generated = HledgerForecast::Generator
|
|
47
|
-
generated.generate(base_config)
|
|
48
|
-
tracked = generated.tracked
|
|
49
|
-
|
|
50
|
-
expect(tracked[0]['transaction']).to eq(
|
|
51
|
-
{ "amount" => "£3,000.00", "category" => "Expenses:Tax", "description" => "Tax owed",
|
|
52
|
-
"inverse_amount" => "£-3,000.00", "track" => true }
|
|
53
|
-
)
|
|
54
|
-
expect(tracked[0]['account']).to eq("Assets:Bank")
|
|
55
|
-
|
|
56
|
-
expect(tracked[1]['transaction']).to eq(
|
|
57
|
-
{ "amount" => "£-1,500.00", "category" => "Income:Salary", "description" => "Salary", "to" => "2023-08-01",
|
|
58
|
-
"inverse_amount" => "£1,500.00", "track" => true }
|
|
59
|
-
)
|
|
60
|
-
expect(tracked[1]['account']).to eq("Assets:Bank")
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
it 'marks a transaction as NOT FOUND if it doesnt exist' do
|
|
64
|
-
generated = HledgerForecast::Generator
|
|
65
|
-
generated.tracked = {} # Clear tracked transactions
|
|
66
|
-
generated.generate(base_config)
|
|
67
|
-
transactions_to_track = generated.tracked
|
|
68
|
-
|
|
69
|
-
track = HledgerForecast::Tracker.track(transactions_to_track, 'spec/stubs/transactions_not_found.journal')
|
|
70
|
-
|
|
71
|
-
expect(track[0]['found']).to eq(false)
|
|
72
|
-
expect(track[1]['found']).to eq(false)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
it 'marks a transaction as FOUND if it exists' do
|
|
76
|
-
generated = HledgerForecast::Generator
|
|
77
|
-
generated.tracked = {} # Clear tracked transactions
|
|
78
|
-
generated.generate(base_config)
|
|
79
|
-
transactions_to_track = generated.tracked
|
|
80
|
-
|
|
81
|
-
track = HledgerForecast::Tracker.track(transactions_to_track, 'spec/stubs/transactions_found.journal')
|
|
82
|
-
|
|
83
|
-
expect(track[0]['found']).to eq(true)
|
|
84
|
-
expect(track[1]['found']).to eq(true)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
it 'marks a transaction as FOUND if it exists, even if the category/amount are inversed' do
|
|
88
|
-
generated = HledgerForecast::Generator
|
|
89
|
-
generated.tracked = {} # Clear tracked transactions
|
|
90
|
-
generated.generate(base_config)
|
|
91
|
-
transactions_to_track = generated.tracked
|
|
92
|
-
|
|
93
|
-
track = HledgerForecast::Tracker.track(transactions_to_track, 'spec/stubs/transactions_found_inverse.journal')
|
|
94
|
-
|
|
95
|
-
expect(track[0]['found']).to eq(true)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
45
|
it 'writes a NON-FOUND entry into a journal' do
|
|
99
46
|
options = {}
|
|
100
47
|
options[:transaction_file] = 'spec/stubs/transactions_not_found.journal'
|
|
101
48
|
|
|
102
|
-
|
|
103
|
-
generated.tracked = {} # Clear tracked transactions
|
|
104
|
-
|
|
105
|
-
generated_journal = generated.generate(base_config, options)
|
|
49
|
+
generated_journal = HledgerForecast::Generator.generate(base_config, options)
|
|
106
50
|
|
|
107
|
-
|
|
108
|
-
expect(generated_journal).to eq(expected_output)
|
|
51
|
+
expect(generated_journal).to eq(base_output)
|
|
109
52
|
end
|
|
110
53
|
|
|
111
54
|
it 'writes a NON-FOUND entry for dates that are close to the current period' do
|
|
@@ -147,12 +90,10 @@ RSpec.describe 'Tracking transactions -' do
|
|
|
147
90
|
options = {}
|
|
148
91
|
options[:transaction_file] = temp_file.path
|
|
149
92
|
|
|
150
|
-
|
|
151
|
-
generated.tracked = {} # Clear tracked transactions
|
|
152
|
-
|
|
153
|
-
generated_journal = generated.generate(forecast_config, options)
|
|
93
|
+
generated_journal = HledgerForecast::Generator.generate(forecast_config, options)
|
|
154
94
|
|
|
155
95
|
expected_output = <<~JOURNAL
|
|
96
|
+
|
|
156
97
|
~ #{next_month} * [TRACKED] New kitchen
|
|
157
98
|
Expenses:House £5,000.00; New kitchen
|
|
158
99
|
Assets:Bank
|
|
@@ -180,10 +121,7 @@ RSpec.describe 'Tracking transactions -' do
|
|
|
180
121
|
options = {}
|
|
181
122
|
options[:transaction_file] = 'spec/stubs/transactions_not_found.journal'
|
|
182
123
|
|
|
183
|
-
|
|
184
|
-
generated.tracked = {} # Clear tracked transactions
|
|
185
|
-
|
|
186
|
-
generated_journal = generated.generate(forecast_config, options)
|
|
124
|
+
generated_journal = HledgerForecast::Generator.generate(forecast_config, options)
|
|
187
125
|
|
|
188
126
|
output = <<~JOURNAL
|
|
189
127
|
~ monthly from #{next_month} * Food expenses
|
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: 0.
|
|
4
|
+
version: 1.0.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: 2023-05-
|
|
11
|
+
date: 2023-05-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: colorize
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: 0.8.1
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: dentaku
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 3.5.1
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 3.5.1
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: highline
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -99,12 +113,18 @@ files:
|
|
|
99
113
|
- example.yml
|
|
100
114
|
- hledger-forecast.gemspec
|
|
101
115
|
- lib/hledger_forecast.rb
|
|
116
|
+
- lib/hledger_forecast/calculator.rb
|
|
102
117
|
- lib/hledger_forecast/cli.rb
|
|
118
|
+
- lib/hledger_forecast/formatter.rb
|
|
103
119
|
- lib/hledger_forecast/generator.rb
|
|
104
|
-
- lib/hledger_forecast/
|
|
105
|
-
- lib/hledger_forecast/
|
|
120
|
+
- lib/hledger_forecast/settings.rb
|
|
121
|
+
- lib/hledger_forecast/summarizer.rb
|
|
122
|
+
- lib/hledger_forecast/transactions/default.rb
|
|
123
|
+
- lib/hledger_forecast/transactions/modifiers.rb
|
|
124
|
+
- lib/hledger_forecast/transactions/trackers.rb
|
|
106
125
|
- lib/hledger_forecast/version.rb
|
|
107
126
|
- spec/command_spec.rb
|
|
127
|
+
- spec/computed_amounts_spec.rb
|
|
108
128
|
- spec/custom_spec.rb
|
|
109
129
|
- spec/half-yearly_spec.rb
|
|
110
130
|
- spec/modifier_spec.rb
|
|
@@ -129,21 +149,22 @@ require_paths:
|
|
|
129
149
|
- lib
|
|
130
150
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
131
151
|
requirements:
|
|
132
|
-
- - "
|
|
152
|
+
- - "~>"
|
|
133
153
|
- !ruby/object:Gem::Version
|
|
134
|
-
version: '0'
|
|
154
|
+
version: '3.0'
|
|
135
155
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
156
|
requirements:
|
|
137
157
|
- - ">="
|
|
138
158
|
- !ruby/object:Gem::Version
|
|
139
159
|
version: '0'
|
|
140
160
|
requirements: []
|
|
141
|
-
rubygems_version: 3.
|
|
161
|
+
rubygems_version: 3.2.3
|
|
142
162
|
signing_key:
|
|
143
163
|
specification_version: 4
|
|
144
164
|
summary: An extended wrapper around hledger's forecasting functionality
|
|
145
165
|
test_files:
|
|
146
166
|
- spec/command_spec.rb
|
|
167
|
+
- spec/computed_amounts_spec.rb
|
|
147
168
|
- spec/custom_spec.rb
|
|
148
169
|
- spec/half-yearly_spec.rb
|
|
149
170
|
- spec/modifier_spec.rb
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
module HledgerForecast
|
|
2
|
-
# Checks for the existence of a transaction in a journal file and tracks it
|
|
3
|
-
class Tracker
|
|
4
|
-
def self.track(transactions, transaction_file)
|
|
5
|
-
next_month = Date.new(Date.today.year, Date.today.month, 1).next_month
|
|
6
|
-
|
|
7
|
-
transactions.each_with_object({}) do |(key, transaction), updated_transactions|
|
|
8
|
-
found = transaction_exists?(transaction_file, transaction['from'], Date.today, transaction['account'],
|
|
9
|
-
transaction['transaction'])
|
|
10
|
-
updated_transactions[key] = transaction.merge('from' => next_month, 'found' => found)
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def self.latest_date(file)
|
|
15
|
-
command = %(hledger print --file #{file} | grep '^[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}' | awk '{print $1}' | sort -r | head -n 1)
|
|
16
|
-
|
|
17
|
-
date_output = `#{command}`
|
|
18
|
-
date_output.strip
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def self.transaction_exists?(file, from, to, account, transaction)
|
|
22
|
-
category = escape_str(transaction['category'])
|
|
23
|
-
amount = transaction['amount']
|
|
24
|
-
inverse_amount = transaction['inverse_amount']
|
|
25
|
-
|
|
26
|
-
# We run two commands and check to see if category +/- amount or account +/- amount exists
|
|
27
|
-
command1 = %(hledger print -f #{file} "date:#{from}..#{to}" | tr -s '[:space:]' ' ' | grep -q -Eo "#{category} (#{amount}|#{inverse_amount})")
|
|
28
|
-
command2 = %(hledger print -f #{file} "date:#{from}..#{to}" | tr -s '[:space:]' ' ' | grep -q -Eo "#{account} (#{amount}|#{inverse_amount})")
|
|
29
|
-
|
|
30
|
-
system(command1) || system(command2)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def self.escape_str(str)
|
|
34
|
-
str.gsub('[', '\\[').gsub(']', '\\]').gsub('(', '\\(').gsub(')', '\\)')
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|