mixlib-cli 2.0.6 → 2.1.0

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