cri 2.4.1 → 2.5.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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NmEyNjRhYjcyOTg2MzdiNWM1MDA5OWJiYTM0ZWNiMGIzYWM1ZjAxNw==
4
+ YTY4ZDU4NjdmNGI4NDNhM2UxMTA1YTA1MDBjZmEzYWZiNDgwOTY0ZA==
5
5
  data.tar.gz: !binary |-
6
- MGUyZjJjNjdkMGY2ZjAxYWZjYzdjMzgzMTAzOWZiMzgwZWYzYTkzOA==
6
+ MjkxMmY1ZDA1M2ExNDUxYmUxOTYyNjVjZTE3OWExM2Y1NmVhODg2Mg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MjhlMjE2ODU5OTNiODMxZTY4ZGFlMzA3YjM0OGY3MDRiYmI2MzUwMjNhMTY1
10
- YmY2YTZkODk2NGEyNjYwMmZiOTM0ZjQ2ZDQ3YWE5NDIxMWNkZGZhY2UzNjVk
11
- MmJmNDI0ZjYwMDQ5ODYyNzE4N2QwNjQ3NzVlMTJmZmZlZTFlODg=
9
+ MTNjMmU4ZTViYjg2YzFiYjI5MjZiZTcyNzViNGYyYTEzZjdiMzY5MTc4Mzlk
10
+ MWIwYWNlNzI4Njk3Y2JlYTFkNDI4NDkyNGQ0YmVhMWE5Mzk1YjFhM2VjZDBm
11
+ ZjMxZmYzMDVkMzM2MGQ1OTVhYmI4NDUxMTFiNWEyZDU5MGQ5ZWE=
12
12
  data.tar.gz: !binary |-
13
- OGZjNzRmYzk5ZmYzMmVhZDlhOTcyM2IzOGY2OTZhOWQ5YmFmMzdjODZmYzk2
14
- MjhmMDkzOTQ3Yjc2Mzc4MjExMTg4ZjU4ZTM5N2YzNTgwZWViMTgxZTdjZjJl
15
- NTYxYWJjOGE5ZDM2MWY3NzQ2M2FjYWU0NjEyNGY3YmZmOGQ5MmI=
13
+ MmEwYjAzNDVhYzg0MWExM2MyODhmMzJlMDE1ZDkwY2U4Y2U2ZGFkNjYxZjk5
14
+ YjYxNGMxNTJlYjk5N2U0MDRkYzYyNDg3Mjc0NjlhODg5MDkyNGFmNWZlYTU2
15
+ YjRlY2EyNGU0NjlhYWM2NTMzZmE1MGU1MjRkMDRkNjZiODFiYjc=
@@ -1,16 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cri (2.4.1)
4
+ cri (2.5.0)
5
5
  colored (>= 1.2)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  colored (1.2)
11
- minitest (5.0.6)
12
- rake (10.1.0)
13
- yard (0.8.7)
11
+ minitest (5.3.0)
12
+ rake (10.1.1)
13
+ yard (0.8.7.3)
14
14
 
15
15
  PLATFORMS
16
16
  ruby
data/NEWS.md CHANGED
@@ -1,6 +1,12 @@
1
1
  Cri News
2
2
  ========
3
3
 
4
+ 2.5.0
5
+ -----
6
+
7
+ * Made the default help command handle subcommands
8
+ * Added `#raw` method to argument arrays, returning all arguments including `--`
9
+
4
10
  2.4.1
5
11
  -----
6
12
 
@@ -0,0 +1,158 @@
1
+ = Cri =
2
+
3
+ Cri is a library for building easy-to-use commandline tools with support for
4
+ nested commands.
5
+
6
+ == Usage ==
7
+
8
+ The central concept in Cri is the _command_, which has option definitions as
9
+ well as code for actually executing itself. In Cri, the commandline tool
10
+ itself is a command as well.
11
+
12
+ Here’s a sample command definition:
13
+
14
+ [source,ruby]
15
+ --------------------------------------------------------------------------------
16
+ command = Cri::Command.define do
17
+ name 'dostuff'
18
+ usage 'dostuff [options]'
19
+ aliases :ds, :stuff
20
+ summary 'does stuff'
21
+ description 'This command does a lot of stuff. I really mean a lot.'
22
+
23
+ flag :h, :help, 'show help for this command' do |value, cmd|
24
+ puts cmd.help
25
+ exit 0
26
+ end
27
+ flag nil, :more, 'do even more stuff'
28
+ option :s, :stuff, 'specify stuff to do', :argument => :required
29
+
30
+ run do |opts, args, cmd|
31
+ stuff = opts.fetch(:stuff, 'generic stuff')
32
+ puts "Doing #{stuff}!"
33
+
34
+ if opts[:more]
35
+ puts 'Doing it even more!'
36
+ end
37
+ end
38
+ end
39
+ --------------------------------------------------------------------------------
40
+
41
+ To run this command, invoke the `#run` method with the raw arguments. For
42
+ example, for a root command (the commandline tool itself), the command could
43
+ be called like this:
44
+
45
+ [source,ruby]
46
+ --------------------------------------------------------------------------------
47
+ command.run(ARGV)
48
+ --------------------------------------------------------------------------------
49
+
50
+ Each command has automatically generated help. This help can be printed using
51
+ `Cri::Command#help`; something like this will be shown:
52
+
53
+ --------------------------------------------------------------------------------
54
+ usage: dostuff [options]
55
+
56
+ does stuff
57
+
58
+ This command does a lot of stuff. I really mean a lot.
59
+
60
+ options:
61
+
62
+ -h --help show help for this command
63
+ --more do even more stuff
64
+ -s --stuff specify stuff to do
65
+ --------------------------------------------------------------------------------
66
+
67
+ Let’s disect the command definition and start with the first five lines:
68
+
69
+ [source,ruby]
70
+ --------------------------------------------------------------------------------
71
+ name 'dostuff'
72
+ usage 'dostuff [options]'
73
+ aliases :ds, :stuff
74
+ summary 'does stuff'
75
+ description 'This command does a lot of stuff. I really mean a lot.'
76
+ --------------------------------------------------------------------------------
77
+
78
+ These lines of the command definition specify the name of the command (or the
79
+ commandline tool, if the command is the root command), the usage, a list of
80
+ aliases that can be used to call this command, a one-line summary and a (long)
81
+ description. The usage should not include a “usage:” prefix nor the name of
82
+ the supercommand, because the latter will be automatically prepended.
83
+
84
+ Aliases don’t make sense for root commands, but for subcommands they do.
85
+
86
+ The next few lines contain the command’s option definitions:
87
+
88
+ [source,ruby]
89
+ --------------------------------------------------------------------------------
90
+ flag :h, :help, 'show help for this command' do |value, cmd|
91
+ puts cmd.help
92
+ exit 0
93
+ end
94
+ flag nil, :more, 'do even more stuff'
95
+ option :s, :stuff, 'specify stuff to do', :argument => :required
96
+ --------------------------------------------------------------------------------
97
+
98
+ Options can be defined using the following methods:
99
+
100
+ * `Cri::CommandDSL#option` or `Cri::CommandDSL#opt`
101
+ * `Cri::CommandDSL#flag` (implies no arguments passed to option)
102
+ * `Cri::CommandDSL#required` (implies required argument)
103
+ * `Cri::CommandDSL#optional` (implies optional argument)
104
+
105
+ All these methods take the short option form as their first argument, and a
106
+ long option form as their second argument. Either the short or the long form
107
+ can be nil, but not both (because that would not make any sense). In the
108
+ example above, the `--more` option has no short form.
109
+
110
+ Each of the above methods also take a block, which will be executed when the
111
+ option is found. The argument to the block are the option value (`true` in
112
+ case the option does not have an argument) and the command.
113
+
114
+ The last part of the command defines the execution itself:
115
+
116
+ [source,ruby]
117
+ --------------------------------------------------------------------------------
118
+ run do |opts, args, cmd|
119
+ stuff = opts.fetch(:stuff, 'generic stuff')
120
+ puts "Doing #{stuff}!"
121
+
122
+ if opts[:more]
123
+ puts 'Doing it even more!'
124
+ end
125
+ end
126
+ --------------------------------------------------------------------------------
127
+
128
+ The +Cri::CommandDSL#run+ method takes a block with the actual code to
129
+ execute. This block takes three arguments: the options, any arguments passed
130
+ to the command, and the command itself.
131
+
132
+ Instead of defining a run block, it is possible to declare a class, the
133
+ _command runner_ class (`Cri::CommandRunner`) that will perform the actual
134
+ execution of the command. This makes it easier to break up large run blocks
135
+ into manageable pieces.
136
+
137
+ Commands can have subcommands. For example, the `git` commandline tool would be
138
+ represented by a command that has subcommands named `commit`, `add`, and so on.
139
+ Commands with subcommands do not use a run block; execution will always be
140
+ dispatched to a subcommand (or none, if no subcommand is found).
141
+
142
+ To add a command as a subcommand to another command, use the
143
+ `Cri::Command#add_command` method, like this:
144
+
145
+ [source,ruby]
146
+ --------------------------------------------------------------------------------
147
+ root_cmd.add_command(cmd_add)
148
+ root_cmd.add_command(cmd_commit)
149
+ root.cmd.add_command(cmd_init)
150
+ --------------------------------------------------------------------------------
151
+
152
+ == Contributors ==
153
+
154
+ * Toon Willems
155
+ * Ken Coar
156
+
157
+ Thanks for Lee “injekt” Jarvis for link:https://github.com/injekt/slop[Slop],
158
+ which has inspired the design of Cri 2.0.
@@ -19,12 +19,12 @@ Gem::Specification.new do |s|
19
19
  [ 'cri.gemspec' ]
20
20
  s.require_paths = [ 'lib' ]
21
21
 
22
- s.add_dependency('colored', '>= 1.2')
22
+ s.add_dependency('colored', '~> 1.2')
23
23
 
24
- s.add_development_dependency('rake')
25
- s.add_development_dependency('minitest')
26
- s.add_development_dependency('yard')
24
+ s.add_development_dependency('rake', '~> 10.1')
25
+ s.add_development_dependency('minitest', '~> 5.3')
26
+ s.add_development_dependency('yard', '~> 0.8')
27
27
 
28
- s.rdoc_options = [ '--main', 'README.md' ]
29
- s.extra_rdoc_files = [ 'LICENSE', 'README.md', 'NEWS.md' ]
28
+ s.rdoc_options = [ '--main', 'README.adoc' ]
29
+ s.extra_rdoc_files = [ 'LICENSE', 'README.adoc', 'NEWS.md' ]
30
30
  end
data/lib/cri.rb CHANGED
@@ -20,6 +20,7 @@ module Cri
20
20
  autoload 'Command', 'cri/command'
21
21
  autoload 'CommandDSL', 'cri/command_dsl'
22
22
  autoload 'CommandRunner', 'cri/command_runner'
23
+ autoload 'HelpRenderer', 'cri/help_renderer'
23
24
  autoload 'OptionParser', 'cri/option_parser'
24
25
 
25
26
  end
@@ -27,3 +28,4 @@ end
27
28
  require 'set'
28
29
 
29
30
  require 'cri/core_ext'
31
+ require 'cri/argument_array'
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ module Cri
4
+
5
+ # Represents an array of arguments. It is an array that strips separator
6
+ # arguments (`--`) but provides a `#raw` method to get the raw arguments
7
+ # array, i.e. an array that includes the separator `--` arguments.
8
+ class ArgumentArray < Array
9
+
10
+ # Initializes the array using the given raw arguments.
11
+ #
12
+ # @param [Array<String>] raw_arguments A list of raw arguments, i.e.
13
+ # including any separator arguments (`--`).
14
+ def initialize(raw_arguments)
15
+ super(raw_arguments.reject { |a| '--' == a })
16
+ @raw_arguments = raw_arguments
17
+ end
18
+
19
+ # @return [Array<String>] The arguments, including any separator arguments
20
+ # (`--`)
21
+ def raw
22
+ @raw_arguments
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -300,92 +300,7 @@ module Cri
300
300
 
301
301
  # @return [String] The help text for this command
302
302
  def help(params={})
303
- is_verbose = params.fetch(:verbose, false)
304
-
305
- text = ''
306
-
307
- # Append name and summary
308
- if summary
309
- text << "name".formatted_as_title << "\n"
310
- text << " #{name.formatted_as_command} - #{summary}" << "\n"
311
- unless aliases.empty?
312
- text << " aliases: " << aliases.map { |a| a.formatted_as_command }.join(' ') << "\n"
313
- end
314
- end
315
-
316
- # Append usage
317
- if usage
318
- path = [ self.supercommand ]
319
- path.unshift(path[0].supercommand) until path[0].nil?
320
- formatted_usage = usage.gsub(/^([^\s]+)/) { |m| m.formatted_as_command }
321
- full_usage = path[1..-1].map { |c| c.name.formatted_as_command + ' ' }.join + formatted_usage
322
-
323
- text << "\n"
324
- text << "usage".formatted_as_title << "\n"
325
- text << full_usage.wrap_and_indent(78, 4) << "\n"
326
- end
327
-
328
- # Append long description
329
- if description
330
- text << "\n"
331
- text << "description".formatted_as_title << "\n"
332
- text << description.wrap_and_indent(78, 4) + "\n"
333
- end
334
-
335
- # Append subcommands
336
- unless self.subcommands.empty?
337
- text << "\n"
338
- text << (self.supercommand ? 'subcommands' : 'commands').formatted_as_title
339
- text << "\n"
340
-
341
- shown_subcommands = self.subcommands.select { |c| !c.hidden? || is_verbose }
342
- length = shown_subcommands.map { |c| c.name.formatted_as_command.size }.max
343
-
344
- # Command
345
- shown_subcommands.sort_by { |cmd| cmd.name }.each do |cmd|
346
- text << sprintf(" %-#{length+4}s %s\n",
347
- cmd.name.formatted_as_command,
348
- cmd.summary)
349
- end
350
-
351
- # Hidden notice
352
- if !is_verbose
353
- diff = self.subcommands.size - shown_subcommands.size
354
- case diff
355
- when 0
356
- when 1
357
- text << " (1 hidden command omitted; show it with --verbose)\n"
358
- else
359
- text << " (#{diff} hidden commands omitted; show them with --verbose)\n"
360
- end
361
- end
362
- end
363
-
364
- # Append options
365
- groups = { 'options' => self.option_definitions }
366
- if self.supercommand
367
- groups["options for #{self.supercommand.name}"] = self.supercommand.global_option_definitions
368
- end
369
- length = groups.values.inject(&:+).map { |o| o[:long].to_s.size }.max
370
- groups.keys.sort.each do |name|
371
- defs = groups[name]
372
- unless defs.empty?
373
- text << "\n"
374
- text << "#{name}".formatted_as_title
375
- text << "\n"
376
- ordered_defs = defs.sort_by { |x| x[:short] || x[:long] }
377
- ordered_defs.each do |opt_def|
378
- text << sprintf(
379
- " %-2s %-#{length+6}s",
380
- opt_def[:short] ? ('-' + opt_def[:short]) : '',
381
- opt_def[:long] ? ('--' + opt_def[:long]) : '').formatted_as_option
382
-
383
- text << opt_def[:desc] << "\n"
384
- end
385
- end
386
- end
387
-
388
- text
303
+ HelpRenderer.new(self, params).render
389
304
  end
390
305
 
391
306
  # Compares this command's name to the other given command's name.
@@ -20,12 +20,8 @@ run do |opts, args, cmd|
20
20
 
21
21
  is_verbose = opts.fetch(:verbose, false)
22
22
 
23
- if args.empty?
24
- puts cmd.supercommand.help(:verbose => is_verbose)
25
- elsif args.size == 1
26
- puts cmd.supercommand.command_named(args[0]).help(:verbose => is_verbose)
27
- else
28
- $stderr.puts cmd.usage
29
- exit 1
23
+ resolved_cmd = args.inject(cmd.supercommand) do |acc, name|
24
+ acc.command_named(name)
30
25
  end
26
+ puts resolved_cmd.help(:verbose => is_verbose)
31
27
  end
@@ -0,0 +1,123 @@
1
+ # encoding: utf-8
2
+
3
+ module Cri
4
+
5
+ class HelpRenderer
6
+
7
+ def initialize(cmd, params={})
8
+ @cmd = cmd
9
+ @is_verbose = params.fetch(:verbose, false)
10
+ end
11
+
12
+ def render
13
+ text = ''
14
+
15
+ append_summary(text)
16
+ append_usage(text)
17
+ append_description(text)
18
+ append_subcommands(text)
19
+ append_options(text)
20
+
21
+ text
22
+ end
23
+
24
+ private
25
+
26
+ def append_summary(text)
27
+ return if @cmd.summary.nil?
28
+
29
+ text << "name".formatted_as_title << "\n"
30
+ text << " #{@cmd.name.formatted_as_command} - #{@cmd.summary}" << "\n"
31
+ unless @cmd.aliases.empty?
32
+ text << " aliases: " << @cmd.aliases.map { |a| a.formatted_as_command }.join(' ') << "\n"
33
+ end
34
+ end
35
+
36
+ def append_usage(text)
37
+ return if @cmd.usage.nil?
38
+
39
+ path = [ @cmd.supercommand ]
40
+ path.unshift(path[0].supercommand) until path[0].nil?
41
+ formatted_usage = @cmd.usage.gsub(/^([^\s]+)/) { |m| m.formatted_as_command }
42
+ full_usage = path[1..-1].map { |c| c.name.formatted_as_command + ' ' }.join + formatted_usage
43
+
44
+ text << "\n"
45
+ text << "usage".formatted_as_title << "\n"
46
+ text << full_usage.wrap_and_indent(78, 4) << "\n"
47
+ end
48
+
49
+ def append_description(text)
50
+ return if @cmd.description.nil?
51
+
52
+ text << "\n"
53
+ text << "description".formatted_as_title << "\n"
54
+ text << @cmd.description.wrap_and_indent(78, 4) + "\n"
55
+ end
56
+
57
+ def append_subcommands(text)
58
+ return if @cmd.subcommands.empty?
59
+
60
+ text << "\n"
61
+ text << (@cmd.supercommand ? 'subcommands' : 'commands').formatted_as_title
62
+ text << "\n"
63
+
64
+ shown_subcommands = @cmd.subcommands.select { |c| !c.hidden? || @is_verbose }
65
+ length = shown_subcommands.map { |c| c.name.formatted_as_command.size }.max
66
+
67
+ # Command
68
+ shown_subcommands.sort_by { |cmd| cmd.name }.each do |cmd|
69
+ text << sprintf(" %-#{length+4}s %s\n",
70
+ cmd.name.formatted_as_command,
71
+ cmd.summary)
72
+ end
73
+
74
+ # Hidden notice
75
+ if !@is_verbose
76
+ diff = @cmd.subcommands.size - shown_subcommands.size
77
+ case diff
78
+ when 0
79
+ when 1
80
+ text << " (1 hidden command omitted; show it with --verbose)\n"
81
+ else
82
+ text << " (#{diff} hidden commands omitted; show them with --verbose)\n"
83
+ end
84
+ end
85
+ end
86
+
87
+ def append_options(text)
88
+ groups = { 'options' => @cmd.option_definitions }
89
+ if @cmd.supercommand
90
+ groups["options for #{@cmd.supercommand.name}"] = @cmd.supercommand.global_option_definitions
91
+ end
92
+ length = groups.values.inject(&:+).map { |o| o[:long].to_s.size }.max
93
+ groups.keys.sort.each do |name|
94
+ defs = groups[name]
95
+ append_option_group(text, name, defs, length)
96
+ end
97
+ end
98
+
99
+ def append_option_group(text, name, defs, length)
100
+ return if defs.empty?
101
+
102
+ text << "\n"
103
+ text << "#{name}".formatted_as_title
104
+ text << "\n"
105
+
106
+ ordered_defs = defs.sort_by { |x| x[:short] || x[:long] }
107
+ ordered_defs.each do |opt_def|
108
+ text << format_opt_def(opt_def, length)
109
+ text << opt_def[:desc] << "\n"
110
+ end
111
+ end
112
+
113
+ def format_opt_def(opt_def, length)
114
+ opt_text = sprintf(
115
+ " %-2s %-#{length+6}s",
116
+ opt_def[:short] ? ('-' + opt_def[:short]) : '',
117
+ opt_def[:long] ? ('--' + opt_def[:long]) : '')
118
+ opt_text.formatted_as_option
119
+ end
120
+
121
+ end
122
+
123
+ end
@@ -84,14 +84,9 @@ module Cri
84
84
  # @return [Hash] The already parsed options.
85
85
  attr_reader :options
86
86
 
87
- # The arguments that have already been parsed.
88
- #
89
- # If the parser was stopped before it finished, this will not contain all
90
- # options and `unprocessed_arguments_and_options` will contain what is
91
- # left to be processed.
92
- #
93
- # @return [Array] The already parsed arguments.
94
- attr_reader :arguments
87
+ # @return [Array] The arguments that have already been parsed, including
88
+ # the -- separator.
89
+ attr_reader :raw_arguments
95
90
 
96
91
  # The options and arguments that have not yet been processed. If the
97
92
  # parser wasn’t stopped (using {#stop}), this list will be empty.
@@ -122,13 +117,24 @@ module Cri
122
117
  @unprocessed_arguments_and_options = arguments_and_options.dup
123
118
  @definitions = definitions
124
119
 
125
- @options = {}
126
- @arguments = []
120
+ @options = {}
121
+ @raw_arguments = []
127
122
 
128
123
  @running = false
129
124
  @no_more_options = false
130
125
  end
131
126
 
127
+ # Returns the arguments that have already been parsed.
128
+ #
129
+ # If the parser was stopped before it finished, this will not contain all
130
+ # options and `unprocessed_arguments_and_options` will contain what is
131
+ # left to be processed.
132
+ #
133
+ # @return [Array] The already parsed arguments.
134
+ def arguments
135
+ ArgumentArray.new(@raw_arguments).freeze
136
+ end
137
+
132
138
  # @return [Boolean] true if the parser is running, false otherwise.
133
139
  def running?
134
140
  @running
@@ -161,78 +167,12 @@ module Cri
161
167
  e = @unprocessed_arguments_and_options.shift
162
168
  break if e.nil?
163
169
 
164
- # Handle end-of-options marker
165
170
  if e == '--'
166
- @no_more_options = true
167
- # Handle incomplete options
171
+ handle_dashdash(e)
168
172
  elsif e =~ /^--./ and !@no_more_options
169
- # Get option key, and option value if included
170
- if e =~ /^--([^=]+)=(.+)$/
171
- option_key = $1
172
- option_value = $2
173
- else
174
- option_key = e[2..-1]
175
- option_value = nil
176
- end
177
-
178
- # Find definition
179
- definition = @definitions.find { |d| d[:long] == option_key }
180
- raise IllegalOptionError.new(option_key) if definition.nil?
181
-
182
- if [ :required, :optional ].include?(definition[:argument])
183
- # Get option value if necessary
184
- if option_value.nil?
185
- option_value = @unprocessed_arguments_and_options.shift
186
- if option_value.nil? || option_value =~ /^-/
187
- if definition[:argument] == :required
188
- raise OptionRequiresAnArgumentError.new(option_key)
189
- else
190
- @unprocessed_arguments_and_options.unshift(option_value)
191
- option_value = true
192
- end
193
- end
194
- end
195
-
196
- # Store option
197
- add_option(definition, option_value)
198
- else
199
- # Store option
200
- add_option(definition, true)
201
- end
202
- # Handle -xyz options
173
+ handle_dashdash_option(e)
203
174
  elsif e =~ /^-./ and !@no_more_options
204
- # Get option keys
205
- option_keys = e[1..-1].scan(/./)
206
-
207
- # For each key
208
- option_keys.each do |option_key|
209
- # Find definition
210
- definition = @definitions.find { |d| d[:short] == option_key }
211
- raise IllegalOptionError.new(option_key) if definition.nil?
212
-
213
- if option_keys.length > 1 and definition[:argument] == :required
214
- # This is a combined option and it requires an argument, so complain
215
- raise OptionRequiresAnArgumentError.new(option_key)
216
- elsif [ :required, :optional ].include?(definition[:argument])
217
- # Get option value
218
- option_value = @unprocessed_arguments_and_options.shift
219
- if option_value.nil? || option_value =~ /^-/
220
- if definition[:argument] == :required
221
- raise OptionRequiresAnArgumentError.new(option_key)
222
- else
223
- @unprocessed_arguments_and_options.unshift(option_value)
224
- option_value = true
225
- end
226
- end
227
-
228
- # Store option
229
- add_option(definition, option_value)
230
- else
231
- # Store option
232
- add_option(definition, true)
233
- end
234
- end
235
- # Handle normal arguments
175
+ handle_dash_option(e)
236
176
  else
237
177
  add_argument(e)
238
178
  end
@@ -244,6 +184,78 @@ module Cri
244
184
 
245
185
  private
246
186
 
187
+ def handle_dashdash(e)
188
+ add_argument(e)
189
+ @no_more_options = true
190
+ end
191
+
192
+ def handle_dashdash_option(e)
193
+ # Get option key, and option value if included
194
+ if e =~ /^--([^=]+)=(.+)$/
195
+ option_key = $1
196
+ option_value = $2
197
+ else
198
+ option_key = e[2..-1]
199
+ option_value = nil
200
+ end
201
+
202
+ # Find definition
203
+ definition = @definitions.find { |d| d[:long] == option_key }
204
+ raise IllegalOptionError.new(option_key) if definition.nil?
205
+
206
+ if [ :required, :optional ].include?(definition[:argument])
207
+ # Get option value if necessary
208
+ if option_value.nil?
209
+ option_value = find_option_value(definition, option_key)
210
+ end
211
+
212
+ # Store option
213
+ add_option(definition, option_value)
214
+ else
215
+ # Store option
216
+ add_option(definition, true)
217
+ end
218
+ end
219
+
220
+ def handle_dash_option(e)
221
+ # Get option keys
222
+ option_keys = e[1..-1].scan(/./)
223
+
224
+ # For each key
225
+ option_keys.each do |option_key|
226
+ # Find definition
227
+ definition = @definitions.find { |d| d[:short] == option_key }
228
+ raise IllegalOptionError.new(option_key) if definition.nil?
229
+
230
+ if option_keys.length > 1 and definition[:argument] == :required
231
+ # This is a combined option and it requires an argument, so complain
232
+ raise OptionRequiresAnArgumentError.new(option_key)
233
+ elsif [ :required, :optional ].include?(definition[:argument])
234
+ # Get option value
235
+ option_value = find_option_value(definition, option_key)
236
+
237
+ # Store option
238
+ add_option(definition, option_value)
239
+ else
240
+ # Store option
241
+ add_option(definition, true)
242
+ end
243
+ end
244
+ end
245
+
246
+ def find_option_value(definition, option_key)
247
+ option_value = @unprocessed_arguments_and_options.shift
248
+ if option_value.nil? || option_value =~ /^-/
249
+ if definition[:argument] == :required
250
+ raise OptionRequiresAnArgumentError.new(option_key)
251
+ else
252
+ @unprocessed_arguments_and_options.unshift(option_value)
253
+ option_value = true
254
+ end
255
+ end
256
+ option_value
257
+ end
258
+
247
259
  def add_option(definition, value)
248
260
  key = (definition[:long] || definition[:short]).to_sym
249
261
  options[key] = value
@@ -251,8 +263,11 @@ module Cri
251
263
  end
252
264
 
253
265
  def add_argument(value)
254
- arguments << value
255
- delegate.argument_added(value, self) unless delegate.nil?
266
+ @raw_arguments << value
267
+
268
+ unless '--' == value
269
+ delegate.argument_added(value, self) unless delegate.nil?
270
+ end
256
271
  end
257
272
 
258
273
  end
@@ -1,6 +1,6 @@
1
1
  module Cri
2
2
 
3
3
  # The current Cri version.
4
- VERSION = '2.4.1'
4
+ VERSION = '2.5.0'
5
5
 
6
6
  end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ class Cri::ArgumentArrayTestCase < Cri::TestCase
4
+
5
+ def test_initialize
6
+ arr = Cri::ArgumentArray.new([ 'foo', 'bar', '--', 'baz' ])
7
+ assert_equal [ 'foo', 'bar', 'baz' ], arr
8
+ assert_equal [ 'foo', 'bar', '--', 'baz' ], arr.raw
9
+ end
10
+
11
+ end
@@ -21,4 +21,47 @@ class Cri::BasicHelpTestCase < Cri::TestCase
21
21
  help_cmd.run([])
22
22
  end
23
23
 
24
+ def test_run_with_chain_of_commands
25
+ cmd = Cri::Command.define do
26
+ name 'root'
27
+ summary 'I am root!'
28
+
29
+ subcommand do
30
+ name 'foo'
31
+ summary 'I am foo!'
32
+
33
+ subcommand do
34
+ name 'subsubby'
35
+ summary 'I am subsubby!'
36
+ end
37
+ end
38
+ end
39
+
40
+ help_cmd = Cri::Command.new_basic_help
41
+ cmd.add_command(help_cmd)
42
+
43
+ # Simple call
44
+ stdout, stderr = capture_io_while do
45
+ help_cmd.run([ 'foo' ])
46
+ end
47
+ assert_match(/I am foo!/m, stdout)
48
+ assert_equal('', stderr)
49
+
50
+ # Subcommand
51
+ stdout, stderr = capture_io_while do
52
+ help_cmd.run([ 'foo', 'subsubby' ])
53
+ end
54
+ assert_match(/I am subsubby!/m, stdout)
55
+ assert_equal('', stderr)
56
+
57
+ # Non-existing subcommand
58
+ stdout, stderr = capture_io_while do
59
+ assert_raises SystemExit do
60
+ help_cmd.run([ 'foo', 'mysterycmd' ])
61
+ end
62
+ end
63
+ assert_equal '', stdout
64
+ assert_match(/foo: unknown command 'mysterycmd'/, stderr)
65
+ end
66
+
24
67
  end
@@ -424,4 +424,34 @@ class Cri::CommandTestCase < Cri::TestCase
424
424
  assert_match(pattern, cmd.help(:verbose => true))
425
425
  end
426
426
 
427
+ def test_run_with_raw_args
428
+ cmd = Cri::Command.define do
429
+ name 'moo'
430
+ run do |opts, args|
431
+ puts "args=#{args.join(',')} args.raw=#{args.raw.join(',')}"
432
+ end
433
+ end
434
+
435
+ out, err = capture_io_while do
436
+ cmd.run(%w( foo -- bar ))
437
+ end
438
+ assert_equal "args=foo,bar args.raw=foo,--,bar\n", out
439
+ end
440
+
441
+ def test_runner_with_raw_args
442
+ cmd = Cri::Command.define do
443
+ name 'moo'
444
+ runner(Class.new(Cri::CommandRunner) do
445
+ def run
446
+ puts "args=#{arguments.join(',')} args.raw=#{arguments.raw.join(',')}"
447
+ end
448
+ end)
449
+ end
450
+
451
+ out, err = capture_io_while do
452
+ cmd.run(%w( foo -- bar ))
453
+ end
454
+ assert_equal "args=foo,bar args.raw=foo,--,bar\n", out
455
+ end
456
+
427
457
  end
@@ -263,8 +263,9 @@ class Cri::OptionParserTestCase < Cri::TestCase
263
263
 
264
264
  parser = Cri::OptionParser.parse(input, definitions)
265
265
 
266
- assert_equal({}, parser.options)
267
- assert_equal([ 'foo', 'bar', '-x', '--yyy', '-abc' ], parser.arguments)
266
+ assert_equal({}, parser.options)
267
+ assert_equal([ 'foo', 'bar', '-x', '--yyy', '-abc' ], parser.arguments)
268
+ assert_equal([ 'foo', 'bar', '--', '-x', '--yyy', '-abc' ], parser.arguments.raw)
268
269
  end
269
270
 
270
271
  def test_parse_with_end_marker_between_option_key_and_value
metadata CHANGED
@@ -1,71 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cri
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.1
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Defreyne
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-29 00:00:00.000000000 Z
11
+ date: 2014-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colored
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>='
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '10.1'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - ~>
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '10.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ! '>='
45
+ - - ~>
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '5.3'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ! '>='
52
+ - - ~>
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '5.3'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: yard
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ! '>='
59
+ - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '0.8'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ! '>='
66
+ - - ~>
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '0.8'
69
69
  description: Cri allows building easy-to-use commandline interfaces with support for
70
70
  subcommands.
71
71
  email: denis.defreyne@stoneship.org
@@ -73,26 +73,30 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files:
75
75
  - LICENSE
76
- - README.md
76
+ - README.adoc
77
77
  - NEWS.md
78
78
  files:
79
79
  - Gemfile
80
80
  - Gemfile.lock
81
81
  - LICENSE
82
82
  - NEWS.md
83
+ - README.adoc
83
84
  - Rakefile
84
- - README.md
85
+ - cri.gemspec
86
+ - lib/cri.rb
87
+ - lib/cri/argument_array.rb
85
88
  - lib/cri/command.rb
86
89
  - lib/cri/command_dsl.rb
87
90
  - lib/cri/command_runner.rb
88
91
  - lib/cri/commands/basic_help.rb
89
92
  - lib/cri/commands/basic_root.rb
90
- - lib/cri/core_ext/string.rb
91
93
  - lib/cri/core_ext.rb
94
+ - lib/cri/core_ext/string.rb
95
+ - lib/cri/help_renderer.rb
92
96
  - lib/cri/option_parser.rb
93
97
  - lib/cri/version.rb
94
- - lib/cri.rb
95
98
  - test/helper.rb
99
+ - test/test_argument_array.rb
96
100
  - test/test_base.rb
97
101
  - test/test_basic_help.rb
98
102
  - test/test_basic_root.rb
@@ -100,7 +104,6 @@ files:
100
104
  - test/test_command_dsl.rb
101
105
  - test/test_core_ext.rb
102
106
  - test/test_option_parser.rb
103
- - cri.gemspec
104
107
  homepage: http://stoneship.org/software/cri/
105
108
  licenses:
106
109
  - MIT
@@ -108,7 +111,7 @@ metadata: {}
108
111
  post_install_message:
109
112
  rdoc_options:
110
113
  - --main
111
- - README.md
114
+ - README.adoc
112
115
  require_paths:
113
116
  - lib
114
117
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -123,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
126
  version: '0'
124
127
  requirements: []
125
128
  rubyforge_project:
126
- rubygems_version: 2.1.11
129
+ rubygems_version: 2.2.2
127
130
  signing_key:
128
131
  specification_version: 4
129
132
  summary: a library for building easy-to-use commandline tools
data/README.md DELETED
@@ -1,138 +0,0 @@
1
- Cri
2
- ===
3
-
4
- Cri is a library for building easy-to-use commandline tools with support for
5
- nested commands.
6
-
7
- Usage
8
- -----
9
-
10
- The central concept in Cri is the _command_, which has option definitions as
11
- well as code for actually executing itself. In Cri, the commandline tool
12
- itself is a command as well.
13
-
14
- Here’s a sample command definition:
15
-
16
- command = Cri::Command.define do
17
- name 'dostuff'
18
- usage 'dostuff [options]'
19
- aliases :ds, :stuff
20
- summary 'does stuff'
21
- description 'This command does a lot of stuff. I really mean a lot.'
22
-
23
- flag :h, :help, 'show help for this command' do |value, cmd|
24
- puts cmd.help
25
- exit 0
26
- end
27
- flag nil, :more, 'do even more stuff'
28
- option :s, :stuff, 'specify stuff to do', :argument => :required
29
-
30
- run do |opts, args, cmd|
31
- stuff = opts[:stuff] || 'generic stuff'
32
- puts "Doing #{stuff}!"
33
-
34
- if opts[:more]
35
- puts 'Doing it even more!'
36
- end
37
- end
38
- end
39
-
40
- To run this command, invoke the `#run` method with the raw arguments. For
41
- example, for a root command (the commandline tool itself), the command could
42
- be called like this:
43
-
44
- command.run(ARGS)
45
-
46
- Each command has automatically generated help. This help can be printed using
47
- {Cri::Command#help}; something like this will be shown:
48
-
49
- usage: dostuff [options]
50
-
51
- does stuff
52
-
53
- This command does a lot of stuff. I really mean a lot.
54
-
55
- options:
56
-
57
- -h --help show help for this command
58
- --more do even more stuff
59
- -s --stuff specify stuff to do
60
-
61
- Let’s disect the command definition and start with the first five lines:
62
-
63
- name 'dostuff'
64
- usage 'dostuff [options]'
65
- aliases :ds, :stuff
66
- summary 'does stuff'
67
- description 'This command does a lot of stuff. I really mean a lot.'
68
-
69
- These lines of the command definition specify the name of the command (or the
70
- commandline tool, if the command is the root command), the usage, a list of
71
- aliases that can be used to call this command, a one-line summary and a (long)
72
- description. The usage should not include a “usage:” prefix nor the name of
73
- the supercommand, because the latter will be automatically prepended.
74
-
75
- Aliases don’t make sense for root commands, but for subcommands they do.
76
-
77
- The next few lines contain the command’s option definitions:
78
-
79
- flag :h, :help, 'show help for this command' do |value, cmd|
80
- puts cmd.help
81
- exit 0
82
- end
83
- flag nil, :more, 'do even more stuff'
84
- option :s, :stuff, 'specify stuff to do', :argument => :required
85
-
86
- Options can be defined using the following methods:
87
-
88
- * {Cri::CommandDSL#option} or {Cri::CommandDSL#opt}
89
- * {Cri::CommandDSL#flag} (implies forbidden argument)
90
- * {Cri::CommandDSL#required} (implies required argument)
91
- * {Cri::CommandDSL#optional} (implies optional argument)
92
-
93
- All these methods take the short option form as their first argument, and a
94
- long option form as their second argument. Either the short or the long form
95
- can be nil, but not both (because that would not make any sense). In the
96
- example above, the `--more` option has no short form.
97
-
98
- Each of the above methods also take a block, which will be executed when the
99
- option is found. The argument to the block are the option value (`true` in
100
- case the option does not have an argument) and the command.
101
-
102
- The last part of the command defines the execution itself:
103
-
104
- run do |opts, args, cmd|
105
- stuff = opts[:stuff] || 'generic stuff'
106
- puts "Doing #{stuff}!"
107
-
108
- if opts[:more]
109
- puts 'Doing it even more!'
110
- end
111
- end
112
-
113
- The {Cri::CommandDSL#run} method takes a block with the actual code to
114
- execute. This block takes three arguments: the options, any arguments passed
115
- to the command, and the command itself.
116
-
117
- Instead of defining a run block, it is possible to declare a class, the
118
- _command runner_ class ({Cri::CommandRunner}) that will perform the actual
119
- execution of the command. This makes it easier to break up large run blocks
120
- into manageable pieces.
121
-
122
- Commands can have subcommands. For example, the `git` commandline tool would be represented by a command that has subcommands named `commit`, `add`, and so on. Commands with subcommands do not use a run block; execution will always be dispatched to a subcommand (or none, if no subcommand is found).
123
-
124
- To add a command as a subcommand to another command, use the {Cri::Command#add_command} method, like this:
125
-
126
- root_cmd.add_command cmd_add
127
- root_cmd.add_command cmd_commit
128
- root.cmd.add_command cmd_init
129
-
130
- Contributors
131
- ------------
132
-
133
- * Toon Willems
134
- * Ken Coar
135
-
136
- Thanks for Lee “injekt” Jarvis for [Slop][1], which has inspired the design of Cri 2.0.
137
-
138
- [1]: https://github.com/injekt/slop