hledger-forecast 1.5.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +22 -0
- data/.github/workflows/{ci.yml → test.yml} +5 -5
- data/.rubocop.yml +17 -17
- data/README.md +8 -172
- data/example.journal +14 -14
- data/hledger-forecast.gemspec +1 -1
- data/lib/hledger_forecast/calculator.rb +5 -1
- data/lib/hledger_forecast/cli.rb +5 -18
- data/lib/hledger_forecast/generator.rb +52 -35
- data/lib/hledger_forecast/settings.rb +42 -27
- data/lib/hledger_forecast/summarizer.rb +28 -62
- data/lib/hledger_forecast/summarizer_formatter.rb +12 -3
- data/lib/hledger_forecast/transactions/default.rb +28 -57
- data/lib/hledger_forecast/transactions/trackers.rb +34 -40
- data/lib/hledger_forecast/utilities.rb +14 -0
- data/lib/hledger_forecast/version.rb +1 -1
- data/lib/hledger_forecast.rb +1 -2
- data/spec/cli_spec.rb +3 -12
- data/spec/computed_amounts_spec.rb +11 -22
- data/spec/custom_spec.rb +15 -35
- data/spec/half-yearly_spec.rb +6 -13
- data/spec/monthly_end_date_spec.rb +8 -18
- data/spec/monthly_end_date_transaction_spec.rb +20 -45
- data/spec/monthly_spec.rb +11 -28
- data/spec/once_spec.rb +6 -13
- data/spec/quarterly_spec.rb +5 -12
- data/spec/summarizer_spec.rb +11 -42
- data/spec/track_spec.rb +19 -49
- data/spec/verbose_output_spec.rb +3 -3
- data/spec/yearly_spec.rb +5 -12
- metadata +10 -18
- data/example.yml +0 -98
- data/lib/hledger_forecast/csv_parser.rb +0 -106
- data/spec/csv_and_yml_comparison_spec.rb +0 -32
- data/spec/csv_parser_spec.rb +0 -110
- data/spec/modifier_spec.rb +0 -102
- data/spec/stubs/forecast.yml +0 -19
data/example.yml
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
monthly:
|
2
|
-
- account: "Assets:Bank"
|
3
|
-
from: "2023-03-01"
|
4
|
-
transactions:
|
5
|
-
- amount: -3500
|
6
|
-
category: "Income:Salary"
|
7
|
-
description: Salary
|
8
|
-
- amount: 2000
|
9
|
-
category: "Expenses:Mortgage"
|
10
|
-
description: Mortgage
|
11
|
-
to: "2025-01-01"
|
12
|
-
- amount: 175
|
13
|
-
category: "Expenses:Bills"
|
14
|
-
description: Bills
|
15
|
-
- amount: 500
|
16
|
-
category: "Expenses:Food"
|
17
|
-
description: Food
|
18
|
-
modifiers:
|
19
|
-
- amount: 0.02
|
20
|
-
description: "Inflation"
|
21
|
-
from: "2024-01-01"
|
22
|
-
to: "2024-12-31"
|
23
|
-
- amount: 0.05
|
24
|
-
description: "Inflation"
|
25
|
-
from: "2025-01-01"
|
26
|
-
to: "2025-12-31"
|
27
|
-
- amount: "=5000/24"
|
28
|
-
category: "Expenses:House"
|
29
|
-
description: New Kitchen
|
30
|
-
- amount: 125
|
31
|
-
category: "Expenses:Holiday"
|
32
|
-
description: Holiday
|
33
|
-
to: "=12"
|
34
|
-
- account: "Assets:Bank"
|
35
|
-
from: "2023-03-01"
|
36
|
-
to: "2025-01-01"
|
37
|
-
transactions:
|
38
|
-
- amount: 300
|
39
|
-
category: "Assets:Savings"
|
40
|
-
description: "Rainy day fund"
|
41
|
-
- account: "Assets:Pension"
|
42
|
-
from: "2024-01-01"
|
43
|
-
transactions:
|
44
|
-
- amount: -500
|
45
|
-
category: "Income:Pension"
|
46
|
-
description: Pension draw down
|
47
|
-
|
48
|
-
quarterly:
|
49
|
-
- account: "Assets:Bank"
|
50
|
-
from: "2023-04-01"
|
51
|
-
transactions:
|
52
|
-
- amount: -1000.00
|
53
|
-
category: "Income:Bonus"
|
54
|
-
description: Quarterly bonus
|
55
|
-
|
56
|
-
half-yearly:
|
57
|
-
- account: "Assets:Bank"
|
58
|
-
from: "2023-04-01"
|
59
|
-
transactions:
|
60
|
-
- amount: 500
|
61
|
-
category: "Expenses:Holiday"
|
62
|
-
description: Top up holiday funds
|
63
|
-
|
64
|
-
yearly:
|
65
|
-
- account: "Assets:Bank"
|
66
|
-
from: "2023-04-01"
|
67
|
-
transactions:
|
68
|
-
- amount: -2000.00
|
69
|
-
category: "Income:Bonus"
|
70
|
-
description: Annual Bonus
|
71
|
-
|
72
|
-
once:
|
73
|
-
- account: "Assets:Bank"
|
74
|
-
from: "2023-03-05"
|
75
|
-
transactions:
|
76
|
-
- amount: -3000
|
77
|
-
category: "Expenses:Shopping"
|
78
|
-
description: Refund for that damn laptop
|
79
|
-
summary_exclude: true
|
80
|
-
track: true
|
81
|
-
|
82
|
-
custom:
|
83
|
-
- account: "Assets:Bank"
|
84
|
-
from: "2023-03-01"
|
85
|
-
transactions:
|
86
|
-
- amount: 80
|
87
|
-
category: "Expenses:Personal Care"
|
88
|
-
description: Hair and beauty
|
89
|
-
frequency: "every 2 weeks"
|
90
|
-
roll-up: 26
|
91
|
-
- amount: 30
|
92
|
-
category: "Expenses:General Expenses"
|
93
|
-
description: Misc expenses
|
94
|
-
frequency: "every 5 weeks"
|
95
|
-
roll-up: 10.4
|
96
|
-
|
97
|
-
settings:
|
98
|
-
currency: USD
|
@@ -1,106 +0,0 @@
|
|
1
|
-
module HledgerForecast
|
2
|
-
# Formats various items used throughout the application
|
3
|
-
class CSVParser
|
4
|
-
def self.parse(csv_data, cli_options = nil)
|
5
|
-
new.parse(csv_data, cli_options)
|
6
|
-
end
|
7
|
-
|
8
|
-
def parse(csv_data, _cli_options)
|
9
|
-
csv_data = CSV.parse(csv_data, headers: true)
|
10
|
-
yaml_data = {}
|
11
|
-
group_by_type(csv_data, yaml_data)
|
12
|
-
yaml_data.to_yaml
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def group_by_type(csv_data, yaml_data)
|
18
|
-
csv_data.group_by { |row| row['type'] }.each do |type, rows|
|
19
|
-
if type == 'settings'
|
20
|
-
handle_settings(rows, yaml_data)
|
21
|
-
else
|
22
|
-
yaml_data[type] ||= []
|
23
|
-
group_by_account_and_from(rows, yaml_data[type], type)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def handle_settings(rows, yaml_data)
|
29
|
-
yaml_data['settings'] ||= {}
|
30
|
-
rows.each do |row|
|
31
|
-
yaml_data['settings'][row['frequency']] = cast_to_proper_type(row['account'])
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def group_by_account_and_from(rows, yaml_rows, type)
|
36
|
-
rows.group_by { |row| [row['account'], row['from']] }.each do |(account, from), transactions|
|
37
|
-
yaml_rows << if type == 'custom'
|
38
|
-
build_custom_transaction(account, from, transactions)
|
39
|
-
else
|
40
|
-
build_transaction(account, from, transactions)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def build_transaction(account, from, transactions)
|
46
|
-
transaction = {
|
47
|
-
'account' => account,
|
48
|
-
'from' => Date.parse(from).strftime('%Y-%m-%d'),
|
49
|
-
'transactions' => []
|
50
|
-
}
|
51
|
-
|
52
|
-
transactions.each do |row|
|
53
|
-
transaction['transactions'] << build_transaction_data(row)
|
54
|
-
end
|
55
|
-
|
56
|
-
transaction
|
57
|
-
end
|
58
|
-
|
59
|
-
def build_custom_transaction(account, from, transactions)
|
60
|
-
transaction = {
|
61
|
-
'account' => account,
|
62
|
-
'from' => Date.parse(from).strftime('%Y-%m-%d'),
|
63
|
-
'transactions' => []
|
64
|
-
}
|
65
|
-
|
66
|
-
transactions.each do |row|
|
67
|
-
transaction_data = build_transaction_data(row)
|
68
|
-
transaction_data['frequency'] = row['frequency']
|
69
|
-
transaction_data['roll-up'] = row['roll-up'].to_f if row['roll-up']
|
70
|
-
transaction['transactions'] << transaction_data
|
71
|
-
end
|
72
|
-
|
73
|
-
transaction
|
74
|
-
end
|
75
|
-
|
76
|
-
def build_transaction_data(row)
|
77
|
-
transaction_data = {
|
78
|
-
'amount' => row['amount'].start_with?("=") ? row['amount'].to_s : row['amount'].to_f,
|
79
|
-
'category' => row['category'],
|
80
|
-
'description' => row['description']
|
81
|
-
}
|
82
|
-
|
83
|
-
if row['to']
|
84
|
-
transaction_data['to'] = if row['to'].start_with?("=")
|
85
|
-
row['to']
|
86
|
-
else
|
87
|
-
Date.parse(row['to']).strftime('%Y-%m-%d')
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
transaction_data['summary_exclude'] = true if row['summary_exclude'] && row['summary_exclude'].downcase == "true"
|
92
|
-
transaction_data['track'] = true if row['track'] && row['track'].downcase == "true"
|
93
|
-
|
94
|
-
transaction_data
|
95
|
-
end
|
96
|
-
|
97
|
-
def cast_to_proper_type(str)
|
98
|
-
case str.downcase
|
99
|
-
when 'true', 'false'
|
100
|
-
str.downcase == 'true'
|
101
|
-
else
|
102
|
-
str
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
require_relative '../lib/hledger_forecast'
|
2
|
-
|
3
|
-
RSpec.describe 'CSV and yml outputs' do
|
4
|
-
it 'should return the same value when ran through hledger' do
|
5
|
-
generated_journal = './test_output.journal'
|
6
|
-
File.delete(generated_journal) if File.exist?(generated_journal)
|
7
|
-
|
8
|
-
system("./bin/hledger-forecast generate -f example.csv -o ./test_output.journal -t ./spec/stubs/transactions_not_found.journal --force")
|
9
|
-
csv_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-06`
|
10
|
-
|
11
|
-
system("./bin/hledger-forecast generate -f example.yml -o ./test_output.journal -t ./spec/stubs/transactions_not_found.journal --force")
|
12
|
-
yml_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-06`
|
13
|
-
|
14
|
-
expect(csv_output).to eq(yml_output)
|
15
|
-
end
|
16
|
-
|
17
|
-
# it 'check that it can fail!' do
|
18
|
-
# generated_journal = './test_output.journal'
|
19
|
-
# File.delete(generated_journal) if File.exist?(generated_journal)
|
20
|
-
#
|
21
|
-
# system("./bin/hledger-forecast generate -f ./spec/stubs/csv_and_yml/forecast.csv -o ./test_output.journal --force > /dev/null 2>&1")
|
22
|
-
#
|
23
|
-
# ### CHANGE DATE!!!!!!!!!!!!!!!!
|
24
|
-
# csv_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-03`
|
25
|
-
# ### CHANGE DATE!!!!!!!!!!!!!!!!
|
26
|
-
#
|
27
|
-
# system("./bin/hledger-forecast generate -f ./spec/stubs/csv_and_yml/forecast.yml -o ./test_output.journal --force > /dev/null 2>&1")
|
28
|
-
# yml_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-06`
|
29
|
-
#
|
30
|
-
# expect(csv_output).not_to eq(yml_output)
|
31
|
-
# end
|
32
|
-
end
|
data/spec/csv_parser_spec.rb
DELETED
@@ -1,110 +0,0 @@
|
|
1
|
-
require_relative '../lib/hledger_forecast'
|
2
|
-
|
3
|
-
input = <<~CSV
|
4
|
-
type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
|
5
|
-
monthly,,Assets:Bank,01/03/2023,,Salary,Income:Salary,-3500,,,
|
6
|
-
monthly,,Assets:Bank,01/03/2023,01/01/2025,Mortgage,Expenses:Mortgage,2000,,,
|
7
|
-
monthly,,Assets:Bank,01/03/2023,,Bills,Expenses:Bills,175,,,
|
8
|
-
monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,500,,,
|
9
|
-
monthly,,Assets:Bank,01/03/2023,,New Kitchen,Expenses:House,=5000/24,,,
|
10
|
-
monthly,,Assets:Bank,01/03/2023,=12,Holiday,Expenses:Holiday,125,,,
|
11
|
-
monthly,,Assets:Bank,01/03/2023,01/01/2025,Rainy day fund,Assets:Savings,300,,,
|
12
|
-
monthly,,Assets:Pension,01/01/2024,,Pension draw down,Income:Pension,-500,,,
|
13
|
-
quarterly,,Assets:Bank,01/04/2023,,Quarterly bonus,Income:Bonus,-1000,,,
|
14
|
-
half-yearly,,Assets:Bank,01/04/2023,,Top up holiday funds,Expenses:Holiday,500,,,
|
15
|
-
yearly,,Assets:Bank,01/04/2023,,Annual bonus,Income:Bonus,-2000,,,
|
16
|
-
once,,Assets:Bank,05/03/2023,,Refund for that damn laptop,Expenses:Shopping,-3000,,TRUE,TRUE
|
17
|
-
custom,every 2 weeks,Assets:Bank,01/03/2023,,Hair and beauty,Expenses:Personal Care,80,26,,
|
18
|
-
settings,currency,USD,,,,,,,,
|
19
|
-
settings,show_symbol,TRUE,,,,,,,,
|
20
|
-
settings,thousands_separator,TRUE,,,,,,,,
|
21
|
-
CSV
|
22
|
-
|
23
|
-
output = <<~YAML
|
24
|
-
---
|
25
|
-
monthly:
|
26
|
-
- account: Assets:Bank
|
27
|
-
from: '2023-03-01'
|
28
|
-
transactions:
|
29
|
-
- amount: -3500.0
|
30
|
-
category: Income:Salary
|
31
|
-
description: Salary
|
32
|
-
- amount: 2000.0
|
33
|
-
category: Expenses:Mortgage
|
34
|
-
description: Mortgage
|
35
|
-
to: '2025-01-01'
|
36
|
-
- amount: 175.0
|
37
|
-
category: Expenses:Bills
|
38
|
-
description: Bills
|
39
|
-
- amount: 500.0
|
40
|
-
category: Expenses:Food
|
41
|
-
description: Food
|
42
|
-
- amount: "=5000/24"
|
43
|
-
category: Expenses:House
|
44
|
-
description: New Kitchen
|
45
|
-
- amount: 125.0
|
46
|
-
category: Expenses:Holiday
|
47
|
-
description: Holiday
|
48
|
-
to: "=12"
|
49
|
-
- amount: 300.0
|
50
|
-
category: Assets:Savings
|
51
|
-
description: Rainy day fund
|
52
|
-
to: '2025-01-01'
|
53
|
-
- account: Assets:Pension
|
54
|
-
from: '2024-01-01'
|
55
|
-
transactions:
|
56
|
-
- amount: -500.0
|
57
|
-
category: Income:Pension
|
58
|
-
description: Pension draw down
|
59
|
-
quarterly:
|
60
|
-
- account: Assets:Bank
|
61
|
-
from: '2023-04-01'
|
62
|
-
transactions:
|
63
|
-
- amount: -1000.0
|
64
|
-
category: Income:Bonus
|
65
|
-
description: Quarterly bonus
|
66
|
-
half-yearly:
|
67
|
-
- account: Assets:Bank
|
68
|
-
from: '2023-04-01'
|
69
|
-
transactions:
|
70
|
-
- amount: 500.0
|
71
|
-
category: Expenses:Holiday
|
72
|
-
description: Top up holiday funds
|
73
|
-
yearly:
|
74
|
-
- account: Assets:Bank
|
75
|
-
from: '2023-04-01'
|
76
|
-
transactions:
|
77
|
-
- amount: -2000.0
|
78
|
-
category: Income:Bonus
|
79
|
-
description: Annual bonus
|
80
|
-
once:
|
81
|
-
- account: Assets:Bank
|
82
|
-
from: '2023-03-05'
|
83
|
-
transactions:
|
84
|
-
- amount: -3000.0
|
85
|
-
category: Expenses:Shopping
|
86
|
-
description: Refund for that damn laptop
|
87
|
-
summary_exclude: true
|
88
|
-
track: true
|
89
|
-
custom:
|
90
|
-
- account: Assets:Bank
|
91
|
-
from: '2023-03-01'
|
92
|
-
transactions:
|
93
|
-
- amount: 80.0
|
94
|
-
category: Expenses:Personal Care
|
95
|
-
description: Hair and beauty
|
96
|
-
frequency: every 2 weeks
|
97
|
-
roll-up: 26.0
|
98
|
-
settings:
|
99
|
-
currency: USD
|
100
|
-
show_symbol: true
|
101
|
-
thousands_separator: true
|
102
|
-
YAML
|
103
|
-
|
104
|
-
RSpec.describe 'CSV parser' do
|
105
|
-
it 'converts a CSV file to the YML output needed for the plugin' do
|
106
|
-
computed_yaml = HledgerForecast::CSVParser.parse(input)
|
107
|
-
|
108
|
-
expect(computed_yaml).to eq(output)
|
109
|
-
end
|
110
|
-
end
|
data/spec/modifier_spec.rb
DELETED
@@ -1,102 +0,0 @@
|
|
1
|
-
require_relative '../lib/hledger_forecast'
|
2
|
-
|
3
|
-
base_config = <<~YAML
|
4
|
-
monthly:
|
5
|
-
- account: "Assets:Bank"
|
6
|
-
from: "2023-01-01"
|
7
|
-
transactions:
|
8
|
-
- amount: 300
|
9
|
-
category: "Expenses:Groceries"
|
10
|
-
description: Food shopping
|
11
|
-
modifiers:
|
12
|
-
- amount: 0.02
|
13
|
-
description: "Y1 inflation"
|
14
|
-
from: "2024-01-01"
|
15
|
-
to: "2024-12-31"
|
16
|
-
- amount: 0.05
|
17
|
-
description: "Y2 inflation"
|
18
|
-
from: "2025-01-01"
|
19
|
-
to: "2025-12-31"
|
20
|
-
- account: "Assets:Savings"
|
21
|
-
from: "2023-05-01"
|
22
|
-
transactions:
|
23
|
-
- amount: 500
|
24
|
-
category: "Assets:Bank"
|
25
|
-
description: Savings
|
26
|
-
modifiers:
|
27
|
-
- amount: 0.1
|
28
|
-
description: "Savings uplift"
|
29
|
-
from: "2024-05-01"
|
30
|
-
to: "2025-04-30"
|
31
|
-
|
32
|
-
settings:
|
33
|
-
currency: USD
|
34
|
-
YAML
|
35
|
-
|
36
|
-
base_journal = <<~JOURNAL
|
37
|
-
~ monthly from 2023-01-01 * Food shopping
|
38
|
-
Expenses:Groceries $300.00; Food shopping
|
39
|
-
Assets:Bank
|
40
|
-
|
41
|
-
~ monthly from 2023-05-01 * Savings
|
42
|
-
Assets:Bank $500.00; Savings
|
43
|
-
Assets:Savings
|
44
|
-
|
45
|
-
= Expenses:Groceries date:2024-01-01..2024-12-31
|
46
|
-
Expenses:Groceries *0.02 ; Food shopping - Y1 inflation
|
47
|
-
Assets:Bank *-0.02
|
48
|
-
|
49
|
-
= Expenses:Groceries date:2025-01-01..2025-12-31
|
50
|
-
Expenses:Groceries *0.05 ; Food shopping - Y2 inflation
|
51
|
-
Assets:Bank *-0.05
|
52
|
-
|
53
|
-
= Assets:Bank date:2024-05-01..2025-04-30
|
54
|
-
Assets:Bank *0.1 ; Savings - Savings uplift
|
55
|
-
Assets:Savings *-0.1
|
56
|
-
|
57
|
-
JOURNAL
|
58
|
-
|
59
|
-
no_date_config = <<~YAML
|
60
|
-
monthly:
|
61
|
-
- account: "Assets:Bank"
|
62
|
-
from: "2023-01-01"
|
63
|
-
transactions:
|
64
|
-
- amount: 500
|
65
|
-
category: "Expenses:Groceries"
|
66
|
-
description: Food shopping
|
67
|
-
modifiers:
|
68
|
-
- amount: 0.1
|
69
|
-
description: "Inflation"
|
70
|
-
|
71
|
-
settings:
|
72
|
-
currency: USD
|
73
|
-
YAML
|
74
|
-
|
75
|
-
no_date_journal = <<~JOURNAL
|
76
|
-
~ monthly from 2023-01-01 * Food shopping
|
77
|
-
Expenses:Groceries $500.00; Food shopping
|
78
|
-
Assets:Bank
|
79
|
-
|
80
|
-
= Expenses:Groceries date:2023-01-01
|
81
|
-
Expenses:Groceries *0.1 ; Food shopping - Inflation
|
82
|
-
Assets:Bank *-0.1
|
83
|
-
|
84
|
-
JOURNAL
|
85
|
-
|
86
|
-
RSpec.describe 'Applying modifiers to transactions -' do
|
87
|
-
it 'Auto-postings should be created correctly' do
|
88
|
-
generated = HledgerForecast::Generator
|
89
|
-
|
90
|
-
generated_journal = generated.generate(base_config)
|
91
|
-
|
92
|
-
expect(generated_journal).to eq(base_journal)
|
93
|
-
end
|
94
|
-
|
95
|
-
it 'Auto-postings should be created correctly if no dates are set' do
|
96
|
-
generated = HledgerForecast::Generator
|
97
|
-
|
98
|
-
generated_journal = generated.generate(no_date_config)
|
99
|
-
|
100
|
-
expect(generated_journal).to eq(no_date_journal)
|
101
|
-
end
|
102
|
-
end
|
data/spec/stubs/forecast.yml
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
monthly:
|
2
|
-
- account: "Assets:Bank"
|
3
|
-
from: "2023-03-01"
|
4
|
-
transactions:
|
5
|
-
- amount: 2000.55
|
6
|
-
category: "Expenses:Mortgage"
|
7
|
-
description: Mortgage
|
8
|
-
- amount: 100
|
9
|
-
category: "Expenses:Food"
|
10
|
-
description: Food
|
11
|
-
- account: "Assets:Savings"
|
12
|
-
from: "2023-03-01"
|
13
|
-
transactions:
|
14
|
-
- amount: -1000
|
15
|
-
category: "Assets:Bank"
|
16
|
-
description: Savings
|
17
|
-
|
18
|
-
settings:
|
19
|
-
currency: GBP
|