cli_helper 0.0.4 → 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 +4 -4
- data/README.md +222 -11
- data/cli_helper.gemspec +17 -14
- data/example/cli_stub.rb +64 -11
- data/lib/cli_helper.rb +251 -277
- data/tests/README.txt +18 -0
- data/tests/cli_helper_test.rb +217 -0
- data/tests/config/sample.ini +6 -0
- data/tests/config/sample.ini.erb +9 -0
- data/tests/config/sample.rb +15 -0
- data/tests/config/sample.rb.erb +20 -0
- data/tests/config/sample.txt +12 -0
- data/tests/config/sample.txt.erb +16 -0
- data/tests/config/sample.xyzzy +1 -0
- data/tests/config/sample.yml +16 -0
- data/tests/config/sample.yml.erb +17 -0
- metadata +61 -23
data/lib/cli_helper.rb
CHANGED
@@ -6,22 +6,15 @@
|
|
6
6
|
## By: Dewayne VanHoozer (dvanhoozer@gmail.com)
|
7
7
|
#
|
8
8
|
|
9
|
-
#
|
10
|
-
|
11
|
-
# NOTE: this conditional required by test_inline
|
12
|
-
if caller.empty?
|
13
|
-
required_by_filename = __FILE__
|
14
|
-
else
|
15
|
-
required_by_filename = caller.last.split(':').first
|
16
|
-
end
|
17
|
-
|
18
|
-
require 'debug_me'
|
19
|
-
include DebugMe
|
20
|
-
|
21
|
-
require 'awesome_print'
|
22
|
-
|
9
|
+
# System Libraries
|
23
10
|
require 'pathname'
|
11
|
+
require 'erb'
|
12
|
+
require 'yaml'
|
13
|
+
|
14
|
+
# Third-party gems
|
15
|
+
require 'configatron'
|
24
16
|
require 'nenv'
|
17
|
+
require 'parseconfig'
|
25
18
|
require 'slop'
|
26
19
|
|
27
20
|
# Example Custom Type for Slop
|
@@ -48,288 +41,269 @@ module Slop
|
|
48
41
|
end
|
49
42
|
end # module Slop
|
50
43
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
# Invoke Slop with common CLI parameters and custom
|
85
|
-
# parameters provided via a block. Create '?'
|
86
|
-
# for all boolean parameters that have a '--name' flag form.
|
87
|
-
# Returns a Slop::Options object
|
88
|
-
def cli_helper(my_banner='')
|
89
|
-
param = Slop::Options.new
|
90
|
-
|
91
|
-
if my_banner.empty?
|
92
|
-
param.banner = "Usage: #{my_name} [options] ..."
|
93
|
-
else
|
94
|
-
param.banner = my_banner
|
95
|
-
param.separator "\nUsage: #{my_name} [options] ..."
|
44
|
+
module CliHelper
|
45
|
+
|
46
|
+
DEFAULTS = {
|
47
|
+
version: '0.0.1',# the version of this program
|
48
|
+
arguments: [], # whats left after options and parameters are extracted
|
49
|
+
verbose: false,
|
50
|
+
debug: false,
|
51
|
+
help: false,
|
52
|
+
support_config_files: false,
|
53
|
+
disable_help: false,
|
54
|
+
disable_debug: false,
|
55
|
+
disable_verbose: false,
|
56
|
+
disable_version: false,
|
57
|
+
suppress_errors: false,
|
58
|
+
user_name: Nenv.user || Nenv.user_name || Nenv.logname || 'The Unknown Programmer',
|
59
|
+
home_path: Pathname.new(Nenv.home),
|
60
|
+
cli: 'a place holder for the Slop object',
|
61
|
+
errors: [],
|
62
|
+
warnings: []
|
63
|
+
}
|
64
|
+
|
65
|
+
configatron.configure_from_hash DEFAULTS
|
66
|
+
|
67
|
+
configatron.required_by_filename = caller.last.split(':').first
|
68
|
+
configatron.me = Pathname.new(configatron.required_by_filename).realpath
|
69
|
+
configatron.my_dir = Pathname.new(configatron.required_by_filename).realpath.parent
|
70
|
+
configatron.my_name = Pathname.new(configatron.required_by_filename).realpath.basename.to_s
|
71
|
+
|
72
|
+
|
73
|
+
# Return full pathname of program
|
74
|
+
def me
|
75
|
+
configatron.me
|
96
76
|
end
|
97
77
|
|
98
|
-
|
99
|
-
|
78
|
+
# Returns the basename of the program as a string
|
79
|
+
def my_name
|
80
|
+
configatron.my_name
|
81
|
+
end
|
100
82
|
|
101
|
-
|
102
|
-
|
103
|
-
|
83
|
+
# Returns the version of the program as a string
|
84
|
+
def version
|
85
|
+
configatron.version
|
86
|
+
end
|
104
87
|
|
105
|
-
|
106
|
-
|
107
|
-
|
88
|
+
def cli_helper_process_erb(file_contents)
|
89
|
+
erb_contents = ERB.new(file_contents).result
|
90
|
+
return erb_contents
|
108
91
|
end
|
109
92
|
|
110
|
-
|
93
|
+
def cli_helper_process_yaml(file_contents='')
|
94
|
+
a_hash = YAML.load file_contents
|
95
|
+
return a_hash
|
96
|
+
end
|
111
97
|
|
112
|
-
|
98
|
+
def cli_helper_process_ini(file_path, file_contents='')
|
99
|
+
# FIXME: mod the parseconfig gem to use a parse method on strings
|
100
|
+
an_ini_object = ParseConfig.new(file_path)
|
101
|
+
return an_ini_object.params
|
102
|
+
end
|
113
103
|
|
114
|
-
|
115
|
-
|
104
|
+
# Invoke Slop with common CLI parameters and custom
|
105
|
+
# parameters provided via a block. Create '?'
|
106
|
+
# for all boolean parameters that have a '--name' flag form.
|
107
|
+
# Returns a Slop::Options object
|
108
|
+
def cli_helper(my_banner='')
|
109
|
+
param = Slop::Options.new
|
116
110
|
|
117
|
-
|
118
|
-
|
111
|
+
if my_banner.empty?
|
112
|
+
param.banner = "Usage: #{my_name} [options] ..."
|
113
|
+
else
|
114
|
+
param.banner = my_banner
|
115
|
+
param.separator "\nUsage: #{my_name} [options] ..."
|
116
|
+
end
|
119
117
|
|
118
|
+
param.separator "\nWhere:"
|
120
119
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
120
|
+
if configatron.disable_help &&
|
121
|
+
configatron.disable_verbose &&
|
122
|
+
configatron.disable_debug &&
|
123
|
+
configatron.disable_version
|
124
|
+
# NOOP
|
125
|
+
else
|
126
|
+
param.separator "\n Common Options Are:"
|
127
|
+
end
|
125
128
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
$options[s]
|
130
|
-
end unless self.respond_to?(m+'?')
|
131
|
-
define_method(m+'!') do
|
132
|
-
$options[s] = true
|
133
|
-
end unless self.respond_to?(m+'!')
|
134
|
-
end
|
129
|
+
unless configatron.disable_help
|
130
|
+
param.bool '-h', '--help', 'show this message'
|
131
|
+
end
|
135
132
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
end
|
133
|
+
unless configatron.disable_verbose
|
134
|
+
param.bool '-v', '--verbose', 'enable verbose mode'
|
135
|
+
end
|
140
136
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
# Returns the usage/help information as a string
|
145
|
-
def usage
|
146
|
-
a_string = $cli.to_s + "\n"
|
147
|
-
a_string += HELP + "\n" if defined?(HELP)
|
148
|
-
return a_string
|
149
|
-
end
|
150
|
-
|
151
|
-
# Prints to STDOUT the usage/help string
|
152
|
-
def show_usage
|
153
|
-
puts usage()
|
154
|
-
end
|
155
|
-
|
156
|
-
|
157
|
-
# Returns an array of valid files of one type
|
158
|
-
def get_pathnames_from(an_array, extnames=['.json', '.txt', '.docx'])
|
159
|
-
an_array = [an_array] unless an_array.is_a? Array
|
160
|
-
extnames = [extnames] unless extnames.is_a? Array
|
161
|
-
extnames = extnames.map{|e| e.downcase}
|
162
|
-
file_array = []
|
163
|
-
an_array.each do |a|
|
164
|
-
pfn = Pathname.new(a)
|
165
|
-
if pfn.directory?
|
166
|
-
file_array << get_pathnames_from(pfn.children, extnames)
|
167
|
-
else
|
168
|
-
file_array << pfn if pfn.exist? && extnames.include?(pfn.extname.downcase)
|
137
|
+
unless configatron.disable_debug
|
138
|
+
param.bool '-d', '--debug', 'enable debug mode'
|
169
139
|
end
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
def abort_if_errors
|
177
|
-
unless $warnings.empty?
|
178
|
-
STDERR.puts
|
179
|
-
STDERR.puts "The following warnings were generated:"
|
180
|
-
STDERR.puts
|
181
|
-
$warnings.each do |w|
|
182
|
-
STDERR.puts "\tWarning: #{w}"
|
140
|
+
|
141
|
+
unless configatron.disable_version
|
142
|
+
param.on '--version', "print the version: #{configatron.version}" do
|
143
|
+
puts configatron.version
|
144
|
+
exit
|
145
|
+
end
|
183
146
|
end
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
147
|
+
|
148
|
+
param.separator "\n Program Options Are:"
|
149
|
+
|
150
|
+
yield(param) if block_given?
|
151
|
+
|
152
|
+
if configatron.support_config_files
|
153
|
+
param.paths '--config', 'read config file(s) [*.rb, *.yml, *.ini]'
|
154
|
+
end
|
155
|
+
|
156
|
+
parser = Slop::Parser.new(param, suppress_errors: configatron.suppress_errors)
|
157
|
+
configatron.cli = parser.parse(ARGV)
|
158
|
+
|
159
|
+
# TODO: DRY this conditional block
|
160
|
+
if configatron.support_config_files
|
161
|
+
|
162
|
+
configatron.cli[:config].each do |cf|
|
163
|
+
unless cf.exist? || cf.directory?
|
164
|
+
error "Config file is missing: #{cf}"
|
165
|
+
else
|
166
|
+
file_type = case cf.extname.downcase
|
167
|
+
when '.rb'
|
168
|
+
:ruby
|
169
|
+
when '.yml', '.yaml'
|
170
|
+
:yaml
|
171
|
+
when '.ini', '.txt'
|
172
|
+
:ini
|
173
|
+
when '.erb'
|
174
|
+
extname = cf.basename.to_s.downcase.gsub('.erb','').split('.').last
|
175
|
+
if %w[ yml yaml].include? extname
|
176
|
+
:yaml
|
177
|
+
elsif %w[ ini txt ].include? extname
|
178
|
+
:ini
|
179
|
+
elsif 'rb' == extname
|
180
|
+
raise 'MakesNoSenseToMe: *.rb.erb is not supported'
|
181
|
+
else
|
182
|
+
:unknown
|
183
|
+
end
|
184
|
+
else
|
185
|
+
:unknown
|
186
|
+
end
|
187
|
+
|
188
|
+
case file_type
|
189
|
+
when :ruby
|
190
|
+
load cf
|
191
|
+
when :yaml
|
192
|
+
configatron.configure_from_hash(
|
193
|
+
config_file_hash = configatron.configure_from_hash(
|
194
|
+
cli_helper_process_yaml(
|
195
|
+
cli_helper_process_erb(cf.read)
|
196
|
+
)
|
197
|
+
)
|
198
|
+
)
|
199
|
+
when :ini
|
200
|
+
configatron.configure_from_hash(
|
201
|
+
configatron.configure_from_hash(
|
202
|
+
cli_helper_process_ini( cf # FIXME: fork parseconfig
|
203
|
+
# cli_helper_process_erb(cf.read)
|
204
|
+
)
|
205
|
+
)
|
206
|
+
)
|
207
|
+
else
|
208
|
+
error "Do not know how to parse this file: #{cf}"
|
209
|
+
end # case type_type
|
210
|
+
end # unless cf.exist? || cf.directory?
|
211
|
+
end # configatron.cli.config.each do |cf|
|
212
|
+
end # if configatron.support_config_files
|
213
|
+
|
214
|
+
configatron.configure_from_hash(configatron.cli.to_hash)
|
215
|
+
configatron.arguments = configatron.cli.arguments
|
216
|
+
|
217
|
+
bools = param.options.select do |o|
|
218
|
+
o.is_a? Slop::BoolOption
|
219
|
+
end.select{|o| o.flags.select{|f|f.start_with?('--')}}.
|
220
|
+
map{|o| o.flags.last.gsub('--','')} # SMELL: depends on convention
|
221
|
+
|
222
|
+
bools.each do |m|
|
223
|
+
s = m.to_sym
|
224
|
+
define_method(m+'?') do
|
225
|
+
configatron[s]
|
226
|
+
end unless self.respond_to?(m+'?')
|
227
|
+
define_method((m+'!').to_sym) do
|
228
|
+
configatron[s] = true
|
229
|
+
end unless self.respond_to?(m+'!')
|
195
230
|
end
|
196
|
-
|
197
|
-
|
231
|
+
|
232
|
+
|
233
|
+
if self.respond_to?(:help?) && help?
|
234
|
+
show_usage
|
235
|
+
exit
|
236
|
+
end
|
237
|
+
|
238
|
+
return param
|
239
|
+
end # def cli_helper
|
240
|
+
|
241
|
+
# Returns the usage/help information as a string
|
242
|
+
def usage
|
243
|
+
a_string = configatron.cli.to_s + "\n"
|
244
|
+
a_string += HELP + "\n" if defined?(HELP)
|
245
|
+
return a_string
|
198
246
|
end
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
$errors << a_string
|
204
|
-
end
|
205
|
-
|
206
|
-
# Adds a string to the global $warnings array
|
207
|
-
def warning(a_string)
|
208
|
-
$warnings << a_string
|
209
|
-
end
|
210
|
-
|
211
|
-
|
212
|
-
__END__
|
213
|
-
################################################
|
214
|
-
## Here is how it works
|
215
|
-
|
216
|
-
Test '000 supports basic common parameters' do
|
217
|
-
params = cli_helper
|
218
|
-
assert params.is_a? Slop::Options
|
219
|
-
assert usage.include?('--help')
|
220
|
-
assert usage.include?('--debug')
|
221
|
-
assert usage.include?('--verbose')
|
222
|
-
assert usage.include?('--version')
|
223
|
-
refute usage.include?('--xyzzy')
|
224
|
-
end
|
225
|
-
|
226
|
-
=begin
|
227
|
-
# NOTE: The Test construct creates a dynamic class which
|
228
|
-
# does not incorporate 'define_method' This Test results
|
229
|
-
# in errors that are a consequence of the testing framework
|
230
|
-
# not the object under test.
|
231
|
-
Test '002 creates accessor methods to boolean options' do
|
232
|
-
cli_helper
|
233
|
-
all_methods = methods
|
234
|
-
%w[ help? debug? verbose? version?
|
235
|
-
help! debug! verbose! version!].each do |m|
|
236
|
-
assert all_methods.include?(m)
|
247
|
+
|
248
|
+
# Prints to STDOUT the usage/help string
|
249
|
+
def show_usage
|
250
|
+
puts usage()
|
237
251
|
end
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
=
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
252
|
+
|
253
|
+
|
254
|
+
# Returns an array of valid files of one type
|
255
|
+
def get_pathnames_from(an_array, extnames=['.json', '.txt', '.docx'])
|
256
|
+
an_array = [an_array] unless an_array.is_a? Array
|
257
|
+
extnames = [extnames] unless extnames.is_a? Array
|
258
|
+
extnames = extnames.map{|e| e.downcase}
|
259
|
+
file_array = []
|
260
|
+
an_array.each do |a|
|
261
|
+
pfn = Pathname.new(a)
|
262
|
+
if pfn.directory?
|
263
|
+
file_array << get_pathnames_from(pfn.children, extnames)
|
264
|
+
else
|
265
|
+
file_array << pfn if pfn.exist? && extnames.include?(pfn.extname.downcase)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
return file_array.flatten
|
269
|
+
end # def get_pathnames_from(an_array, extname='.json')
|
270
|
+
|
271
|
+
|
272
|
+
# Display global warnings and errors arrays and exit if necessary
|
273
|
+
def abort_if_errors
|
274
|
+
unless configatron.warnings.empty?
|
275
|
+
STDERR.puts
|
276
|
+
STDERR.puts "The following warnings were generated:"
|
277
|
+
STDERR.puts
|
278
|
+
configatron.warnings.each do |w|
|
279
|
+
STDERR.puts "\tWarning: #{w}"
|
280
|
+
end
|
281
|
+
STDERR.print "\nAbort program? (y/N) "
|
282
|
+
answer = (STDIN.gets).chomp.strip.downcase
|
283
|
+
configatron.errors << "Aborted by user" if answer.size>0 && 'y' == answer[0]
|
284
|
+
configatron.warnings = []
|
285
|
+
end
|
286
|
+
unless configatron.errors.empty?
|
287
|
+
STDERR.puts
|
288
|
+
STDERR.puts "Correct the following errors and try again:"
|
289
|
+
STDERR.puts
|
290
|
+
configatron.errors.each do |e|
|
291
|
+
STDERR.puts "\t#{e}"
|
292
|
+
end
|
293
|
+
STDERR.puts
|
294
|
+
exit(-1)
|
295
|
+
end
|
296
|
+
end # def abort_if_errors
|
297
|
+
|
298
|
+
# Adds a string to the global $errors array
|
299
|
+
def error(a_string)
|
300
|
+
configatron.errors << a_string
|
246
301
|
end
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
refute usage().include?('BANNER')
|
252
|
-
cli_helper('This is my BANNER')
|
253
|
-
assert usage().include?('BANNER')
|
254
|
-
end
|
255
|
-
|
256
|
-
Test '015 supports additional help in usage' do
|
257
|
-
refute usage.include?('HELP')
|
258
|
-
HELP = "Do you need some HELP?"
|
259
|
-
assert usage.include?('HELP')
|
260
|
-
end
|
261
|
-
|
262
|
-
Test '020 put it all together' do
|
263
|
-
cli_helper('This is my BANNER') do |o|
|
264
|
-
o.string '-x', '--xyxxy', 'example MAGIC parameter', default: 'IamDefault'
|
302
|
+
|
303
|
+
# Adds a string to the global $warnings array
|
304
|
+
def warning(a_string)
|
305
|
+
configatron.warnings << a_string
|
265
306
|
end
|
266
|
-
|
267
|
-
|
268
|
-
assert usage.include?('MAGIC')
|
269
|
-
assert usage.include?('HELP')
|
270
|
-
end
|
271
|
-
|
272
|
-
Test '025 Add to $errors' do
|
273
|
-
assert $errors.empty?
|
274
|
-
a_message = 'There is a serious problem here'
|
275
|
-
error a_message
|
276
|
-
refute $errors.empty?
|
277
|
-
assert_equal 1, $errors.size
|
278
|
-
assert_equal a_message, $errors.first
|
279
|
-
end
|
280
|
-
|
281
|
-
Test '030 Add to $warnings' do
|
282
|
-
assert $warnings.empty?
|
283
|
-
a_message = 'There is a minor problem here'
|
284
|
-
warning a_message
|
285
|
-
refute $warnings.empty?
|
286
|
-
assert_equal 1, $warnings.size
|
287
|
-
assert_equal a_message, $warnings.first
|
288
|
-
end
|
289
|
-
|
290
|
-
Test '035 Add to $errors' do
|
291
|
-
refute $errors.empty?
|
292
|
-
a_message = 'There is another serious problem here'
|
293
|
-
error a_message
|
294
|
-
assert_equal 2, $errors.size
|
295
|
-
assert_equal a_message, $errors.last
|
296
|
-
end
|
297
|
-
|
298
|
-
Test '040 Add to $warnings' do
|
299
|
-
refute $warnings.empty?
|
300
|
-
a_message = 'There is a another minor problem here'
|
301
|
-
warning a_message
|
302
|
-
assert_equal 2, $warnings.size
|
303
|
-
assert_equal a_message, $warnings.last
|
304
|
-
end
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
Test '999 prints usage()' do
|
311
|
-
puts
|
312
|
-
puts "="*45
|
313
|
-
show_usage
|
314
|
-
puts "="*45
|
315
|
-
puts
|
316
|
-
a_string = <<EOS
|
317
|
-
This is my BANNER
|
318
|
-
|
319
|
-
Usage: cli_helper.rb [options] ...
|
320
|
-
|
321
|
-
Where:
|
322
|
-
Common Options Are:
|
323
|
-
-h, --help show this message
|
324
|
-
-v, --verbose enable verbose mode
|
325
|
-
-d, --debug enable debug mode
|
326
|
-
--version print the version: 0.0.1
|
327
|
-
Program Options Are:
|
328
|
-
-x, --xyxxy example MAGIC parameter
|
329
|
-
|
330
|
-
Do you need some HELP?
|
331
|
-
EOS
|
332
|
-
|
333
|
-
assert_equal a_string, usage
|
334
|
-
end
|
307
|
+
|
308
|
+
end # module CliHelper
|
335
309
|
|
data/tests/README.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
The minitest framework depends upon pry.
|
2
|
+
|
3
|
+
HOWEVER, pry currently requires an older version
|
4
|
+
of Slop. CliHelper is based upon version 4+ of Slop;
|
5
|
+
When pry/minitest gets updated, I will un update the
|
6
|
+
test suite for full automation.
|
7
|
+
|
8
|
+
For now the test file is more manual. Run tests like
|
9
|
+
the following from the command line:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
./cli_helper_test.rb --config config/sample.ini,config/sample.ini.erb
|
13
|
+
./cli_helper_test.rb --config config/sample.rb,config/sample.rb.erb
|
14
|
+
./cli_helper_test.rb --config config/sample.txt,config/sample.txt.erb
|
15
|
+
./cli_helper_test.rb --config config/sample.yml,config/sample.yml.erb
|
16
|
+
```
|
17
|
+
|
18
|
+
The test with config/sample.rb.erb with raise a MakesNoSenseToMe exception.
|