cri 2.10.1 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/cri.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'cri/version'
1
+ # frozen_string_literal: true
2
2
 
3
3
  # The namespace for Cri, a library for building easy-to-use command-line tools
4
4
  # with support for nested commands.
@@ -16,17 +16,17 @@ module Cri
16
16
  # command has no supercommand for which to show help.
17
17
  class NoHelpAvailableError < Error
18
18
  end
19
-
20
- autoload 'Command', 'cri/command'
21
- autoload 'StringFormatter', 'cri/string_formatter'
22
- autoload 'CommandDSL', 'cri/command_dsl'
23
- autoload 'CommandRunner', 'cri/command_runner'
24
- autoload 'HelpRenderer', 'cri/help_renderer'
25
- autoload 'OptionParser', 'cri/option_parser'
26
- autoload 'Platform', 'cri/platform'
27
19
  end
28
20
 
29
21
  require 'set'
30
22
 
31
- require 'cri/core_ext'
32
- require 'cri/argument_array'
23
+ require 'colored'
24
+
25
+ require_relative 'cri/version'
26
+ require_relative 'cri/command'
27
+ require_relative 'cri/string_formatter'
28
+ require_relative 'cri/command_dsl'
29
+ require_relative 'cri/command_runner'
30
+ require_relative 'cri/help_renderer'
31
+ require_relative 'cri/option_parser'
32
+ require_relative 'cri/platform'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cri
2
4
  # Cri::Command represents a command that can be executed on the command line.
3
5
  # It is also used for the command-line tool itself.
@@ -357,7 +359,7 @@ module Cri
357
359
  opts.each_pair do |key, value|
358
360
  opt_def = global_option_definitions.find { |o| (o[:long] || o[:short]) == key.to_s }
359
361
  block = opt_def[:block]
360
- block.call(value, self) if block
362
+ block&.call(value, self)
361
363
  end
362
364
  end
363
365
 
@@ -381,11 +383,14 @@ module Cri
381
383
  def handle_parser_errors_while
382
384
  yield
383
385
  rescue Cri::OptionParser::IllegalOptionError => e
384
- warn "#{name}: illegal option -- #{e}"
386
+ warn "#{name}: unrecognised option -- #{e}"
385
387
  raise CriExitException.new(is_error: true)
386
388
  rescue Cri::OptionParser::OptionRequiresAnArgumentError => e
387
389
  warn "#{name}: option requires an argument -- #{e}"
388
390
  raise CriExitException.new(is_error: true)
391
+ rescue Cri::OptionParser::IllegalOptionValueError => e
392
+ warn "#{name}: #{e.message}"
393
+ raise CriExitException.new(is_error: true)
389
394
  end
390
395
  end
391
396
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cri
2
4
  # The command DSL is a class that is used for building and modifying
3
5
  # commands.
@@ -130,6 +132,7 @@ module Cri
130
132
  multiple = params.fetch(:multiple, false)
131
133
  hidden = params.fetch(:hidden, false)
132
134
  default = params.fetch(:default, nil)
135
+ transform = params.fetch(:transform, nil)
133
136
 
134
137
  if short.nil? && long.nil?
135
138
  raise ArgumentError, 'short and long options cannot both be nil'
@@ -148,6 +151,7 @@ module Cri
148
151
  block: block,
149
152
  hidden: hidden,
150
153
  default: default,
154
+ transform: transform,
151
155
  }
152
156
  end
153
157
  alias opt option
@@ -169,6 +173,8 @@ module Cri
169
173
  #
170
174
  # @return [void]
171
175
  #
176
+ # @deprecated
177
+ #
172
178
  # @see {#option}
173
179
  def required(short, long, desc, params = {}, &block)
174
180
  params = params.merge(argument: :required)
@@ -216,6 +222,8 @@ module Cri
216
222
  #
217
223
  # @return [void]
218
224
  #
225
+ # @deprecated
226
+ #
219
227
  # @see {#option}
220
228
  def optional(short, long, desc, params = {}, &block)
221
229
  params = params.merge(argument: :optional)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cri
2
4
  # A command runner is responsible for the execution of a command. Using it
3
5
  # is optional, but it is useful for commands whose execution block is large.
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  name 'help'
2
4
  usage 'help [command_name]'
3
5
  summary 'show help'
4
- description <<-EOS
5
- Show help for the given command, or show general help. When no command is
6
- given, a list of available commands is displayed, as well as a list of global
7
- command-line options. When a command is given, a command description, as well
8
- as command-specific command-line options, are shown.
9
- EOS
6
+ description <<~DESC
7
+ Show help for the given command, or show general help. When no command is
8
+ given, a list of available commands is displayed, as well as a list of global
9
+ command-line options. When a command is given, a command description, as well
10
+ as command-specific command-line options, are shown.
11
+ DESC
10
12
 
11
13
  flag :v, :verbose, 'show more detailed help'
12
14
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  flag :h, :help, 'show help for this command' do |_value, cmd|
2
4
  puts cmd.help
3
5
  raise CriExitException.new(is_error: false)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cri
2
4
  # The {HelpRenderer} class is responsible for generating a string containing
3
5
  # the help for a given command, intended to be printed on the command line.
@@ -25,7 +27,7 @@ module Cri
25
27
 
26
28
  # @return [String] The help text for this command
27
29
  def render
28
- text = ''
30
+ text = +''
29
31
 
30
32
  append_summary(text)
31
33
  append_usage(text)
@@ -39,7 +41,7 @@ module Cri
39
41
  private
40
42
 
41
43
  def fmt
42
- @_formatter ||= Cri::StringFormatter.new
44
+ @fmt ||= Cri::StringFormatter.new
43
45
  end
44
46
 
45
47
  def append_summary(text)
@@ -87,9 +89,9 @@ module Cri
87
89
  shown_subcommands.sort_by(&:name).each do |cmd|
88
90
  text <<
89
91
  format(
90
- " %-#{length + DESC_INDENT}s %s\n",
91
- fmt.format_as_command(cmd.name, @io),
92
- cmd.summary,
92
+ " %<name>-#{length + DESC_INDENT}s %<summary>s\n",
93
+ name: fmt.format_as_command(cmd.name, @io),
94
+ summary: cmd.summary,
93
95
  )
94
96
  end
95
97
 
@@ -106,7 +108,7 @@ module Cri
106
108
 
107
109
  def length_for_opt_defs(opt_defs)
108
110
  opt_defs.map do |opt_def|
109
- string = ''
111
+ string = +''
110
112
 
111
113
  # Always pretend there is a short option
112
114
  string << '-X'
@@ -189,7 +191,7 @@ module Cri
189
191
  short_value_postfix = short_value_postfix_for(opt_def)
190
192
  long_value_postfix = long_value_postfix_for(opt_def)
191
193
 
192
- opt_text = ''
194
+ opt_text = +''
193
195
  opt_text_len = 0
194
196
  if opt_def[:short]
195
197
  opt_text << fmt.format_as_option('-' + opt_def[:short], @io)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cri
2
4
  # Cri::OptionParser is used for parsing command-line options.
3
5
  #
@@ -57,6 +59,23 @@ module Cri
57
59
  class IllegalOptionError < Cri::Error
58
60
  end
59
61
 
62
+ # Error that will be raised when an option with an invalid or
63
+ # non-transformable value is encountered.
64
+ class IllegalOptionValueError < Cri::Error
65
+ attr_reader :definition
66
+ attr_reader :value
67
+
68
+ def initialize(definition, value)
69
+ @definition = definition
70
+ @value = value
71
+ end
72
+
73
+ def message
74
+ name = @definition[:long] ? '--' + @definition[:long] : '-' + @definition[:short]
75
+ "invalid value #{value.inspect} for #{name} option"
76
+ end
77
+ end
78
+
60
79
  # Error that will be raised when an option without argument is
61
80
  # encountered.
62
81
  class OptionRequiresAnArgumentError < Cri::Error
@@ -128,7 +147,7 @@ module Cri
128
147
  #
129
148
  # @return [Array] The already parsed arguments.
130
149
  def arguments
131
- ArgumentArray.new(@raw_arguments).freeze
150
+ @raw_arguments.reject { |a| a == '--' }.freeze
132
151
  end
133
152
 
134
153
  # @return [Boolean] true if the parser is running, false otherwise.
@@ -187,18 +206,18 @@ module Cri
187
206
  @definitions.each { |d| add_default_option(d) }
188
207
  end
189
208
 
190
- def handle_dashdash(e)
191
- add_argument(e)
209
+ def handle_dashdash(elem)
210
+ add_argument(elem)
192
211
  @no_more_options = true
193
212
  end
194
213
 
195
- def handle_dashdash_option(e)
214
+ def handle_dashdash_option(elem)
196
215
  # Get option key, and option value if included
197
- if e =~ /^--([^=]+)=(.+)$/
216
+ if elem =~ /^--([^=]+)=(.+)$/
198
217
  option_key = Regexp.last_match[1]
199
218
  option_value = Regexp.last_match[2]
200
219
  else
201
- option_key = e[2..-1]
220
+ option_key = elem[2..-1]
202
221
  option_value = nil
203
222
  end
204
223
 
@@ -220,9 +239,9 @@ module Cri
220
239
  end
221
240
  end
222
241
 
223
- def handle_dash_option(e)
242
+ def handle_dash_option(elem)
224
243
  # Get option keys
225
- option_keys = e[1..-1].scan(/./)
244
+ option_keys = elem[1..-1].scan(/./)
226
245
 
227
246
  # For each key
228
247
  option_keys.each do |option_key|
@@ -258,9 +277,11 @@ module Cri
258
277
  option_value
259
278
  end
260
279
 
261
- def add_option(definition, value)
280
+ def add_option(definition, value, transform: true)
262
281
  key = key_for(definition)
263
282
 
283
+ value = transform ? transform_value(definition, value) : value
284
+
264
285
  if definition[:multiple]
265
286
  options[key] ||= []
266
287
  options[key] << value
@@ -268,7 +289,7 @@ module Cri
268
289
  options[key] = value
269
290
  end
270
291
 
271
- delegate.option_added(key, value, self) unless delegate.nil?
292
+ delegate&.option_added(key, value, self)
272
293
  end
273
294
 
274
295
  def add_default_option(definition)
@@ -278,11 +299,20 @@ module Cri
278
299
  value = definition[:default]
279
300
  return unless value
280
301
 
281
- if definition[:multiple]
282
- options[key] ||= []
283
- options[key] << value
302
+ add_option(definition, value, transform: false)
303
+ end
304
+
305
+ def transform_value(definition, value)
306
+ transformer = definition[:transform]
307
+
308
+ if transformer
309
+ begin
310
+ transformer.call(value)
311
+ rescue StandardError
312
+ raise IllegalOptionValueError.new(definition, value)
313
+ end
284
314
  else
285
- options[key] = value
315
+ value
286
316
  end
287
317
  end
288
318
 
@@ -294,7 +324,7 @@ module Cri
294
324
  @raw_arguments << value
295
325
 
296
326
  unless value == '--'
297
- delegate.argument_added(value, self) unless delegate.nil?
327
+ delegate&.argument_added(value, self)
298
328
  end
299
329
  end
300
330
  end
@@ -1,4 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cri
4
+ # Provides tools to detect platform and environment configuration (e.g. is
5
+ # color support available?)
6
+ #
7
+ # @api private
2
8
  module Platform
3
9
  # @return [Boolean] true if the current platform is Windows, false
4
10
  # otherwise.
@@ -1,14 +1,18 @@
1
- require 'colored'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Cri
4
+ # Used for formatting strings (e.g. converting to paragraphs, wrapping,
5
+ # formatting as title)
6
+ #
7
+ # @api private
4
8
  class StringFormatter
5
9
  # Extracts individual paragraphs (separated by two newlines).
6
10
  #
7
11
  # @param [String] s The string to format
8
12
  #
9
13
  # @return [Array<String>] A list of paragraphs in the string
10
- def to_paragraphs(s)
11
- lines = s.scan(/([^\n]+\n|[^\n]*$)/).map { |l| l[0].strip }
14
+ def to_paragraphs(str)
15
+ lines = str.scan(/([^\n]+\n|[^\n]*$)/).map { |l| l[0].strip }
12
16
 
13
17
  paragraphs = [[]]
14
18
  lines.each do |line|
@@ -36,11 +40,11 @@ module Cri
36
40
  # line is already indented
37
41
  #
38
42
  # @return [String] The word-wrapped and indented string
39
- def wrap_and_indent(s, width, indentation, first_line_already_indented = false)
43
+ def wrap_and_indent(str, width, indentation, first_line_already_indented = false)
40
44
  indented_width = width - indentation
41
45
  indent = ' ' * indentation
42
46
  # Split into paragraphs
43
- paragraphs = to_paragraphs(s)
47
+ paragraphs = to_paragraphs(str)
44
48
 
45
49
  # Wrap and indent each paragraph
46
50
  text = paragraphs.map do |paragraph|
@@ -76,11 +80,11 @@ module Cri
76
80
  #
77
81
  # @return [String] The string, formatted to be used as a title in a section
78
82
  # in the help
79
- def format_as_title(s, io)
83
+ def format_as_title(str, io)
80
84
  if Cri::Platform.color?(io)
81
- s.upcase.red.bold
85
+ str.upcase.red.bold
82
86
  else
83
- s.upcase
87
+ str.upcase
84
88
  end
85
89
  end
86
90
 
@@ -88,11 +92,11 @@ module Cri
88
92
  #
89
93
  # @return [String] The string, formatted to be used as the name of a command
90
94
  # in the help
91
- def format_as_command(s, io)
95
+ def format_as_command(str, io)
92
96
  if Cri::Platform.color?(io)
93
- s.green
97
+ str.green
94
98
  else
95
- s
99
+ str
96
100
  end
97
101
  end
98
102
 
@@ -100,11 +104,11 @@ module Cri
100
104
  #
101
105
  # @return [String] The string, formatted to be used as an option definition
102
106
  # of a command in the help
103
- def format_as_option(s, io)
107
+ def format_as_option(str, io)
104
108
  if Cri::Platform.color?(io)
105
- s.yellow
109
+ str.yellow
106
110
  else
107
- s
111
+ str
108
112
  end
109
113
  end
110
114
  end
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cri
2
4
  # The current Cri version.
3
- VERSION = '2.10.1'.freeze
5
+ VERSION = '2.11.0'
4
6
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'coveralls'
2
4
  Coveralls.wear!
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'helper'
2
4
 
3
5
  module Cri
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'helper'
2
4
 
3
5
  module Cri
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'helper'
2
4
 
3
5
  module Cri
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'helper'
2
4
 
3
5
  module Cri
@@ -16,6 +18,7 @@ module Cri
16
18
  optional :c, :ccc, 'opt c'
17
19
  flag :d, :ddd, 'opt d'
18
20
  forbidden :e, :eee, 'opt e'
21
+ required :t, :transform, 'opt t', transform: method(:Integer)
19
22
 
20
23
  run do |opts, args, c|
21
24
  $stdout.puts "Awesome #{c.name}!"
@@ -175,7 +178,7 @@ module Cri
175
178
  end
176
179
 
177
180
  assert_equal [], lines(out)
178
- assert_equal ['moo: illegal option -- z'], lines(err)
181
+ assert_equal ['moo: unrecognised option -- z'], lines(err)
179
182
  end
180
183
 
181
184
  def test_invoke_simple_with_illegal_opt_no_exit
@@ -184,7 +187,28 @@ module Cri
184
187
  end
185
188
 
186
189
  assert_equal [], lines(out)
187
- assert_equal ['moo: illegal option -- z'], lines(err)
190
+ assert_equal ['moo: unrecognised option -- z'], lines(err)
191
+ end
192
+
193
+ def test_invoke_simple_with_invalid_value_for_opt
194
+ out, err = capture_io_while do
195
+ err = assert_raises SystemExit do
196
+ simple_cmd.run(%w[-t nope])
197
+ end
198
+ assert_equal 1, err.status
199
+ end
200
+
201
+ assert_equal [], lines(out)
202
+ assert_equal ['moo: invalid value "nope" for --transform option'], lines(err)
203
+ end
204
+
205
+ def test_invoke_simple_with_invalid_value_for_opt_no_exit
206
+ out, err = capture_io_while do
207
+ simple_cmd.run(%w[-t nope], {}, hard_exit: false)
208
+ end
209
+
210
+ assert_equal [], lines(out)
211
+ assert_equal ['moo: invalid value "nope" for --transform option'], lines(err)
188
212
  end
189
213
 
190
214
  def test_invoke_simple_with_opt_with_block
@@ -568,14 +592,14 @@ module Cri
568
592
  cmd = Cri::Command.define do
569
593
  name 'moo'
570
594
  run do |_opts, args|
571
- puts "args=#{args.join(',')} args.raw=#{args.raw.join(',')}"
595
+ puts "args=#{args.join(',')}"
572
596
  end
573
597
  end
574
598
 
575
599
  out, _err = capture_io_while do
576
600
  cmd.run(%w[foo -- bar])
577
601
  end
578
- assert_equal "args=foo,bar args.raw=foo,--,bar\n", out
602
+ assert_equal "args=foo,bar\n", out
579
603
  end
580
604
 
581
605
  def test_run_without_block
@@ -593,7 +617,7 @@ module Cri
593
617
  name 'moo'
594
618
  runner(Class.new(Cri::CommandRunner) do
595
619
  def run
596
- puts "args=#{arguments.join(',')} args.raw=#{arguments.raw.join(',')}"
620
+ puts "args=#{arguments.join(',')}"
597
621
  end
598
622
  end)
599
623
  end
@@ -601,7 +625,7 @@ module Cri
601
625
  out, _err = capture_io_while do
602
626
  cmd.run(%w[foo -- bar])
603
627
  end
604
- assert_equal "args=foo,bar args.raw=foo,--,bar\n", out
628
+ assert_equal "args=foo,bar\n", out
605
629
  end
606
630
 
607
631
  def test_compare