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 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: {}