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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +23 -0
  4. data/LICENSE +504 -0
  5. data/README.md +223 -0
  6. data/Rakefile +32 -0
  7. data/bin/rvgp +8 -0
  8. data/lib/rvgp/application/config.rb +159 -0
  9. data/lib/rvgp/application/descendant_registry.rb +122 -0
  10. data/lib/rvgp/application/status_output.rb +139 -0
  11. data/lib/rvgp/application.rb +170 -0
  12. data/lib/rvgp/base/command.rb +457 -0
  13. data/lib/rvgp/base/grid.rb +531 -0
  14. data/lib/rvgp/base/reader.rb +29 -0
  15. data/lib/rvgp/base/reconciler.rb +434 -0
  16. data/lib/rvgp/base/validation.rb +261 -0
  17. data/lib/rvgp/commands/cashflow.rb +160 -0
  18. data/lib/rvgp/commands/grid.rb +70 -0
  19. data/lib/rvgp/commands/ireconcile.rb +95 -0
  20. data/lib/rvgp/commands/new_project.rb +296 -0
  21. data/lib/rvgp/commands/plot.rb +41 -0
  22. data/lib/rvgp/commands/publish_gsheets.rb +83 -0
  23. data/lib/rvgp/commands/reconcile.rb +58 -0
  24. data/lib/rvgp/commands/rotate_year.rb +202 -0
  25. data/lib/rvgp/commands/validate_journal.rb +59 -0
  26. data/lib/rvgp/commands/validate_system.rb +44 -0
  27. data/lib/rvgp/commands.rb +160 -0
  28. data/lib/rvgp/dashboard.rb +252 -0
  29. data/lib/rvgp/fakers/fake_feed.rb +245 -0
  30. data/lib/rvgp/fakers/fake_journal.rb +57 -0
  31. data/lib/rvgp/fakers/fake_reconciler.rb +88 -0
  32. data/lib/rvgp/fakers/faker_helpers.rb +25 -0
  33. data/lib/rvgp/gem.rb +80 -0
  34. data/lib/rvgp/journal/commodity.rb +453 -0
  35. data/lib/rvgp/journal/complex_commodity.rb +214 -0
  36. data/lib/rvgp/journal/currency.rb +101 -0
  37. data/lib/rvgp/journal/journal.rb +141 -0
  38. data/lib/rvgp/journal/posting.rb +156 -0
  39. data/lib/rvgp/journal/pricer.rb +267 -0
  40. data/lib/rvgp/journal.rb +24 -0
  41. data/lib/rvgp/plot/gnuplot.rb +478 -0
  42. data/lib/rvgp/plot/google-drive/output_csv.rb +44 -0
  43. data/lib/rvgp/plot/google-drive/output_google_sheets.rb +434 -0
  44. data/lib/rvgp/plot/google-drive/sheet.rb +67 -0
  45. data/lib/rvgp/plot.rb +293 -0
  46. data/lib/rvgp/pta/hledger.rb +237 -0
  47. data/lib/rvgp/pta/ledger.rb +308 -0
  48. data/lib/rvgp/pta.rb +311 -0
  49. data/lib/rvgp/reconcilers/csv_reconciler.rb +424 -0
  50. data/lib/rvgp/reconcilers/journal_reconciler.rb +41 -0
  51. data/lib/rvgp/reconcilers/shorthand/finance_gem_hacks.rb +48 -0
  52. data/lib/rvgp/reconcilers/shorthand/international_atm.rb +152 -0
  53. data/lib/rvgp/reconcilers/shorthand/investment.rb +144 -0
  54. data/lib/rvgp/reconcilers/shorthand/mortgage.rb +195 -0
  55. data/lib/rvgp/utilities/grid_query.rb +190 -0
  56. data/lib/rvgp/utilities/yaml.rb +131 -0
  57. data/lib/rvgp/utilities.rb +44 -0
  58. data/lib/rvgp/validations/balance_validation.rb +68 -0
  59. data/lib/rvgp/validations/duplicate_tags_validation.rb +48 -0
  60. data/lib/rvgp/validations/uncategorized_validation.rb +15 -0
  61. data/lib/rvgp.rb +66 -0
  62. data/resources/README.MD/2022-cashflow-google.png +0 -0
  63. data/resources/README.MD/2022-cashflow.png +0 -0
  64. data/resources/README.MD/all-wealth-growth-google.png +0 -0
  65. data/resources/README.MD/all-wealth-growth.png +0 -0
  66. data/resources/gnuplot/default.yml +80 -0
  67. data/resources/i18n/en.yml +192 -0
  68. data/resources/iso-4217-currencies.json +171 -0
  69. data/resources/skel/Rakefile +5 -0
  70. data/resources/skel/app/grids/cashflow_grid.rb +27 -0
  71. data/resources/skel/app/grids/monthly_income_and_expenses_grid.rb +25 -0
  72. data/resources/skel/app/grids/wealth_growth_grid.rb +35 -0
  73. data/resources/skel/app/plots/cashflow.yml +33 -0
  74. data/resources/skel/app/plots/monthly-income-and-expenses.yml +17 -0
  75. data/resources/skel/app/plots/wealth-growth.yml +20 -0
  76. data/resources/skel/config/csv-format-acme-checking.yml +9 -0
  77. data/resources/skel/config/google-secrets.yml +5 -0
  78. data/resources/skel/config/rvgp.yml +0 -0
  79. data/resources/skel/journals/prices.db +0 -0
  80. data/rvgp.gemspec +6 -0
  81. data/test/assets/ledger_total_monthly_liabilities_with_empty.xml +383 -0
  82. data/test/assets/ledger_total_monthly_liabilities_with_empty2.xml +428 -0
  83. data/test/test_command_base.rb +61 -0
  84. data/test/test_commodity.rb +270 -0
  85. data/test/test_csv_reconciler.rb +60 -0
  86. data/test/test_currency.rb +24 -0
  87. data/test/test_fake_feed.rb +228 -0
  88. data/test/test_fake_journal.rb +98 -0
  89. data/test/test_fake_reconciler.rb +60 -0
  90. data/test/test_journal_parse.rb +545 -0
  91. data/test/test_ledger.rb +102 -0
  92. data/test/test_plot.rb +133 -0
  93. data/test/test_posting.rb +50 -0
  94. data/test/test_pricer.rb +139 -0
  95. data/test/test_pta_adapter.rb +575 -0
  96. data/test/test_utilities.rb +45 -0
  97. 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