hledger-forecast 0.4.0 → 1.1.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.
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.0
4
+ version: 1.1.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-09 00:00:00.000000000 Z
11
+ date: 2023-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -113,10 +113,16 @@ files:
113
113
  - example.yml
114
114
  - hledger-forecast.gemspec
115
115
  - lib/hledger_forecast.rb
116
+ - lib/hledger_forecast/calculator.rb
116
117
  - lib/hledger_forecast/cli.rb
118
+ - lib/hledger_forecast/formatter.rb
117
119
  - lib/hledger_forecast/generator.rb
118
- - lib/hledger_forecast/summarize.rb
119
- - lib/hledger_forecast/tracker.rb
120
+ - lib/hledger_forecast/settings.rb
121
+ - lib/hledger_forecast/summarizer.rb
122
+ - lib/hledger_forecast/summarizer_formatter.rb
123
+ - lib/hledger_forecast/transactions/default.rb
124
+ - lib/hledger_forecast/transactions/modifiers.rb
125
+ - lib/hledger_forecast/transactions/trackers.rb
120
126
  - lib/hledger_forecast/version.rb
121
127
  - spec/command_spec.rb
122
128
  - spec/computed_amounts_spec.rb
@@ -132,6 +138,7 @@ files:
132
138
  - spec/stubs/transactions_found.journal
133
139
  - spec/stubs/transactions_found_inverse.journal
134
140
  - spec/stubs/transactions_not_found.journal
141
+ - spec/summarizer_spec.rb
135
142
  - spec/track_spec.rb
136
143
  - spec/yearly_spec.rb
137
144
  homepage: https://github.com/olimorris/hledger-forecast
@@ -144,16 +151,16 @@ require_paths:
144
151
  - lib
145
152
  required_ruby_version: !ruby/object:Gem::Requirement
146
153
  requirements:
147
- - - ">="
154
+ - - "~>"
148
155
  - !ruby/object:Gem::Version
149
- version: '0'
156
+ version: '3.0'
150
157
  required_rubygems_version: !ruby/object:Gem::Requirement
151
158
  requirements:
152
159
  - - ">="
153
160
  - !ruby/object:Gem::Version
154
161
  version: '0'
155
162
  requirements: []
156
- rubygems_version: 3.4.12
163
+ rubygems_version: 3.2.3
157
164
  signing_key:
158
165
  specification_version: 4
159
166
  summary: An extended wrapper around hledger's forecasting functionality
@@ -172,5 +179,6 @@ test_files:
172
179
  - spec/stubs/transactions_found.journal
173
180
  - spec/stubs/transactions_found_inverse.journal
174
181
  - spec/stubs/transactions_not_found.journal
182
+ - spec/summarizer_spec.rb
175
183
  - spec/track_spec.rb
176
184
  - spec/yearly_spec.rb
@@ -1,134 +0,0 @@
1
- module HledgerForecast
2
- # Summarise a forecast YAML file and output it to the CLI
3
- class Summarize
4
- @table = nil
5
- @generator = nil
6
-
7
- def self.init_table
8
- table = Terminal::Table.new
9
-
10
- table.add_row([{ value: 'FORECAST SUMMARY'.bold, colspan: 3, alignment: :center }])
11
- table.add_separator
12
-
13
- @table = table
14
- end
15
-
16
- def self.init_generator(forecast_data)
17
- generator = HledgerForecast::Generator
18
- generator.set_options(forecast_data)
19
-
20
- @generator = generator
21
- end
22
-
23
- def self.sum_transactions(forecast_data, period)
24
- category_total = Hash.new(0)
25
- forecast_data[period]&.each do |entry|
26
- entry['transactions'].each do |transaction|
27
- category_total[transaction['category']] += transaction['amount']
28
- end
29
- end
30
-
31
- category_total
32
- end
33
-
34
- def self.sum_custom_transactions(forecast_data)
35
- category_total = Hash.new(0)
36
- custom_periods = []
37
-
38
- forecast_data['custom']&.each do |entry|
39
- period_data = {}
40
- period_data[:frequency] = entry['frequency']
41
- period_data[:category] = entry['transactions'].first['category']
42
- period_data[:amount] = entry['transactions'].first['amount']
43
-
44
- entry['transactions'].each do |transaction|
45
- category_total[transaction['category']] += transaction['amount']
46
- end
47
-
48
- custom_periods << period_data
49
- end
50
-
51
- { totals: category_total, periods: custom_periods }
52
- end
53
-
54
- def self.format_amount(amount)
55
- formatted_amount = @generator.format_amount(amount)
56
- amount.to_f < 0 ? formatted_amount.green : formatted_amount.red
57
- end
58
-
59
- def self.add_rows_to_table(row_data, period_total, custom: false)
60
- if custom
61
- row_data[:periods].each do |period|
62
- @table.add_row [{ value: period[:category], alignment: :left },
63
- { value: period[:frequency], alignment: :right },
64
- { value: format_amount(period[:amount]), alignment: :right }]
65
-
66
- period_total += period[:amount]
67
- end
68
- else
69
- row_data.each do |category, amount|
70
- @table.add_row [{ value: category, colspan: 2, alignment: :left },
71
- { value: format_amount(amount), alignment: :right }]
72
-
73
- period_total += amount
74
- end
75
- end
76
-
77
- period_total
78
- end
79
-
80
- def self.add_categories_to_table(categories, forecast_data)
81
- first_period = true
82
- categories.each do |period, total|
83
- category_total = total.reject { |_, amount| amount == 0 }
84
- next if category_total.empty?
85
-
86
- sorted_category_total = sort_transactions(category_total)
87
-
88
- @table.add_separator unless first_period
89
- @table.add_row([{ value: period.capitalize.bold, colspan: 3, alignment: :center }])
90
-
91
- period_total = 0
92
- period_total += if period == 'custom'
93
- add_rows_to_table(sum_custom_transactions(forecast_data), period_total, custom: true)
94
- else
95
- add_rows_to_table(sorted_category_total, period_total)
96
- end
97
-
98
- format_total("#{period.capitalize} TOTAL", period_total)
99
- first_period = false
100
- end
101
- end
102
-
103
- def self.sort_transactions(category_total)
104
- negatives = category_total.select { |_, amount| amount < 0 }.sort_by { |_, amount| amount }
105
- positives = category_total.select { |_, amount| amount > 0 }.sort_by { |_, amount| -amount }
106
-
107
- negatives.concat(positives).to_h
108
- end
109
-
110
- def self.format_total(text, total)
111
- @table.add_row [{ value: text.bold, colspan: 2, alignment: :left },
112
- { value: format_amount(total).bold, alignment: :right }]
113
- end
114
-
115
- def self.generate(forecast)
116
- forecast_data = YAML.safe_load(forecast)
117
-
118
- init_table
119
- init_generator(forecast_data)
120
-
121
- category_totals = {}
122
- %w[monthly quarterly half-yearly yearly once custom].each do |period|
123
- category_totals[period] = sum_transactions(forecast_data, period)
124
- end
125
-
126
- add_categories_to_table(category_totals, forecast_data)
127
-
128
- @table.add_separator
129
- format_total("TOTAL", category_totals.values.map(&:values).flatten.sum)
130
-
131
- puts @table
132
- end
133
- end
134
- end
@@ -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