cri 2.4.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/Gemfile.lock +4 -4
- data/NEWS.md +6 -0
- data/README.adoc +158 -0
- data/cri.gemspec +6 -6
- data/lib/cri.rb +2 -0
- data/lib/cri/argument_array.rb +27 -0
- data/lib/cri/command.rb +1 -86
- data/lib/cri/commands/basic_help.rb +3 -7
- data/lib/cri/help_renderer.rb +123 -0
- data/lib/cri/option_parser.rb +96 -81
- data/lib/cri/version.rb +1 -1
- data/test/test_argument_array.rb +11 -0
- data/test/test_basic_help.rb +43 -0
- data/test/test_command.rb +30 -0
- data/test/test_option_parser.rb +3 -2
- metadata +26 -23
- data/README.md +0 -138
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YTY4ZDU4NjdmNGI4NDNhM2UxMTA1YTA1MDBjZmEzYWZiNDgwOTY0ZA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MjkxMmY1ZDA1M2ExNDUxYmUxOTYyNjVjZTE3OWExM2Y1NmVhODg2Mg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MTNjMmU4ZTViYjg2YzFiYjI5MjZiZTcyNzViNGYyYTEzZjdiMzY5MTc4Mzlk
|
10
|
+
MWIwYWNlNzI4Njk3Y2JlYTFkNDI4NDkyNGQ0YmVhMWE5Mzk1YjFhM2VjZDBm
|
11
|
+
ZjMxZmYzMDVkMzM2MGQ1OTVhYmI4NDUxMTFiNWEyZDU5MGQ5ZWE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MmEwYjAzNDVhYzg0MWExM2MyODhmMzJlMDE1ZDkwY2U4Y2U2ZGFkNjYxZjk5
|
14
|
+
YjYxNGMxNTJlYjk5N2U0MDRkYzYyNDg3Mjc0NjlhODg5MDkyNGFmNWZlYTU2
|
15
|
+
YjRlY2EyNGU0NjlhYWM2NTMzZmE1MGU1MjRkMDRkNjZiODFiYjc=
|
data/Gemfile.lock
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cri (2.
|
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
|
12
|
-
rake (10.1.
|
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
data/README.adoc
ADDED
@@ -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.
|
data/cri.gemspec
CHANGED
@@ -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', '
|
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.
|
29
|
-
s.extra_rdoc_files = [ 'LICENSE', 'README.
|
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
|
data/lib/cri/command.rb
CHANGED
@@ -300,92 +300,7 @@ module Cri
|
|
300
300
|
|
301
301
|
# @return [String] The help text for this command
|
302
302
|
def help(params={})
|
303
|
-
|
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
|
-
|
24
|
-
|
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
|
data/lib/cri/option_parser.rb
CHANGED
@@ -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
|
-
|
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
|
-
@
|
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
|
-
|
167
|
-
# Handle incomplete options
|
171
|
+
handle_dashdash(e)
|
168
172
|
elsif e =~ /^--./ and !@no_more_options
|
169
|
-
|
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
|
-
|
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
|
-
|
255
|
-
|
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
|
data/lib/cri/version.rb
CHANGED
@@ -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
|
data/test/test_basic_help.rb
CHANGED
@@ -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
|
data/test/test_command.rb
CHANGED
@@ -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
|
data/test/test_option_parser.rb
CHANGED
@@ -263,8 +263,9 @@ class Cri::OptionParserTestCase < Cri::TestCase
|
|
263
263
|
|
264
264
|
parser = Cri::OptionParser.parse(input, definitions)
|
265
265
|
|
266
|
-
assert_equal({},
|
267
|
-
assert_equal([ 'foo', 'bar', '-x', '--yyy', '-abc' ],
|
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
|
+
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:
|
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: '
|
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: '
|
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: '
|
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: '
|
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.
|
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
|
-
-
|
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.
|
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.
|
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
|