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 +5 -5
- data/.gitlab-ci.yml +8 -0
- data/.rubocop.yml +13 -14
- data/.simplecov +2 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +52 -35
- data/README.md +58 -18
- data/Rakefile +5 -0
- data/bouch.gemspec +8 -8
- data/lib/bouch/calc.rb +25 -10
- data/lib/bouch/cli.rb +12 -4
- data/lib/bouch/example.rb +43 -0
- data/lib/bouch/version.rb +1 -1
- data/lib/bouch.rb +83 -32
- data/spec/bouch_calc_spec.rb +21 -22
- data/spec/bouch_cli_spec.rb +64 -3
- data/spec/bouch_pouch_yaml_spec.rb +7 -3
- data/spec/bouch_spec.rb +66 -4
- data/spec/spec_helper.rb +10 -0
- metadata +25 -22
- data/pouch.example.yml +0 -36
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1703d82906883118087626db522e171f841ca1b3bfa9792731da9e05ebbbf08c
|
|
4
|
+
data.tar.gz: 8a956f18ac52e62a4648172743eb9ecbcd0e846913abc9ac6dac1f615b0e7a9d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,48 +1,65 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
bouch (
|
|
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.
|
|
10
|
-
diff-lcs (1.
|
|
11
|
-
docile (1.
|
|
12
|
-
json (2.1
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
rspec
|
|
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.
|
|
28
|
-
rspec-mocks (3.7
|
|
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.
|
|
31
|
-
rspec-support (3.7
|
|
32
|
-
rubocop (
|
|
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
|
|
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 (
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
simplecov-html (0.
|
|
45
|
-
|
|
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 (
|
|
72
|
+
rubocop (~> 1.72)
|
|
56
73
|
simplecov
|
|
57
74
|
|
|
58
75
|
RUBY VERSION
|
|
59
|
-
|
|
76
|
+
ruby 4.0.1
|
|
60
77
|
|
|
61
78
|
BUNDLED WITH
|
|
62
|
-
|
|
79
|
+
4.0.6
|
data/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Bouch
|
|
2
|
-
[](https://gitlab.com/ssofos/bouch/commits/master) [](https://gitlab.com/ssofos/bouch/commits/master)
|
|
2
|
+
[](https://badge.fury.io/rb/bouch) [](https://gitlab.com/ssofos/bouch/commits/master) [](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
|
-
|
|
26
|
+
Generate an example budget pouch file and run it:
|
|
27
27
|
|
|
28
|
-
|
|
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
|
|
44
|
+
bouch my_budget.yml
|
|
32
45
|
```
|
|
33
46
|
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
62
|
-
* Think of hashes as simple groupings of keys
|
|
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
|
-
* **
|
|
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
|
|
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
|
-
* **
|
|
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
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(
|
|
21
|
-
s.bindir =
|
|
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', '
|
|
29
|
+
s.add_development_dependency 'rubocop', '~> 1.72'
|
|
30
30
|
s.add_development_dependency 'simplecov'
|
|
31
31
|
|
|
32
|
-
s.required_ruby_version = '>= 2
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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[
|
|
67
|
+
@quarters[idx] << calc_repeating(value['cost'])
|
|
53
68
|
end
|
|
54
69
|
else
|
|
55
|
-
@quarters[
|
|
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[
|
|
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.
|
|
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}
|
|
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
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 =
|
|
14
|
-
@debts =
|
|
15
|
-
@
|
|
16
|
-
@
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
data/spec/bouch_calc_spec.rb
CHANGED
|
@@ -6,8 +6,12 @@ require 'bouch/calc'
|
|
|
6
6
|
|
|
7
7
|
describe BouchCalculate do
|
|
8
8
|
before :all do
|
|
9
|
-
@
|
|
10
|
-
@bouch = Bouch.new(@
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/spec/bouch_cli_spec.rb
CHANGED
|
@@ -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
|
-
@
|
|
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 =
|
|
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 '
|
|
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
|
-
@
|
|
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(
|
|
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
|
-
@
|
|
8
|
-
@bouch = Bouch.new(@
|
|
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(
|
|
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
|
|
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:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shane R. Sofos
|
|
8
|
-
|
|
9
|
-
bindir:
|
|
10
|
-
- bin
|
|
8
|
+
bindir: bin
|
|
11
9
|
cert_chain: []
|
|
12
|
-
date:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
...
|