hledger-forecast 0.1.7 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +95 -33
- data/example.yml +27 -8
- data/hledger-forecast.gemspec +1 -0
- data/lib/hledger_forecast/generator.rb +39 -5
- data/lib/hledger_forecast/options.rb +1 -0
- data/lib/hledger_forecast/summarize.rb +105 -38
- data/lib/hledger_forecast/version.rb +1 -1
- data/lib/hledger_forecast.rb +1 -0
- data/spec/custom_spec.rb +47 -0
- data/spec/start_date_spec.rb +12 -0
- data/spec/stubs/custom/forecast_custom_days.yml +14 -0
- data/spec/stubs/custom/forecast_custom_months.yml +14 -0
- data/spec/stubs/custom/forecast_custom_weeks.yml +14 -0
- data/spec/stubs/custom/forecast_custom_weeks_twice.yml +24 -0
- data/spec/stubs/custom/output_custom_days.journal +24 -0
- data/spec/stubs/custom/output_custom_months.journal +20 -0
- data/spec/stubs/custom/output_custom_weeks.journal +28 -0
- data/spec/stubs/custom/output_custom_weeks_twice.journal +44 -0
- data/spec/stubs/modifiers/forecast_modifiers.yml +13 -0
- data/spec/stubs/modifiers/output_modifiers.journal +44 -0
- data/spec/stubs/monthly/forecast_monthly.yml +7 -1
- data/spec/stubs/monthly/output_monthly.journal +15 -3
- data/spec/stubs/start_date/forecast_startdate.yml +26 -0
- data/spec/stubs/start_date/output_startdate.journal +56 -0
- metadata +45 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6dc9ef51b0f8d7927eb2b798d66257b740fad83be62d86c8207a5982aac752f
|
4
|
+
data.tar.gz: aec51b551655e2927de40237c64db32cfd4f4ebc40c855cd2bf97601e27112e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf00818fbbb03977e633c1e4c4eba8dd3596a6150e87117aa04e2d4b0df94c83593bef598200fa524a5d7c1777ce0be4bec2c0c4f4f0f8c9cc362732a3c8a1dc
|
7
|
+
data.tar.gz: 7a093260f8624ba0a27ead898fcda52926a00cfa12ed9f3bf67e020eb8d3df0cdfe0aa498164f0d07975f7c39efac9f0c39dd282fdd14e6684328d94b3ebbac1
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,18 +2,17 @@
|
|
2
2
|
|
3
3
|
[![Tests](https://github.com/olimorris/hledger-forecast/actions/workflows/ci.yml/badge.svg)](https://github.com/olimorris/hledger-forecast/actions/workflows/ci.yml)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
Uses a YAML file to generate monthly, quarterly, yearly and one-off transactions for better forecasting in [Hledger](https://github.com/simonmichael/hledger).
|
5
|
+
Uses a YAML file to generate periodic transactions for better forecasting in [Hledger](https://github.com/simonmichael/hledger).
|
8
6
|
|
9
7
|
See the [rationale](#brain-rationale) section for why this gem may be useful to you.
|
10
8
|
|
11
9
|
## :sparkles: Features
|
12
10
|
|
13
11
|
- :book: Uses a simple YAML config file to generate periodic transactions
|
14
|
-
- :date:
|
12
|
+
- :date: Generate forecasts between specified start and end dates
|
15
13
|
- :heavy_dollar_sign: Full currency support (uses the [RubyMoney](https://github.com/RubyMoney/money) gem)
|
16
14
|
- :computer: Simple and easy to use CLI
|
15
|
+
- :chart_with_upwards_trend: Summarize your forecasts by period and category and output to the CLI
|
17
16
|
|
18
17
|
## :package: Installation
|
19
18
|
|
@@ -23,10 +22,12 @@ Assuming you have Ruby and [Rubygems](http://rubygems.org/pages/download) instal
|
|
23
22
|
|
24
23
|
## :rocket: Usage
|
25
24
|
|
26
|
-
|
25
|
+
Run:
|
27
26
|
|
28
27
|
hledger-forecast
|
29
28
|
|
29
|
+
> **Note**: This assumes that a `forecast.yml` exists in the current working directory
|
30
|
+
|
30
31
|
Running `hledger-forecast -h` shows the available options:
|
31
32
|
|
32
33
|
Usage: Hledger-Forecast [options]
|
@@ -41,18 +42,28 @@ Running `hledger-forecast -h` shows the available options:
|
|
41
42
|
-h, --help Show this message
|
42
43
|
--version Show version
|
43
44
|
|
44
|
-
|
45
|
+
Another example of a common command:
|
46
|
+
|
47
|
+
hledger-forecast -f my_forecast.yml -s 2023-05-01 -e 2024-12-31
|
48
|
+
|
49
|
+
This will generate an output file (`my_forecast.journal`) from the forecast file between the two date ranges.
|
50
|
+
|
51
|
+
### Using with Hledger
|
52
|
+
|
53
|
+
To use the outputs in Hledger:
|
45
54
|
|
46
|
-
hledger -f transactions.journal -f
|
55
|
+
hledger -f transactions.journal -f my_forecast.journal
|
47
56
|
|
48
57
|
where:
|
49
58
|
|
50
59
|
- `transactions.journal` might be your bank transactions (your "_actuals_")
|
51
|
-
- `
|
60
|
+
- `my_forecast.journal` is the generated forecast file
|
52
61
|
|
53
|
-
|
62
|
+
## :gear: Configuration
|
54
63
|
|
55
|
-
|
64
|
+
### The YAML file
|
65
|
+
|
66
|
+
> **Note**: See the [example.yml](https://github.com/olimorris/hledger-forecast/blob/main/example.yml) file for an example of a complex config file
|
56
67
|
|
57
68
|
Firstly, create a `yml` file which will contain the transactions you'd like to forecast:
|
58
69
|
|
@@ -68,6 +79,9 @@ monthly:
|
|
68
79
|
- amount: 500
|
69
80
|
category: "[Expenses:Food]"
|
70
81
|
description: Food
|
82
|
+
|
83
|
+
settings:
|
84
|
+
currency: GBP
|
71
85
|
```
|
72
86
|
|
73
87
|
Let's examine what's going on in this config file:
|
@@ -77,50 +91,98 @@ Let's examine what's going on in this config file:
|
|
77
91
|
- Notice we're also using [virtual postings](https://hledger.org/1.29/hledger.html#virtual-postings) (designated by the brackets). This makes it easy to filter them out with the `-R` or `--real` option in Hledger
|
78
92
|
- We also have not specified a currency; the default (`USD`) will be used
|
79
93
|
|
80
|
-
###
|
94
|
+
### Periods
|
81
95
|
|
82
|
-
|
96
|
+
Besides monthly recurring transactions, the app also supports the following periods:
|
83
97
|
|
84
|
-
|
98
|
+
- `quarterly` - For transactions every _3 months_ from the given start date
|
99
|
+
- `half-yearly` - For transactions every _6 months_ from the given start date
|
100
|
+
- `yearly` - Generate transactions _once a year_ from the given start date
|
101
|
+
- `once` - Generate _one-time_ transactions on a specified date
|
102
|
+
- `custom` - Generate transactions every _n days/weeks/months_
|
85
103
|
|
86
|
-
|
87
|
-
- `half-yearly`
|
88
|
-
- `yearly`
|
89
|
-
- `once`
|
104
|
+
#### Custom period
|
90
105
|
|
91
|
-
|
106
|
+
A custom period allows you to specify a given number of days, weeks or months for a transaction to repeat within. These can be included in the config file as follows:
|
92
107
|
|
93
|
-
|
108
|
+
```yaml
|
109
|
+
custom:
|
110
|
+
- description: Fortnightly hair and beauty spend
|
111
|
+
recurrence:
|
112
|
+
period: weeks
|
113
|
+
quantity: 2
|
114
|
+
account: "[Assets:Bank]"
|
115
|
+
start: "2023-03-01"
|
116
|
+
transactions:
|
117
|
+
- amount: 80
|
118
|
+
category: "[Expenses:Personal Care]"
|
119
|
+
description: Hair and beauty
|
120
|
+
```
|
121
|
+
|
122
|
+
Where `quantity` is an integer and `period` is one of:
|
123
|
+
|
124
|
+
- days
|
125
|
+
- weeks
|
126
|
+
- months
|
94
127
|
|
95
|
-
|
128
|
+
### Dates
|
96
129
|
|
97
|
-
|
130
|
+
The core of any solid forecast is predicting the correct periods that costs will fall into. When running the app from the CLI, you can specify specific dates to generate transactions over (see the [usage](#rocket-usage) section).
|
131
|
+
|
132
|
+
You can further control the dates at a period/top-level as well as at a transaction level:
|
133
|
+
|
134
|
+
#### Top level
|
135
|
+
|
136
|
+
In the example below, all transactions in the `monthly` block will be constrained by the end date:
|
98
137
|
|
99
138
|
```yaml
|
100
|
-
|
101
|
-
|
102
|
-
|
139
|
+
monthly:
|
140
|
+
- account: "[Assets:Bank]"
|
141
|
+
start: "2023-03-01"
|
142
|
+
end: "2025-01-01"
|
143
|
+
transactions:
|
144
|
+
# details omitted for brevity
|
103
145
|
```
|
104
146
|
|
105
|
-
####
|
147
|
+
#### Transaction level
|
106
148
|
|
107
|
-
|
149
|
+
In the example below, only the single transaction will be constrained by the end date and controlled via an additional start date:
|
150
|
+
|
151
|
+
```yaml
|
152
|
+
monthly:
|
153
|
+
- account: "[Assets:Bank]"
|
154
|
+
start: "2023-03-01"
|
155
|
+
transactions:
|
156
|
+
- amount: 2000
|
157
|
+
category: "[Expenses:Mortgage]"
|
158
|
+
description: Mortgage
|
159
|
+
start: "2023-05-01"
|
160
|
+
end: "2025-01-01"
|
161
|
+
```
|
162
|
+
|
163
|
+
The addition of the `start` key means that while the block will start on 2023-03-01, the transaction for the mortgage won't start until `2023-05-01`.
|
164
|
+
|
165
|
+
### Additional settings
|
166
|
+
|
167
|
+
Additional settings in the config file to consider:
|
108
168
|
|
109
169
|
```yaml
|
110
|
-
# forecast.yml
|
111
170
|
settings:
|
171
|
+
currency: GBP # Specify the currency to use
|
112
172
|
show_symbol: true # Show the currency symbol?
|
113
173
|
sign_before_symbol: true # Show the negative sign before the symbol?
|
114
174
|
thousands_separator: true # Separate thousands with a comma?
|
115
175
|
```
|
116
176
|
|
117
|
-
|
177
|
+
## :rainbow: Helpers
|
178
|
+
|
179
|
+
### Summarizing the forecast file
|
118
180
|
|
119
181
|
As your config file grows, it can be helpful to sum up the total amounts and output them in the CLI. This can be achieved by:
|
120
182
|
|
121
|
-
hledger-forecast -f
|
183
|
+
hledger-forecast -f my_forecast.yml --summarize
|
122
184
|
|
123
|
-
where `
|
185
|
+
where `my_forecast.yml` is the config file to sum up.
|
124
186
|
|
125
187
|
## :brain: Rationale
|
126
188
|
|
@@ -128,8 +190,8 @@ Firstly, I've come to realise from reading countless blog and Reddit posts on [p
|
|
128
190
|
|
129
191
|
My days working in financial modelling have meant that a big macro-enabled spreadsheet was my go-to tool. Growing tired with the manual approach of importing transactions, heavily manipulating them, watching Excel become increasingly slower lead me to PTA. It's been a wonderful discovery.
|
130
192
|
|
131
|
-
One of the aspects of my previous approach to personal finance that I liked was the monthly recap of my performance and the looking ahead to the future. Am I still on track to hit my year-end savings goal? Am I still on track to hit my savings goal
|
193
|
+
One of the aspects of my previous approach to personal finance that I liked was the monthly recap of my performance and the looking ahead to the future. Am I still on track to hit my year-end savings goal given my future commitments? Am I still on track to hit my savings goal in 12 and 24 months time? It was at this point in my shift to PTA that I found it difficult to answer those questions with Hledger.
|
132
194
|
|
133
|
-
While
|
195
|
+
While there is support for [forecasting](https://hledger.org/1.29/hledger.html#forecasting) using periodic transactions in Hledger, these are computed virtually at runtime. If I notice a big difference in my forecasted year-end balance compared to what I'm expecting, I want to investigate and start reconcilling. Computed transactions make this nigh on impossible to unpick. Also, I get a lot of value out of running different forecast scenarios and seeing the impact. For example, _"what's my savings balance looking like in 3 years time if I get the kitchen remodelled?"_.
|
134
196
|
|
135
|
-
With this gem, my aim was to make it easy for users to change
|
197
|
+
With this gem, my aim was to make it easy for users to change their config file, regenerate the forecast and open a journal file and see the transactions. Or, use multiple forecast files for different scenarios and pass them in turn to Hledger to observe the impact.
|
data/example.yml
CHANGED
@@ -1,17 +1,24 @@
|
|
1
1
|
monthly:
|
2
2
|
- account: "[Assets:Bank]"
|
3
|
-
|
3
|
+
start: "2023-03-01"
|
4
4
|
transactions:
|
5
|
-
- amount:
|
6
|
-
category: "[
|
7
|
-
description:
|
5
|
+
- amount: -100
|
6
|
+
category: "[Income:Bonus]"
|
7
|
+
description: Bonus
|
8
|
+
- amount: -2000
|
9
|
+
category: "[Income:Salary]"
|
10
|
+
description: Salary
|
8
11
|
- amount: 500.00
|
9
12
|
category: "[Expenses:Food]"
|
10
13
|
description: Food
|
14
|
+
- amount: 1000.00
|
15
|
+
category: "[Expenses:Mortgage]"
|
16
|
+
description: Mortgage
|
17
|
+
end: "2024-01-01"
|
11
18
|
|
12
19
|
quarterly:
|
13
20
|
- account: "[Assets:Bank]"
|
14
|
-
|
21
|
+
start: "2023-04-01"
|
15
22
|
transactions:
|
16
23
|
- amount: -1000.00
|
17
24
|
category: "[Income:Bonus]"
|
@@ -19,7 +26,7 @@ quarterly:
|
|
19
26
|
|
20
27
|
half-yearly:
|
21
28
|
- account: "[Assets:Bank]"
|
22
|
-
|
29
|
+
start: "2023-04-01"
|
23
30
|
transactions:
|
24
31
|
- amount: 500
|
25
32
|
category: "[Expenses:Holiday]"
|
@@ -27,7 +34,7 @@ half-yearly:
|
|
27
34
|
|
28
35
|
yearly:
|
29
36
|
- account: "[Assets:Bank]"
|
30
|
-
|
37
|
+
start: "2023-04-01"
|
31
38
|
transactions:
|
32
39
|
- amount: -2000.00
|
33
40
|
category: "[Income:Bonus]"
|
@@ -35,12 +42,24 @@ yearly:
|
|
35
42
|
|
36
43
|
once:
|
37
44
|
- account: "[Assets:Bank]"
|
38
|
-
|
45
|
+
start: "2024-01-01"
|
39
46
|
transactions:
|
40
47
|
- amount: 5000.00
|
41
48
|
category: "[Expenses:Car]"
|
42
49
|
description: Forecast new car cost
|
43
50
|
|
51
|
+
custom:
|
52
|
+
- description: Fuel every 5 days
|
53
|
+
recurrence:
|
54
|
+
period: days
|
55
|
+
quantity: 5
|
56
|
+
account: "[Assets:Bank]"
|
57
|
+
start: "2023-03-01"
|
58
|
+
transactions:
|
59
|
+
- amount: 150
|
60
|
+
category: "[Expenses:Car:Fuel]"
|
61
|
+
description: Car fuel
|
62
|
+
|
44
63
|
settings:
|
45
64
|
currency: GBP
|
46
65
|
show_symbol: true
|
data/hledger-forecast.gemspec
CHANGED
@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.add_dependency "highline", "~> 2.1.0"
|
16
16
|
s.add_dependency "money", "~> 6.16.0"
|
17
17
|
s.add_dependency "colorize", "~> 0.8.1"
|
18
|
+
s.add_dependency "terminal-table", "~> 3.0.2"
|
18
19
|
s.add_development_dependency 'rspec', '~> 3.12'
|
19
20
|
|
20
21
|
s.files = `git ls-files`.split("\n")
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module HledgerForecast
|
2
2
|
# Generates journal entries based on a YAML forecast file.
|
3
3
|
# on forecast data and optional existing transactions.
|
4
|
-
#
|
5
4
|
class Generator
|
6
5
|
class << self
|
7
6
|
attr_accessor :settings
|
@@ -26,15 +25,46 @@ module HledgerForecast
|
|
26
25
|
formatted_transaction = transaction.clone
|
27
26
|
|
28
27
|
formatted_transaction['amount'] =
|
29
|
-
Money.from_cents(formatted_transaction['amount'].
|
28
|
+
Money.from_cents(formatted_transaction['amount'].to_f * 100, @settings[:currency]).format(
|
30
29
|
symbol: @settings[:show_symbol],
|
31
30
|
sign_before_symbol: @settings[:sign_before_symbol],
|
32
|
-
thousands_separator: @settings[:thousands_separator] ? ',' : nil
|
31
|
+
thousands_separator: @settings[:thousands_separator] ? ',' : nil
|
33
32
|
)
|
34
33
|
|
35
34
|
formatted_transaction
|
36
35
|
end
|
37
36
|
|
37
|
+
def self.process_custom(output, forecast_data, date)
|
38
|
+
forecast_data['custom']&.each do |forecast|
|
39
|
+
start_date = Date.parse(forecast['start'])
|
40
|
+
end_date = forecast['end'] ? Date.parse(forecast['end']) : nil
|
41
|
+
account = forecast['account']
|
42
|
+
period = forecast['recurrence']['period']
|
43
|
+
quantity = forecast['recurrence']['quantity']
|
44
|
+
|
45
|
+
next if end_date && date > end_date
|
46
|
+
|
47
|
+
date_matches = case period
|
48
|
+
when 'days'
|
49
|
+
(date - start_date).to_i % quantity == 0
|
50
|
+
when 'weeks'
|
51
|
+
(date - start_date).to_i % (quantity * 7) == 0
|
52
|
+
when 'months'
|
53
|
+
((date.year * 12 + date.month) - (start_date.year * 12 + start_date.month)) % quantity == 0 && date.day == start_date.day
|
54
|
+
end
|
55
|
+
|
56
|
+
if date_matches
|
57
|
+
forecast['transactions'].each do |transaction|
|
58
|
+
end_date = transaction['end'] ? Date.parse(transaction['end']) : nil
|
59
|
+
|
60
|
+
next unless end_date.nil? || date <= end_date
|
61
|
+
|
62
|
+
write_transactions(output, date, account, format_transaction(transaction))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
38
68
|
def self.process_forecast(output_file, forecast_data, type, date)
|
39
69
|
forecast_data[type]&.each do |forecast|
|
40
70
|
start_date = Date.parse(forecast['start'])
|
@@ -58,9 +88,12 @@ module HledgerForecast
|
|
58
88
|
|
59
89
|
if date_matches
|
60
90
|
forecast['transactions'].each do |transaction|
|
61
|
-
|
91
|
+
transaction_start_date = transaction['start'] ? Date.parse(transaction['start']) : nil
|
92
|
+
transaction_end_date = transaction['end'] ? Date.parse(transaction['end']) : nil
|
62
93
|
|
63
|
-
|
94
|
+
if (transaction_start_date && date < transaction_start_date) || (transaction_end_date && date > transaction_end_date)
|
95
|
+
next
|
96
|
+
end
|
64
97
|
|
65
98
|
write_transactions(output_file, date, account, format_transaction(transaction))
|
66
99
|
end
|
@@ -86,6 +119,7 @@ module HledgerForecast
|
|
86
119
|
process_forecast(output, forecast_data, 'half-yearly', date)
|
87
120
|
process_forecast(output, forecast_data, 'yearly', date)
|
88
121
|
process_forecast(output, forecast_data, 'once', date)
|
122
|
+
process_custom(output, forecast_data, date)
|
89
123
|
|
90
124
|
date = date.next_day
|
91
125
|
end
|
@@ -1,68 +1,135 @@
|
|
1
1
|
module HledgerForecast
|
2
2
|
# Summarise a forecast YAML file and output it to the CLI
|
3
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.configure_settings(forecast_data)
|
19
|
+
|
20
|
+
@generator = generator
|
21
|
+
end
|
22
|
+
|
4
23
|
def self.sum_transactions(forecast_data, period)
|
5
|
-
|
24
|
+
category_total = Hash.new(0)
|
6
25
|
forecast_data[period]&.each do |entry|
|
7
26
|
entry['transactions'].each do |transaction|
|
8
|
-
|
27
|
+
category_total[transaction['category']] += transaction['amount']
|
9
28
|
end
|
10
29
|
end
|
11
|
-
|
30
|
+
|
31
|
+
category_total
|
12
32
|
end
|
13
33
|
|
14
|
-
def self.
|
15
|
-
|
16
|
-
|
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[:quantity] = entry['recurrence']['quantity']
|
41
|
+
period_data[:period] = entry['recurrence']['period']
|
42
|
+
period_data[:category] = entry['transactions'].first['category']
|
43
|
+
period_data[:amount] = entry['transactions'].first['amount']
|
44
|
+
|
45
|
+
entry['transactions'].each do |transaction|
|
46
|
+
category_total[transaction['category']] += transaction['amount']
|
47
|
+
end
|
17
48
|
|
18
|
-
|
19
|
-
formatted_amount = generator.format_transaction({ 'amount' => amount })['amount']
|
20
|
-
formatted_amount = amount.to_i < 0 ? formatted_amount.red : formatted_amount.green
|
21
|
-
puts " #{category.ljust(40)}#{formatted_amount}"
|
22
|
-
period_total += amount
|
49
|
+
custom_periods << period_data
|
23
50
|
end
|
24
51
|
|
25
|
-
|
26
|
-
formatted_period_total = period_total.to_i < 0 ? formatted_period_total.red : formatted_period_total.green
|
27
|
-
puts " TOTAL".ljust(42) + formatted_period_total
|
52
|
+
{ totals: category_total, periods: custom_periods }
|
28
53
|
end
|
29
54
|
|
30
|
-
def self.
|
31
|
-
|
32
|
-
|
33
|
-
|
55
|
+
def self.format_amount(amount)
|
56
|
+
formatted_amount = @generator.format_transaction({ 'amount' => amount })['amount']
|
57
|
+
amount.to_f < 0 ? formatted_amount.green : formatted_amount.red
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.add_rows_to_table(row_data, period_total, custom: false)
|
61
|
+
if custom
|
62
|
+
row_data[:periods].each do |period|
|
63
|
+
@table.add_row [{ value: period[:category], alignment: :left },
|
64
|
+
{ value: "every #{period[:quantity]} #{period[:period]}", alignment: :right },
|
65
|
+
{ value: format_amount(period[:amount]), alignment: :right }]
|
66
|
+
|
67
|
+
period_total += period[:amount]
|
68
|
+
end
|
69
|
+
else
|
70
|
+
row_data.each do |category, amount|
|
71
|
+
@table.add_row [{ value: category, colspan: 2, alignment: :left },
|
72
|
+
{ value: format_amount(amount), alignment: :right }]
|
34
73
|
|
35
|
-
|
36
|
-
|
37
|
-
grand_total += total[period]
|
74
|
+
period_total += amount
|
75
|
+
end
|
38
76
|
end
|
39
77
|
|
40
|
-
|
41
|
-
total
|
78
|
+
period_total
|
42
79
|
end
|
43
80
|
|
44
|
-
def self.
|
45
|
-
|
81
|
+
def self.add_categories_to_table(categories, forecast_data)
|
82
|
+
first_period = true
|
83
|
+
categories.each do |period, total|
|
84
|
+
category_total = total.reject { |_, amount| amount == 0 }
|
85
|
+
next if category_total.empty?
|
86
|
+
|
87
|
+
sorted_category_total = sort_transactions(category_total)
|
88
|
+
|
89
|
+
@table.add_separator unless first_period
|
90
|
+
@table.add_row([{ value: period.capitalize.bold, colspan: 3, alignment: :center }])
|
46
91
|
|
47
|
-
|
48
|
-
|
49
|
-
|
92
|
+
period_total = 0
|
93
|
+
period_total += if period == 'custom'
|
94
|
+
add_rows_to_table(sum_custom_transactions(forecast_data), period_total, custom: true)
|
95
|
+
else
|
96
|
+
add_rows_to_table(sorted_category_total, period_total)
|
97
|
+
end
|
98
|
+
|
99
|
+
format_total("#{period.capitalize} TOTAL", period_total)
|
100
|
+
first_period = false
|
50
101
|
end
|
102
|
+
end
|
51
103
|
|
52
|
-
|
104
|
+
def self.sort_transactions(category_total)
|
105
|
+
negatives = category_total.select { |_, amount| amount < 0 }.sort_by { |_, amount| amount }
|
106
|
+
positives = category_total.select { |_, amount| amount > 0 }.sort_by { |_, amount| -amount }
|
53
107
|
|
54
|
-
|
55
|
-
|
108
|
+
negatives.concat(positives).to_h
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.format_total(text, total)
|
112
|
+
@table.add_row [{ value: text.bold, colspan: 2, alignment: :left },
|
113
|
+
{ value: format_amount(total).bold, alignment: :right }]
|
114
|
+
end
|
56
115
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
116
|
+
def self.generate(forecast)
|
117
|
+
forecast_data = YAML.safe_load(forecast)
|
118
|
+
|
119
|
+
init_table
|
120
|
+
init_generator(forecast_data)
|
121
|
+
|
122
|
+
category_totals = {}
|
123
|
+
%w[monthly quarterly half-yearly yearly once custom].each do |period|
|
124
|
+
category_totals[period] = sum_transactions(forecast_data, period)
|
61
125
|
end
|
62
126
|
|
63
|
-
|
64
|
-
|
65
|
-
|
127
|
+
add_categories_to_table(category_totals, forecast_data)
|
128
|
+
|
129
|
+
@table.add_separator
|
130
|
+
format_total("TOTAL", category_totals.values.map(&:values).flatten.sum)
|
131
|
+
|
132
|
+
puts @table
|
66
133
|
end
|
67
134
|
end
|
68
135
|
end
|
data/lib/hledger_forecast.rb
CHANGED
data/spec/custom_spec.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../lib/hledger_forecast'
|
2
|
+
|
3
|
+
RSpec.describe 'generate' do
|
4
|
+
it 'generates a forecast with correct CUSTOM DAILY transactions' do
|
5
|
+
transactions = File.read('spec/stubs/transactions.journal')
|
6
|
+
forecast = File.read('spec/stubs/custom/forecast_custom_days.yml')
|
7
|
+
|
8
|
+
generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
|
9
|
+
'2023-03-10')
|
10
|
+
|
11
|
+
expected_output = File.read('spec/stubs/custom/output_custom_days.journal')
|
12
|
+
expect(generated_journal).to eq(expected_output)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'generates a forecast with correct CUSTOM WEEKlY transactions' do
|
16
|
+
transactions = File.read('spec/stubs/transactions.journal')
|
17
|
+
forecast = File.read('spec/stubs/custom/forecast_custom_weeks.yml')
|
18
|
+
|
19
|
+
generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
|
20
|
+
'2023-04-30')
|
21
|
+
|
22
|
+
expected_output = File.read('spec/stubs/custom/output_custom_weeks.journal')
|
23
|
+
expect(generated_journal).to eq(expected_output)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'generates a forecast with MULTIPLE correct CUSTOM WEEKlY transactions' do
|
27
|
+
transactions = File.read('spec/stubs/transactions.journal')
|
28
|
+
forecast = File.read('spec/stubs/custom/forecast_custom_weeks_twice.yml')
|
29
|
+
|
30
|
+
generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
|
31
|
+
'2023-03-30')
|
32
|
+
|
33
|
+
expected_output = File.read('spec/stubs/custom/output_custom_weeks_twice.journal')
|
34
|
+
expect(generated_journal).to eq(expected_output)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'generates a forecast with correct CUSTOM MONTHLY transactions' do
|
38
|
+
transactions = File.read('spec/stubs/transactions.journal')
|
39
|
+
forecast = File.read('spec/stubs/custom/forecast_custom_months.yml')
|
40
|
+
|
41
|
+
generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
|
42
|
+
'2024-02-28')
|
43
|
+
|
44
|
+
expected_output = File.read('spec/stubs/custom/output_custom_months.journal')
|
45
|
+
expect(generated_journal).to eq(expected_output)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative '../lib/hledger_forecast'
|
2
|
+
RSpec.describe 'generate' do
|
3
|
+
it 'generates a forecast with correct MONTHLY transactions that have a START DATE' do
|
4
|
+
transactions = File.read('spec/stubs/transactions.journal')
|
5
|
+
forecast = File.read('spec/stubs/start_date/forecast_startdate.yml')
|
6
|
+
|
7
|
+
generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01', '2023-08-30')
|
8
|
+
|
9
|
+
expected_output = File.read('spec/stubs/start_date/output_startdate.journal')
|
10
|
+
expect(generated_journal).to eq(expected_output)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
custom:
|
2
|
+
- description: Repeat every 3 days
|
3
|
+
recurrence:
|
4
|
+
period: days
|
5
|
+
quantity: 3
|
6
|
+
account: "[Assets:Bank]"
|
7
|
+
start: "2023-03-01"
|
8
|
+
transactions:
|
9
|
+
- amount: 80
|
10
|
+
category: "[Expenses:Personal Care]"
|
11
|
+
description: Hair and beauty
|
12
|
+
|
13
|
+
settings:
|
14
|
+
currency: GBP
|
@@ -0,0 +1,14 @@
|
|
1
|
+
custom:
|
2
|
+
- description: Repeat every 5 months
|
3
|
+
recurrence:
|
4
|
+
period: months
|
5
|
+
quantity: 5
|
6
|
+
account: "[Assets:Bank]"
|
7
|
+
start: "2023-03-01"
|
8
|
+
transactions:
|
9
|
+
- amount: 100
|
10
|
+
category: "[Expenses:Personal Care]"
|
11
|
+
description: Hair and beauty
|
12
|
+
|
13
|
+
settings:
|
14
|
+
currency: GBP
|
@@ -0,0 +1,14 @@
|
|
1
|
+
custom:
|
2
|
+
- description: Repeat every 2 weeks
|
3
|
+
recurrence:
|
4
|
+
period: weeks
|
5
|
+
quantity: 2
|
6
|
+
account: "[Assets:Bank]"
|
7
|
+
start: "2023-03-01"
|
8
|
+
transactions:
|
9
|
+
- amount: 75
|
10
|
+
category: "[Expenses:Personal Care]"
|
11
|
+
description: Hair and beauty
|
12
|
+
|
13
|
+
settings:
|
14
|
+
currency: GBP
|
@@ -0,0 +1,24 @@
|
|
1
|
+
custom:
|
2
|
+
- description: Beauty expenses every 2 weeks
|
3
|
+
recurrence:
|
4
|
+
period: weeks
|
5
|
+
quantity: 2
|
6
|
+
account: "[Assets:Bank]"
|
7
|
+
start: "2023-03-01"
|
8
|
+
transactions:
|
9
|
+
- amount: 75
|
10
|
+
category: "[Expenses:Personal Care]"
|
11
|
+
description: Hair and beauty
|
12
|
+
- description: Car fuel every 5 days
|
13
|
+
recurrence:
|
14
|
+
period: days
|
15
|
+
quantity: 5
|
16
|
+
account: "[Assets:Bank]"
|
17
|
+
start: "2023-03-01"
|
18
|
+
transactions:
|
19
|
+
- amount: 150
|
20
|
+
category: "[Expenses:Car:Fuel]"
|
21
|
+
description: Car Fuel
|
22
|
+
|
23
|
+
settings:
|
24
|
+
currency: GBP
|
@@ -0,0 +1,24 @@
|
|
1
|
+
2023-02-01 * Opening balance
|
2
|
+
Assets:Bank £1,000.00
|
3
|
+
Equity:Opening balance
|
4
|
+
|
5
|
+
2023-02-05 * Mortgage payment
|
6
|
+
Expenses:Mortgage £1,500.00
|
7
|
+
Assets:Bank
|
8
|
+
|
9
|
+
2023-03-01 * Hair and beauty
|
10
|
+
[Expenses:Personal Care] £80.00
|
11
|
+
[Assets:Bank]
|
12
|
+
|
13
|
+
2023-03-04 * Hair and beauty
|
14
|
+
[Expenses:Personal Care] £80.00
|
15
|
+
[Assets:Bank]
|
16
|
+
|
17
|
+
2023-03-07 * Hair and beauty
|
18
|
+
[Expenses:Personal Care] £80.00
|
19
|
+
[Assets:Bank]
|
20
|
+
|
21
|
+
2023-03-10 * Hair and beauty
|
22
|
+
[Expenses:Personal Care] £80.00
|
23
|
+
[Assets:Bank]
|
24
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
2023-02-01 * Opening balance
|
2
|
+
Assets:Bank £1,000.00
|
3
|
+
Equity:Opening balance
|
4
|
+
|
5
|
+
2023-02-05 * Mortgage payment
|
6
|
+
Expenses:Mortgage £1,500.00
|
7
|
+
Assets:Bank
|
8
|
+
|
9
|
+
2023-03-01 * Hair and beauty
|
10
|
+
[Expenses:Personal Care] £100.00
|
11
|
+
[Assets:Bank]
|
12
|
+
|
13
|
+
2023-08-01 * Hair and beauty
|
14
|
+
[Expenses:Personal Care] £100.00
|
15
|
+
[Assets:Bank]
|
16
|
+
|
17
|
+
2024-01-01 * Hair and beauty
|
18
|
+
[Expenses:Personal Care] £100.00
|
19
|
+
[Assets:Bank]
|
20
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
2023-02-01 * Opening balance
|
2
|
+
Assets:Bank £1,000.00
|
3
|
+
Equity:Opening balance
|
4
|
+
|
5
|
+
2023-02-05 * Mortgage payment
|
6
|
+
Expenses:Mortgage £1,500.00
|
7
|
+
Assets:Bank
|
8
|
+
|
9
|
+
2023-03-01 * Hair and beauty
|
10
|
+
[Expenses:Personal Care] £75.00
|
11
|
+
[Assets:Bank]
|
12
|
+
|
13
|
+
2023-03-15 * Hair and beauty
|
14
|
+
[Expenses:Personal Care] £75.00
|
15
|
+
[Assets:Bank]
|
16
|
+
|
17
|
+
2023-03-29 * Hair and beauty
|
18
|
+
[Expenses:Personal Care] £75.00
|
19
|
+
[Assets:Bank]
|
20
|
+
|
21
|
+
2023-04-12 * Hair and beauty
|
22
|
+
[Expenses:Personal Care] £75.00
|
23
|
+
[Assets:Bank]
|
24
|
+
|
25
|
+
2023-04-26 * Hair and beauty
|
26
|
+
[Expenses:Personal Care] £75.00
|
27
|
+
[Assets:Bank]
|
28
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
2023-02-01 * Opening balance
|
2
|
+
Assets:Bank £1,000.00
|
3
|
+
Equity:Opening balance
|
4
|
+
|
5
|
+
2023-02-05 * Mortgage payment
|
6
|
+
Expenses:Mortgage £1,500.00
|
7
|
+
Assets:Bank
|
8
|
+
|
9
|
+
2023-03-01 * Hair and beauty
|
10
|
+
[Expenses:Personal Care] £75.00
|
11
|
+
[Assets:Bank]
|
12
|
+
|
13
|
+
2023-03-01 * Car Fuel
|
14
|
+
[Expenses:Car:Fuel] £150.00
|
15
|
+
[Assets:Bank]
|
16
|
+
|
17
|
+
2023-03-06 * Car Fuel
|
18
|
+
[Expenses:Car:Fuel] £150.00
|
19
|
+
[Assets:Bank]
|
20
|
+
|
21
|
+
2023-03-11 * Car Fuel
|
22
|
+
[Expenses:Car:Fuel] £150.00
|
23
|
+
[Assets:Bank]
|
24
|
+
|
25
|
+
2023-03-15 * Hair and beauty
|
26
|
+
[Expenses:Personal Care] £75.00
|
27
|
+
[Assets:Bank]
|
28
|
+
|
29
|
+
2023-03-16 * Car Fuel
|
30
|
+
[Expenses:Car:Fuel] £150.00
|
31
|
+
[Assets:Bank]
|
32
|
+
|
33
|
+
2023-03-21 * Car Fuel
|
34
|
+
[Expenses:Car:Fuel] £150.00
|
35
|
+
[Assets:Bank]
|
36
|
+
|
37
|
+
2023-03-26 * Car Fuel
|
38
|
+
[Expenses:Car:Fuel] £150.00
|
39
|
+
[Assets:Bank]
|
40
|
+
|
41
|
+
2023-03-29 * Hair and beauty
|
42
|
+
[Expenses:Personal Care] £75.00
|
43
|
+
[Assets:Bank]
|
44
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
2023-02-01 * Opening balance
|
2
|
+
Assets:Bank £1,000.00
|
3
|
+
Equity:Opening balance
|
4
|
+
|
5
|
+
2023-02-05 * Mortgage payment
|
6
|
+
Expenses:Mortgage £1,500.00
|
7
|
+
Assets:Bank
|
8
|
+
|
9
|
+
2023-03-01 * Mortgage
|
10
|
+
[Expenses:Mortgage] £2,000.00
|
11
|
+
[Assets:Bank]
|
12
|
+
|
13
|
+
2023-03-01 * Food
|
14
|
+
[Expenses:Food] £100.00
|
15
|
+
[Assets:Bank]
|
16
|
+
|
17
|
+
2023-03-01 * Savings
|
18
|
+
[Assets:Bank] -£1,000.00
|
19
|
+
[Assets:Savings]
|
20
|
+
|
21
|
+
2023-04-01 * Mortgage
|
22
|
+
[Expenses:Mortgage] £2,000.00
|
23
|
+
[Assets:Bank]
|
24
|
+
|
25
|
+
2023-04-01 * Food
|
26
|
+
[Expenses:Food] £100.00
|
27
|
+
[Assets:Bank]
|
28
|
+
|
29
|
+
2023-04-01 * Savings
|
30
|
+
[Assets:Bank] -£1,000.00
|
31
|
+
[Assets:Savings]
|
32
|
+
|
33
|
+
2023-05-01 * Mortgage
|
34
|
+
[Expenses:Mortgage] £2,000.00
|
35
|
+
[Assets:Bank]
|
36
|
+
|
37
|
+
2023-05-01 * Food
|
38
|
+
[Expenses:Food] £100.00
|
39
|
+
[Assets:Bank]
|
40
|
+
|
41
|
+
2023-05-01 * Savings
|
42
|
+
[Assets:Bank] -£1,000.00
|
43
|
+
[Assets:Savings]
|
44
|
+
|
@@ -2,12 +2,18 @@ monthly:
|
|
2
2
|
- account: "[Assets:Bank]"
|
3
3
|
start: "2023-03-01"
|
4
4
|
transactions:
|
5
|
-
- amount: 2000
|
5
|
+
- amount: 2000.55
|
6
6
|
category: "[Expenses:Mortgage]"
|
7
7
|
description: Mortgage
|
8
8
|
- amount: 100
|
9
9
|
category: "[Expenses:Food]"
|
10
10
|
description: Food
|
11
|
+
- account: "[Assets:Savings]"
|
12
|
+
start: "2023-03-01"
|
13
|
+
transactions:
|
14
|
+
- amount: -1000
|
15
|
+
category: "[Assets:Bank]"
|
16
|
+
description: Savings
|
11
17
|
|
12
18
|
settings:
|
13
19
|
currency: GBP
|
@@ -7,26 +7,38 @@
|
|
7
7
|
Assets:Bank
|
8
8
|
|
9
9
|
2023-03-01 * Mortgage
|
10
|
-
[Expenses:Mortgage] £2,000.
|
10
|
+
[Expenses:Mortgage] £2,000.55
|
11
11
|
[Assets:Bank]
|
12
12
|
|
13
13
|
2023-03-01 * Food
|
14
14
|
[Expenses:Food] £100.00
|
15
15
|
[Assets:Bank]
|
16
16
|
|
17
|
+
2023-03-01 * Savings
|
18
|
+
[Assets:Bank] -£1,000.00
|
19
|
+
[Assets:Savings]
|
20
|
+
|
17
21
|
2023-04-01 * Mortgage
|
18
|
-
[Expenses:Mortgage] £2,000.
|
22
|
+
[Expenses:Mortgage] £2,000.55
|
19
23
|
[Assets:Bank]
|
20
24
|
|
21
25
|
2023-04-01 * Food
|
22
26
|
[Expenses:Food] £100.00
|
23
27
|
[Assets:Bank]
|
24
28
|
|
29
|
+
2023-04-01 * Savings
|
30
|
+
[Assets:Bank] -£1,000.00
|
31
|
+
[Assets:Savings]
|
32
|
+
|
25
33
|
2023-05-01 * Mortgage
|
26
|
-
[Expenses:Mortgage] £2,000.
|
34
|
+
[Expenses:Mortgage] £2,000.55
|
27
35
|
[Assets:Bank]
|
28
36
|
|
29
37
|
2023-05-01 * Food
|
30
38
|
[Expenses:Food] £100.00
|
31
39
|
[Assets:Bank]
|
32
40
|
|
41
|
+
2023-05-01 * Savings
|
42
|
+
[Assets:Bank] -£1,000.00
|
43
|
+
[Assets:Savings]
|
44
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
settings:
|
2
|
+
currency: GBP
|
3
|
+
|
4
|
+
monthly:
|
5
|
+
- start: "2023-03-01"
|
6
|
+
account: "[Assets:Bank]"
|
7
|
+
transactions:
|
8
|
+
- description: Monthly Mortgage
|
9
|
+
start: "2023-06-01"
|
10
|
+
category: "[Expenses:Mortgage]"
|
11
|
+
amount: 2000
|
12
|
+
- description: Monthly Food
|
13
|
+
category: "[Expenses:Food]"
|
14
|
+
amount: 100
|
15
|
+
|
16
|
+
quarterly:
|
17
|
+
- start: "2023-04-01"
|
18
|
+
account: "[Assets:Bank]"
|
19
|
+
transactions:
|
20
|
+
- description: Quarterly Mortgage
|
21
|
+
start: "2023-07-01"
|
22
|
+
category: "[Expenses:Mortgage]"
|
23
|
+
amount: 1000
|
24
|
+
- description: Quarterly Food
|
25
|
+
category: "[Expenses:Food]"
|
26
|
+
amount: 50
|
@@ -0,0 +1,56 @@
|
|
1
|
+
2023-02-01 * Opening balance
|
2
|
+
Assets:Bank £1,000.00
|
3
|
+
Equity:Opening balance
|
4
|
+
|
5
|
+
2023-02-05 * Mortgage payment
|
6
|
+
Expenses:Mortgage £1,500.00
|
7
|
+
Assets:Bank
|
8
|
+
|
9
|
+
2023-03-01 * Monthly Food
|
10
|
+
[Expenses:Food] £100.00
|
11
|
+
[Assets:Bank]
|
12
|
+
|
13
|
+
2023-04-01 * Monthly Food
|
14
|
+
[Expenses:Food] £100.00
|
15
|
+
[Assets:Bank]
|
16
|
+
|
17
|
+
2023-04-01 * Quarterly Food
|
18
|
+
[Expenses:Food] £50.00
|
19
|
+
[Assets:Bank]
|
20
|
+
|
21
|
+
2023-05-01 * Monthly Food
|
22
|
+
[Expenses:Food] £100.00
|
23
|
+
[Assets:Bank]
|
24
|
+
|
25
|
+
2023-06-01 * Monthly Mortgage
|
26
|
+
[Expenses:Mortgage] £2,000.00
|
27
|
+
[Assets:Bank]
|
28
|
+
|
29
|
+
2023-06-01 * Monthly Food
|
30
|
+
[Expenses:Food] £100.00
|
31
|
+
[Assets:Bank]
|
32
|
+
|
33
|
+
2023-07-01 * Monthly Mortgage
|
34
|
+
[Expenses:Mortgage] £2,000.00
|
35
|
+
[Assets:Bank]
|
36
|
+
|
37
|
+
2023-07-01 * Monthly Food
|
38
|
+
[Expenses:Food] £100.00
|
39
|
+
[Assets:Bank]
|
40
|
+
|
41
|
+
2023-07-01 * Quarterly Mortgage
|
42
|
+
[Expenses:Mortgage] £1,000.00
|
43
|
+
[Assets:Bank]
|
44
|
+
|
45
|
+
2023-07-01 * Quarterly Food
|
46
|
+
[Expenses:Food] £50.00
|
47
|
+
[Assets:Bank]
|
48
|
+
|
49
|
+
2023-08-01 * Monthly Mortgage
|
50
|
+
[Expenses:Mortgage] £2,000.00
|
51
|
+
[Assets:Bank]
|
52
|
+
|
53
|
+
2023-08-01 * Monthly Food
|
54
|
+
[Expenses:Food] £100.00
|
55
|
+
[Assets:Bank]
|
56
|
+
|
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.1.
|
4
|
+
version: 0.1.9
|
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-04-
|
11
|
+
date: 2023-04-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.8.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: terminal-table
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.2
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.2
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rspec
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,12 +104,24 @@ files:
|
|
90
104
|
- lib/hledger_forecast/summarize.rb
|
91
105
|
- lib/hledger_forecast/version.rb
|
92
106
|
- spec/command_spec.rb
|
107
|
+
- spec/custom_spec.rb
|
93
108
|
- spec/half-yearly_spec.rb
|
94
109
|
- spec/monthly_spec.rb
|
95
110
|
- spec/once_spec.rb
|
96
111
|
- spec/quarterly_spec.rb
|
112
|
+
- spec/start_date_spec.rb
|
113
|
+
- spec/stubs/custom/forecast_custom_days.yml
|
114
|
+
- spec/stubs/custom/forecast_custom_months.yml
|
115
|
+
- spec/stubs/custom/forecast_custom_weeks.yml
|
116
|
+
- spec/stubs/custom/forecast_custom_weeks_twice.yml
|
117
|
+
- spec/stubs/custom/output_custom_days.journal
|
118
|
+
- spec/stubs/custom/output_custom_months.journal
|
119
|
+
- spec/stubs/custom/output_custom_weeks.journal
|
120
|
+
- spec/stubs/custom/output_custom_weeks_twice.journal
|
97
121
|
- spec/stubs/half-yearly/forecast_half-yearly.yml
|
98
122
|
- spec/stubs/half-yearly/output_half-yearly.journal
|
123
|
+
- spec/stubs/modifiers/forecast_modifiers.yml
|
124
|
+
- spec/stubs/modifiers/output_modifiers.journal
|
99
125
|
- spec/stubs/monthly/forecast_monthly.yml
|
100
126
|
- spec/stubs/monthly/forecast_monthly_enddate.yml
|
101
127
|
- spec/stubs/monthly/forecast_monthly_enddate_top.yml
|
@@ -108,6 +134,8 @@ files:
|
|
108
134
|
- spec/stubs/once/output_once.journal
|
109
135
|
- spec/stubs/quarterly/forecast_quarterly.yml
|
110
136
|
- spec/stubs/quarterly/output_quarterly.journal
|
137
|
+
- spec/stubs/start_date/forecast_startdate.yml
|
138
|
+
- spec/stubs/start_date/output_startdate.journal
|
111
139
|
- spec/stubs/transactions.journal
|
112
140
|
- spec/stubs/yearly/forecast_yearly.yml
|
113
141
|
- spec/stubs/yearly/output_yearly.journal
|
@@ -131,18 +159,30 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
159
|
- !ruby/object:Gem::Version
|
132
160
|
version: '0'
|
133
161
|
requirements: []
|
134
|
-
rubygems_version: 3.4.
|
162
|
+
rubygems_version: 3.4.12
|
135
163
|
signing_key:
|
136
164
|
specification_version: 4
|
137
165
|
summary: Utility to generate forecasts in Hledger
|
138
166
|
test_files:
|
139
167
|
- spec/command_spec.rb
|
168
|
+
- spec/custom_spec.rb
|
140
169
|
- spec/half-yearly_spec.rb
|
141
170
|
- spec/monthly_spec.rb
|
142
171
|
- spec/once_spec.rb
|
143
172
|
- spec/quarterly_spec.rb
|
173
|
+
- spec/start_date_spec.rb
|
174
|
+
- spec/stubs/custom/forecast_custom_days.yml
|
175
|
+
- spec/stubs/custom/forecast_custom_months.yml
|
176
|
+
- spec/stubs/custom/forecast_custom_weeks.yml
|
177
|
+
- spec/stubs/custom/forecast_custom_weeks_twice.yml
|
178
|
+
- spec/stubs/custom/output_custom_days.journal
|
179
|
+
- spec/stubs/custom/output_custom_months.journal
|
180
|
+
- spec/stubs/custom/output_custom_weeks.journal
|
181
|
+
- spec/stubs/custom/output_custom_weeks_twice.journal
|
144
182
|
- spec/stubs/half-yearly/forecast_half-yearly.yml
|
145
183
|
- spec/stubs/half-yearly/output_half-yearly.journal
|
184
|
+
- spec/stubs/modifiers/forecast_modifiers.yml
|
185
|
+
- spec/stubs/modifiers/output_modifiers.journal
|
146
186
|
- spec/stubs/monthly/forecast_monthly.yml
|
147
187
|
- spec/stubs/monthly/forecast_monthly_enddate.yml
|
148
188
|
- spec/stubs/monthly/forecast_monthly_enddate_top.yml
|
@@ -155,6 +195,8 @@ test_files:
|
|
155
195
|
- spec/stubs/once/output_once.journal
|
156
196
|
- spec/stubs/quarterly/forecast_quarterly.yml
|
157
197
|
- spec/stubs/quarterly/output_quarterly.journal
|
198
|
+
- spec/stubs/start_date/forecast_startdate.yml
|
199
|
+
- spec/stubs/start_date/output_startdate.journal
|
158
200
|
- spec/stubs/transactions.journal
|
159
201
|
- spec/stubs/yearly/forecast_yearly.yml
|
160
202
|
- spec/stubs/yearly/output_yearly.journal
|