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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68387a8a44b1950e412ec9223768a222b4c1660aa020612acab1469d30ffb329
4
- data.tar.gz: 5aca34f4245d223175b88531c14950652211defd8fbe39ebdbac73d79e33f59d
3
+ metadata.gz: d4d1591454c7940424cf9b65bc490f10e651dfd09a54effab6665bcdb08d5d60
4
+ data.tar.gz: 8225a500779099fa3f0b7103a396f428dadccbd24b393acd8b3a2523c4e6212a
5
5
  SHA512:
6
- metadata.gz: aaf246f2fbcadda3e6ad29cdd9b2e0b6bc5b68b4471dbad56828ef499f2cfbbee9c00ffe7ca9543ef7c38d8f260711766a759bd7117885f758edd387fa9fe8ef
7
- data.tar.gz: 944784207d6b274ec01b87bec93c23a98cfd1357a6a3f5cdf2c38de6fb3e71caf8c7f9c020ad7fc5aa539c459622003529e8c964461ba11f12d35c07c5d07d8f
6
+ metadata.gz: dc73cfd3404053a3fbf661a217a7198957677e3e229d143f95b1d69cf5da5f8f534835979a8b0979b07ad7f979e4245e34b1522d96b1c94a48197aab0a370c79
7
+ data.tar.gz: 9e3ace92718b1a365ca18ed1e12b0d4a945ae38b2c417b45188f0982626fbaa679d383ff5f7c82ab59f695d842fba669776bfe69a90ed36759e189f375d537b5
@@ -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, and
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.kind_of?(Enumerable)
55
- if cloned_object.kind_of?(Hash)
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
- # === Returns
97
- # true:: Always returns true.
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.kind_of?(Symbol)
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 recieve a hash") unless val.kind_of?(Hash)
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 = Hash.new
185
- @config = Hash.new
186
- @default_config = Hash.new
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, opt_value|
234
- if opt_value[:required] && !config.key?(opt_key)
235
- reqarg = opt_value[:short] || opt_value[:long]
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 opt_value[:in]
241
- unless opt_value[:in].kind_of?(Array)
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
- if config[opt_key] && !opt_value[:in].include?(config[opt_key])
245
- reqarg = opt_value[:short] || opt_value[:long]
246
- puts "#{reqarg}: #{config[opt_key]} is not one of the allowed values: #{friendly_opt_list(opt_value[:in])}"
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() do |c|
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 = Array.new
433
+ arguments = []
310
434
 
311
- arguments << opt_setting[:short] if opt_setting.key?(:short)
312
- arguments << opt_setting[:long] if opt_setting.key?(:long)
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
@@ -1,5 +1,5 @@
1
1
  module Mixlib
2
2
  module CLI
3
- VERSION = "2.0.6".freeze
3
+ VERSION = "2.1.8".freeze
4
4
  end
5
5
  end
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.0.6
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: 2019-05-14 00:00:00.000000000 Z
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://www.github.com/mixlib-cli
24
+ homepage: https://github.com/chef/mixlib-cli
24
25
  licenses:
25
26
  - Apache-2.0
26
27
  metadata: {}