command_line_boss 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +34 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +34 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +340 -0
- data/Rakefile +90 -0
- data/examples/create_spreadsheet/.gitignore +21 -0
- data/examples/create_spreadsheet/.rspec +3 -0
- data/examples/create_spreadsheet/.rubocop.yml +34 -0
- data/examples/create_spreadsheet/Gemfile +16 -0
- data/examples/create_spreadsheet/README.md +314 -0
- data/examples/create_spreadsheet/Rakefile +50 -0
- data/examples/create_spreadsheet/create_spreadsheet.gemspec +39 -0
- data/examples/create_spreadsheet/exe/create-spreadsheet +28 -0
- data/examples/create_spreadsheet/lib/create_spreadsheet/command_line.rb +221 -0
- data/examples/create_spreadsheet/lib/create_spreadsheet.rb +8 -0
- data/examples/create_spreadsheet/spec/create_spreadsheet/command_line.feature +239 -0
- data/examples/create_spreadsheet/spec/create_spreadsheet/command_line_steps.rb +93 -0
- data/examples/create_spreadsheet/spec/spec_helper.rb +130 -0
- data/examples/readme_example/create-spreadsheet +64 -0
- data/lib/command_line_boss/help_option.rb +82 -0
- data/lib/command_line_boss/logger_options.rb +103 -0
- data/lib/command_line_boss/version.rb +6 -0
- data/lib/command_line_boss.rb +246 -0
- metadata +266 -0
@@ -0,0 +1,239 @@
|
|
1
|
+
Feature: CreateSpreadsheet::Cli#parse
|
2
|
+
|
3
|
+
Parse the create-spreadsheet command line
|
4
|
+
|
5
|
+
Example: Nothing is given on the command line
|
6
|
+
Given the command line ""
|
7
|
+
When the command line is parsed
|
8
|
+
Then the parser should have succeeded
|
9
|
+
And the title attribute should be nil
|
10
|
+
And the sheets attribute should be empty
|
11
|
+
And the permissions attribute should be empty
|
12
|
+
And the folder_id attribute should be nil
|
13
|
+
|
14
|
+
Example: A spreadsheet title is given
|
15
|
+
Given the command line "MySpreadsheet"
|
16
|
+
When the command line is parsed
|
17
|
+
Then the parser should have succeeded
|
18
|
+
And the title attribute should be "MySpreadsheet"
|
19
|
+
|
20
|
+
Example: A sheet is defined with a title
|
21
|
+
Given the command line "--sheet=Summary"
|
22
|
+
When the command line is parsed
|
23
|
+
Then the parser should have succeeded
|
24
|
+
And the sheets attribute should contain the following Sheets:
|
25
|
+
"""
|
26
|
+
[
|
27
|
+
{
|
28
|
+
title: "Summary",
|
29
|
+
data: nil
|
30
|
+
}
|
31
|
+
]
|
32
|
+
"""
|
33
|
+
|
34
|
+
Example: A sheet is defined with a title and data
|
35
|
+
Given the command line "--sheet=Summary --data=data1.csv"
|
36
|
+
And a file "data1.csv" containing:
|
37
|
+
"""
|
38
|
+
1,2,3
|
39
|
+
4,5,6
|
40
|
+
"""
|
41
|
+
When the command line is parsed
|
42
|
+
Then the parser should have succeeded
|
43
|
+
And the sheets attribute should contain the following Sheets:
|
44
|
+
"""
|
45
|
+
[
|
46
|
+
{
|
47
|
+
"title": "Summary",
|
48
|
+
"data": [["1", "2", "3"], ["4", "5", "6"]]
|
49
|
+
}
|
50
|
+
]
|
51
|
+
"""
|
52
|
+
|
53
|
+
Example: Multiple sheets are defined both with data
|
54
|
+
Given the command line "--sheet=Summary --data=summary.csv --sheet=Detail --data=detail.csv"
|
55
|
+
And a file "summary.csv" containing:
|
56
|
+
"""
|
57
|
+
6
|
58
|
+
"""
|
59
|
+
And a file "detail.csv" containing:
|
60
|
+
"""
|
61
|
+
1
|
62
|
+
2
|
63
|
+
3
|
64
|
+
"""
|
65
|
+
When the command line is parsed
|
66
|
+
Then the parser should have succeeded
|
67
|
+
And the sheets attribute should contain the following Sheets:
|
68
|
+
"""
|
69
|
+
[
|
70
|
+
{ "title": "Summary", "data": [["6"]] },
|
71
|
+
{ "title": "Detail", "data": [["1"], ["2"], ["3"]] }
|
72
|
+
]
|
73
|
+
"""
|
74
|
+
|
75
|
+
Example: Multiple sheets are defined only one with data
|
76
|
+
Given the command line "--sheet=Summary --sheet=Detail --data=detail.csv"
|
77
|
+
And a file "detail.csv" containing:
|
78
|
+
"""
|
79
|
+
Name,Age
|
80
|
+
John,25
|
81
|
+
Jane,23
|
82
|
+
"""
|
83
|
+
When the command line is parsed
|
84
|
+
Then the parser should have succeeded
|
85
|
+
And the sheets attribute should contain the following Sheets:
|
86
|
+
"""
|
87
|
+
[
|
88
|
+
{ title: "Summary", data: nil },
|
89
|
+
{ title: "Detail", data: [["Name", "Age"], ["John", "25"], ["Jane", "23"]] }
|
90
|
+
]
|
91
|
+
"""
|
92
|
+
|
93
|
+
Example: A user permission is given
|
94
|
+
Given the command line "--permission=user:bob@example.com:reader"
|
95
|
+
When the command line is parsed
|
96
|
+
Then the parser should have succeeded
|
97
|
+
And the permissions attribute should contain the following Permissions:
|
98
|
+
"""
|
99
|
+
[
|
100
|
+
{
|
101
|
+
"permission_spec": "user:bob@example.com:reader",
|
102
|
+
"type": "user", "subject": "bob@example.com", "role": "reader"
|
103
|
+
}
|
104
|
+
]
|
105
|
+
"""
|
106
|
+
|
107
|
+
Example: A group permission is given
|
108
|
+
Given the command line "--permission=group:admins@example.com:writer"
|
109
|
+
|
110
|
+
When the command line is parsed
|
111
|
+
Then the parser should have succeeded
|
112
|
+
And the permissions attribute should contain the following Permissions:
|
113
|
+
"""
|
114
|
+
[
|
115
|
+
{
|
116
|
+
"permission_spec": "group:admins@example.com:writer",
|
117
|
+
"type": "group", "subject": "admins@example.com", "role": "writer"
|
118
|
+
}
|
119
|
+
]
|
120
|
+
"""
|
121
|
+
|
122
|
+
Example: A domain permission is given
|
123
|
+
Given the command line "--permission=domain:domain_name:reader"
|
124
|
+
When the command line is parsed
|
125
|
+
Then the parser should have succeeded
|
126
|
+
And the permissions attribute should contain the following Permissions:
|
127
|
+
"""
|
128
|
+
[
|
129
|
+
{
|
130
|
+
"permission_spec": "domain:domain_name:reader",
|
131
|
+
"type": "domain", "subject": "domain_name", "role": "reader"
|
132
|
+
}
|
133
|
+
]
|
134
|
+
"""
|
135
|
+
|
136
|
+
Example: An anyone permission is given
|
137
|
+
Given the command line "--permission=anyone:reader"
|
138
|
+
When the command line is parsed
|
139
|
+
Then the parser should have succeeded
|
140
|
+
And the permissions attribute should contain the following Permissions:
|
141
|
+
"""
|
142
|
+
[
|
143
|
+
{
|
144
|
+
permission_spec: "anyone:reader", type: "anyone", subject: nil, role: "reader"
|
145
|
+
}
|
146
|
+
]
|
147
|
+
"""
|
148
|
+
|
149
|
+
Example: Multiple permissions are given
|
150
|
+
Given the command line "--permission=user:bob@example.com:writer --permission=anyone:reader"
|
151
|
+
When the command line is parsed
|
152
|
+
Then the parser should have succeeded
|
153
|
+
And the permissions attribute should contain the following Permissions:
|
154
|
+
"""
|
155
|
+
[
|
156
|
+
{
|
157
|
+
permission_spec: "user:bob@example.com:writer", type: "user", subject: "bob@example.com", role: "writer"
|
158
|
+
},
|
159
|
+
{
|
160
|
+
permission_spec: "anyone:reader", type: "anyone", subject: nil, role: "reader"
|
161
|
+
}
|
162
|
+
]
|
163
|
+
"""
|
164
|
+
|
165
|
+
Example: A folder is given
|
166
|
+
Given the command line "--folder=0ALLuhm2AwwlJUk9PVA"
|
167
|
+
When the command line is parsed
|
168
|
+
Then the parser should have succeeded
|
169
|
+
And the folder_id attribute should be "0ALLuhm2AwwlJUk9PVA"
|
170
|
+
|
171
|
+
# Failure cases
|
172
|
+
|
173
|
+
Example: A sheet given without a name
|
174
|
+
Given the command line "--sheet"
|
175
|
+
When the command line is parsed
|
176
|
+
Then the parser should have failed with the error "ERROR: missing argument: --sheet"
|
177
|
+
|
178
|
+
Example: A permission is given without a permission spec
|
179
|
+
Given the command line "--permission"
|
180
|
+
When the command line is parsed
|
181
|
+
Then the parser should have failed with the error "ERROR: missing argument: --permission"
|
182
|
+
|
183
|
+
Example: A invalid permission is given
|
184
|
+
Given the command line "--permission=anyone-writer"
|
185
|
+
When the command line is parsed
|
186
|
+
Then the parser should have failed with the error "ERROR: Invalid permission: anyone-writer"
|
187
|
+
|
188
|
+
Example: The permission spec has an invalid type
|
189
|
+
Given the command line "--permission=invalid:test@example.com:reader"
|
190
|
+
When the command line is parsed
|
191
|
+
Then the parser should have failed with the error "ERROR: Invalid permission type: invalid"
|
192
|
+
|
193
|
+
Example: A permission spec has an invalid role
|
194
|
+
Given the command line "--permission=user:test@example.com:invalid"
|
195
|
+
When the command line is parsed
|
196
|
+
Then the parser should have failed with the error "ERROR: Invalid permission role: invalid"
|
197
|
+
|
198
|
+
Example: A subject is given for an anyone permission
|
199
|
+
Given the command line "--permission=anyone:test@example.com:reader"
|
200
|
+
When the command line is parsed
|
201
|
+
Then the parser should have failed with the error "ERROR: An anyone permission must not have a subject"
|
202
|
+
|
203
|
+
Example: A subject is not given for a user permission
|
204
|
+
Given the command line "--permission=user:writer"
|
205
|
+
When the command line is parsed
|
206
|
+
Then the parser should have failed with the error "ERROR: A user permission must have a subject"
|
207
|
+
|
208
|
+
Example: Data is given without a path
|
209
|
+
Given the command line "--sheet=Summary --data"
|
210
|
+
When the command line is parsed
|
211
|
+
Then the parser should have failed with the error "ERROR: missing argument: --data"
|
212
|
+
|
213
|
+
Example: Data is given with an non-existant path
|
214
|
+
Given the command line "--sheet=Summary --data=nonexistent.csv"
|
215
|
+
And the file "nonexistent.csv" does not exist
|
216
|
+
When the command line is parsed
|
217
|
+
Then the parser should have failed with the error "ERROR: Data file not found: nonexistent.csv"
|
218
|
+
|
219
|
+
Example: The folder option is given twice
|
220
|
+
Given the command line "--folder=0ALLuhm2AwwlJUk9PVA --folder=0ALLuhm2AwwlJUk9PVA"
|
221
|
+
When the command line is parsed
|
222
|
+
|
223
|
+
Example: A same sheet name is given twice
|
224
|
+
Given the command line "--sheet=Summary --sheet=Summary"
|
225
|
+
When the command line is parsed
|
226
|
+
Then the parser should have failed with the error "ERROR: The sheet Summary was given more than once"
|
227
|
+
|
228
|
+
Example: Data is given twice for the same sheet
|
229
|
+
Given the command line "--sheet=Summary --data=data1.csv --data=data2.csv"
|
230
|
+
And a file "data1.csv" containing:
|
231
|
+
"""
|
232
|
+
1
|
233
|
+
"""
|
234
|
+
And a file "data2.csv" containing:
|
235
|
+
"""
|
236
|
+
2
|
237
|
+
"""
|
238
|
+
When the command line is parsed
|
239
|
+
Then the parser should have failed with the error "ERROR: Only one data file is allowed per sheet"
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
step 'the command line :command_line' do |command_line|
|
4
|
+
@args = command_line.split
|
5
|
+
end
|
6
|
+
|
7
|
+
# step 'the command line is parsed' do
|
8
|
+
# @options = CreateSpreadsheetCli.new.parse(@args)
|
9
|
+
# end
|
10
|
+
|
11
|
+
step 'the command line is parsed' do
|
12
|
+
stdout = StringIO.new
|
13
|
+
stderr = StringIO.new
|
14
|
+
original_stdout = $stdout
|
15
|
+
original_stderr = $stderr
|
16
|
+
|
17
|
+
begin
|
18
|
+
$stdout = stdout
|
19
|
+
$stderr = stderr
|
20
|
+
@options = CreateSpreadsheet::CommandLine.new.parse(@args)
|
21
|
+
rescue StandardError => e
|
22
|
+
@error = e
|
23
|
+
@options = e.parser
|
24
|
+
ensure
|
25
|
+
$stdout = original_stdout
|
26
|
+
$stderr = original_stderr
|
27
|
+
end
|
28
|
+
|
29
|
+
@stdout = stdout.string
|
30
|
+
@stderr = stderr.string
|
31
|
+
end
|
32
|
+
|
33
|
+
step 'the parser should have succeeded' do
|
34
|
+
expect(@options.error_messages).to eq([])
|
35
|
+
expect(@error).to be_nil
|
36
|
+
expect(@stdout).to be_empty
|
37
|
+
expect(@stderr).to be_empty
|
38
|
+
end
|
39
|
+
|
40
|
+
step 'the parser should have failed' do
|
41
|
+
expect(@options.error_messages).not_to be_empty
|
42
|
+
expect(@error).to be_nil
|
43
|
+
expect(@stdout).to be_empty
|
44
|
+
expect(@stderr).to be_empty
|
45
|
+
end
|
46
|
+
|
47
|
+
step 'the parser should have failed with the error :message' do |message|
|
48
|
+
expect(@options.error_messages).not_to be_empty
|
49
|
+
expect(@error).to be_nil
|
50
|
+
expect(@stdout).to be_empty
|
51
|
+
expect(@stderr).to be_empty
|
52
|
+
expect(@options.error_messages).to include(message)
|
53
|
+
end
|
54
|
+
|
55
|
+
step 'the :attribute attribute should be nil' do |attribute|
|
56
|
+
expect(@options.send(attribute.to_sym)).to be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
step 'the :attribute attribute should be empty' do |attribute|
|
60
|
+
expect(@options.send(attribute.to_sym)).to be_empty
|
61
|
+
end
|
62
|
+
|
63
|
+
step 'the :attribute attribute should be ":value"' do |attribute, value|
|
64
|
+
expect(@options.send(attribute.to_sym)).to eq(value)
|
65
|
+
end
|
66
|
+
|
67
|
+
step 'the sheets attribute should contain the following Sheets:' do |hash_as_string|
|
68
|
+
# rubocop:disable Security/Eval
|
69
|
+
expected_sheets = eval(hash_as_string).map { |s| CreateSpreadsheet::Sheet.new(s) }
|
70
|
+
# rubocop:enable Security/Eval
|
71
|
+
expect(@options.sheets).to match_array(expected_sheets)
|
72
|
+
end
|
73
|
+
|
74
|
+
RSpec.configure do |config|
|
75
|
+
config.before(type: :feature) do
|
76
|
+
allow(File).to receive(:read).and_call_original
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
step 'a file :path containing:' do |path, content|
|
81
|
+
allow(File).to receive(:read).with(path).and_return(content)
|
82
|
+
end
|
83
|
+
|
84
|
+
step 'the file :path does not exist' do |path|
|
85
|
+
allow(File).to receive(:read).with(path).and_raise(Errno::ENOENT, "No such file or directory @ rb_sysopen - #{path}")
|
86
|
+
end
|
87
|
+
|
88
|
+
step 'the permissions attribute should contain the following Permissions:' do |hash_as_string|
|
89
|
+
# rubocop:disable Security/Eval
|
90
|
+
expected_permissions = eval(hash_as_string).map { |p| CreateSpreadsheet::Permission.new(p) }
|
91
|
+
# rubocop:enable Security/Eval
|
92
|
+
expect(@options.permissions).to match_array(expected_permissions)
|
93
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Turnip setup
|
4
|
+
#
|
5
|
+
require 'turnip/rspec'
|
6
|
+
Dir[File.join(__dir__, '**/*_steps.rb')].each { |f| require f }
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
# Enable flags like --only-failures and --next-failure
|
10
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
11
|
+
|
12
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
13
|
+
config.disable_monkey_patching!
|
14
|
+
|
15
|
+
config.expect_with :rspec do |c|
|
16
|
+
c.syntax = :expect
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Setup simplecov
|
21
|
+
|
22
|
+
require 'simplecov'
|
23
|
+
require 'simplecov-lcov'
|
24
|
+
require 'json'
|
25
|
+
|
26
|
+
SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, SimpleCov::Formatter::LcovFormatter]
|
27
|
+
|
28
|
+
# Return `true` if the environment variable is set to a truthy value
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# env_true?('COV_SHOW_UNCOVERED')
|
32
|
+
#
|
33
|
+
# @param name [String] the name of the environment variable
|
34
|
+
# @return [Boolean]
|
35
|
+
#
|
36
|
+
def env_true?(name)
|
37
|
+
value = ENV.fetch(name, '').downcase
|
38
|
+
%w[yes on true 1].include?(value)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return `true` if the environment variable is NOT set to a truthy value
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# env_false?('COV_NO_FAIL')
|
45
|
+
#
|
46
|
+
# @param name [String] the name of the environment variable
|
47
|
+
# @return [Boolean]
|
48
|
+
#
|
49
|
+
def env_false?(name)
|
50
|
+
!env_true?(name)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return `true` if the the test run should fail if the coverage is below the threshold
|
54
|
+
#
|
55
|
+
# @return [Boolean]
|
56
|
+
#
|
57
|
+
def fail_on_low_coverage?
|
58
|
+
!(RSpec.configuration.dry_run? || env_true?('COV_NO_FAIL'))
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return `true` if the the test run should show the lines not covered by tests
|
62
|
+
#
|
63
|
+
# @return [Boolean]
|
64
|
+
#
|
65
|
+
def show_lines_not_covered?
|
66
|
+
env_true?('COV_SHOW_UNCOVERED')
|
67
|
+
end
|
68
|
+
|
69
|
+
# Report if the test coverage was below the configured threshold
|
70
|
+
#
|
71
|
+
# The threshold is configured by setting the `test_coverage_threshold` variable
|
72
|
+
# in this file.
|
73
|
+
#
|
74
|
+
# Example:
|
75
|
+
#
|
76
|
+
# ```Ruby
|
77
|
+
# test_coverage_threshold = 100
|
78
|
+
# ```
|
79
|
+
#
|
80
|
+
# Coverage below the threshold will cause the rspec run to fail unless the
|
81
|
+
# `COV_NO_FAIL` environment variable is set to TRUE.
|
82
|
+
#
|
83
|
+
# ```Shell
|
84
|
+
# COV_NO_FAIL=TRUE rspec
|
85
|
+
# ```
|
86
|
+
#
|
87
|
+
# Example of running the tests in an infinite loop writing failures to `fail.txt`:
|
88
|
+
#
|
89
|
+
# ```Shell
|
90
|
+
# while true; do COV_NO_FAIL=TRUE rspec >> fail.txt; done
|
91
|
+
# ````
|
92
|
+
#
|
93
|
+
# The lines missing coverage will be displayed if the `COV_SHOW_UNCOVERED`
|
94
|
+
# environment variable is set to TRUE.
|
95
|
+
#
|
96
|
+
# ```Shell
|
97
|
+
# COV_SHOW_UNCOVERED=TRUE rspec
|
98
|
+
# ```
|
99
|
+
#
|
100
|
+
test_coverage_threshold = 100
|
101
|
+
|
102
|
+
SimpleCov.at_exit do
|
103
|
+
SimpleCov.result.format!
|
104
|
+
# rubocop:disable Style/StderrPuts
|
105
|
+
if SimpleCov.result.covered_percent < test_coverage_threshold
|
106
|
+
$stderr.puts
|
107
|
+
$stderr.print 'FAIL: ' if fail_on_low_coverage?
|
108
|
+
$stderr.puts "RSpec Test coverage fell below #{test_coverage_threshold}%"
|
109
|
+
|
110
|
+
if show_lines_not_covered?
|
111
|
+
$stderr.puts "\nThe following lines were not covered by tests:\n"
|
112
|
+
SimpleCov.result.files.each do |source_file| # SimpleCov::SourceFile
|
113
|
+
source_file.missed_lines.each do |line| # SimpleCov::SourceFile::Line
|
114
|
+
$stderr.puts " .#{source_file.project_filename}:#{line.number}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
$stderr.puts
|
120
|
+
|
121
|
+
exit 1 if fail_on_low_coverage?
|
122
|
+
end
|
123
|
+
# rubocop:enable Style/StderrPuts
|
124
|
+
end
|
125
|
+
|
126
|
+
SimpleCov.start do
|
127
|
+
enable_coverage :branch
|
128
|
+
end
|
129
|
+
|
130
|
+
require 'create_spreadsheet'
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'command_line_boss'
|
5
|
+
|
6
|
+
# Define a command line interface for creating a spreadsheet
|
7
|
+
class CreateSpreadsheetCli < CommandLineBoss
|
8
|
+
attr_reader :spreadsheet_name, :sheet_names
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def set_defaults
|
13
|
+
@spreadsheet_name = nil
|
14
|
+
@sheet_names = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def define_sheet_option
|
18
|
+
parser.on('--sheet=SHEET_NAME', 'Name of a sheet to create') do |name|
|
19
|
+
add_error_message('Sheet names must be unique!') if sheet_names.include?(name)
|
20
|
+
sheet_names << name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_spreadsheet_name_given
|
25
|
+
add_error_message('A spreadsheet name is required') if spreadsheet_name.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate_at_least_one_sheet_name_given
|
29
|
+
add_error_message('At least one sheet name is required') if sheet_names.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_arguments
|
33
|
+
@spreadsheet_name = args.shift
|
34
|
+
end
|
35
|
+
|
36
|
+
include CommandLineBoss::HelpOption
|
37
|
+
|
38
|
+
def banner = <<~BANNER
|
39
|
+
Create a spreadsheetasdf
|
40
|
+
|
41
|
+
Usage:
|
42
|
+
create_spreadsheet SPREADSHEET_NAME --sheet=SHEET_NAME [--sheet=SHEET_NAME ...]
|
43
|
+
|
44
|
+
BANNER
|
45
|
+
end
|
46
|
+
|
47
|
+
# Parse the command line arguments
|
48
|
+
|
49
|
+
options = CreateSpreadsheetCli.new.parse(ARGV)
|
50
|
+
|
51
|
+
# Report errors
|
52
|
+
|
53
|
+
if options.failed?
|
54
|
+
warn options.error_messages.join("\n")
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
# Do something with the result
|
59
|
+
|
60
|
+
require 'pp'
|
61
|
+
|
62
|
+
puts \
|
63
|
+
"Creating spreadsheet #{options.spreadsheet_name.pretty_inspect.chomp} " \
|
64
|
+
"with sheets #{options.sheet_names.map(&:pretty_inspect).map(&:chomp).join(', ')}"
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CommandLineBoss
|
4
|
+
# Add the --help option
|
5
|
+
module HelpOption
|
6
|
+
private
|
7
|
+
|
8
|
+
# Define the command line options
|
9
|
+
#
|
10
|
+
# @return [void]
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
#
|
14
|
+
def define_options
|
15
|
+
add_banner
|
16
|
+
add_header
|
17
|
+
super
|
18
|
+
add_footer
|
19
|
+
end
|
20
|
+
|
21
|
+
# Define the --help option
|
22
|
+
#
|
23
|
+
# @return [void]
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
#
|
27
|
+
def define_help_option
|
28
|
+
parser.on('-h', '--help', 'Show this message') do
|
29
|
+
puts parser.help
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Adds the banner to the parser output
|
35
|
+
# @return [void]
|
36
|
+
# @api private
|
37
|
+
#
|
38
|
+
def add_banner
|
39
|
+
return unless banner
|
40
|
+
|
41
|
+
parser.banner = banner
|
42
|
+
end
|
43
|
+
|
44
|
+
# Derived classes should override this method to provide a banner
|
45
|
+
# @return [String, nil]
|
46
|
+
# @api private
|
47
|
+
#
|
48
|
+
def banner = nil
|
49
|
+
|
50
|
+
# Adds the header to the parser output
|
51
|
+
# @return [void]
|
52
|
+
# @api private
|
53
|
+
#
|
54
|
+
def add_header
|
55
|
+
return unless header
|
56
|
+
|
57
|
+
parser.separator header
|
58
|
+
end
|
59
|
+
|
60
|
+
# Derived classes should override this method to provide a header
|
61
|
+
# @return [String, nil]
|
62
|
+
# @api private
|
63
|
+
#
|
64
|
+
def header = nil
|
65
|
+
|
66
|
+
# Adds the footer to the parser output
|
67
|
+
# @return [void]
|
68
|
+
# @api private
|
69
|
+
#
|
70
|
+
def add_footer
|
71
|
+
return unless footer
|
72
|
+
|
73
|
+
parser.separator footer
|
74
|
+
end
|
75
|
+
|
76
|
+
# Derived classes should override this method to provide a footer
|
77
|
+
# @return [String, nil]
|
78
|
+
# @api private
|
79
|
+
#
|
80
|
+
def footer = nil
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CommandLineBoss
|
4
|
+
# Add --debug and --verbose options and a logger method
|
5
|
+
module LoggerOptions
|
6
|
+
# true if the --debug option was given
|
7
|
+
#
|
8
|
+
# @return [Boolean]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
#
|
12
|
+
attr_reader :debug
|
13
|
+
|
14
|
+
# true if the --verbose option was given
|
15
|
+
#
|
16
|
+
# @return [Boolean]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
#
|
20
|
+
attr_reader :verbose
|
21
|
+
|
22
|
+
# The logger to use to report progress
|
23
|
+
#
|
24
|
+
# Messages are logged at info and debug levels. The logger returned is one of
|
25
|
+
# the following:
|
26
|
+
#
|
27
|
+
# * A logger that logs to the console at the :info level if verbose mode is enabled
|
28
|
+
# * A logger that logs to the console at the :debug level if debug mode is enabled
|
29
|
+
# * Otherwise a null logger that does not log anything
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# options.logger #=> #<Logger:0x00007f9e3b8b3e08>
|
33
|
+
#
|
34
|
+
# @return [Logger]
|
35
|
+
#
|
36
|
+
def logger
|
37
|
+
@logger ||=
|
38
|
+
if verbose
|
39
|
+
verbose_logger
|
40
|
+
elsif debug
|
41
|
+
debug_logger
|
42
|
+
else
|
43
|
+
Logger.new(nil, level: Logger::UNKNOWN + 1)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Define the --verbose option
|
50
|
+
#
|
51
|
+
# @return [void]
|
52
|
+
#
|
53
|
+
# @api private
|
54
|
+
#
|
55
|
+
def define_verbose_option
|
56
|
+
parser.on('-v', '--verbose', 'Enable verbose mode (default is off)') do |verbose|
|
57
|
+
@verbose = verbose
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Define the --debug option
|
62
|
+
#
|
63
|
+
# @return [void]
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
#
|
67
|
+
def define_debug_option
|
68
|
+
parser.on('-D', '--debug', 'Enable debug mode default is off') do |debug|
|
69
|
+
@debug = debug
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Ensure that the --debug and --verbose options are not both given
|
74
|
+
#
|
75
|
+
# @return [void]
|
76
|
+
#
|
77
|
+
# @api private
|
78
|
+
#
|
79
|
+
def validate_debug_verbose_option
|
80
|
+
add_error_message('Can not give both --debug and --verbose') if debug && verbose
|
81
|
+
end
|
82
|
+
|
83
|
+
# A Logger that logs to the console at the :debug level with a simple formatter
|
84
|
+
#
|
85
|
+
# @return [Logger]
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
#
|
89
|
+
def debug_logger
|
90
|
+
Logger.new($stdout, level: 'debug', formatter: ->(_severity, _datetime, _progname, msg) { "#{msg}\n" })
|
91
|
+
end
|
92
|
+
|
93
|
+
# A Logger that logs to the console at the :info level with a simple formatter
|
94
|
+
#
|
95
|
+
# @return [Logger]
|
96
|
+
#
|
97
|
+
# @api private
|
98
|
+
#
|
99
|
+
def verbose_logger
|
100
|
+
Logger.new($stdout, level: 'info', formatter: ->(_severity, _datetime, _progname, msg) { "#{msg}\n" })
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|