rvgp 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +23 -0
- data/LICENSE +504 -0
- data/README.md +223 -0
- data/Rakefile +32 -0
- data/bin/rvgp +8 -0
- data/lib/rvgp/application/config.rb +159 -0
- data/lib/rvgp/application/descendant_registry.rb +122 -0
- data/lib/rvgp/application/status_output.rb +139 -0
- data/lib/rvgp/application.rb +170 -0
- data/lib/rvgp/base/command.rb +457 -0
- data/lib/rvgp/base/grid.rb +531 -0
- data/lib/rvgp/base/reader.rb +29 -0
- data/lib/rvgp/base/reconciler.rb +434 -0
- data/lib/rvgp/base/validation.rb +261 -0
- data/lib/rvgp/commands/cashflow.rb +160 -0
- data/lib/rvgp/commands/grid.rb +70 -0
- data/lib/rvgp/commands/ireconcile.rb +95 -0
- data/lib/rvgp/commands/new_project.rb +296 -0
- data/lib/rvgp/commands/plot.rb +41 -0
- data/lib/rvgp/commands/publish_gsheets.rb +83 -0
- data/lib/rvgp/commands/reconcile.rb +58 -0
- data/lib/rvgp/commands/rotate_year.rb +202 -0
- data/lib/rvgp/commands/validate_journal.rb +59 -0
- data/lib/rvgp/commands/validate_system.rb +44 -0
- data/lib/rvgp/commands.rb +160 -0
- data/lib/rvgp/dashboard.rb +252 -0
- data/lib/rvgp/fakers/fake_feed.rb +245 -0
- data/lib/rvgp/fakers/fake_journal.rb +57 -0
- data/lib/rvgp/fakers/fake_reconciler.rb +88 -0
- data/lib/rvgp/fakers/faker_helpers.rb +25 -0
- data/lib/rvgp/gem.rb +80 -0
- data/lib/rvgp/journal/commodity.rb +453 -0
- data/lib/rvgp/journal/complex_commodity.rb +214 -0
- data/lib/rvgp/journal/currency.rb +101 -0
- data/lib/rvgp/journal/journal.rb +141 -0
- data/lib/rvgp/journal/posting.rb +156 -0
- data/lib/rvgp/journal/pricer.rb +267 -0
- data/lib/rvgp/journal.rb +24 -0
- data/lib/rvgp/plot/gnuplot.rb +478 -0
- data/lib/rvgp/plot/google-drive/output_csv.rb +44 -0
- data/lib/rvgp/plot/google-drive/output_google_sheets.rb +434 -0
- data/lib/rvgp/plot/google-drive/sheet.rb +67 -0
- data/lib/rvgp/plot.rb +293 -0
- data/lib/rvgp/pta/hledger.rb +237 -0
- data/lib/rvgp/pta/ledger.rb +308 -0
- data/lib/rvgp/pta.rb +311 -0
- data/lib/rvgp/reconcilers/csv_reconciler.rb +424 -0
- data/lib/rvgp/reconcilers/journal_reconciler.rb +41 -0
- data/lib/rvgp/reconcilers/shorthand/finance_gem_hacks.rb +48 -0
- data/lib/rvgp/reconcilers/shorthand/international_atm.rb +152 -0
- data/lib/rvgp/reconcilers/shorthand/investment.rb +144 -0
- data/lib/rvgp/reconcilers/shorthand/mortgage.rb +195 -0
- data/lib/rvgp/utilities/grid_query.rb +190 -0
- data/lib/rvgp/utilities/yaml.rb +131 -0
- data/lib/rvgp/utilities.rb +44 -0
- data/lib/rvgp/validations/balance_validation.rb +68 -0
- data/lib/rvgp/validations/duplicate_tags_validation.rb +48 -0
- data/lib/rvgp/validations/uncategorized_validation.rb +15 -0
- data/lib/rvgp.rb +66 -0
- data/resources/README.MD/2022-cashflow-google.png +0 -0
- data/resources/README.MD/2022-cashflow.png +0 -0
- data/resources/README.MD/all-wealth-growth-google.png +0 -0
- data/resources/README.MD/all-wealth-growth.png +0 -0
- data/resources/gnuplot/default.yml +80 -0
- data/resources/i18n/en.yml +192 -0
- data/resources/iso-4217-currencies.json +171 -0
- data/resources/skel/Rakefile +5 -0
- data/resources/skel/app/grids/cashflow_grid.rb +27 -0
- data/resources/skel/app/grids/monthly_income_and_expenses_grid.rb +25 -0
- data/resources/skel/app/grids/wealth_growth_grid.rb +35 -0
- data/resources/skel/app/plots/cashflow.yml +33 -0
- data/resources/skel/app/plots/monthly-income-and-expenses.yml +17 -0
- data/resources/skel/app/plots/wealth-growth.yml +20 -0
- data/resources/skel/config/csv-format-acme-checking.yml +9 -0
- data/resources/skel/config/google-secrets.yml +5 -0
- data/resources/skel/config/rvgp.yml +0 -0
- data/resources/skel/journals/prices.db +0 -0
- data/rvgp.gemspec +6 -0
- data/test/assets/ledger_total_monthly_liabilities_with_empty.xml +383 -0
- data/test/assets/ledger_total_monthly_liabilities_with_empty2.xml +428 -0
- data/test/test_command_base.rb +61 -0
- data/test/test_commodity.rb +270 -0
- data/test/test_csv_reconciler.rb +60 -0
- data/test/test_currency.rb +24 -0
- data/test/test_fake_feed.rb +228 -0
- data/test/test_fake_journal.rb +98 -0
- data/test/test_fake_reconciler.rb +60 -0
- data/test/test_journal_parse.rb +545 -0
- data/test/test_ledger.rb +102 -0
- data/test/test_plot.rb +133 -0
- data/test/test_posting.rb +50 -0
- data/test/test_pricer.rb +139 -0
- data/test/test_pta_adapter.rb +575 -0
- data/test/test_utilities.rb +45 -0
- metadata +268 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'minitest/autorun'
|
|
5
|
+
|
|
6
|
+
require_relative '../lib/rvgp'
|
|
7
|
+
require_relative '../lib/rvgp/fakers/fake_journal'
|
|
8
|
+
|
|
9
|
+
# Minitest class, used to test RVGP::Fakers::FakeJournal
|
|
10
|
+
class TestFakeJournal < Minitest::Test
|
|
11
|
+
def test_basic_cash
|
|
12
|
+
journal = RVGP::Fakers::FakeJournal.basic_cash from: Date.new(2020, 1, 1), to: Date.new(2020, 1, 10)
|
|
13
|
+
|
|
14
|
+
assert_kind_of RVGP::Journal, journal
|
|
15
|
+
assert_equal 10, journal.postings.length
|
|
16
|
+
assert_equal %w[2020-01-01 2020-01-02 2020-01-03 2020-01-04 2020-01-05
|
|
17
|
+
2020-01-06 2020-01-07 2020-01-08 2020-01-09 2020-01-10],
|
|
18
|
+
journal.postings.map(&:date).map(&:to_s)
|
|
19
|
+
journal.postings.each do |posting|
|
|
20
|
+
assert_kind_of String, posting.description
|
|
21
|
+
assert !posting.description.empty?
|
|
22
|
+
end
|
|
23
|
+
assert_equal [2], journal.postings.map { |posting| posting.transfers.length }.uniq
|
|
24
|
+
assert_equal ['$ 10.00'], journal.postings.map { |posting| posting.transfers[0].commodity.to_s }.uniq
|
|
25
|
+
assert_equal ['Expense'], journal.postings.map { |posting| posting.transfers[0].account.to_s }.uniq
|
|
26
|
+
assert_equal ['Cash'], journal.postings.map { |posting| posting.transfers[1].account.to_s }.uniq
|
|
27
|
+
|
|
28
|
+
balance = RVGP::Pta::Ledger.new.balance 'Expense', from_s: journal.to_s
|
|
29
|
+
assert_equal 1, balance.accounts.length
|
|
30
|
+
assert_equal 'Expense', balance.accounts[0].fullname
|
|
31
|
+
assert_equal 1, balance.accounts[0].amounts.length
|
|
32
|
+
assert_equal '$ 100.00', balance.accounts[0].amounts[0].to_s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_basic_cash_balance
|
|
36
|
+
10.times.map { rand(1..10_000_000) }.each do |i|
|
|
37
|
+
amount = format('$ %.2f', i.to_f / 100).to_commodity
|
|
38
|
+
|
|
39
|
+
journal = RVGP::Fakers::FakeJournal.basic_cash from: Date.new(2020, 1, 1), sum: amount
|
|
40
|
+
|
|
41
|
+
assert_kind_of RVGP::Journal, journal
|
|
42
|
+
assert_equal(10, journal.postings.length)
|
|
43
|
+
|
|
44
|
+
balance = RVGP::Pta::Ledger.new.balance 'Expense', from_s: journal.to_s
|
|
45
|
+
|
|
46
|
+
assert_equal 1, balance.accounts.length
|
|
47
|
+
assert_equal 'Expense', balance.accounts[0].fullname
|
|
48
|
+
assert_equal 1, balance.accounts[0].amounts.length
|
|
49
|
+
assert_equal amount.to_s, balance.accounts[0].amounts[0].to_s
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_basic_cash_dates
|
|
54
|
+
assert_equal %w[2020-01-01 2020-01-03 2020-01-05 2020-01-07 2020-01-09
|
|
55
|
+
2020-01-12 2020-01-14 2020-01-16 2020-01-18 2020-01-20],
|
|
56
|
+
RVGP::Fakers::FakeJournal.basic_cash(from: Date.new(2020, 1, 1), to: Date.new(2020, 1, 20))
|
|
57
|
+
.postings.map(&:date).map(&:to_s)
|
|
58
|
+
|
|
59
|
+
assert_equal %w[2020-01-01 2020-02-09 2020-03-19 2020-04-28 2020-06-06
|
|
60
|
+
2020-07-16 2020-08-24 2020-10-03 2020-11-11 2020-12-20],
|
|
61
|
+
RVGP::Fakers::FakeJournal.basic_cash(from: Date.new(2020, 1, 1), to: Date.new(2020, 12, 20))
|
|
62
|
+
.postings.map(&:date).map(&:to_s)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_basic_cash_postings
|
|
66
|
+
postings = [
|
|
67
|
+
RVGP::Journal::Posting.new(
|
|
68
|
+
Date.new(2019, 12, 31), 'Posting from Dec 2019',
|
|
69
|
+
transfers: [
|
|
70
|
+
RVGP::Journal::Posting::Transfer.new('Personal:Expenses:Testing', commodity: '$ 9.00'.to_commodity),
|
|
71
|
+
RVGP::Journal::Posting::Transfer.new('Cash')
|
|
72
|
+
]
|
|
73
|
+
),
|
|
74
|
+
RVGP::Journal::Posting.new(
|
|
75
|
+
Date.new(2020, 2, 1), 'Posting from Feb 2020',
|
|
76
|
+
transfers: [
|
|
77
|
+
RVGP::Journal::Posting::Transfer.new('Personal:Expenses:Testing', commodity: '$ 8.00'.to_commodity),
|
|
78
|
+
RVGP::Journal::Posting::Transfer.new('Cash')
|
|
79
|
+
]
|
|
80
|
+
)
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
journal = RVGP::Fakers::FakeJournal.basic_cash from: Date.new(2020, 1, 1),
|
|
84
|
+
to: Date.new(2020, 1, 10),
|
|
85
|
+
postings: postings
|
|
86
|
+
# Just make sure these postings are part of the output, and in the expected alphabetical order
|
|
87
|
+
assert_equal 12, journal.postings.length
|
|
88
|
+
|
|
89
|
+
assert_equal ['2019-12-31 Posting from Dec 2019',
|
|
90
|
+
' Personal:Expenses:Testing $ 9.00',
|
|
91
|
+
' Cash'].join("\n"),
|
|
92
|
+
journal.postings.first.to_ledger
|
|
93
|
+
assert_equal ['2020-02-01 Posting from Feb 2020',
|
|
94
|
+
' Personal:Expenses:Testing $ 8.00',
|
|
95
|
+
' Cash'].join("\n"),
|
|
96
|
+
journal.postings.last.to_ledger
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'minitest/autorun'
|
|
5
|
+
|
|
6
|
+
require_relative '../lib/rvgp'
|
|
7
|
+
require_relative '../lib/rvgp/fakers/fake_reconciler'
|
|
8
|
+
|
|
9
|
+
# Minitest class, used to test RVGP::Fakers::FakeReconciler
|
|
10
|
+
class TestFakeReconciler < Minitest::Test
|
|
11
|
+
def test_basic_reconciler_with_format_path
|
|
12
|
+
reconciler = Psych.load RVGP::Fakers::FakeReconciler.basic_checking(
|
|
13
|
+
label: 'Personal AcmeBank:Checking (2020)',
|
|
14
|
+
input_path: '2020-personal-basic-checking.csv',
|
|
15
|
+
output_path: '2020-personal-basic-checking.journal',
|
|
16
|
+
format_path: 'config/csv-format-acme-checking.yml',
|
|
17
|
+
income: incomes,
|
|
18
|
+
expense: expenses
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
assert_equal 'Personal:Assets:AcmeBank:Checking', reconciler['from']
|
|
22
|
+
assert_equal 'Personal AcmeBank:Checking (2020)', reconciler['label']
|
|
23
|
+
assert_equal 'config/csv-format-acme-checking.yml', reconciler['format'].path
|
|
24
|
+
assert_equal '2020-personal-basic-checking.csv', reconciler['input']
|
|
25
|
+
assert_equal '2020-personal-basic-checking.journal', reconciler['output']
|
|
26
|
+
assert_nil reconciler['balances']
|
|
27
|
+
|
|
28
|
+
assert_equal incomes + [{ 'match' => '/.*/', 'to' => 'Personal:Income:Unknown' }], reconciler['income']
|
|
29
|
+
assert_equal expenses + [{ 'match' => '/.*/', 'to' => 'Personal:Expenses:Unknown' }], reconciler['expense']
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_basic_reconciler_without_format
|
|
33
|
+
reconciler = Psych.load RVGP::Fakers::FakeReconciler.basic_checking(
|
|
34
|
+
label: 'Personal AcmeBank:Checking (2020)',
|
|
35
|
+
input_path: '2020-personal-basic-checking.csv',
|
|
36
|
+
output_path: '2020-personal-basic-checking.journal',
|
|
37
|
+
income: incomes,
|
|
38
|
+
expense: expenses
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
assert_equal true, reconciler['format']['csv_headers']
|
|
42
|
+
assert_equal true, reconciler['format']['reverse_order']
|
|
43
|
+
assert_equal '$', reconciler['format']['default_currency']
|
|
44
|
+
assert_equal %w[date amount description], reconciler['format']['fields'].keys
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def incomes
|
|
50
|
+
[{ 'match' => '/MC DONALDS INC DIRECT DEP/', 'to' => 'Personal:Income:McDonalds' },
|
|
51
|
+
{ 'match' => '/Airbnb Payments/', 'to' => 'Personal:Income:AirBNB' }]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def expenses
|
|
55
|
+
[{ 'match' => '/(?:Burger King|KFC|Taco Bell)/', 'to' => 'Personal:Expenses:Food:Restaurants' },
|
|
56
|
+
{ 'match' => '/(?:Publix|Kroger|Trader Joe)/', 'to' => 'Personal:Expenses:Food:Groceries' },
|
|
57
|
+
{ 'match' => '/CHECK (?:100|105|110)/', 'to' => 'Personal:Expenses:Rent' },
|
|
58
|
+
{ 'match' => '/T[-]?Mobile/i', 'to' => 'Personal:Expenses:Phone' }]
|
|
59
|
+
end
|
|
60
|
+
end
|