mixlib-cli 2.0.6 → 2.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mixlib/cli.rb +153 -40
- data/lib/mixlib/cli/formatter.rb +33 -0
- data/lib/mixlib/cli/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4d1591454c7940424cf9b65bc490f10e651dfd09a54effab6665bcdb08d5d60
|
4
|
+
data.tar.gz: 8225a500779099fa3f0b7103a396f428dadccbd24b393acd8b3a2523c4e6212a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc73cfd3404053a3fbf661a217a7198957677e3e229d143f95b1d69cf5da5f8f534835979a8b0979b07ad7f979e4245e34b1522d96b1c94a48197aab0a370c79
|
7
|
+
data.tar.gz: 9e3ace92718b1a365ca18ed1e12b0d4a945ae38b2c417b45188f0982626fbaa679d383ff5f7c82ab59f695d842fba669776bfe69a90ed36759e189f375d537b5
|
data/lib/mixlib/cli.rb
CHANGED
@@ -16,8 +16,8 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
|
19
|
-
require "optparse"
|
20
|
-
|
19
|
+
require "optparse" unless defined?(OptionParser)
|
20
|
+
require_relative "cli/formatter"
|
21
21
|
module Mixlib
|
22
22
|
|
23
23
|
# == Mixlib::CLI
|
@@ -30,8 +30,9 @@ module Mixlib
|
|
30
30
|
# === DSL
|
31
31
|
# When included, Mixlib::CLI also extends the including class with its
|
32
32
|
# ClassMethods, which define the DSL. The primary methods of the DSL are
|
33
|
-
# ClassMethods#option, which defines a command line option
|
34
|
-
# ClassMethods#banner, which defines the "usage" banner
|
33
|
+
# ClassMethods#option, which defines a command line option;
|
34
|
+
# ClassMethods#banner, which defines the "usage" banner;
|
35
|
+
# and ClassMethods#deprecated_option, which defines a deprecated command-line option.
|
35
36
|
#
|
36
37
|
# === Parsing
|
37
38
|
# Command line options are parsed by calling the instance method
|
@@ -51,8 +52,8 @@ module Mixlib
|
|
51
52
|
# contents will be iterated and cloned as well.
|
52
53
|
def deep_dup(object)
|
53
54
|
cloned_object = object.respond_to?(:dup) ? object.dup : object
|
54
|
-
if cloned_object.
|
55
|
-
if cloned_object.
|
55
|
+
if cloned_object.is_a?(Enumerable)
|
56
|
+
if cloned_object.is_a?(Hash)
|
56
57
|
new_hash = cloned_object.class.new
|
57
58
|
cloned_object.each do |key, value|
|
58
59
|
cloned_key = deep_dup(key)
|
@@ -93,14 +94,89 @@ module Mixlib
|
|
93
94
|
# === Parameters
|
94
95
|
# name<Symbol>:: The name of the option to add
|
95
96
|
# args<Hash>:: A hash of arguments for the option, specifying how it should be parsed.
|
96
|
-
#
|
97
|
-
#
|
97
|
+
# Supported arguments:
|
98
|
+
# :short - The short option, just like from optparse. Example: "-l LEVEL"
|
99
|
+
# :long - The long option, just like from optparse. Example: "--level LEVEL"
|
100
|
+
# :description - The description for this item, just like from optparse.
|
101
|
+
# :default - A default value for this option. Default values will be populated
|
102
|
+
# on parse into `config` or `default_default`, depending `use_separate_defaults`
|
103
|
+
# :boolean - indicates the flag is a boolean. You can use this if the flag takes no arguments
|
104
|
+
# The config value will be set to 'true' if the flag is provided on the CLI and this
|
105
|
+
# argument is set to true. The config value will be set to false only
|
106
|
+
# if it has a default value of false
|
107
|
+
# :required - When set, the option is required. If the command is run without this option,
|
108
|
+
# it will print a message informing the user of the missing requirement, and exit. Default is false.
|
109
|
+
# :proc - Proc that will be invoked if the human has specified this option.
|
110
|
+
# Two forms are supported:
|
111
|
+
# Proc/1 - provided value is passed in.
|
112
|
+
# Proc/2 - first argument is provided value. Second is the cli flag option hash.
|
113
|
+
# Both versions return the value to be assigned to the option.
|
114
|
+
# :show_options - this option is designated as one that shows all supported options/help when invoked.
|
115
|
+
# :exit - exit your program with the exit code when this option is given. Example: 0
|
116
|
+
# :in - array containing a list of valid values. The value provided at run-time for the option is
|
117
|
+
# validated against this. If it is not in the list, it will print a message and exit.
|
118
|
+
# :on :head OR :tail - force this option to display at the beginning or end of the
|
119
|
+
# option list, respectively
|
120
|
+
# =
|
121
|
+
# @return <Hash> :: the config hash for the created option
|
122
|
+
# i
|
98
123
|
def option(name, args)
|
99
124
|
@options ||= {}
|
100
|
-
raise(ArgumentError, "Option name must be a symbol") unless name.
|
125
|
+
raise(ArgumentError, "Option name must be a symbol") unless name.is_a?(Symbol)
|
126
|
+
|
101
127
|
@options[name.to_sym] = args
|
102
128
|
end
|
103
129
|
|
130
|
+
# Declare a deprecated option
|
131
|
+
#
|
132
|
+
# Add a deprecated command line option.
|
133
|
+
#
|
134
|
+
# name<Symbol> :: The name of the deprecated option
|
135
|
+
# replacement<Symbol> :: The name of the option that replaces this option.
|
136
|
+
# long<String> :: The original long flag name, or flag name with argument, eg "--user USER"
|
137
|
+
# short<String> :: The original short-form flag name, eg "-u USER"
|
138
|
+
# boolean<String> :: true if this is a boolean flag, eg "--[no-]option".
|
139
|
+
# value_mapper<Proc/1> :: a block that accepts the original value from the deprecated option,
|
140
|
+
# and converts it to a value suitable for the new option.
|
141
|
+
# If not provided, the value provided to the deprecated option will be
|
142
|
+
# assigned directly to the converted option.
|
143
|
+
# keep<Boolean> :: Defaults to true, this ensures that `options[:deprecated_flag]` is
|
144
|
+
# populated when the deprecated flag is used. If set to false,
|
145
|
+
# only the value in `replacement` will be set. Results undefined
|
146
|
+
# if no replacement is provided. You can use this to enforce the transition
|
147
|
+
# to non-deprecated keys in your code.
|
148
|
+
#
|
149
|
+
# === Returns
|
150
|
+
# <Hash> :: The config hash for the created option.
|
151
|
+
def deprecated_option(name,
|
152
|
+
replacement: nil,
|
153
|
+
long: nil,
|
154
|
+
short: nil,
|
155
|
+
boolean: false,
|
156
|
+
value_mapper: nil,
|
157
|
+
keep: true)
|
158
|
+
|
159
|
+
description = if replacement
|
160
|
+
replacement_cfg = options[replacement]
|
161
|
+
display_name = CLI::Formatter.combined_option_display_name(replacement_cfg[:short], replacement_cfg[:long])
|
162
|
+
"This flag is deprecated. Use #{display_name} instead."
|
163
|
+
else
|
164
|
+
"This flag is deprecated and will be removed in a future release."
|
165
|
+
end
|
166
|
+
value_mapper ||= Proc.new { |v| v }
|
167
|
+
|
168
|
+
option(name,
|
169
|
+
long: long,
|
170
|
+
short: short,
|
171
|
+
boolean: boolean,
|
172
|
+
description: description,
|
173
|
+
on: :tail,
|
174
|
+
deprecated: true,
|
175
|
+
keep: keep,
|
176
|
+
replacement: replacement,
|
177
|
+
value_mapper: value_mapper)
|
178
|
+
end
|
179
|
+
|
104
180
|
# Get the hash of current options.
|
105
181
|
#
|
106
182
|
# === Returns
|
@@ -118,7 +194,8 @@ module Mixlib
|
|
118
194
|
# === Returns
|
119
195
|
# @options<Hash>:: The current options hash.
|
120
196
|
def options=(val)
|
121
|
-
raise(ArgumentError, "Options must
|
197
|
+
raise(ArgumentError, "Options must receive a hash") unless val.is_a?(Hash)
|
198
|
+
|
122
199
|
@options = val
|
123
200
|
end
|
124
201
|
|
@@ -181,9 +258,9 @@ module Mixlib
|
|
181
258
|
# === Returns
|
182
259
|
# object<Mixlib::Config>:: Returns an instance of whatever you wanted :)
|
183
260
|
def initialize(*args)
|
184
|
-
@options =
|
185
|
-
@config =
|
186
|
-
@default_config =
|
261
|
+
@options = {}
|
262
|
+
@config = {}
|
263
|
+
@default_config = {}
|
187
264
|
@opt_parser = nil
|
188
265
|
|
189
266
|
# Set the banner
|
@@ -209,7 +286,6 @@ module Mixlib
|
|
209
286
|
config_opts[:show_options] ||= false
|
210
287
|
config_opts[:exit] ||= nil
|
211
288
|
config_opts[:in] ||= nil
|
212
|
-
|
213
289
|
if config_opts.key?(:default)
|
214
290
|
defaults_container[config_key] = config_opts[:default]
|
215
291
|
end
|
@@ -225,25 +301,31 @@ module Mixlib
|
|
225
301
|
#
|
226
302
|
# === Returns
|
227
303
|
# argv<Array>:: Returns any un-parsed elements.
|
228
|
-
def parse_options(argv = ARGV)
|
304
|
+
def parse_options(argv = ARGV, show_deprecations: true)
|
229
305
|
argv = argv.dup
|
230
306
|
opt_parser.parse!(argv)
|
307
|
+
# Do this before our custom validations, so that custom
|
308
|
+
# validations apply to any converted deprecation values;
|
309
|
+
# but after parse! so that everything is populated.
|
310
|
+
handle_deprecated_options(show_deprecations)
|
231
311
|
|
232
312
|
# Deal with any required values
|
233
|
-
options.each do |opt_key,
|
234
|
-
if
|
235
|
-
reqarg =
|
313
|
+
options.each do |opt_key, opt_config|
|
314
|
+
if opt_config[:required] && !config.key?(opt_key)
|
315
|
+
reqarg = opt_config[:short] || opt_config[:long]
|
236
316
|
puts "You must supply #{reqarg}!"
|
237
317
|
puts @opt_parser
|
238
318
|
exit 2
|
239
319
|
end
|
240
|
-
if
|
241
|
-
unless
|
320
|
+
if opt_config[:in]
|
321
|
+
unless opt_config[:in].is_a?(Array)
|
242
322
|
raise(ArgumentError, "Options config key :in must receive an Array")
|
243
323
|
end
|
244
|
-
|
245
|
-
|
246
|
-
|
324
|
+
|
325
|
+
if config[opt_key] && !opt_config[:in].include?(config[opt_key])
|
326
|
+
reqarg = Formatter.combined_option_display_name(opt_config[:short], opt_config[:long])
|
327
|
+
puts "#{reqarg}: #{config[opt_key]} is not one of the allowed values: #{Formatter.friendly_opt_list(opt_config[:in])}"
|
328
|
+
# TODO - get rid of this. nobody wants to be spammed with a ton of information, particularly since we just told them the exact problem and how to fix it.
|
247
329
|
puts @opt_parser
|
248
330
|
exit 2
|
249
331
|
end
|
@@ -267,7 +349,6 @@ module Mixlib
|
|
267
349
|
# Create new options
|
268
350
|
options.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |opt_key, opt_val|
|
269
351
|
opt_args = build_option_arguments(opt_val)
|
270
|
-
|
271
352
|
opt_method = case opt_val[:on]
|
272
353
|
when :on
|
273
354
|
:on
|
@@ -280,7 +361,7 @@ module Mixlib
|
|
280
361
|
end
|
281
362
|
|
282
363
|
parse_block =
|
283
|
-
Proc.new
|
364
|
+
Proc.new do |c|
|
284
365
|
config[opt_key] = if opt_val[:proc]
|
285
366
|
if opt_val[:proc].arity == 2
|
286
367
|
# New hotness to allow for reducer-style procs.
|
@@ -305,15 +386,58 @@ module Mixlib
|
|
305
386
|
end
|
306
387
|
end
|
307
388
|
|
389
|
+
# Iterates through options declared as deprecated,
|
390
|
+
# maps values to their replacement options,
|
391
|
+
# and prints deprecation warnings.
|
392
|
+
#
|
393
|
+
# @return NilClass
|
394
|
+
def handle_deprecated_options(show_deprecations)
|
395
|
+
merge_in_values = {}
|
396
|
+
config.each_key do |opt_key|
|
397
|
+
opt_cfg = options[opt_key]
|
398
|
+
|
399
|
+
# Deprecated entries do not have defaults so no matter what
|
400
|
+
# separate_default_options are set, if we see a 'config'
|
401
|
+
# entry that contains a deprecated indicator, then the option was
|
402
|
+
# explicitly provided by the caller.
|
403
|
+
#
|
404
|
+
# opt_cfg may not exist if an inheriting application
|
405
|
+
# has directly inserted values info config.
|
406
|
+
next unless opt_cfg && opt_cfg[:deprecated]
|
407
|
+
|
408
|
+
replacement_key = opt_cfg[:replacement]
|
409
|
+
if replacement_key
|
410
|
+
# This is the value passed into the deprecated flag. We'll use
|
411
|
+
# the declared value mapper (defaults to return the same value if caller hasn't
|
412
|
+
# provided a mapper).
|
413
|
+
deprecated_val = config[opt_key]
|
414
|
+
|
415
|
+
# We can't modify 'config' since we're iterating it, apply updates
|
416
|
+
# at the end.
|
417
|
+
merge_in_values[replacement_key] = opt_cfg[:value_mapper].call(deprecated_val)
|
418
|
+
config.delete(opt_key) unless opt_cfg[:keep]
|
419
|
+
end
|
420
|
+
|
421
|
+
# Warn about the deprecation.
|
422
|
+
if show_deprecations
|
423
|
+
# Description is also the deprecation message.
|
424
|
+
display_name = CLI::Formatter.combined_option_display_name(opt_cfg[:short], opt_cfg[:long])
|
425
|
+
puts "#{display_name}: #{opt_cfg[:description]}"
|
426
|
+
end
|
427
|
+
end
|
428
|
+
config.merge!(merge_in_values)
|
429
|
+
nil
|
430
|
+
end
|
431
|
+
|
308
432
|
def build_option_arguments(opt_setting)
|
309
|
-
arguments =
|
433
|
+
arguments = []
|
310
434
|
|
311
|
-
arguments << opt_setting[:short] if opt_setting
|
312
|
-
arguments << opt_setting[:long] if opt_setting
|
435
|
+
arguments << opt_setting[:short] if opt_setting[:short]
|
436
|
+
arguments << opt_setting[:long] if opt_setting[:long]
|
313
437
|
if opt_setting.key?(:description)
|
314
438
|
description = opt_setting[:description].dup
|
315
439
|
description << " (required)" if opt_setting[:required]
|
316
|
-
description << " (valid options: #{friendly_opt_list(opt_setting[:in])})" if opt_setting[:in]
|
440
|
+
description << " (valid options: #{Formatter.friendly_opt_list(opt_setting[:in])})" if opt_setting[:in]
|
317
441
|
opt_setting[:description] = description
|
318
442
|
arguments << description
|
319
443
|
end
|
@@ -321,20 +445,9 @@ module Mixlib
|
|
321
445
|
arguments
|
322
446
|
end
|
323
447
|
|
324
|
-
# @private
|
325
|
-
# @param opt_arry [Array]
|
326
|
-
#
|
327
|
-
# @return [String] a friendly quoted list of items complete with "or"
|
328
|
-
def friendly_opt_list(opt_array)
|
329
|
-
opts = opt_array.map { |x| "'#{x}'" }
|
330
|
-
return opts.join(" or ") if opts.size < 3
|
331
|
-
opts[0..-2].join(", ") + ", or " + opts[-1]
|
332
|
-
end
|
333
|
-
|
334
448
|
def self.included(receiver)
|
335
449
|
receiver.extend(Mixlib::CLI::ClassMethods)
|
336
450
|
receiver.extend(Mixlib::CLI::InheritMethods)
|
337
451
|
end
|
338
|
-
|
339
452
|
end
|
340
453
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
module Mixlib
|
3
|
+
module CLI
|
4
|
+
class Formatter
|
5
|
+
# Create a string that includes both versions (short/long) of a flag name
|
6
|
+
# based on on whether short/long/both/neither are provided
|
7
|
+
#
|
8
|
+
# @param short [String] the short name of the option. Can be nil.
|
9
|
+
# @param long [String] the long name of the option. Can be nil.
|
10
|
+
# @return [String] the formatted flag name as described above
|
11
|
+
def self.combined_option_display_name(short, long)
|
12
|
+
usage = ""
|
13
|
+
# short/long may have an argument (--long ARG)
|
14
|
+
# splitting on " " and taking first ensures that we get just
|
15
|
+
# the flag name without the argument if one is present.
|
16
|
+
usage << short.split(" ").first if short
|
17
|
+
usage << "/" if long && short
|
18
|
+
usage << long.split(" ").first if long
|
19
|
+
usage
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param opt_array [Array]
|
23
|
+
#
|
24
|
+
# @return [String] a friendly quoted list of items complete with "or"
|
25
|
+
def self.friendly_opt_list(opt_array)
|
26
|
+
opts = opt_array.map { |x| "'#{x}'" }
|
27
|
+
return opts.join(" or ") if opts.size < 3
|
28
|
+
|
29
|
+
opts[0..-2].join(", ") + ", or " + opts[-1]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/mixlib/cli/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mixlib-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chef Software, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A simple mixin for CLI interfaces, including option parsing
|
14
14
|
email: info@chef.io
|
@@ -19,8 +19,9 @@ files:
|
|
19
19
|
- LICENSE
|
20
20
|
- NOTICE
|
21
21
|
- lib/mixlib/cli.rb
|
22
|
+
- lib/mixlib/cli/formatter.rb
|
22
23
|
- lib/mixlib/cli/version.rb
|
23
|
-
homepage: https://
|
24
|
+
homepage: https://github.com/chef/mixlib-cli
|
24
25
|
licenses:
|
25
26
|
- Apache-2.0
|
26
27
|
metadata: {}
|