mixlib-cli 2.0.6 → 2.1.0

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: fe2f048e8780700eefac7a4cdf7f7208e5026e46fccd8ba1d174d0ac2392c789
4
+ data.tar.gz: f16971b7985e2ed441ae186f1aecb8decf8b3e230912fd3690e00b2c3d5ff42b
5
5
  SHA512:
6
- metadata.gz: aaf246f2fbcadda3e6ad29cdd9b2e0b6bc5b68b4471dbad56828ef499f2cfbbee9c00ffe7ca9543ef7c38d8f260711766a759bd7117885f758edd387fa9fe8ef
7
- data.tar.gz: 944784207d6b274ec01b87bec93c23a98cfd1357a6a3f5cdf2c38de6fb3e71caf8c7f9c020ad7fc5aa539c459622003529e8c964461ba11f12d35c07c5d07d8f
6
+ metadata.gz: ae94cbf04f14035a78a90ed4a76b2682cc3a59c794c2b7df3148d38946a7fead9cb5bb65d95599b34fdcc215b513441393b4f3b382606d9675088a418b7f6e16
7
+ data.tar.gz: 6b922bc226625b74d7a2ad3b998a388a0347ac8e0438a6e7c563b98e43d9fb7cc22589c2eca3a398dad99567d8573c2a2725de37edde5f942a505c8b834878a0
@@ -17,7 +17,7 @@
17
17
  #
18
18
 
19
19
  require "optparse"
20
-
20
+ require "mixlib/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
@@ -93,14 +94,88 @@ 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
125
  raise(ArgumentError, "Option name must be a symbol") unless name.kind_of?(Symbol)
101
126
  @options[name.to_sym] = args
102
127
  end
103
128
 
129
+ # Declare a deprecated option
130
+ #
131
+ # Add a deprecated command line option.
132
+ #
133
+ # name<Symbol> :: The name of the deprecated option
134
+ # replacement<Symbol> :: The name of the option that replaces this option.
135
+ # long<String> :: The original long flag name, or flag name with argument, eg "--user USER"
136
+ # short<String> :: The original short-form flag name, eg "-u USER"
137
+ # boolean<String> :: true if this is a boolean flag, eg "--[no-]option".
138
+ # value_mapper<Proc/1> :: a block that accepts the original value from the deprecated option,
139
+ # and converts it to a value suitable for the new option.
140
+ # If not provided, the value provided to the deprecated option will be
141
+ # assigned directly to the converted option.
142
+ # keep<Boolean> :: Defaults to true, this ensure sthat `options[:deprecated_flag]` is
143
+ # populated when the deprecated flag is used. If set to false,
144
+ # only the value in `replacement` will be set. Results undefined
145
+ # if no replacement is provided. You can use this to enforce the transition
146
+ # to non-deprecated keys in your code.
147
+ #
148
+ # === Returns
149
+ # <Hash> :: The config hash for the created option.
150
+ def deprecated_option(name,
151
+ replacement: nil,
152
+ long: nil,
153
+ short: nil,
154
+ boolean: false,
155
+ value_mapper: nil,
156
+ keep: true)
157
+
158
+ description = if replacement
159
+ replacement_cfg = options[replacement]
160
+ display_name = CLI::Formatter.combined_option_display_name(replacement_cfg[:short], replacement_cfg[:long])
161
+ "This flag is deprecated. Use #{display_name} instead."
162
+ else
163
+ "This flag is deprecated and will be removed in a future release."
164
+ end
165
+ value_mapper ||= Proc.new { |v| v }
166
+
167
+ option(name,
168
+ long: long,
169
+ short: short,
170
+ boolean: boolean,
171
+ description: description,
172
+ on: :tail,
173
+ deprecated: true,
174
+ keep: keep,
175
+ replacement: replacement,
176
+ value_mapper: value_mapper)
177
+ end
178
+
104
179
  # Get the hash of current options.
105
180
  #
106
181
  # === Returns
@@ -209,7 +284,6 @@ module Mixlib
209
284
  config_opts[:show_options] ||= false
210
285
  config_opts[:exit] ||= nil
211
286
  config_opts[:in] ||= nil
212
-
213
287
  if config_opts.key?(:default)
214
288
  defaults_container[config_key] = config_opts[:default]
215
289
  end
@@ -225,25 +299,30 @@ module Mixlib
225
299
  #
226
300
  # === Returns
227
301
  # argv<Array>:: Returns any un-parsed elements.
228
- def parse_options(argv = ARGV)
302
+ def parse_options(argv = ARGV, show_deprecations: true)
229
303
  argv = argv.dup
230
304
  opt_parser.parse!(argv)
305
+ # Do this before our custom validations, so that custom
306
+ # validations apply to any converted deprecation values;
307
+ # but after parse! so that everything is populated.
308
+ handle_deprecated_options(show_deprecations)
231
309
 
232
310
  # 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]
311
+ options.each do |opt_key, opt_config|
312
+ if opt_config[:required] && !config.key?(opt_key)
313
+ reqarg = opt_config[:short] || opt_config[:long]
236
314
  puts "You must supply #{reqarg}!"
237
315
  puts @opt_parser
238
316
  exit 2
239
317
  end
240
- if opt_value[:in]
241
- unless opt_value[:in].kind_of?(Array)
318
+ if opt_config[:in]
319
+ unless opt_config[:in].kind_of?(Array)
242
320
  raise(ArgumentError, "Options config key :in must receive an Array")
243
321
  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])}"
322
+ if config[opt_key] && !opt_config[:in].include?(config[opt_key])
323
+ reqarg = Formatter.combined_option_display_name(opt_config[:short], opt_config[:long])
324
+ puts "#{reqarg}: #{config[opt_key]} is not one of the allowed values: #{Formatter.friendly_opt_list(opt_config[:in])}"
325
+ # 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
326
  puts @opt_parser
248
327
  exit 2
249
328
  end
@@ -267,7 +346,6 @@ module Mixlib
267
346
  # Create new options
268
347
  options.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |opt_key, opt_val|
269
348
  opt_args = build_option_arguments(opt_val)
270
-
271
349
  opt_method = case opt_val[:on]
272
350
  when :on
273
351
  :on
@@ -305,15 +383,53 @@ module Mixlib
305
383
  end
306
384
  end
307
385
 
386
+ # Iterates through options declared as deprecated,
387
+ # maps values to their replacement options,
388
+ # and prints deprecation warnings.
389
+ #
390
+ # @return NilClass
391
+ def handle_deprecated_options(show_deprecations)
392
+ merge_in_values = {}
393
+ config.each_key do |opt_key|
394
+ opt_cfg = options[opt_key]
395
+ # Deprecated entries do not have defaults so no matter what
396
+ # separate_default_options are set, if we see a 'config'
397
+ # entry that contains a deprecated indicator, then the option was
398
+ # explicitly provided by the caller.
399
+ next unless opt_cfg[:deprecated]
400
+ replacement_key = opt_cfg[:replacement]
401
+ if replacement_key
402
+ # This is the value passed into the deprecated flag. We'll use
403
+ # the declared value mapper (defaults to return the same value if caller hasn't
404
+ # provided a mapper).
405
+ deprecated_val = config[opt_key]
406
+
407
+ # We can't modify 'config' since we're iterating it, apply updates
408
+ # at the end.
409
+ merge_in_values[replacement_key] = opt_cfg[:value_mapper].call(deprecated_val)
410
+ config.delete(opt_key) unless opt_cfg[:keep]
411
+ end
412
+
413
+ # Warn about the deprecation.
414
+ if show_deprecations
415
+ # Description is also the deprecation message.
416
+ display_name = CLI::Formatter.combined_option_display_name(opt_cfg[:short], opt_cfg[:long])
417
+ puts "#{display_name}: #{opt_cfg[:description]}"
418
+ end
419
+ end
420
+ config.merge!(merge_in_values)
421
+ nil
422
+ end
423
+
308
424
  def build_option_arguments(opt_setting)
309
425
  arguments = Array.new
310
426
 
311
- arguments << opt_setting[:short] if opt_setting.key?(:short)
312
- arguments << opt_setting[:long] if opt_setting.key?(:long)
427
+ arguments << opt_setting[:short] if opt_setting[:short]
428
+ arguments << opt_setting[:long] if opt_setting[:long]
313
429
  if opt_setting.key?(:description)
314
430
  description = opt_setting[:description].dup
315
431
  description << " (required)" if opt_setting[:required]
316
- description << " (valid options: #{friendly_opt_list(opt_setting[:in])})" if opt_setting[:in]
432
+ description << " (valid options: #{Formatter.friendly_opt_list(opt_setting[:in])})" if opt_setting[:in]
317
433
  opt_setting[:description] = description
318
434
  arguments << description
319
435
  end
@@ -321,20 +437,9 @@ module Mixlib
321
437
  arguments
322
438
  end
323
439
 
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
440
  def self.included(receiver)
335
441
  receiver.extend(Mixlib::CLI::ClassMethods)
336
442
  receiver.extend(Mixlib::CLI::InheritMethods)
337
443
  end
338
-
339
444
  end
340
445
  end
@@ -0,0 +1,32 @@
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_arry [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
+ opts[0..-2].join(", ") + ", or " + opts[-1]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,5 @@
1
1
  module Mixlib
2
2
  module CLI
3
- VERSION = "2.0.6".freeze
3
+ VERSION = "2.1.0".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.0
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: 2019-06-07 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: {}