bouch 1.1.0 → 2.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9d3a4f13ab472a43a9e8b40cbfe763c10966bdea
4
- data.tar.gz: 16fe655a734aa70473303c03244b9656c175be9e
2
+ SHA256:
3
+ metadata.gz: 1703d82906883118087626db522e171f841ca1b3bfa9792731da9e05ebbbf08c
4
+ data.tar.gz: 8a956f18ac52e62a4648172743eb9ecbcd0e846913abc9ac6dac1f615b0e7a9d
5
5
  SHA512:
6
- metadata.gz: 57f18fa8111bdba78c8a91db428c7d0f335c8f84862ceb2ff4efe07ac2d8985a0091eb195d1bb6c5d259d316df34e6100c1c8fe103fd0c5979df85b71def8c43
7
- data.tar.gz: e3a2f08b1fe37e449bd065eb6f6f70de9e8b446dbb8fd95410d5e9d763597cfc61c522d5206fbbc98a0c753c203a682deb9c3b1485420b431bbfd00176d97731
6
+ metadata.gz: f8eee437173dc3411bd5378553995c633a266d4ebdbe1539cd9eb26dc10ee148c13c85190c90516ebd8d9304810804f93923c13535db87224c5e81e6a1ee6adc
7
+ data.tar.gz: be2a6e3cd8771180dcd430bd931326af2c8a261f9333c340be766cfc2ab85bce7475a18e97ceb596a57df2a59fd4471123c11314a9855009bf6fd4cb3d645b56
data/.gitlab-ci.yml CHANGED
@@ -1,4 +1,6 @@
1
1
  ---
2
+ image: ruby:4.0
3
+
2
4
  rspec:
3
5
  script:
4
6
  - bundle install
@@ -8,4 +10,10 @@ rubocop:
8
10
  script:
9
11
  - bundle install
10
12
  - bundle exec rubocop
13
+
14
+ integration:
15
+ script:
16
+ - bundle install
17
+ - bundle exec bouch example > /tmp/my_budget.yml
18
+ - bundle exec bouch /tmp/my_budget.yml
11
19
  ...
data/.rubocop.yml CHANGED
@@ -3,16 +3,24 @@ AllCops:
3
3
  - 'vendor/**/*'
4
4
  - 'spec/fixtures/**/*'
5
5
  - 'tmp/**/*'
6
- TargetRubyVersion: 2.4
6
+ NewCops: disable
7
+ TargetRubyVersion: 4.0
8
+ SuggestExtensions: false
9
+
10
+ #################### Gemspec #########################
11
+
12
+ Gemspec/RequiredRubyVersion:
13
+ Enabled: false
7
14
 
8
15
  #################### Layout #############################
9
16
  Layout/IndentationWidth:
10
17
  Width: 2
11
18
 
12
- #################### Lint ###############################
13
-
14
- Lint/DeprecatedClassMethods:
15
- Enabled: false
19
+ Layout/LineLength:
20
+ Max: 140
21
+ AllowURI: true
22
+ URISchemes:
23
+ - https
16
24
 
17
25
  #################### Metrics ############################
18
26
 
@@ -31,12 +39,6 @@ Metrics/ClassLength:
31
39
  Metrics/CyclomaticComplexity:
32
40
  Enabled: false
33
41
 
34
- Metrics/LineLength:
35
- Max: 120
36
- AllowURI: true
37
- URISchemes:
38
- - https
39
-
40
42
  Metrics/MethodLength:
41
43
  Enabled: false
42
44
 
@@ -53,9 +55,6 @@ Naming/FileName:
53
55
 
54
56
  #################### Style ###########################
55
57
 
56
- Style/BracesAroundHashParameters:
57
- Enabled: false
58
-
59
58
  Style/CommentAnnotation:
60
59
  Enabled: false
61
60
 
data/.simplecov CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  SimpleCov.start do
2
4
  add_group 'Libraries', 'lib'
3
5
  add_group 'RSpec Unit Tests', 'spec'
data/Gemfile CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- ruby '>=2.4.3'
5
+ ruby '>=3.2.10'
6
6
 
7
7
  gemspec
data/Gemfile.lock CHANGED
@@ -1,48 +1,65 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bouch (1.1.0)
4
+ bouch (2.0.0)
5
+ paint (~> 2.3)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
- ast (2.4.0)
10
- diff-lcs (1.3)
11
- docile (1.3.1)
12
- json (2.1.0)
13
- parallel (1.12.1)
14
- parser (2.5.1.0)
15
- ast (~> 2.4.0)
16
- powerpack (0.1.1)
17
- rainbow (3.0.0)
18
- rake (12.3.1)
19
- rspec (3.7.0)
20
- rspec-core (~> 3.7.0)
21
- rspec-expectations (~> 3.7.0)
22
- rspec-mocks (~> 3.7.0)
23
- rspec-core (3.7.1)
24
- rspec-support (~> 3.7.0)
25
- rspec-expectations (3.7.0)
10
+ ast (2.4.3)
11
+ diff-lcs (1.6.2)
12
+ docile (1.4.1)
13
+ json (2.18.1)
14
+ language_server-protocol (3.17.0.5)
15
+ lint_roller (1.1.0)
16
+ paint (2.3.0)
17
+ parallel (1.27.0)
18
+ parser (3.3.10.2)
19
+ ast (~> 2.4.1)
20
+ racc
21
+ prism (1.9.0)
22
+ racc (1.8.1)
23
+ rainbow (3.1.1)
24
+ rake (13.3.1)
25
+ regexp_parser (2.11.3)
26
+ rspec (3.13.2)
27
+ rspec-core (~> 3.13.0)
28
+ rspec-expectations (~> 3.13.0)
29
+ rspec-mocks (~> 3.13.0)
30
+ rspec-core (3.13.6)
31
+ rspec-support (~> 3.13.0)
32
+ rspec-expectations (3.13.5)
26
33
  diff-lcs (>= 1.2.0, < 2.0)
27
- rspec-support (~> 3.7.0)
28
- rspec-mocks (3.7.0)
34
+ rspec-support (~> 3.13.0)
35
+ rspec-mocks (3.13.7)
29
36
  diff-lcs (>= 1.2.0, < 2.0)
30
- rspec-support (~> 3.7.0)
31
- rspec-support (3.7.1)
32
- rubocop (0.56.0)
37
+ rspec-support (~> 3.13.0)
38
+ rspec-support (3.13.7)
39
+ rubocop (1.84.2)
40
+ json (~> 2.3)
41
+ language_server-protocol (~> 3.17.0.2)
42
+ lint_roller (~> 1.1.0)
33
43
  parallel (~> 1.10)
34
- parser (>= 2.5)
35
- powerpack (~> 0.1)
44
+ parser (>= 3.3.0.2)
36
45
  rainbow (>= 2.2.2, < 4.0)
46
+ regexp_parser (>= 2.9.3, < 3.0)
47
+ rubocop-ast (>= 1.49.0, < 2.0)
37
48
  ruby-progressbar (~> 1.7)
38
- unicode-display_width (~> 1.0, >= 1.0.1)
39
- ruby-progressbar (1.9.0)
40
- simplecov (0.16.1)
49
+ unicode-display_width (>= 2.4.0, < 4.0)
50
+ rubocop-ast (1.49.0)
51
+ parser (>= 3.3.7.2)
52
+ prism (~> 1.7)
53
+ ruby-progressbar (1.13.0)
54
+ simplecov (0.22.0)
41
55
  docile (~> 1.1)
42
- json (>= 1.8, < 3)
43
- simplecov-html (~> 0.10.0)
44
- simplecov-html (0.10.2)
45
- unicode-display_width (1.4.0)
56
+ simplecov-html (~> 0.11)
57
+ simplecov_json_formatter (~> 0.1)
58
+ simplecov-html (0.13.2)
59
+ simplecov_json_formatter (0.1.4)
60
+ unicode-display_width (3.2.0)
61
+ unicode-emoji (~> 4.1)
62
+ unicode-emoji (4.2.0)
46
63
 
47
64
  PLATFORMS
48
65
  ruby
@@ -52,11 +69,11 @@ DEPENDENCIES
52
69
  bundler
53
70
  rake
54
71
  rspec
55
- rubocop (<= 0.56.0)
72
+ rubocop (~> 1.72)
56
73
  simplecov
57
74
 
58
75
  RUBY VERSION
59
- ruby 2.5.0p0
76
+ ruby 4.0.1
60
77
 
61
78
  BUNDLED WITH
62
- 1.16.1
79
+ 4.0.6
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Bouch
2
- [![pipeline status](https://gitlab.com/ssofos/bouch/badges/master/pipeline.svg)](https://gitlab.com/ssofos/bouch/commits/master) [![coverage report](https://gitlab.com/ssofos/bouch/badges/master/coverage.svg)](https://gitlab.com/ssofos/bouch/commits/master)
2
+ [![Gem Version](https://badge.fury.io/rb/bouch.svg)](https://badge.fury.io/rb/bouch) [![pipeline status](https://gitlab.com/ssofos/bouch/badges/master/pipeline.svg)](https://gitlab.com/ssofos/bouch/commits/master) [![coverage report](https://gitlab.com/ssofos/bouch/badges/master/coverage.svg)](https://gitlab.com/ssofos/bouch/commits/master)
3
3
 
4
4
  `Bouch` is the budget pouch. A simple tool to calculate and project your annual personal budget based on fiscal quarter expenditures, income, assets, and debts.
5
5
 
@@ -23,33 +23,67 @@ gem 'bouch'
23
23
 
24
24
  Bouch takes a simple YAML file as its primary data input. This will be referred to as a budget pouch file.
25
25
 
26
- There is an example budget pouch file called **pouch.example.yml**.
26
+ Generate an example budget pouch file and run it:
27
27
 
28
- Use this budget pouch file and execute the following command on your favorite terminal emulator's CLI:
28
+ ```
29
+ bouch example > my_budget.yml
30
+ bouch my_budget.yml
31
+ ```
32
+
33
+ ### Generate an Example Pouch File
34
+
35
+ You can generate an example budget pouch YAML file to get started:
36
+
37
+ ```
38
+ bouch example > my_budget.yml
39
+ ```
40
+
41
+ Edit `my_budget.yml` with your own data, then run:
29
42
 
30
43
  ```
31
- bouch pouch.example.yml
44
+ bouch my_budget.yml
32
45
  ```
33
46
 
34
- You should see the example budget summary output:
47
+ ### CLI Flags
35
48
 
36
49
  ```
37
- ----------------------
50
+ bouch [YAML_FILE] # Calculate and display budget
51
+ bouch example # Print an example budget pouch YAML
52
+ bouch --version / -v # Print version
53
+ bouch --help / -h # Print usage help
54
+ ```
55
+
56
+ ### Example Output
57
+
58
+ You should see the example budget summary output (with colorized sections):
59
+
60
+ ```
61
+ ──────────────────────────────────────────
62
+ BUDGET
63
+ ──────────────────────────────────────────
38
64
  Quarter 1: 3025.00
39
65
  Quarter 2: 3025.00
40
66
  Quarter 3: 3025.00
41
67
  Quarter 4: 3025.00
42
- ----------------------
43
68
  Budget Annual Total: 12100.00
69
+ ──────────────────────────────────────────
70
+ INCOME
71
+ ──────────────────────────────────────────
44
72
  Budget Annual Income: 28808.00
45
73
  Budget Income Percent: 42.00%
46
- ----------------------
47
- Assets Total: 1000.00
48
- ----------------------
74
+ ──────────────────────────────────────────
75
+ ASSETS
76
+ ──────────────────────────────────────────
77
+ Assets Total: 500.00
78
+ Equity Total: 500.00
79
+ Net Worth: 1000.00
80
+ ──────────────────────────────────────────
81
+ DEBTS
82
+ ──────────────────────────────────────────
49
83
  Debt Total: 420.00
50
84
  Debt Ratio: 0.4200
51
85
  Debt Ratio Percent: 42.00%
52
- ----------------------
86
+ ──────────────────────────────────────────
53
87
  ```
54
88
 
55
89
  ### Pouch Schema
@@ -58,22 +92,23 @@ To use bouch to calculate your own budget you must create a customized budget po
58
92
 
59
93
  Budget pouch files are written in Ruby friendly YAML and currently use the following schema:
60
94
 
61
- * Deeply nested mappings, also know as hashes
62
- * Think of hashes as simple groupings of keys and values
95
+ * Nested mappings, also know as hashes in Ruby
96
+ * Think of hashes as simple groupings of keys value pairs
63
97
  * Primary hash keys:
64
98
  * **Budget**
65
99
  * **Salary**
66
- * **Assets**
67
- * **Debts**
100
+ * **Assets** (optional)
101
+ * **Equity** (optional)
102
+ * **Debts** (optional)
68
103
  * **Budget** nested keys and values:
69
104
  * **Q1**
70
105
  * foo
71
106
  * Value: Integer or Float
72
- * Equals a budget item's quarterly cost
107
+ * Equals a single budget item's quarterly cost
73
108
  * bar
74
109
  * **cost**
75
110
  * Value: Integer or Float
76
- * Equals the monthly cost of any repeated payment
111
+ * Equals the repeated budget item's monthly cost
77
112
  * **repeat**
78
113
  * Value: true
79
114
  * Enables quarterly auto-calculation of the repeat payment
@@ -94,8 +129,12 @@ Budget pouch files are written in Ruby friendly YAML and currently use the follo
94
129
  * foo
95
130
  * Value: Integer or Float
96
131
  * Equals an asset's total valued amount
97
- * **Debts** nested keys and values:
132
+ * **Equity** nested keys and values:
98
133
  * bar
134
+ * Value: Integer or Float
135
+ * Equals the total value of equity in a specific asset
136
+ * **Debts** nested keys and values:
137
+ * fizz
99
138
  * Value: Integer or Float
100
139
  * Equals a debt's total valued amount
101
140
  * All non-primary nested keys are case insensitive
@@ -134,6 +173,7 @@ Salary:
134
173
  frequency: 2
135
174
  Assets:
136
175
  foo: 500
176
+ Equity:
137
177
  bar: 500
138
178
  Debts:
139
179
  baz: 120
data/Rakefile CHANGED
@@ -8,3 +8,8 @@ RSpec::Core::RakeTask.new
8
8
  RuboCop::RakeTask.new
9
9
 
10
10
  task default: %i[spec rubocop]
11
+
12
+ desc 'Sign in to RubyGems.org'
13
+ task :signin do
14
+ sh 'gem signin'
15
+ end
data/bouch.gemspec CHANGED
@@ -5,7 +5,6 @@ require 'bouch/version'
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = 'bouch'
8
- s.date = '2018-06-09'
9
8
  s.version = Bouch::VERSION
10
9
  s.authors = ['Shane R. Sofos']
11
10
  s.email = ['ssofos@gmail.com']
@@ -13,21 +12,22 @@ Gem::Specification.new do |s|
13
12
  s.license = 'GPL-3.0'
14
13
  s.description = 'The Budget Pouch. Fast annual budget projections.'
15
14
  s.summary =
16
- 'A simple tool to calculate and project your ' +
17
- 'annual personal budget based on fiscal quarters ' +
15
+ 'A simple tool to calculate and project your ' \
16
+ 'annual personal budget based on fiscal quarters ' \
18
17
  'expenditures, income, assets, and debts.'
19
18
 
20
- s.files = %x(git ls-files).split($INPUT_RECORD_SEPARATOR)
21
- s.bindir = ['bin']
19
+ s.files = %x(git ls-files).split("\n")
20
+ s.bindir = 'bin'
22
21
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
23
- s.test_files = s.files.grep(%r{^(test|spec|features)/})
24
22
  s.require_paths = ['lib']
25
23
 
24
+ s.add_dependency 'paint', '~> 2.3'
25
+
26
26
  s.add_development_dependency 'bundler'
27
27
  s.add_development_dependency 'rake'
28
28
  s.add_development_dependency 'rspec'
29
- s.add_development_dependency 'rubocop', '<=0.56.0'
29
+ s.add_development_dependency 'rubocop', '~> 1.72'
30
30
  s.add_development_dependency 'simplecov'
31
31
 
32
- s.required_ruby_version = '>= 2.4.0'
32
+ s.required_ruby_version = '>= 3.2'
33
33
  end
data/lib/bouch/calc.rb CHANGED
@@ -3,27 +3,36 @@
3
3
  # Financial budget calculation and operations for Bouch
4
4
  module BouchCalculate
5
5
  # Calculate asset value aggregate amount
6
- def calc_assets(assets)
6
+ def calc_assets(assets, type = 'assets')
7
+ return if assets.nil?
8
+
7
9
  assets.each_value do |value|
8
- @assets.push(value)
10
+ case type
11
+ when 'assets'
12
+ @assets << value
13
+ when 'equity'
14
+ @equity << value
15
+ end
9
16
  end
10
17
  end
11
18
 
12
19
  # Calculate the percentage of budget of a salary/income
13
20
  def calc_budget_percentage(total, salary)
14
- ((total.to_f / salary.to_f) * 100).round(2)
21
+ ((total.to_f / salary) * 100).round(2)
15
22
  end
16
23
 
17
24
  # Calculate debt/liability aggregate amount
18
25
  def calc_debts(debts)
26
+ return if debts.nil?
27
+
19
28
  debts.each_value do |value|
20
- @debts.push(value)
29
+ @debts << value
21
30
  end
22
31
  end
23
32
 
24
33
  # Calculate a debt ratio: total debts divided by total assets
25
34
  def calc_debt_ratio(debts, assets)
26
- (debts.to_f / assets.to_f).round(4)
35
+ (debts.to_f / assets).round(4)
27
36
  end
28
37
 
29
38
  # Calculate a debt ratio percentage
@@ -31,6 +40,11 @@ module BouchCalculate
31
40
  (debt_ratio.to_f * 100).round(2)
32
41
  end
33
42
 
43
+ # Calculate a Net Worth sum
44
+ def calc_net_worth(assets = [], equity = [])
45
+ @net_worth = (assets + equity).sum.to_f.round(2)
46
+ end
47
+
34
48
  # Calculate a quarterly repeating budget item amount
35
49
  def calc_repeating(item)
36
50
  (item.to_f * 3).round(2)
@@ -38,21 +52,22 @@ module BouchCalculate
38
52
 
39
53
  # Calculate an annual income based on a weekly frequency salary
40
54
  def calc_salary(amount, freq)
41
- (amount.to_f * (52 / freq)).round(2)
55
+ (amount.to_f * (52.0 / freq)).round(2)
42
56
  end
43
57
 
44
58
  # Calculate each financial quarters budget items, including repeating ones
45
59
  def calc_quarters_raw(budget)
46
60
  budget.each_value do |items|
47
- @quarters[@quarters.length.to_s] = Array.new
61
+ idx = @quarters.length
62
+ @quarters[idx] = []
48
63
  items.each_value do |value|
49
64
  case value
50
65
  when Hash
51
66
  if value.key?('repeat')
52
- @quarters[(@quarters.length - 1).to_s].push(calc_repeating(value['cost']))
67
+ @quarters[idx] << calc_repeating(value['cost'])
53
68
  end
54
69
  else
55
- @quarters[(@quarters.length - 1).to_s].push(value)
70
+ @quarters[idx] << value
56
71
  end
57
72
  end
58
73
  end
@@ -60,6 +75,6 @@ module BouchCalculate
60
75
 
61
76
  # Calculate all financial quarter budget into an aggregate annual budget
62
77
  def calc_quarters_raw_total
63
- @quarters['0'].sum + @quarters['1'].sum + @quarters['2'].sum + @quarters['3'].sum
78
+ @quarters[0].sum + @quarters[1].sum + @quarters[2].sum + @quarters[3].sum
64
79
  end
65
80
  end
data/lib/bouch/cli.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'bouch'
4
4
  require 'bouch/version'
5
+ require 'bouch/example'
6
+ require 'paint'
5
7
 
6
8
  class Bouch
7
9
  # Parse the command line
@@ -13,20 +15,26 @@ class Bouch
13
15
  end
14
16
 
15
17
  def start
16
- if @yaml_file.eql?(nil)
17
- puts 'Please supply budget pouch YAML file path and rerun bouch.'
18
+ if @yaml_file.nil? || %w[--help -h].include?(@yaml_file)
18
19
  usage
20
+ elsif %w[--version -v].include?(@yaml_file)
21
+ puts Paint["bouch #{Bouch::VERSION}", :magenta, :bold]
22
+ elsif @yaml_file == 'example'
23
+ puts Bouch::EXAMPLE_POUCH
19
24
  elsif File.exist?(@yaml_file)
20
25
  budget = Bouch.new(@yaml_file)
21
26
  budget.show_budget
22
27
  else
23
- puts "Whoops. The budget pouch file specified: #{@yaml_file} ; does not exist!"
28
+ puts Paint["Whoops. The budget pouch file specified: #{@yaml_file} ; does not exist!", :red]
24
29
  usage
25
30
  end
26
31
  end
27
32
 
28
33
  def usage
29
- puts "<<bouch #{Bouch::VERSION}>>\nUsage: #{File.basename($PROGRAM_NAME)} [YAML_FILE]"
34
+ puts Paint["<<bouch #{Bouch::VERSION}>>", :magenta, :bold]
35
+ puts "Usage: #{Paint[File.basename($PROGRAM_NAME), :green]} #{Paint['[YAML_FILE]', :faint]}"
36
+ puts " #{Paint[File.basename($PROGRAM_NAME), :green]} #{Paint['example', :magenta]} # Print an example budget pouch YAML"
37
+ puts " #{Paint[File.basename($PROGRAM_NAME), :green]} #{Paint['--version', :magenta]} # Print version"
30
38
  end
31
39
  end
32
40
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Bouch
4
+ EXAMPLE_POUCH = <<~YAML
5
+ ---
6
+ Budget:
7
+ Q1:
8
+ Rent:
9
+ cost: 1000
10
+ repeat: true
11
+ foo: 20
12
+ bar: 5
13
+ Q2:
14
+ Rent:
15
+ cost: 1000
16
+ repeat: true
17
+ fizz: 20
18
+ buzz: 5
19
+ Q3:
20
+ Rent:
21
+ cost: 1000
22
+ repeat: true
23
+ bubble: 20
24
+ sort: 5
25
+ Q4:
26
+ Rent:
27
+ cost: 1000
28
+ repeat: true
29
+ baz: 20
30
+ qax: 5
31
+ Salary:
32
+ quantity: 1108.00
33
+ frequency: 2
34
+ Assets:
35
+ foo: 500
36
+ Equity:
37
+ bar: 500
38
+ Debts:
39
+ baz: 120
40
+ qax: 300
41
+ ...
42
+ YAML
43
+ end
data/lib/bouch/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Bouch
4
- VERSION = '1.1.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/bouch.rb CHANGED
@@ -1,89 +1,140 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
+ require 'paint'
4
5
  require 'bouch/calc'
5
6
 
6
7
  # Calculate and create simple financial budgets
7
8
  # by parsing a single YAML file as input
8
9
  class Bouch
9
- attr_accessor :assets, :debts, :pouch, :quarters
10
+ attr_accessor :assets, :debts, :equity, :net_worth, :pouch, :quarters
11
+
10
12
  include BouchCalculate
11
13
 
14
+ SEPARATOR = Paint["\u2500" * 42, :faint]
15
+
12
16
  def initialize(file)
13
- @assets = Array.new
14
- @debts = Array.new
15
- @pouch = YAML.safe_load(IO.read(file))
16
- @quarters = Hash.new
17
+ @assets = []
18
+ @debts = []
19
+ @equity = []
20
+ @net_worth = nil
21
+ @pouch = YAML.safe_load(File.read(file))
22
+ @quarters = {}
17
23
  end
18
24
 
19
25
  # Summarize and show aggregate asset totals
20
26
  def show_assets_total
21
- calc_assets(@pouch['Assets']) if @assets.empty?
22
- puts '----------------------'
23
- puts format('%-30s %.2f', 'Assets Total:', @assets.sum)
27
+ calc_assets(@pouch['Assets'], 'assets') if @assets.empty?
28
+ label = Paint[format('%-30s', 'Assets Total:'), :green]
29
+ value = Paint[format('%.2f', @assets.sum), :green, :bright]
30
+ puts "#{label} #{value}"
24
31
  end
25
32
 
26
33
  # Summarize and show aggregate liabilities totals
27
34
  def show_debts_total
28
35
  calc_debts(@pouch['Debts']) if @debts.empty?
29
- puts '----------------------'
30
- puts format('%-30s %.2f', 'Debt Total:', @debts.sum)
36
+ label = Paint[format('%-30s', 'Debt Total:'), :red]
37
+ value = Paint[format('%.2f', @debts.sum), :red, :bright]
38
+ puts "#{label} #{value}"
31
39
  end
32
40
 
33
41
  # Summarize and show debt ratio
34
42
  def show_debt_ratio
35
43
  calc_assets(@pouch['Assets']) if @assets.empty?
44
+ calc_assets(@pouch['Equity'], 'equity') if @equity.empty?
45
+ calc_net_worth(@assets, @equity) if @net_worth.nil?
36
46
  calc_debts(@pouch['Debts']) if @debts.empty?
37
- puts format('%-30s %.4f', 'Debt Ratio:', calc_debt_ratio(@debts.sum, @assets.sum))
38
- puts format('%-30s %.2f%s', 'Debt Ratio Percent:',
39
- calc_debt_ratio_percent(calc_debt_ratio(@debts.sum, @assets.sum)),
40
- '%')
41
- puts '----------------------'
47
+ ratio = calc_debt_ratio(@debts.sum, @net_worth)
48
+ label1 = Paint[format('%-30s', 'Debt Ratio:'), :red]
49
+ value1 = Paint[format('%.4f', ratio), :red, :bright]
50
+ puts "#{label1} #{value1}"
51
+ label2 = Paint[format('%-30s', 'Debt Ratio Percent:'), :red]
52
+ value2 = Paint[format('%.2f%s', calc_debt_ratio_percent(ratio), '%'), :red, :bright]
53
+ puts "#{label2} #{value2}"
54
+ end
55
+
56
+ # Summarize and show aggregate equity totals
57
+ def show_equity_total
58
+ calc_assets(@pouch['Equity'], 'equity') if @equity.empty?
59
+ label = Paint[format('%-30s', 'Equity Total:'), :green]
60
+ value = Paint[format('%.2f', @equity.sum), :green, :bright]
61
+ puts "#{label} #{value}"
62
+ end
63
+
64
+ # Summarize and show Net Worth
65
+ def show_net_worth
66
+ calc_net_worth(@assets, @equity) if @net_worth.nil?
67
+ label = Paint[format('%-30s', 'Net Worth:'), :green]
68
+ value = Paint[format('%.2f', @net_worth), :green, :bright]
69
+ puts "#{label} #{value}"
42
70
  end
43
71
 
44
72
  # Summarize and show all financial quarter budgets
45
73
  def show_quarters
46
74
  calc_quarters_raw(@pouch['Budget']) if @quarters.empty?
47
- puts '----------------------'
75
+ puts SEPARATOR
76
+ puts Paint['BUDGET', :magenta, :bold]
77
+ puts SEPARATOR
48
78
  4.times do |n|
49
- puts format('%-30s %.2f', 'Quarter ' + (n + 1).to_s + ':', @quarters[n.to_s].sum)
79
+ label = Paint[format('%-30s', "Quarter #{n + 1}:"), :magenta]
80
+ value = Paint[format('%.2f', @quarters[n].sum), :magenta, :bright]
81
+ puts "#{label} #{value}"
50
82
  end
51
- puts '----------------------'
52
83
  end
53
84
 
54
85
  # Summarize and show the aggregate annual budget totals
55
86
  def show_annual_total
56
87
  calc_quarters_raw(@pouch['Budget']) if @quarters.empty?
57
- puts format('%-30s %.2f', 'Budget Annual Total:', calc_quarters_raw_total.to_s)
88
+ label = Paint[format('%-30s', 'Budget Annual Total:'), :magenta]
89
+ value = Paint[format('%.2f', calc_quarters_raw_total), :magenta, :bright]
90
+ puts "#{label} #{value}"
58
91
  end
59
92
 
60
93
  # Summarize and show the aggregate annual budget as a percentage of income
61
94
  def show_budget_percentage
62
95
  calc_quarters_raw(@pouch['Budget']) if @quarters.empty?
63
- puts format('%-30s %.2f%s',
64
- 'Budget Income Percent:',
65
- calc_budget_percentage(
66
- calc_quarters_raw_total,
67
- calc_salary(@pouch['Salary']['quantity'], @pouch['Salary']['frequency'])
68
- ),
69
- '%')
96
+ label = Paint[format('%-30s', 'Budget Income Percent:'), :magenta]
97
+ value = Paint[format('%.2f%s',
98
+ calc_budget_percentage(
99
+ calc_quarters_raw_total,
100
+ calc_salary(@pouch['Salary']['quantity'], @pouch['Salary']['frequency'])
101
+ ),
102
+ '%'), :magenta, :bright]
103
+ puts "#{label} #{value}"
70
104
  end
71
105
 
72
106
  # Summarize and show annual income
73
107
  def show_annual_income
74
- puts format('%-30s %.2f',
75
- 'Budget Annual Income:',
76
- calc_salary(@pouch['Salary']['quantity'], @pouch['Salary']['frequency']))
108
+ label = Paint[format('%-30s', 'Budget Annual Income:'), :magenta]
109
+ value = Paint[format('%.2f',
110
+ calc_salary(@pouch['Salary']['quantity'], @pouch['Salary']['frequency'])), :magenta, :bright]
111
+ puts "#{label} #{value}"
77
112
  end
78
113
 
79
114
  # Summarize and show all aggregate components of the quarterly, annual budgets, assets
80
115
  def show_budget
81
116
  show_quarters
82
117
  show_annual_total
118
+ puts SEPARATOR
119
+ puts Paint['INCOME', :magenta, :bold]
120
+ puts SEPARATOR
83
121
  show_annual_income
84
122
  show_budget_percentage
85
- show_assets_total
86
- show_debts_total
87
- show_debt_ratio
123
+ if @pouch['Assets'] || @pouch['Equity']
124
+ puts SEPARATOR
125
+ puts Paint['ASSETS', :green, :bold]
126
+ puts SEPARATOR
127
+ show_assets_total if @pouch['Assets']
128
+ show_equity_total if @pouch['Equity']
129
+ show_net_worth
130
+ end
131
+ if @pouch['Debts']
132
+ puts SEPARATOR
133
+ puts Paint['DEBTS', :red, :bold]
134
+ puts SEPARATOR
135
+ show_debts_total
136
+ show_debt_ratio if @net_worth
137
+ end
138
+ puts SEPARATOR
88
139
  end
89
140
  end
@@ -6,8 +6,12 @@ require 'bouch/calc'
6
6
 
7
7
  describe BouchCalculate do
8
8
  before :all do
9
- @yaml = File.realdirpath('pouch.example.yml')
10
- @bouch = Bouch.new(@yaml)
9
+ @tempfile = create_example_pouch_tempfile
10
+ @bouch = Bouch.new(@tempfile.path)
11
+ end
12
+
13
+ after :all do
14
+ @tempfile.unlink
11
15
  end
12
16
 
13
17
  describe '.calc_assets' do
@@ -15,8 +19,6 @@ describe BouchCalculate do
15
19
  expect(@bouch.calc_assets(@bouch.pouch['Assets'])).to be_a_kind_of(Hash)
16
20
  end
17
21
  it 'creates array of assets greater than zero' do
18
- # Uncomment line below for Debug
19
- # puts @bouch.assets.inspect
20
22
  expect(@bouch.assets.length).to be > 0
21
23
  end
22
24
  end
@@ -34,8 +36,6 @@ describe BouchCalculate do
34
36
  expect(@bouch.calc_debts(@bouch.pouch['Debts'])).to be_a_kind_of(Hash)
35
37
  end
36
38
  it 'creates an array of debts greater than zero' do
37
- # Uncomment line below for Debug
38
- # puts @bouch.debts.inspect
39
39
  expect(@bouch.debts.length).to be > 0
40
40
  end
41
41
  end
@@ -55,19 +55,26 @@ describe BouchCalculate do
55
55
  context 'given a debt ratio' do
56
56
  it 'returns a Float number' do
57
57
  debt_ratio = @bouch.calc_debt_ratio(420.00, 1000.00)
58
- # Uncomment line below for Debug
59
- # puts debt_ratio
60
58
  expect(@bouch.calc_debt_ratio_percent(debt_ratio)).to be_a_kind_of(Float)
61
59
  end
62
60
  it 'returns a debt ratio percentage' do
63
61
  debt_ratio = @bouch.calc_debt_ratio(420.00, 1000.00)
64
- # Uncomment line below for Debug
65
- # puts debt_ratio
66
62
  expect(@bouch.calc_debt_ratio_percent(debt_ratio)).to eq(42.00)
67
63
  end
68
64
  end
69
65
  end
70
66
 
67
+ describe '.calc_net_worth' do
68
+ context 'given an two Arrays of assets and equity' do
69
+ it 'returns a Float number' do
70
+ expect(@bouch.calc_net_worth([500], [500])).to be_a_kind_of(Float)
71
+ end
72
+ it 'returns a net worth sum of assets and equity' do
73
+ expect(@bouch.calc_net_worth([500], [500])).to eq(1000.00)
74
+ end
75
+ end
76
+ end
77
+
71
78
  describe '.calc_repeating' do
72
79
  context 'given a numerical cost amount' do
73
80
  it 'returns a Float number' do
@@ -96,24 +103,16 @@ describe BouchCalculate do
96
103
  expect(@bouch.calc_quarters_raw(@bouch.pouch['Budget'])).to be_a_kind_of(Hash)
97
104
  end
98
105
  it 'creates an array of financial quarter one budget items greater than zero' do
99
- # Uncomment line below for Debug
100
- # puts @bouch.quarters['0'].inspect
101
- expect(@bouch.quarters['0'].length).to be > 0
106
+ expect(@bouch.quarters[0].length).to be > 0
102
107
  end
103
108
  it 'creates an array of financial quarter two budget items greater than zero' do
104
- # Uncomment line below for Debug
105
- # puts @bouch.quarters['1'].inspect
106
- expect(@bouch.quarters['1'].length).to be > 0
109
+ expect(@bouch.quarters[1].length).to be > 0
107
110
  end
108
111
  it 'creates an array of financial quarter three budget items greater than zero' do
109
- # Uncomment line below for Debug
110
- # puts @bouch.quarters['2'].inspect
111
- expect(@bouch.quarters['2'].length).to be > 0
112
+ expect(@bouch.quarters[2].length).to be > 0
112
113
  end
113
114
  it 'creates an array of financial quarter four budget items greater than zero' do
114
- # Uncomment line below for Debug
115
- # puts @bouch.quarters['3'].inspect
116
- expect(@bouch.quarters['3'].length).to be > 0
115
+ expect(@bouch.quarters[3].length).to be > 0
117
116
  end
118
117
  end
119
118
  end
@@ -1,10 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'stringio'
4
+ require 'yaml'
3
5
  require 'bouch/cli'
4
6
 
5
7
  describe 'Bouch::CLI' do
6
8
  before :all do
7
- @cli = Bouch::CLI.new('pouch.example.yml')
9
+ @tempfile = create_example_pouch_tempfile
10
+ @cli = Bouch::CLI.new(@tempfile.path)
11
+ end
12
+
13
+ after :all do
14
+ @tempfile.unlink
8
15
  end
9
16
 
10
17
  it 'is a Bouch::CLI object' do
@@ -25,15 +32,56 @@ describe 'Bouch::CLI' do
25
32
  end
26
33
  end
27
34
 
35
+ context 'when --help flag is supplied' do
36
+ it 'outputs usage help message' do
37
+ @cli.yaml_file = '--help'
38
+ expect { @cli.start }.to output(/Usage:/).to_stdout
39
+ end
40
+ end
41
+
42
+ context 'when -h flag is supplied' do
43
+ it 'outputs usage help message' do
44
+ @cli.yaml_file = '-h'
45
+ expect { @cli.start }.to output(/Usage:/).to_stdout
46
+ end
47
+ end
48
+
49
+ context 'when --version flag is supplied' do
50
+ it 'outputs the version' do
51
+ @cli.yaml_file = '--version'
52
+ expect { @cli.start }.to output(/bouch #{Bouch::VERSION}/).to_stdout
53
+ end
54
+ end
55
+
56
+ context 'when -v flag is supplied' do
57
+ it 'outputs the version' do
58
+ @cli.yaml_file = '-v'
59
+ expect { @cli.start }.to output(/bouch #{Bouch::VERSION}/).to_stdout
60
+ end
61
+ end
62
+
63
+ context 'when example subcommand is supplied' do
64
+ it 'outputs valid YAML with expected top-level keys' do
65
+ @cli.yaml_file = 'example'
66
+ output = capture_stdout { @cli.start }
67
+ parsed = YAML.safe_load(output)
68
+ expect(parsed).to have_key('Budget')
69
+ expect(parsed).to have_key('Salary')
70
+ expect(parsed).to have_key('Assets')
71
+ expect(parsed).to have_key('Equity')
72
+ expect(parsed).to have_key('Debts')
73
+ end
74
+ end
75
+
28
76
  context 'when an existent file is supplied as the first CLI argument' do
29
77
  it 'calculates a budget from a budget pouch file and outputs it' do
30
- @cli.yaml_file = 'pouch.example.yml'
78
+ @cli.yaml_file = @tempfile.path
31
79
  expect { @cli.start }.to output.to_stdout
32
80
  end
33
81
  end
34
82
 
35
83
  context 'when a non-existent file is supplied as the first CLI argument' do
36
- it 'ouputs a warning and usage help messsages' do
84
+ it 'outputs a warning and usage help messages' do
37
85
  @cli.yaml_file = 'foobar.yml'
38
86
  expect { @cli.start }.to output.to_stdout
39
87
  end
@@ -44,5 +92,18 @@ describe 'Bouch::CLI' do
44
92
  it 'outputs a bouch usage help message' do
45
93
  expect { @cli.usage }.to output.to_stdout
46
94
  end
95
+
96
+ it 'includes example subcommand in usage' do
97
+ expect { @cli.usage }.to output(/example/).to_stdout
98
+ end
47
99
  end
48
100
  end
101
+
102
+ def capture_stdout
103
+ original_stdout = $stdout
104
+ $stdout = StringIO.new
105
+ yield
106
+ $stdout.string
107
+ ensure
108
+ $stdout = original_stdout
109
+ end
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
+ require 'bouch/example'
4
5
 
5
6
  describe 'Pouch YAML Schema' do
6
7
  before :all do
7
- @yaml = File.realdirpath('pouch.example.yml')
8
- @pouch = YAML.safe_load(IO.read(@yaml))
8
+ @pouch = YAML.safe_load(Bouch::EXAMPLE_POUCH)
9
9
  end
10
10
 
11
11
  it 'has four major Hash keys' do
12
- expect(@pouch.keys.length).to eq(4)
12
+ expect(@pouch.keys.length).to eq(5)
13
13
  end
14
14
 
15
15
  it 'has a Hash key named Budget' do
@@ -24,6 +24,10 @@ describe 'Pouch YAML Schema' do
24
24
  expect(@pouch.key?('Assets')).to be_truthy
25
25
  end
26
26
 
27
+ it 'has a Hash key named Equity' do
28
+ expect(@pouch.key?('Equity')).to be_truthy
29
+ end
30
+
27
31
  it 'has a Hash key named Debts' do
28
32
  expect(@pouch.key?('Debts')).to be_truthy
29
33
  end
data/spec/bouch_spec.rb CHANGED
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tempfile'
3
4
  require 'bouch'
4
5
 
5
6
  describe Bouch do
6
7
  before :all do
7
- @yaml = File.realdirpath('pouch.example.yml')
8
- @bouch = Bouch.new(@yaml)
8
+ @tempfile = create_example_pouch_tempfile
9
+ @bouch = Bouch.new(@tempfile.path)
10
+ end
11
+
12
+ after :all do
13
+ @tempfile.unlink
9
14
  end
10
15
 
11
16
  it 'is a Bouch object' do
@@ -24,9 +29,21 @@ describe Bouch do
24
29
  end
25
30
  end
26
31
 
32
+ describe '#equity' do
33
+ it 'returns an empty Array of equity' do
34
+ expect(@bouch.equity).to be_empty
35
+ end
36
+ end
37
+
38
+ describe '#net_worth' do
39
+ it 'returns a Nil net_worth' do
40
+ expect(@bouch.net_worth).to be_nil
41
+ end
42
+ end
43
+
27
44
  describe '#pouch' do
28
45
  it 'loads the budget pouch YAML file' do
29
- expect(@bouch.pouch).to eq(YAML.safe_load(IO.read(@yaml)))
46
+ expect(@bouch.pouch).to eq(YAML.safe_load(Bouch::EXAMPLE_POUCH))
30
47
  end
31
48
  end
32
49
 
@@ -37,7 +54,7 @@ describe Bouch do
37
54
  end
38
55
 
39
56
  describe '.show_assets_total' do
40
- it 'outputs financial budget assests sum' do
57
+ it 'outputs financial budget assets sum' do
41
58
  expect { @bouch.show_assets_total }.to output.to_stdout
42
59
  end
43
60
  end
@@ -54,6 +71,18 @@ describe Bouch do
54
71
  end
55
72
  end
56
73
 
74
+ describe '.show_equity_total' do
75
+ it 'outputs financial budget equity sum' do
76
+ expect { @bouch.show_equity_total }.to output.to_stdout
77
+ end
78
+ end
79
+
80
+ describe '.show_net_worth' do
81
+ it 'outputs financial budget net worth' do
82
+ expect { @bouch.show_net_worth }.to output.to_stdout
83
+ end
84
+ end
85
+
57
86
  describe '.show_quarters' do
58
87
  it 'outputs financial budget quarters sums' do
59
88
  expect { @bouch.show_quarters }.to output.to_stdout
@@ -83,4 +112,37 @@ describe Bouch do
83
112
  expect { @bouch.show_budget }.to output.to_stdout
84
113
  end
85
114
  end
115
+
116
+ describe 'missing YAML sections' do
117
+ before :all do
118
+ minimal = <<~YAML
119
+ ---
120
+ Budget:
121
+ Q1:
122
+ foo: 100
123
+ Q2:
124
+ bar: 200
125
+ Q3:
126
+ baz: 300
127
+ Q4:
128
+ qax: 400
129
+ Salary:
130
+ quantity: 1000
131
+ frequency: 2
132
+ ...
133
+ YAML
134
+ @minimal_yaml = Tempfile.new(['pouch', '.yml'])
135
+ @minimal_yaml.write(minimal)
136
+ @minimal_yaml.close
137
+ @minimal_bouch = Bouch.new(@minimal_yaml.path)
138
+ end
139
+
140
+ after :all do
141
+ @minimal_yaml.unlink
142
+ end
143
+
144
+ it 'does not crash when Assets, Equity, and Debts are missing' do
145
+ expect { @minimal_bouch.show_budget }.to output.to_stdout
146
+ end
147
+ end
86
148
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'simplecov'
4
+ require 'tempfile'
5
+ require 'bouch/example'
4
6
 
5
7
  # This file was generated by the `rspec --init` command. Conventionally, all
6
8
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
@@ -79,3 +81,11 @@ RSpec.configure do |config|
79
81
  # as the one that triggered the failure.
80
82
  Kernel.srand config.seed
81
83
  end
84
+
85
+ # Write the embedded example pouch to a tempfile for use across specs
86
+ def create_example_pouch_tempfile
87
+ tempfile = Tempfile.new(['pouch', '.yml'])
88
+ tempfile.write(Bouch::EXAMPLE_POUCH)
89
+ tempfile.close
90
+ tempfile
91
+ end
metadata CHANGED
@@ -1,16 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bouch
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane R. Sofos
8
- autorequire:
9
- bindir:
10
- - bin
8
+ bindir: bin
11
9
  cert_chain: []
12
- date: 2018-06-09 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
13
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: paint
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.3'
14
26
  - !ruby/object:Gem::Dependency
15
27
  name: bundler
16
28
  requirement: !ruby/object:Gem::Requirement
@@ -57,16 +69,16 @@ dependencies:
57
69
  name: rubocop
58
70
  requirement: !ruby/object:Gem::Requirement
59
71
  requirements:
60
- - - "<="
72
+ - - "~>"
61
73
  - !ruby/object:Gem::Version
62
- version: 0.56.0
74
+ version: '1.72'
63
75
  type: :development
64
76
  prerelease: false
65
77
  version_requirements: !ruby/object:Gem::Requirement
66
78
  requirements:
67
- - - "<="
79
+ - - "~>"
68
80
  - !ruby/object:Gem::Version
69
- version: 0.56.0
81
+ version: '1.72'
70
82
  - !ruby/object:Gem::Dependency
71
83
  name: simplecov
72
84
  requirement: !ruby/object:Gem::Requirement
@@ -104,8 +116,8 @@ files:
104
116
  - lib/bouch.rb
105
117
  - lib/bouch/calc.rb
106
118
  - lib/bouch/cli.rb
119
+ - lib/bouch/example.rb
107
120
  - lib/bouch/version.rb
108
- - pouch.example.yml
109
121
  - spec/bouch_calc_spec.rb
110
122
  - spec/bouch_cli_spec.rb
111
123
  - spec/bouch_pouch_yaml_spec.rb
@@ -116,7 +128,6 @@ homepage: https://gitlab.com/ssofos/bouch
116
128
  licenses:
117
129
  - GPL-3.0
118
130
  metadata: {}
119
- post_install_message:
120
131
  rdoc_options: []
121
132
  require_paths:
122
133
  - lib
@@ -124,23 +135,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
124
135
  requirements:
125
136
  - - ">="
126
137
  - !ruby/object:Gem::Version
127
- version: 2.4.0
138
+ version: '3.2'
128
139
  required_rubygems_version: !ruby/object:Gem::Requirement
129
140
  requirements:
130
141
  - - ">="
131
142
  - !ruby/object:Gem::Version
132
143
  version: '0'
133
144
  requirements: []
134
- rubyforge_project:
135
- rubygems_version: 2.6.14
136
- signing_key:
145
+ rubygems_version: 4.0.3
137
146
  specification_version: 4
138
147
  summary: A simple tool to calculate and project your annual personal budget based
139
148
  on fiscal quarters expenditures, income, assets, and debts.
140
- test_files:
141
- - spec/bouch_calc_spec.rb
142
- - spec/bouch_cli_spec.rb
143
- - spec/bouch_pouch_yaml_spec.rb
144
- - spec/bouch_spec.rb
145
- - spec/bouch_version_spec.rb
146
- - spec/spec_helper.rb
149
+ test_files: []
data/pouch.example.yml DELETED
@@ -1,36 +0,0 @@
1
- ---
2
- Budget:
3
- Q1:
4
- Rent:
5
- cost: 1000
6
- repeat: true
7
- foo: 20
8
- bar: 5
9
- Q2:
10
- Rent:
11
- cost: 1000
12
- repeat: true
13
- fizz: 20
14
- buzz: 5
15
- Q3:
16
- Rent:
17
- cost: 1000
18
- repeat: true
19
- bubble: 20
20
- sort: 5
21
- Q4:
22
- Rent:
23
- cost: 1000
24
- repeat: true
25
- baz: 20
26
- qax: 5
27
- Salary:
28
- quantity: 1108.00
29
- frequency: 2
30
- Assets:
31
- foo: 500
32
- bar: 500
33
- Debts:
34
- baz: 120
35
- qax: 300
36
- ...