mixlib-cli 2.0.6 → 2.1.8
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/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: {}
|