hledger-forecast 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e4232c29fd944d0483590e646c0c5e1566357ec8dfc9f942007cdea6121dcad
4
- data.tar.gz: 2a8636024b63cdce5cfe7bbec937c123a9f78492bfb53ac873f40e9aa00d204d
3
+ metadata.gz: a21d32c5ee145d0bca5cb25f07a2fbb6f65126f16c5e15faf2ca11b3fbfac03c
4
+ data.tar.gz: bbf889db9f129ca62550399b2f2e7818936eee667d356bbeb52b476f305fe6b1
5
5
  SHA512:
6
- metadata.gz: 0a725ff17d252a8d2427ba4e2dd2c6fa90d10e4cc9e853a6d9293eebde6aa9a594c96a9e57f387c858767b03b56e3bb3717b49bee7adc64d32a47bcd93118ebe
7
- data.tar.gz: 118d14cde7575795a9e6632b856b4a9a60392aa19d88ce4030e555f49b580aa0cd924a61e9d6970bfe1e26306b848af756337f50eb2fc48203662714f1edd4a3
6
+ metadata.gz: b0fa4ad6d50c30eac65acc292d96f500182e211b1a4b9e039dfcff0f204a32ad01d9c01a7570535301bffeb6be865c720d6b8be8ba3e17370a2b992586f351d5
7
+ data.tar.gz: 554a06876170c161d35b68eececa7904b190aaa0fb6611e2a8bd672fad9b02b48a5a5e26e013db857671fd6f50e33242cb19ddf907fafb31b1b1bd27b74f86df
data/README.md CHANGED
@@ -8,7 +8,7 @@ See the [rationale](#brain-rationale) section for why this gem may be useful to
8
8
 
9
9
  ## :sparkles: Features
10
10
 
11
- - :book: Uses a simple YAML config file to generate periodic transactions
11
+ - :book: Uses simple YAML files to generate forecasts from periodic transactions
12
12
  - :date: Generate forecasts between specified start and end dates
13
13
  - :heavy_dollar_sign: Full currency support (uses the [RubyMoney](https://github.com/RubyMoney/money) gem)
14
14
  - :computer: Simple and easy to use CLI
@@ -24,27 +24,25 @@ Assuming you have Ruby and [Rubygems](http://rubygems.org/pages/download) instal
24
24
 
25
25
  Run:
26
26
 
27
- hledger-forecast
27
+ hledger-forecast generate
28
28
 
29
29
  > **Note**: This assumes that a `forecast.yml` exists in the current working directory
30
30
 
31
31
  Running `hledger-forecast -h` shows the available options:
32
32
 
33
- Usage: Hledger-Forecast [options]
33
+ Usage: Hledger-Forecast generate [options]
34
34
 
35
- -f, --forecast FILE The FORECAST yaml file to generate from
36
35
  -t, --transaction FILE The base TRANSACTIONS file to extend from
36
+ -f, --forecast FILE The FORECAST yaml file to generate from
37
37
  -o, --output-file FILE The OUTPUT file to create
38
38
  -s, --start-date DATE The date to start generating from (yyyy-mm-dd)
39
39
  -e, --end-date DATE The date to start generating to (yyyy-mm-dd)
40
40
  --force Force an overwrite of the output file
41
- --summarize Summarize the forecast file and output to the terminal
42
- -h, --help Show this message
43
- --version Show version
41
+ -h, --help Show this help message
44
42
 
45
43
  Another example of a common command:
46
44
 
47
- hledger-forecast -f my_forecast.yml -s 2023-05-01 -e 2024-12-31
45
+ hledger-forecast generate -f my_forecast.yml -s 2023-05-01 -e 2024-12-31
48
46
 
49
47
  This will generate an output file (`my_forecast.journal`) from the forecast file between the two date ranges.
50
48
 
@@ -59,7 +57,9 @@ where:
59
57
  - `transactions.journal` might be your bank transactions (your "_actuals_")
60
58
  - `my_forecast.journal` is the generated forecast file
61
59
 
62
- ### A simple config file
60
+ ## :gear: Configuration
61
+
62
+ ### The YAML file
63
63
 
64
64
  > **Note**: See the [example.yml](https://github.com/olimorris/hledger-forecast/blob/main/example.yml) file for an example of a complex config file
65
65
 
@@ -89,9 +89,7 @@ Let's examine what's going on in this config file:
89
89
  - 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
90
90
  - We also have not specified a currency; the default (`USD`) will be used
91
91
 
92
- ### Extending the config file
93
-
94
- #### Periods
92
+ ### Periods
95
93
 
96
94
  Besides monthly recurring transactions, the app also supports the following periods:
97
95
 
@@ -101,12 +99,11 @@ Besides monthly recurring transactions, the app also supports the following peri
101
99
  - `once` - Generate _one-time_ transactions on a specified date
102
100
  - `custom` - Generate transactions every _n days/weeks/months_
103
101
 
104
- ##### Custom period
102
+ #### Custom period
105
103
 
106
104
  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:
107
105
 
108
106
  ```yaml
109
- # forecast.yml
110
107
  custom:
111
108
  - description: Fortnightly hair and beauty spend
112
109
  recurrence:
@@ -126,18 +123,17 @@ Where `quantity` is an integer and `period` is one of:
126
123
  - weeks
127
124
  - months
128
125
 
129
- #### Date constraints
126
+ ### Dates
130
127
 
131
128
  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).
132
129
 
133
130
  You can further control the dates at a period/top-level as well as at a transaction level:
134
131
 
135
- ##### Top level
132
+ #### Top level
136
133
 
137
134
  In the example below, all transactions in the `monthly` block will be constrained by the end date:
138
135
 
139
136
  ```yaml
140
- # forecast.yml
141
137
  monthly:
142
138
  - account: "[Assets:Bank]"
143
139
  start: "2023-03-01"
@@ -146,12 +142,11 @@ monthly:
146
142
  # details omitted for brevity
147
143
  ```
148
144
 
149
- ##### Transaction level
145
+ #### Transaction level
150
146
 
151
- In the example below, only the single transaction will be constrained by the end date:
147
+ In the example below, only the single transaction will be constrained by the end date and controlled via an additional start date:
152
148
 
153
149
  ```yaml
154
- # forecast.yml
155
150
  monthly:
156
151
  - account: "[Assets:Bank]"
157
152
  start: "2023-03-01"
@@ -159,15 +154,17 @@ monthly:
159
154
  - amount: 2000
160
155
  category: "[Expenses:Mortgage]"
161
156
  description: Mortgage
157
+ start: "2023-05-01"
162
158
  end: "2025-01-01"
163
159
  ```
164
160
 
165
- #### Additional settings
161
+ 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`.
162
+
163
+ ### Additional settings
166
164
 
167
165
  Additional settings in the config file to consider:
168
166
 
169
167
  ```yaml
170
- # forecast.yml
171
168
  settings:
172
169
  currency: GBP # Specify the currency to use
173
170
  show_symbol: true # Show the currency symbol?
@@ -175,11 +172,13 @@ settings:
175
172
  thousands_separator: true # Separate thousands with a comma?
176
173
  ```
177
174
 
178
- ### Summarizing the config file
175
+ ## :rainbow: Helpers
176
+
177
+ ### Summarizing the forecast file
179
178
 
180
179
  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:
181
180
 
182
- hledger-forecast -f my_forecast.yml --summarize
181
+ hledger-forecast summarize -f my_forecast.yml
183
182
 
184
183
  where `my_forecast.yml` is the config file to sum up.
185
184
 
@@ -189,8 +188,8 @@ Firstly, I've come to realise from reading countless blog and Reddit posts on [p
189
188
 
190
189
  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.
191
190
 
192
- 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.
191
+ 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 performance to date and my future commitments? And what about my savings goal in 12 and 24 months time? Or, how much are my financial positions impacted if inflation increases by x%? It was at this point in my shift to PTA that I found it difficult to answer those questions quickly.
193
192
 
194
- 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?"_.
193
+ While there is support in Hledger for [forecasting](https://hledger.org/1.29/hledger.html#forecasting) using periodic transactions, 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?"_.
195
194
 
196
195
  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/bin/hledger-forecast CHANGED
@@ -3,10 +3,10 @@
3
3
  require_relative '../lib/hledger_forecast'
4
4
 
5
5
  begin
6
- options = HledgerForecast::Options.parse_command_line_options
6
+ subcommand, options = HledgerForecast::Cli.parse_commands
7
7
  rescue RuntimeError => e
8
8
  puts("ERROR: #{e}")
9
9
  exit(1)
10
10
  end
11
11
 
12
- HledgerForecast::Cli.run(options)
12
+ HledgerForecast::Cli.run(subcommand, options)
data/example.yml CHANGED
@@ -2,13 +2,29 @@ monthly:
2
2
  - account: "[Assets:Bank]"
3
3
  start: "2023-03-01"
4
4
  transactions:
5
+ - amount: -100
6
+ category: "[Income:Bonus]"
7
+ description: Bonus
8
+ - amount: -2000
9
+ category: "[Income:Salary]"
10
+ description: Salary
11
+ - amount: 500.00
12
+ category: "[Expenses:Food]"
13
+ description: Food
14
+ - amount: 75
15
+ category: "[Expenses:Phone]"
16
+ description: New cell phone
17
+ start: "2023-08-01"
5
18
  - amount: 1000.00
6
19
  category: "[Expenses:Mortgage]"
7
20
  description: Mortgage
8
21
  end: "2024-01-01"
9
- - amount: 500.00
10
- category: "[Expenses:Food]"
11
- description: Food
22
+ - account: "[Assets:Savings]"
23
+ start: "2023-03-01"
24
+ transactions:
25
+ - amount: -500
26
+ category: "[Income:Pension]"
27
+ description: Pension draw down
12
28
 
13
29
  quarterly:
14
30
  - account: "[Assets:Bank]"
@@ -1,19 +1,163 @@
1
1
  module HledgerForecast
2
+ # The Command Line Interface for the application
3
+ # Takes user arguments and translates them into actions
2
4
  class Cli
3
- def self.run(args)
4
- end_date = args[:end_date]
5
- start_date = args[:start_date]
6
- forecast = File.read(args[:forecast_file])
7
- transactions = args[:transactions_file] ? File.read(args[:transactions_file]) : nil
8
5
 
9
- return HledgerForecast::Summarize.generate(forecast) if args[:summarize]
6
+ def self.run(subcommand, options)
7
+ case subcommand
8
+ when 'generate'
9
+ generate(options)
10
+ when 'summarize'
11
+ summarize(options)
12
+ else
13
+ puts "Unknown command: #{subcommand}"
14
+ exit(1)
15
+ end
16
+ end
17
+
18
+ def self.parse_commands(args = ARGV, _stdin = $stdin)
19
+ subcommand = nil
20
+ options = {}
21
+
22
+ global = OptionParser.new do |opts|
23
+ opts.banner = "Usage: hledger-forecast [subcommand] [options]"
24
+ opts.separator ""
25
+ opts.separator "Subcommands:"
26
+ opts.separator " generate Generate the forecast file"
27
+ opts.separator " summarize Summarize the forecast file and output to the terminal"
28
+ opts.separator ""
29
+ opts.separator "Options:"
30
+
31
+ opts.on_tail("-h", "--help", "Show this help message") do
32
+ puts opts
33
+ exit
34
+ end
35
+
36
+ opts.on_tail("-v", "--version", "Show version") do
37
+ puts VERSION
38
+ exit
39
+ end
40
+ end
41
+
42
+ begin
43
+ global.order!(args)
44
+ subcommand = args.shift || 'generate'
45
+ rescue OptionParser::InvalidOption => e
46
+ puts e
47
+ puts global
48
+ exit(1)
49
+ end
50
+
51
+ case subcommand
52
+ when 'generate'
53
+ options = parse_generate_options(args)
54
+ when 'summarize'
55
+ options = parse_summarize_options(args)
56
+ else
57
+ puts "Unknown subcommand: #{subcommand}"
58
+ puts global
59
+ exit(1)
60
+ end
61
+
62
+ return subcommand, options
63
+ end
64
+
65
+ def self.parse_generate_options(args)
66
+ options = {}
67
+
68
+ OptionParser.new do |opts|
69
+ opts.banner = "Usage: Hledger-Forecast generate [options]"
70
+ opts.separator ""
71
+
72
+ opts.on("-t", "--transaction FILE",
73
+ "The base TRANSACTIONS file to extend from") do |file|
74
+ options[:transactions_file] = file if file && !file.empty?
75
+ end
76
+
77
+ opts.on("-f", "--forecast FILE",
78
+ "The FORECAST yaml file to generate from") do |file|
79
+ options[:forecast_file] = file
80
+ options[:output_file] ||= file.sub(/\.yml$/, '.journal')
81
+ end
82
+
83
+ opts.on("-o", "--output-file FILE",
84
+ "The OUTPUT file to create") do |file|
85
+ options[:output_file] = file
86
+ end
87
+
88
+ opts.on("-s", "--start-date DATE",
89
+ "The date to start generating from (yyyy-mm-dd)") do |a|
90
+ options[:start_date] = a
91
+ end
92
+
93
+ opts.on("-e", "--end-date DATE",
94
+ "The date to start generating to (yyyy-mm-dd)") do |a|
95
+ options[:end_date] = a
96
+ end
97
+
98
+ opts.on("--force",
99
+ "Force an overwrite of the output file") do |a|
100
+ options[:force] = a
101
+ end
102
+
103
+ opts.on_tail("-h", "--help", "Show this help message") do
104
+ puts opts
105
+ exit
106
+ end
107
+ end.parse!(args)
108
+
109
+ options[:forecast_file] = "forecast.yml" unless options[:forecast_file]
110
+ options[:output_file] = "forecast.journal" unless options[:output_file]
111
+
112
+ today = Date.today
113
+
114
+ unless options[:start_date]
115
+ options[:default_dates] = true
116
+ options[:start_date] =
117
+ Date.new(today.year, today.month, 1).next_month.to_s
118
+ end
119
+ unless options[:end_date]
120
+ options[:default_dates] = true
121
+ options[:end_date] = Date.new(today.year + 3, 12, 31).to_s
122
+ end
123
+
124
+ options
125
+ end
126
+
127
+ def self.parse_summarize_options(args)
128
+ options = {}
10
129
 
11
- puts "[Using default dates: #{start_date} to #{end_date}]" if args[:default_dates]
130
+ OptionParser.new do |opts|
131
+ opts.banner = "Usage: Hledger-Forecast summarize [options]"
132
+ opts.separator ""
133
+
134
+ opts.on("-f", "--forecast FILE",
135
+ "The FORECAST yaml file to summarize") do |file|
136
+ options[:forecast_file] = file
137
+ end
12
138
 
13
- transactions = Generator.create_journal_entries(transactions, forecast, start_date, end_date)
139
+ opts.on_tail("-h", "--help", "Show this help message") do
140
+ puts opts
141
+ exit
142
+ end
143
+ end.parse!(args)
144
+
145
+ options
146
+ end
14
147
 
15
- output_file = args[:output_file]
16
- if File.exist?(output_file) && !args[:force]
148
+ def self.generate(options)
149
+ end_date = options[:end_date]
150
+ start_date = options[:start_date]
151
+ forecast = File.read(options[:forecast_file])
152
+ transactions = options[:transactions_file] ? File.read(options[:transactions_file]) : nil
153
+
154
+ # Generate the forecast
155
+ puts "[Using default dates: #{start_date} to #{end_date}]" if options[:default_dates]
156
+
157
+ transactions = Generator.generate(transactions, forecast, start_date, end_date)
158
+
159
+ output_file = options[:output_file]
160
+ if File.exist?(output_file) && !options[:force]
17
161
  print "File '#{output_file}' already exists. Overwrite? (y/n): "
18
162
  overwrite = gets.chomp.downcase
19
163
 
@@ -28,5 +172,49 @@ module HledgerForecast
28
172
  puts "File '#{output_file}' has been created."
29
173
  end
30
174
  end
175
+
176
+ def self.summarize(options)
177
+ forecast = File.read(options[:forecast_file])
178
+ puts Summarize.generate(forecast)
179
+ end
180
+
181
+ # def self.run(args)
182
+ # end_date = args[:end_date]
183
+ # start_date = args[:start_date]
184
+ # forecast = File.read(args[:forecast_file])
185
+ # transactions = args[:transactions_file] ? File.read(args[:transactions_file]) : nil
186
+ #
187
+ # # Output the summary
188
+ # return HledgerForecast::Summarize.generate(forecast) if args[:summarize]
189
+ #
190
+ # # Generate the forecast
191
+ # unless args[:skip]
192
+ #
193
+ # puts "[Using default dates: #{start_date} to #{end_date}]" if args[:default_dates]
194
+ #
195
+ # transactions = Generator.generate(transactions, forecast, start_date, end_date)
196
+ #
197
+ # output_file = args[:output_file]
198
+ # if File.exist?(output_file) && !args[:force]
199
+ # print "File '#{output_file}' already exists. Overwrite? (y/n): "
200
+ # overwrite = gets.chomp.downcase
201
+ #
202
+ # if overwrite == 'y'
203
+ # File.write(output_file, transactions)
204
+ # puts "File '#{output_file}' has been overwritten."
205
+ # else
206
+ # puts "Operation aborted. File '#{output_file}' was not overwritten."
207
+ # end
208
+ # else
209
+ # File.write(output_file, transactions)
210
+ # puts "File '#{output_file}' has been created."
211
+ # end
212
+ # end
213
+ #
214
+ # # Check for missing transactions
215
+ # return unless args[:check] && args[:transactions_file] && args[:forecast_file]
216
+ #
217
+ # HledgerForecast::Checker.check(args)
218
+ # end
31
219
  end
32
220
  end
@@ -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,7 +25,7 @@ module HledgerForecast
26
25
  formatted_transaction = transaction.clone
27
26
 
28
27
  formatted_transaction['amount'] =
29
- Money.from_cents(formatted_transaction['amount'].to_i * 100, @settings[:currency]).format(
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
31
  thousands_separator: @settings[:thousands_separator] ? ',' : nil
@@ -89,9 +88,12 @@ module HledgerForecast
89
88
 
90
89
  if date_matches
91
90
  forecast['transactions'].each do |transaction|
92
- end_date = transaction['end'] ? Date.parse(transaction['end']) : nil
91
+ transaction_start_date = transaction['start'] ? Date.parse(transaction['start']) : nil
92
+ transaction_end_date = transaction['end'] ? Date.parse(transaction['end']) : nil
93
93
 
94
- next unless end_date.nil? || date <= end_date
94
+ if (transaction_start_date && date < transaction_start_date) || (transaction_end_date && date > transaction_end_date)
95
+ next
96
+ end
95
97
 
96
98
  write_transactions(output_file, date, account, format_transaction(transaction))
97
99
  end
@@ -99,7 +101,7 @@ module HledgerForecast
99
101
  end
100
102
  end
101
103
 
102
- def self.create_journal_entries(transactions, forecast, start_date, end_date)
104
+ def self.generate(transactions, forecast, start_date, end_date)
103
105
  start_date = Date.parse(start_date)
104
106
  end_date = Date.parse(end_date)
105
107
  forecast_data = YAML.safe_load(forecast)
@@ -1,132 +1,135 @@
1
1
  module HledgerForecast
2
2
  # Summarise a forecast YAML file and output it to the CLI
3
3
  class Summarize
4
- def self.sum_transactions(forecast_data, period)
5
- category_totals = Hash.new(0)
6
- forecast_data[period]&.each do |entry|
7
- entry['transactions'].each do |transaction|
8
- category_totals[transaction['category']] += transaction['amount']
9
- end
10
- end
11
-
12
- category_totals
13
- end
4
+ @table = nil
5
+ @generator = nil
14
6
 
15
- def self.print_category_totals(period, category_totals, generator)
16
- puts "#{period.capitalize}:"
17
- period_total = 0
7
+ def self.init_table
8
+ table = Terminal::Table.new
18
9
 
19
- category_totals.each do |category, amount|
20
- formatted_amount = generator.format_transaction({ 'amount' => amount })['amount']
21
- formatted_amount = amount.to_i < 0 ? formatted_amount.green : formatted_amount.red
22
- puts " #{category.ljust(40)}#{formatted_amount}"
23
- period_total += amount
24
- end
10
+ table.add_row([{ value: 'FORECAST SUMMARY'.bold, colspan: 3, alignment: :center }])
11
+ table.add_separator
25
12
 
26
- formatted_period_total = generator.format_transaction({ 'amount' => period_total })['amount']
27
- formatted_period_total = period_total.to_i < 0 ? formatted_period_total.green : formatted_period_total.red
28
- puts " TOTAL".ljust(42) + formatted_period_total
13
+ @table = table
29
14
  end
30
15
 
31
- def self.sum_all_periods(forecast_data)
32
- periods = %w[monthly quarterly half-yearly yearly once]
33
- total = {}
34
- grand_total = 0
16
+ def self.init_generator(forecast_data)
17
+ generator = HledgerForecast::Generator
18
+ generator.configure_settings(forecast_data)
35
19
 
36
- (periods + ['custom']).each do |period|
37
- total[period] = sum_transactions(forecast_data, period)
38
- grand_total += total[period]
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
39
29
  end
40
30
 
41
- total['total'] = grand_total
42
- total
31
+ category_total
43
32
  end
44
33
 
45
34
  def self.sum_custom_transactions(forecast_data)
46
- category_totals = Hash.new(0)
35
+ category_total = Hash.new(0)
47
36
  custom_periods = []
48
37
 
49
38
  forecast_data['custom']&.each do |entry|
50
39
  period_data = {}
51
40
  period_data[:quantity] = entry['recurrence']['quantity']
52
41
  period_data[:period] = entry['recurrence']['period']
53
- period_data[:description] = entry['transactions'].first['description']
54
42
  period_data[:category] = entry['transactions'].first['category']
55
43
  period_data[:amount] = entry['transactions'].first['amount']
56
44
 
57
45
  entry['transactions'].each do |transaction|
58
- category_totals[transaction['category']] += transaction['amount']
46
+ category_total[transaction['category']] += transaction['amount']
59
47
  end
60
48
 
61
49
  custom_periods << period_data
62
50
  end
63
51
 
64
- { totals: category_totals, periods: custom_periods }
52
+ { totals: category_total, periods: custom_periods }
65
53
  end
66
54
 
67
- def self.generate(forecast)
68
- forecast_data = YAML.safe_load(forecast)
69
-
70
- category_totals_by_period = {}
71
- %w[monthly quarterly half-yearly yearly once custom].each do |period|
72
- category_totals_by_period[period] = sum_transactions(forecast_data, period)
73
- end
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
74
59
 
75
- grand_total = category_totals_by_period.values.map(&:values).flatten.sum
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 }]
76
66
 
77
- generator = HledgerForecast::Generator
78
- generator.configure_settings(forecast_data)
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 }]
79
73
 
80
- table = Terminal::Table.new
74
+ period_total += amount
75
+ end
76
+ end
81
77
 
82
- table.add_row([{ value: 'FORECAST SUMMARY', colspan: 3, alignment: :center }])
83
- table.add_separator
78
+ period_total
79
+ end
84
80
 
81
+ def self.add_categories_to_table(categories, forecast_data)
85
82
  first_period = true
86
- category_totals_by_period.each do |period, category_totals|
87
- non_zero_totals = category_totals.select { |_, amount| amount != 0 }
88
- next if non_zero_totals.empty?
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)
89
88
 
90
- table.add_separator unless first_period
91
- table.add_row([{ value: period.capitalize, colspan: 3, alignment: :center }])
89
+ @table.add_separator unless first_period
90
+ @table.add_row([{ value: period.capitalize.bold, colspan: 3, alignment: :center }])
92
91
 
93
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
94
98
 
95
- if period == 'custom'
96
- custom_periods_data = sum_custom_transactions(forecast_data)
97
- custom_periods_data[:periods].each do |custom_period|
98
- formatted_amount = generator.format_transaction({ 'amount' => custom_period[:amount] })['amount']
99
- formatted_amount = custom_period[:amount].to_i < 0 ? formatted_amount.green : formatted_amount.red
100
- table.add_row [{ value: custom_period[:category], alignment: :left },
101
- { value: "every #{custom_period[:quantity]} #{custom_period[:period]}", alignment: :right }, { value: formatted_amount, alignment: :right }]
102
- period_total += custom_period[:amount]
103
- end
104
- else
105
- non_zero_totals.each do |category, amount|
106
- formatted_amount = generator.format_transaction({ 'amount' => amount })['amount']
107
- formatted_amount = amount.to_i < 0 ? formatted_amount.green : formatted_amount.red
108
-
109
- table.add_row [{ value: category, colspan: 2, alignment: :left },
110
- { value: formatted_amount, alignment: :right }]
111
- period_total += amount
112
- end
113
- end
99
+ format_total("#{period.capitalize} TOTAL", period_total)
100
+ first_period = false
101
+ end
102
+ end
114
103
 
115
- formatted_period_total = generator.format_transaction({ 'amount' => period_total })['amount']
116
- formatted_period_total = period_total.to_i < 0 ? formatted_period_total.green : formatted_period_total.red
117
- table.add_row [{ value: "#{period.capitalize} TOTAL", colspan: 2, alignment: :left },
118
- { value: formatted_period_total, alignment: :right }]
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 }
119
107
 
120
- first_period = false
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
115
+
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)
121
125
  end
122
126
 
123
- table.add_separator
124
- formatted_grand_total = generator.format_transaction({ 'amount' => grand_total })['amount']
125
- formatted_grand_total = grand_total.to_i < 0 ? formatted_grand_total.green : formatted_grand_total.red
126
- table.add_row [{ value: 'TOTAL', colspan: 2, alignment: :left },
127
- { value: formatted_grand_total, alignment: :right }]
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)
128
131
 
129
- puts table
132
+ puts @table
130
133
  end
131
134
  end
132
135
  end
@@ -1,3 +1,3 @@
1
1
  module HledgerForecast
2
- VERSION = "0.1.8"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -12,7 +12,7 @@ Money.locale_backend = nil
12
12
  Money.rounding_mode = BigDecimal::ROUND_HALF_UP
13
13
 
14
14
  require_relative 'hledger_forecast/version'
15
- require_relative 'hledger_forecast/options'
16
15
  require_relative 'hledger_forecast/generator'
17
16
  require_relative 'hledger_forecast/summarize'
17
+ require_relative 'hledger_forecast/checker'
18
18
  require_relative 'hledger_forecast/cli'
data/spec/command_spec.rb CHANGED
@@ -2,11 +2,14 @@ require_relative '../lib/hledger_forecast'
2
2
 
3
3
  RSpec.describe 'command' do
4
4
  it 'uses the CLI to generate an output' do
5
- system("./bin/hledger-forecast -t ./spec/stubs/transactions.journal -f ./spec/stubs/monthly/forecast_monthly.yml -o ./test_output.journal -s 2023-03-01 -e 2023-05-30 --force")
5
+ # Delete the file if it exists
6
+ generated_journal = './test_output.journal'
7
+ File.delete(generated_journal) if File.exist?(generated_journal)
8
+
9
+ system("./bin/hledger-forecast generate -t ./spec/stubs/transactions.journal -f ./spec/stubs/monthly/forecast_monthly.yml -o ./test_output.journal -s 2023-03-01 -e 2023-05-30 --force")
6
10
 
7
11
  expected_output = File.read('spec/stubs/monthly/output_monthly.journal')
8
- generated_journal = File.read('./test_output.journal')
9
12
 
10
- expect(generated_journal).to eq(expected_output)
13
+ expect(File.read(generated_journal)).to eq(expected_output)
11
14
  end
12
15
  end
data/spec/custom_spec.rb CHANGED
@@ -5,7 +5,7 @@ RSpec.describe 'generate' do
5
5
  transactions = File.read('spec/stubs/transactions.journal')
6
6
  forecast = File.read('spec/stubs/custom/forecast_custom_days.yml')
7
7
 
8
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
8
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01',
9
9
  '2023-03-10')
10
10
 
11
11
  expected_output = File.read('spec/stubs/custom/output_custom_days.journal')
@@ -16,7 +16,7 @@ RSpec.describe 'generate' do
16
16
  transactions = File.read('spec/stubs/transactions.journal')
17
17
  forecast = File.read('spec/stubs/custom/forecast_custom_weeks.yml')
18
18
 
19
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
19
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01',
20
20
  '2023-04-30')
21
21
 
22
22
  expected_output = File.read('spec/stubs/custom/output_custom_weeks.journal')
@@ -27,7 +27,7 @@ RSpec.describe 'generate' do
27
27
  transactions = File.read('spec/stubs/transactions.journal')
28
28
  forecast = File.read('spec/stubs/custom/forecast_custom_weeks_twice.yml')
29
29
 
30
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
30
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01',
31
31
  '2023-03-30')
32
32
 
33
33
  expected_output = File.read('spec/stubs/custom/output_custom_weeks_twice.journal')
@@ -38,7 +38,7 @@ RSpec.describe 'generate' do
38
38
  transactions = File.read('spec/stubs/transactions.journal')
39
39
  forecast = File.read('spec/stubs/custom/forecast_custom_months.yml')
40
40
 
41
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
41
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01',
42
42
  '2024-02-28')
43
43
 
44
44
  expected_output = File.read('spec/stubs/custom/output_custom_months.journal')
@@ -5,7 +5,7 @@ RSpec.describe 'generate' do
5
5
  transactions = File.read('spec/stubs/transactions.journal')
6
6
  forecast = File.read('spec/stubs/half-yearly/forecast_half-yearly.yml')
7
7
 
8
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01', '2024-04-30')
8
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01', '2024-04-30')
9
9
 
10
10
  expected_output = File.read('spec/stubs/half-yearly/output_half-yearly.journal')
11
11
  expect(generated_journal).to eq(expected_output)
data/spec/monthly_spec.rb CHANGED
@@ -5,7 +5,7 @@ RSpec.describe 'generate' do
5
5
  transactions = File.read('spec/stubs/transactions.journal')
6
6
  forecast = File.read('spec/stubs/monthly/forecast_monthly.yml')
7
7
 
8
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01', '2023-05-30')
8
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01', '2023-05-30')
9
9
 
10
10
  expected_output = File.read('spec/stubs/monthly/output_monthly.journal')
11
11
  expect(generated_journal).to eq(expected_output)
@@ -15,7 +15,7 @@ RSpec.describe 'generate' do
15
15
  transactions = File.read('spec/stubs/transactions.journal')
16
16
  forecast = File.read('spec/stubs/monthly/forecast_monthly_enddate.yml')
17
17
 
18
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01', '2023-08-30')
18
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01', '2023-08-30')
19
19
 
20
20
  expected_output = File.read('spec/stubs/monthly/output_monthly_enddate.journal')
21
21
  expect(generated_journal).to eq(expected_output)
@@ -25,7 +25,7 @@ RSpec.describe 'generate' do
25
25
  transactions = File.read('spec/stubs/transactions.journal')
26
26
  forecast = File.read('spec/stubs/monthly/forecast_monthly_enddate_top.yml')
27
27
 
28
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01', '2023-08-30')
28
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01', '2023-08-30')
29
29
 
30
30
  expected_output = File.read('spec/stubs/monthly/output_monthly_enddate_top.journal')
31
31
  expect(generated_journal).to eq(expected_output)
data/spec/once_spec.rb CHANGED
@@ -5,7 +5,7 @@ RSpec.describe 'generate' do
5
5
  transactions = File.read('spec/stubs/transactions.journal')
6
6
  forecast = File.read('spec/stubs/once/forecast_once.yml')
7
7
 
8
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01', '2024-04-30')
8
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01', '2024-04-30')
9
9
 
10
10
  expected_output = File.read('spec/stubs/once/output_once.journal')
11
11
  expect(generated_journal).to eq(expected_output)
@@ -5,7 +5,7 @@ RSpec.describe 'generate' do
5
5
  transactions = File.read('spec/stubs/transactions.journal')
6
6
  forecast = File.read('spec/stubs/quarterly/forecast_quarterly.yml')
7
7
 
8
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01', '2023-10-30')
8
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01', '2023-10-30')
9
9
 
10
10
  expected_output = File.read('spec/stubs/quarterly/output_quarterly.journal')
11
11
  expect(generated_journal).to eq(expected_output)
@@ -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.generate(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,13 @@
1
+ monthly:
2
+ - account: "[Assets:Bank]"
3
+ start: "2023-03-01"
4
+ transactions:
5
+ - amount: 2000
6
+ category: "[Expenses:Mortgage]"
7
+ description: Mortgage
8
+ modifier:
9
+ amount: 2.0
10
+ period: yearly
11
+
12
+ settings:
13
+ currency: GBP
@@ -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,7 +2,7 @@ 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
@@ -7,7 +7,7 @@
7
7
  Assets:Bank
8
8
 
9
9
  2023-03-01 * Mortgage
10
- [Expenses:Mortgage] £2,000.00
10
+ [Expenses:Mortgage] £2,000.55
11
11
  [Assets:Bank]
12
12
 
13
13
  2023-03-01 * Food
@@ -19,7 +19,7 @@
19
19
  [Assets:Savings]
20
20
 
21
21
  2023-04-01 * Mortgage
22
- [Expenses:Mortgage] £2,000.00
22
+ [Expenses:Mortgage] £2,000.55
23
23
  [Assets:Bank]
24
24
 
25
25
  2023-04-01 * Food
@@ -31,7 +31,7 @@
31
31
  [Assets:Savings]
32
32
 
33
33
  2023-05-01 * Mortgage
34
- [Expenses:Mortgage] £2,000.00
34
+ [Expenses:Mortgage] £2,000.55
35
35
  [Assets:Bank]
36
36
 
37
37
  2023-05-01 * Food
@@ -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
+
data/spec/yearly_spec.rb CHANGED
@@ -5,7 +5,7 @@ RSpec.describe 'generate' do
5
5
  transactions = File.read('spec/stubs/transactions.journal')
6
6
  forecast = File.read('spec/stubs/yearly/forecast_yearly.yml')
7
7
 
8
- generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01', '2024-04-30')
8
+ generated_journal = HledgerForecast::Generator.generate(transactions, forecast, '2023-03-01', '2024-04-30')
9
9
 
10
10
  expected_output = File.read('spec/stubs/yearly/output_yearly.journal')
11
11
  expect(generated_journal).to eq(expected_output)
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.8
4
+ version: 0.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: 2023-04-17 00:00:00.000000000 Z
11
+ date: 2023-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -100,7 +100,6 @@ files:
100
100
  - lib/hledger_forecast.rb
101
101
  - lib/hledger_forecast/cli.rb
102
102
  - lib/hledger_forecast/generator.rb
103
- - lib/hledger_forecast/options.rb
104
103
  - lib/hledger_forecast/summarize.rb
105
104
  - lib/hledger_forecast/version.rb
106
105
  - spec/command_spec.rb
@@ -109,6 +108,7 @@ files:
109
108
  - spec/monthly_spec.rb
110
109
  - spec/once_spec.rb
111
110
  - spec/quarterly_spec.rb
111
+ - spec/start_date_spec.rb
112
112
  - spec/stubs/custom/forecast_custom_days.yml
113
113
  - spec/stubs/custom/forecast_custom_months.yml
114
114
  - spec/stubs/custom/forecast_custom_weeks.yml
@@ -119,6 +119,8 @@ files:
119
119
  - spec/stubs/custom/output_custom_weeks_twice.journal
120
120
  - spec/stubs/half-yearly/forecast_half-yearly.yml
121
121
  - spec/stubs/half-yearly/output_half-yearly.journal
122
+ - spec/stubs/modifiers/forecast_modifiers.yml
123
+ - spec/stubs/modifiers/output_modifiers.journal
122
124
  - spec/stubs/monthly/forecast_monthly.yml
123
125
  - spec/stubs/monthly/forecast_monthly_enddate.yml
124
126
  - spec/stubs/monthly/forecast_monthly_enddate_top.yml
@@ -131,6 +133,8 @@ files:
131
133
  - spec/stubs/once/output_once.journal
132
134
  - spec/stubs/quarterly/forecast_quarterly.yml
133
135
  - spec/stubs/quarterly/output_quarterly.journal
136
+ - spec/stubs/start_date/forecast_startdate.yml
137
+ - spec/stubs/start_date/output_startdate.journal
134
138
  - spec/stubs/transactions.journal
135
139
  - spec/stubs/yearly/forecast_yearly.yml
136
140
  - spec/stubs/yearly/output_yearly.journal
@@ -165,6 +169,7 @@ test_files:
165
169
  - spec/monthly_spec.rb
166
170
  - spec/once_spec.rb
167
171
  - spec/quarterly_spec.rb
172
+ - spec/start_date_spec.rb
168
173
  - spec/stubs/custom/forecast_custom_days.yml
169
174
  - spec/stubs/custom/forecast_custom_months.yml
170
175
  - spec/stubs/custom/forecast_custom_weeks.yml
@@ -175,6 +180,8 @@ test_files:
175
180
  - spec/stubs/custom/output_custom_weeks_twice.journal
176
181
  - spec/stubs/half-yearly/forecast_half-yearly.yml
177
182
  - spec/stubs/half-yearly/output_half-yearly.journal
183
+ - spec/stubs/modifiers/forecast_modifiers.yml
184
+ - spec/stubs/modifiers/output_modifiers.journal
178
185
  - spec/stubs/monthly/forecast_monthly.yml
179
186
  - spec/stubs/monthly/forecast_monthly_enddate.yml
180
187
  - spec/stubs/monthly/forecast_monthly_enddate_top.yml
@@ -187,6 +194,8 @@ test_files:
187
194
  - spec/stubs/once/output_once.journal
188
195
  - spec/stubs/quarterly/forecast_quarterly.yml
189
196
  - spec/stubs/quarterly/output_quarterly.journal
197
+ - spec/stubs/start_date/forecast_startdate.yml
198
+ - spec/stubs/start_date/output_startdate.journal
190
199
  - spec/stubs/transactions.journal
191
200
  - spec/stubs/yearly/forecast_yearly.yml
192
201
  - spec/stubs/yearly/output_yearly.journal
@@ -1,77 +0,0 @@
1
- module HledgerForecast
2
- class Options
3
- def self.parse_command_line_options(args = ARGV, _stdin = $stdin)
4
- options = {}
5
-
6
- OptionParser.new do |opts|
7
- opts.banner = "Usage: Hledger-Forecast [options]"
8
- opts.separator ""
9
-
10
- opts.on("-f", "--forecast FILE",
11
- "The FORECAST yaml file to generate from") do |file|
12
- options[:forecast_file] = file
13
- options[:output_file] ||= file.sub(/\.yml$/, '.journal')
14
- end
15
-
16
- opts.on("-t", "--transaction FILE",
17
- "The base TRANSACTIONS file to extend from") do |file|
18
- options[:transactions_file] = file if file && !file.empty?
19
- end
20
-
21
- opts.on("-o", "--output-file FILE",
22
- "The OUTPUT file to create") do |file|
23
- options[:output_file] = file
24
- end
25
-
26
- opts.on("-s", "--start-date DATE",
27
- "The date to start generating from (yyyy-mm-dd)") do |a|
28
- options[:start_date] = a
29
- end
30
-
31
- opts.on("-e", "--end-date DATE",
32
- "The date to start generating to (yyyy-mm-dd)") do |a|
33
- options[:end_date] = a
34
- end
35
-
36
- opts.on("--summarize",
37
- "Summarize the forecast file and output to the terminal") do |a|
38
- options[:summarize] = a
39
- end
40
-
41
- opts.on("--force",
42
- "Force an overwrite of the output file") do |a|
43
- options[:force] = a
44
- end
45
-
46
- opts.on_tail("-h", "--help", "Show this message") do
47
- puts opts
48
- exit
49
- end
50
-
51
- opts.on_tail("--version", "Show version") do
52
- puts VERSION
53
- exit
54
- end
55
-
56
- opts.parse!(args)
57
- end
58
-
59
- options[:forecast_file] = "forecast.yml" unless options[:forecast_file]
60
- options[:output_file] = "forecast.journal" unless options[:output_file]
61
-
62
- today = Date.today
63
-
64
- unless options[:start_date]
65
- options[:default_dates] = true
66
- options[:start_date] =
67
- Date.new(today.year, today.month, 1).next_month.to_s
68
- end
69
- unless options[:end_date]
70
- options[:default_dates] = true
71
- options[:end_date] = Date.new(today.year + 3, 12, 31).to_s
72
- end
73
-
74
- return options
75
- end
76
- end
77
- end