bouch 1.2.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
2
  SHA256:
3
- metadata.gz: fb58a99429b5afdc33cb7f65053580cf742aca9da08f2461861429ceaa75325f
4
- data.tar.gz: e15f9cab5a04bebd6ce4cd6f1aca42943ee82f18a8359911632709572559aabd
3
+ metadata.gz: 1703d82906883118087626db522e171f841ca1b3bfa9792731da9e05ebbbf08c
4
+ data.tar.gz: 8a956f18ac52e62a4648172743eb9ecbcd0e846913abc9ac6dac1f615b0e7a9d
5
5
  SHA512:
6
- metadata.gz: 0a396b50771862a7ae3187f8da5ee6bd9e903b0c2e58a02b383541412bcc131c86f1ed0b0b79633e8cc92d711aa460bc7c1e4ac1f399e7050dd26b0505e297ec
7
- data.tar.gz: b17ba90c8d8e9b4b090282c85accc8a6bcdaa3e9b0983c4b4122e9c72e01bdb2c673db304a2caeeb070a4dfebc829ea5a9bba348982703217a135ee68b6b13e5
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.2.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.2)
12
- json (2.3.0)
13
- parallel (1.19.1)
14
- parser (2.7.0.1)
15
- ast (~> 2.4.0)
16
- powerpack (0.1.2)
17
- rainbow (3.0.0)
18
- rake (13.0.1)
19
- rspec (3.9.0)
20
- rspec-core (~> 3.9.0)
21
- rspec-expectations (~> 3.9.0)
22
- rspec-mocks (~> 3.9.0)
23
- rspec-core (3.9.1)
24
- rspec-support (~> 3.9.1)
25
- rspec-expectations (3.9.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.9.0)
28
- rspec-mocks (3.9.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.9.0)
31
- rspec-support (3.9.2)
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.10.1)
40
- simplecov (0.17.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.6.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.17.3
79
+ 4.0.6
data/README.md CHANGED
@@ -23,35 +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:
29
36
 
30
37
  ```
31
- bouch pouch.example.yml
38
+ bouch example > my_budget.yml
39
+ ```
40
+
41
+ Edit `my_budget.yml` with your own data, then run:
42
+
32
43
  ```
44
+ bouch my_budget.yml
45
+ ```
46
+
47
+ ### CLI Flags
48
+
49
+ ```
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
33
57
 
34
- You should see the example budget summary output:
58
+ You should see the example budget summary output (with colorized sections):
35
59
 
36
60
  ```
37
- ----------------------
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
- ----------------------
74
+ ──────────────────────────────────────────
75
+ ASSETS
76
+ ──────────────────────────────────────────
47
77
  Assets Total: 500.00
48
78
  Equity Total: 500.00
49
79
  Net Worth: 1000.00
50
- ----------------------
80
+ ──────────────────────────────────────────
81
+ DEBTS
82
+ ──────────────────────────────────────────
51
83
  Debt Total: 420.00
52
84
  Debt Ratio: 0.4200
53
85
  Debt Ratio Percent: 42.00%
54
- ----------------------
86
+ ──────────────────────────────────────────
55
87
  ```
56
88
 
57
89
  ### Pouch Schema
@@ -65,9 +97,9 @@ Budget pouch files are written in Ruby friendly YAML and currently use the follo
65
97
  * Primary hash keys:
66
98
  * **Budget**
67
99
  * **Salary**
68
- * **Assets**
69
- * **Equity**
70
- * **Debts**
100
+ * **Assets** (optional)
101
+ * **Equity** (optional)
102
+ * **Debts** (optional)
71
103
  * **Budget** nested keys and values:
72
104
  * **Q1**
73
105
  * foo
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
@@ -4,31 +4,35 @@
4
4
  module BouchCalculate
5
5
  # Calculate asset value aggregate amount
6
6
  def calc_assets(assets, type = 'assets')
7
+ return if assets.nil?
8
+
7
9
  assets.each_value do |value|
8
10
  case type
9
11
  when 'assets'
10
- @assets.push(value)
12
+ @assets << value
11
13
  when 'equity'
12
- @equity.push(value)
14
+ @equity << value
13
15
  end
14
16
  end
15
17
  end
16
18
 
17
19
  # Calculate the percentage of budget of a salary/income
18
20
  def calc_budget_percentage(total, salary)
19
- ((total.to_f / salary.to_f) * 100).round(2)
21
+ ((total.to_f / salary) * 100).round(2)
20
22
  end
21
23
 
22
24
  # Calculate debt/liability aggregate amount
23
25
  def calc_debts(debts)
26
+ return if debts.nil?
27
+
24
28
  debts.each_value do |value|
25
- @debts.push(value)
29
+ @debts << value
26
30
  end
27
31
  end
28
32
 
29
33
  # Calculate a debt ratio: total debts divided by total assets
30
34
  def calc_debt_ratio(debts, assets)
31
- (debts.to_f / assets.to_f).round(4)
35
+ (debts.to_f / assets).round(4)
32
36
  end
33
37
 
34
38
  # Calculate a debt ratio percentage
@@ -36,7 +40,7 @@ module BouchCalculate
36
40
  (debt_ratio.to_f * 100).round(2)
37
41
  end
38
42
 
39
- # Calcuate a Net Worth sum
43
+ # Calculate a Net Worth sum
40
44
  def calc_net_worth(assets = [], equity = [])
41
45
  @net_worth = (assets + equity).sum.to_f.round(2)
42
46
  end
@@ -48,21 +52,22 @@ module BouchCalculate
48
52
 
49
53
  # Calculate an annual income based on a weekly frequency salary
50
54
  def calc_salary(amount, freq)
51
- (amount.to_f * (52 / freq)).round(2)
55
+ (amount.to_f * (52.0 / freq)).round(2)
52
56
  end
53
57
 
54
58
  # Calculate each financial quarters budget items, including repeating ones
55
59
  def calc_quarters_raw(budget)
56
60
  budget.each_value do |items|
57
- @quarters[@quarters.length.to_s] = Array.new
61
+ idx = @quarters.length
62
+ @quarters[idx] = []
58
63
  items.each_value do |value|
59
64
  case value
60
65
  when Hash
61
66
  if value.key?('repeat')
62
- @quarters[(@quarters.length - 1).to_s].push(calc_repeating(value['cost']))
67
+ @quarters[idx] << calc_repeating(value['cost'])
63
68
  end
64
69
  else
65
- @quarters[(@quarters.length - 1).to_s].push(value)
70
+ @quarters[idx] << value
66
71
  end
67
72
  end
68
73
  end
@@ -70,6 +75,6 @@ module BouchCalculate
70
75
 
71
76
  # Calculate all financial quarter budget into an aggregate annual budget
72
77
  def calc_quarters_raw_total
73
- @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
74
79
  end
75
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.2.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/bouch.rb CHANGED
@@ -1,35 +1,41 @@
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
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
- @equity = Array.new
17
+ @assets = []
18
+ @debts = []
19
+ @equity = []
16
20
  @net_worth = nil
17
- @pouch = YAML.safe_load(IO.read(file))
18
- @quarters = Hash.new
21
+ @pouch = YAML.safe_load(File.read(file))
22
+ @quarters = {}
19
23
  end
20
24
 
21
25
  # Summarize and show aggregate asset totals
22
26
  def show_assets_total
23
27
  calc_assets(@pouch['Assets'], 'assets') if @assets.empty?
24
- puts '----------------------'
25
- puts format('%-30s %.2f', 'Assets Total:', @assets.sum)
28
+ label = Paint[format('%-30s', 'Assets Total:'), :green]
29
+ value = Paint[format('%.2f', @assets.sum), :green, :bright]
30
+ puts "#{label} #{value}"
26
31
  end
27
32
 
28
33
  # Summarize and show aggregate liabilities totals
29
34
  def show_debts_total
30
35
  calc_debts(@pouch['Debts']) if @debts.empty?
31
- puts '----------------------'
32
- 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}"
33
39
  end
34
40
 
35
41
  # Summarize and show debt ratio
@@ -38,70 +44,97 @@ class Bouch
38
44
  calc_assets(@pouch['Equity'], 'equity') if @equity.empty?
39
45
  calc_net_worth(@assets, @equity) if @net_worth.nil?
40
46
  calc_debts(@pouch['Debts']) if @debts.empty?
41
- puts format('%-30s %.4f', 'Debt Ratio:', calc_debt_ratio(@debts.sum, @net_worth))
42
- puts format('%-30s %.2f%s', 'Debt Ratio Percent:',
43
- calc_debt_ratio_percent(calc_debt_ratio(@debts.sum, @net_worth)),
44
- '%')
45
- 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}"
46
54
  end
47
55
 
48
56
  # Summarize and show aggregate equity totals
49
57
  def show_equity_total
50
58
  calc_assets(@pouch['Equity'], 'equity') if @equity.empty?
51
- puts format('%-30s %.2f', 'Equity Total:', @equity.sum)
59
+ label = Paint[format('%-30s', 'Equity Total:'), :green]
60
+ value = Paint[format('%.2f', @equity.sum), :green, :bright]
61
+ puts "#{label} #{value}"
52
62
  end
53
63
 
54
64
  # Summarize and show Net Worth
55
65
  def show_net_worth
56
66
  calc_net_worth(@assets, @equity) if @net_worth.nil?
57
- puts format('%-30s %.2f', 'Net Worth:', @net_worth)
67
+ label = Paint[format('%-30s', 'Net Worth:'), :green]
68
+ value = Paint[format('%.2f', @net_worth), :green, :bright]
69
+ puts "#{label} #{value}"
58
70
  end
59
71
 
60
72
  # Summarize and show all financial quarter budgets
61
73
  def show_quarters
62
74
  calc_quarters_raw(@pouch['Budget']) if @quarters.empty?
63
- puts '----------------------'
75
+ puts SEPARATOR
76
+ puts Paint['BUDGET', :magenta, :bold]
77
+ puts SEPARATOR
64
78
  4.times do |n|
65
- 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}"
66
82
  end
67
- puts '----------------------'
68
83
  end
69
84
 
70
85
  # Summarize and show the aggregate annual budget totals
71
86
  def show_annual_total
72
87
  calc_quarters_raw(@pouch['Budget']) if @quarters.empty?
73
- 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}"
74
91
  end
75
92
 
76
93
  # Summarize and show the aggregate annual budget as a percentage of income
77
94
  def show_budget_percentage
78
95
  calc_quarters_raw(@pouch['Budget']) if @quarters.empty?
79
- puts format('%-30s %.2f%s',
80
- 'Budget Income Percent:',
81
- calc_budget_percentage(
82
- calc_quarters_raw_total,
83
- calc_salary(@pouch['Salary']['quantity'], @pouch['Salary']['frequency'])
84
- ),
85
- '%')
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}"
86
104
  end
87
105
 
88
106
  # Summarize and show annual income
89
107
  def show_annual_income
90
- puts format('%-30s %.2f',
91
- 'Budget Annual Income:',
92
- 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}"
93
112
  end
94
113
 
95
114
  # Summarize and show all aggregate components of the quarterly, annual budgets, assets
96
115
  def show_budget
97
116
  show_quarters
98
117
  show_annual_total
118
+ puts SEPARATOR
119
+ puts Paint['INCOME', :magenta, :bold]
120
+ puts SEPARATOR
99
121
  show_annual_income
100
122
  show_budget_percentage
101
- show_assets_total
102
- show_equity_total
103
- show_net_worth
104
- show_debts_total
105
- 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
106
139
  end
107
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,14 +55,10 @@ 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
@@ -107,24 +103,16 @@ describe BouchCalculate do
107
103
  expect(@bouch.calc_quarters_raw(@bouch.pouch['Budget'])).to be_a_kind_of(Hash)
108
104
  end
109
105
  it 'creates an array of financial quarter one budget items greater than zero' do
110
- # Uncomment line below for Debug
111
- # puts @bouch.quarters['0'].inspect
112
- expect(@bouch.quarters['0'].length).to be > 0
106
+ expect(@bouch.quarters[0].length).to be > 0
113
107
  end
114
108
  it 'creates an array of financial quarter two budget items greater than zero' do
115
- # Uncomment line below for Debug
116
- # puts @bouch.quarters['1'].inspect
117
- expect(@bouch.quarters['1'].length).to be > 0
109
+ expect(@bouch.quarters[1].length).to be > 0
118
110
  end
119
111
  it 'creates an array of financial quarter three budget items greater than zero' do
120
- # Uncomment line below for Debug
121
- # puts @bouch.quarters['2'].inspect
122
- expect(@bouch.quarters['2'].length).to be > 0
112
+ expect(@bouch.quarters[2].length).to be > 0
123
113
  end
124
114
  it 'creates an array of financial quarter four budget items greater than zero' do
125
- # Uncomment line below for Debug
126
- # puts @bouch.quarters['3'].inspect
127
- expect(@bouch.quarters['3'].length).to be > 0
115
+ expect(@bouch.quarters[3].length).to be > 0
128
116
  end
129
117
  end
130
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,11 +1,11 @@
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
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
@@ -38,7 +43,7 @@ describe Bouch do
38
43
 
39
44
  describe '#pouch' do
40
45
  it 'loads the budget pouch YAML file' do
41
- expect(@bouch.pouch).to eq(YAML.safe_load(IO.read(@yaml)))
46
+ expect(@bouch.pouch).to eq(YAML.safe_load(Bouch::EXAMPLE_POUCH))
42
47
  end
43
48
  end
44
49
 
@@ -49,7 +54,7 @@ describe Bouch do
49
54
  end
50
55
 
51
56
  describe '.show_assets_total' do
52
- it 'outputs financial budget assests sum' do
57
+ it 'outputs financial budget assets sum' do
53
58
  expect { @bouch.show_assets_total }.to output.to_stdout
54
59
  end
55
60
  end
@@ -107,4 +112,37 @@ describe Bouch do
107
112
  expect { @bouch.show_budget }.to output.to_stdout
108
113
  end
109
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
110
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.2.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,22 +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
- rubygems_version: 3.0.3
135
- signing_key:
145
+ rubygems_version: 4.0.3
136
146
  specification_version: 4
137
147
  summary: A simple tool to calculate and project your annual personal budget based
138
148
  on fiscal quarters expenditures, income, assets, and debts.
139
- test_files:
140
- - spec/bouch_calc_spec.rb
141
- - spec/bouch_cli_spec.rb
142
- - spec/bouch_pouch_yaml_spec.rb
143
- - spec/bouch_spec.rb
144
- - spec/bouch_version_spec.rb
145
- - spec/spec_helper.rb
149
+ test_files: []
data/pouch.example.yml DELETED
@@ -1,37 +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
- Equity:
33
- bar: 500
34
- Debts:
35
- baz: 120
36
- qax: 300
37
- ...