rvgp 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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