cri 2.6.1 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +46 -21
- data/LICENSE +1 -1
- data/NEWS.md +12 -0
- data/README.adoc +1 -1
- data/Rakefile +7 -2
- data/cri.gemspec +5 -7
- data/lib/cri.rb +0 -2
- data/lib/cri/argument_array.rb +0 -4
- data/lib/cri/command.rb +31 -40
- data/lib/cri/command_dsl.rb +28 -18
- data/lib/cri/command_runner.rb +2 -6
- data/lib/cri/commands/basic_help.rb +2 -2
- data/lib/cri/commands/basic_root.rb +1 -1
- data/lib/cri/core_ext.rb +3 -1
- data/lib/cri/core_ext/string.rb +28 -30
- data/lib/cri/help_renderer.rb +105 -23
- data/lib/cri/option_parser.rb +13 -17
- data/lib/cri/platform.rb +1 -5
- data/lib/cri/string_formatter.rb +14 -9
- data/lib/cri/version.rb +1 -3
- data/test/helper.rb +38 -38
- data/test/test_argument_array.rb +7 -7
- data/test/test_base.rb +4 -4
- data/test/test_basic_help.rb +47 -47
- data/test/test_basic_root.rb +10 -10
- data/test/test_command.rb +424 -347
- data/test/test_command_dsl.rb +174 -150
- data/test/test_command_runner.rb +22 -23
- data/test/test_option_parser.rb +212 -224
- data/test/test_string_formatter.rb +97 -82
- metadata +3 -3
data/lib/cri/command_dsl.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Cri
|
4
|
-
|
5
4
|
# The command DSL is a class that is used for building and modifying
|
6
5
|
# commands.
|
7
6
|
class CommandDSL
|
7
|
+
# @return [Cri::Command] The built command
|
8
|
+
attr_reader :command
|
8
9
|
|
9
10
|
# Creates a new DSL, intended to be used for building a single command. A
|
10
11
|
# {CommandDSL} instance is not reusable; create a new instance if you want
|
@@ -12,15 +13,10 @@ module Cri
|
|
12
13
|
#
|
13
14
|
# @param [Cri::Command, nil] command The command to modify, or nil if a
|
14
15
|
# new command should be created
|
15
|
-
def initialize(command=nil)
|
16
|
+
def initialize(command = nil)
|
16
17
|
@command = command || Cri::Command.new
|
17
18
|
end
|
18
19
|
|
19
|
-
# @return [Cri::Command] The built command
|
20
|
-
def command
|
21
|
-
@command
|
22
|
-
end
|
23
|
-
|
24
20
|
# Adds a subcommand to the current command. The command can either be
|
25
21
|
# given explicitly, or a block can be given that defines the command.
|
26
22
|
#
|
@@ -29,7 +25,7 @@ module Cri
|
|
29
25
|
# added as a subcommand
|
30
26
|
#
|
31
27
|
# @return [void]
|
32
|
-
def subcommand(command=nil, &block)
|
28
|
+
def subcommand(command = nil, &block)
|
33
29
|
if command.nil?
|
34
30
|
command = Cri::Command.define(&block)
|
35
31
|
end
|
@@ -108,13 +104,17 @@ module Cri
|
|
108
104
|
# @option params [Boolean] :multiple Whether or not the option should
|
109
105
|
# be multi-valued
|
110
106
|
#
|
107
|
+
# @option params [Boolean] :hidden Whether or not the option should
|
108
|
+
# be printed in the help output
|
109
|
+
#
|
111
110
|
# @return [void]
|
112
|
-
def option(short, long, desc, params={}, &block)
|
111
|
+
def option(short, long, desc, params = {}, &block)
|
113
112
|
requiredness = params.fetch(:argument, :forbidden)
|
114
113
|
multiple = params.fetch(:multiple, false)
|
114
|
+
hidden = params.fetch(:hidden, false)
|
115
115
|
|
116
116
|
if short.nil? && long.nil?
|
117
|
-
|
117
|
+
fail ArgumentError, 'short and long options cannot both be nil'
|
118
118
|
end
|
119
119
|
|
120
120
|
@command.option_definitions << {
|
@@ -124,6 +124,7 @@ module Cri
|
|
124
124
|
:argument => requiredness,
|
125
125
|
:multiple => multiple,
|
126
126
|
:block => block,
|
127
|
+
:hidden => hidden,
|
127
128
|
}
|
128
129
|
end
|
129
130
|
alias_method :opt, :option
|
@@ -140,12 +141,15 @@ module Cri
|
|
140
141
|
# @option params [Boolean] :multiple Whether or not the option should
|
141
142
|
# be multi-valued
|
142
143
|
#
|
144
|
+
# @option params [Boolean] :hidden Whether or not the option should
|
145
|
+
# be printed in the help output
|
146
|
+
#
|
143
147
|
# @return [void]
|
144
148
|
#
|
145
149
|
# @see {#option}
|
146
|
-
def required(short, long, desc, params={}, &block)
|
150
|
+
def required(short, long, desc, params = {}, &block)
|
147
151
|
params = params.merge(:argument => :required)
|
148
|
-
|
152
|
+
option(short, long, desc, params, &block)
|
149
153
|
end
|
150
154
|
|
151
155
|
# Adds a new option with a forbidden argument to the command. If a block
|
@@ -160,12 +164,15 @@ module Cri
|
|
160
164
|
# @option params [Boolean] :multiple Whether or not the option should
|
161
165
|
# be multi-valued
|
162
166
|
#
|
167
|
+
# @option params [Boolean] :hidden Whether or not the option should
|
168
|
+
# be printed in the help output
|
169
|
+
#
|
163
170
|
# @return [void]
|
164
171
|
#
|
165
172
|
# @see {#option}
|
166
|
-
def flag(short, long, desc, params={}, &block)
|
173
|
+
def flag(short, long, desc, params = {}, &block)
|
167
174
|
params = params.merge(:argument => :forbidden)
|
168
|
-
|
175
|
+
option(short, long, desc, params, &block)
|
169
176
|
end
|
170
177
|
alias_method :forbidden, :flag
|
171
178
|
|
@@ -181,12 +188,15 @@ module Cri
|
|
181
188
|
# @option params [Boolean] :multiple Whether or not the option should
|
182
189
|
# be multi-valued
|
183
190
|
#
|
191
|
+
# @option params [Boolean] :hidden Whether or not the option should
|
192
|
+
# be printed in the help output
|
193
|
+
#
|
184
194
|
# @return [void]
|
185
195
|
#
|
186
196
|
# @see {#option}
|
187
|
-
def optional(short, long, desc, params={}, &block)
|
197
|
+
def optional(short, long, desc, params = {}, &block)
|
188
198
|
params = params.merge(:argument => :optional)
|
189
|
-
|
199
|
+
option(short, long, desc, params, &block)
|
190
200
|
end
|
191
201
|
|
192
202
|
# Sets the run block to the given block. The given block should have two
|
@@ -203,8 +213,8 @@ module Cri
|
|
203
213
|
# @return [void]
|
204
214
|
def run(&block)
|
205
215
|
unless [2, 3].include?(block.arity)
|
206
|
-
|
207
|
-
|
216
|
+
fail ArgumentError,
|
217
|
+
'The block given to Cri::Command#run expects two or three args'
|
208
218
|
end
|
209
219
|
|
210
220
|
@command.block = block
|
data/lib/cri/command_runner.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Cri
|
4
|
-
|
5
4
|
# A command runner is responsible for the execution of a command. Using it
|
6
5
|
# is optional, but it is useful for commands whose execution block is large.
|
7
6
|
class CommandRunner
|
8
|
-
|
9
7
|
# @return [Hash] A hash contain the options and their values
|
10
8
|
attr_reader :options
|
11
9
|
|
@@ -33,7 +31,7 @@ module Cri
|
|
33
31
|
#
|
34
32
|
# @return [void]
|
35
33
|
def call
|
36
|
-
|
34
|
+
run
|
37
35
|
end
|
38
36
|
|
39
37
|
# Performs the actual execution of the command.
|
@@ -42,9 +40,7 @@ module Cri
|
|
42
40
|
#
|
43
41
|
# @abstract
|
44
42
|
def run
|
45
|
-
|
43
|
+
fail NotImplementedError, 'Cri::CommandRunner subclasses must implement #run'
|
46
44
|
end
|
47
|
-
|
48
45
|
end
|
49
|
-
|
50
46
|
end
|
@@ -14,8 +14,8 @@ flag :v, :verbose, 'show more detailed help'
|
|
14
14
|
|
15
15
|
run do |opts, args, cmd|
|
16
16
|
if cmd.supercommand.nil?
|
17
|
-
|
18
|
-
|
17
|
+
fail NoHelpAvailableError,
|
18
|
+
'No help available because the help command has no supercommand'
|
19
19
|
end
|
20
20
|
|
21
21
|
is_verbose = opts.fetch(:verbose, false)
|
data/lib/cri/core_ext.rb
CHANGED
data/lib/cri/core_ext/string.rb
CHANGED
@@ -2,36 +2,34 @@
|
|
2
2
|
|
3
3
|
require 'colored'
|
4
4
|
|
5
|
-
module Cri
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
5
|
+
module Cri
|
6
|
+
module CoreExtensions
|
7
|
+
# @deprecated
|
8
|
+
module String
|
9
|
+
# @see Cri::StringFormatter#to_paragraphs
|
10
|
+
def to_paragraphs
|
11
|
+
Cri::StringFormatter.new.to_paragraphs(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @see Cri::StringFormatter#to_paragraphs
|
15
|
+
def wrap_and_indent(width, indentation)
|
16
|
+
Cri::StringFormatter.new.wrap_and_indent(self, width, indentation)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @see Cri::StringFormatter#format_as_title
|
20
|
+
def formatted_as_title
|
21
|
+
Cri::StringFormatter.new.format_as_title(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @see Cri::StringFormatter#format_as_command
|
25
|
+
def formatted_as_command
|
26
|
+
Cri::StringFormatter.new.format_as_command(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @see Cri::StringFormatter#format_as_option
|
30
|
+
def formatted_as_option
|
31
|
+
Cri::StringFormatter.new.format_as_option(self)
|
32
|
+
end
|
23
33
|
end
|
24
|
-
|
25
|
-
# @see Cri::StringFormatter#format_as_command
|
26
|
-
def formatted_as_command
|
27
|
-
Cri::StringFormatter.new.format_as_command(self)
|
28
|
-
end
|
29
|
-
|
30
|
-
# @see Cri::StringFormatter#format_as_option
|
31
|
-
def formatted_as_option
|
32
|
-
Cri::StringFormatter.new.format_as_option(self)
|
33
|
-
end
|
34
|
-
|
35
34
|
end
|
36
|
-
|
37
35
|
end
|
data/lib/cri/help_renderer.rb
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Cri
|
4
|
-
|
5
4
|
# The {HelpRenderer} class is responsible for generating a string containing
|
6
5
|
# the help for a given command, intended to be printed on the command line.
|
7
6
|
class HelpRenderer
|
7
|
+
# The line width of the help output
|
8
|
+
LINE_WIDTH = 78
|
9
|
+
|
10
|
+
# The indentation of descriptions
|
11
|
+
DESC_INDENT = 4
|
12
|
+
|
13
|
+
# The spacing between an option name and option description
|
14
|
+
OPT_DESC_SPACING = 6
|
8
15
|
|
9
16
|
# Creates a new help renderer for the given command.
|
10
17
|
#
|
@@ -12,7 +19,7 @@ module Cri
|
|
12
19
|
#
|
13
20
|
# @option params [Boolean] :verbose true if the help output should be
|
14
21
|
# verbose, false otherwise.
|
15
|
-
def initialize(cmd, params={})
|
22
|
+
def initialize(cmd, params = {})
|
16
23
|
@cmd = cmd
|
17
24
|
@is_verbose = params.fetch(:verbose, false)
|
18
25
|
@io = params.fetch(:io, $stdout)
|
@@ -40,32 +47,32 @@ module Cri
|
|
40
47
|
def append_summary(text)
|
41
48
|
return if @cmd.summary.nil?
|
42
49
|
|
43
|
-
text << fmt.format_as_title(
|
50
|
+
text << fmt.format_as_title('name', @io) << "\n"
|
44
51
|
text << " #{fmt.format_as_command(@cmd.name, @io)} - #{@cmd.summary}" << "\n"
|
45
52
|
unless @cmd.aliases.empty?
|
46
|
-
text <<
|
53
|
+
text << ' aliases: ' << @cmd.aliases.map { |a| fmt.format_as_command(a, @io) }.join(' ') << "\n"
|
47
54
|
end
|
48
55
|
end
|
49
56
|
|
50
57
|
def append_usage(text)
|
51
58
|
return if @cmd.usage.nil?
|
52
59
|
|
53
|
-
path = [
|
60
|
+
path = [@cmd.supercommand]
|
54
61
|
path.unshift(path[0].supercommand) until path[0].nil?
|
55
62
|
formatted_usage = @cmd.usage.gsub(/^([^\s]+)/) { |m| fmt.format_as_command(m, @io) }
|
56
63
|
full_usage = path[1..-1].map { |c| fmt.format_as_command(c.name, @io) + ' ' }.join + formatted_usage
|
57
64
|
|
58
65
|
text << "\n"
|
59
|
-
text << fmt.format_as_title(
|
60
|
-
text << fmt.wrap_and_indent(full_usage,
|
66
|
+
text << fmt.format_as_title('usage', @io) << "\n"
|
67
|
+
text << fmt.wrap_and_indent(full_usage, LINE_WIDTH, DESC_INDENT) << "\n"
|
61
68
|
end
|
62
69
|
|
63
70
|
def append_description(text)
|
64
71
|
return if @cmd.description.nil?
|
65
72
|
|
66
73
|
text << "\n"
|
67
|
-
text << fmt.format_as_title(
|
68
|
-
text << fmt.wrap_and_indent(@cmd.description,
|
74
|
+
text << fmt.format_as_title('description', @io) << "\n"
|
75
|
+
text << fmt.wrap_and_indent(@cmd.description, LINE_WIDTH, DESC_INDENT) + "\n"
|
69
76
|
end
|
70
77
|
|
71
78
|
def append_subcommands(text)
|
@@ -80,13 +87,15 @@ module Cri
|
|
80
87
|
|
81
88
|
# Command
|
82
89
|
shown_subcommands.sort_by { |cmd| cmd.name }.each do |cmd|
|
83
|
-
text <<
|
84
|
-
|
85
|
-
|
90
|
+
text <<
|
91
|
+
format(
|
92
|
+
" %-#{length + DESC_INDENT}s %s\n",
|
93
|
+
fmt.format_as_command(cmd.name, @io),
|
94
|
+
cmd.summary)
|
86
95
|
end
|
87
96
|
|
88
97
|
# Hidden notice
|
89
|
-
|
98
|
+
unless @is_verbose
|
90
99
|
diff = @cmd.subcommands.size - shown_subcommands.size
|
91
100
|
case diff
|
92
101
|
when 0
|
@@ -98,12 +107,34 @@ module Cri
|
|
98
107
|
end
|
99
108
|
end
|
100
109
|
|
110
|
+
def length_for_opt_defs(opt_defs)
|
111
|
+
opt_defs.map do |opt_def|
|
112
|
+
string = ''
|
113
|
+
|
114
|
+
# Always pretend there is a short option
|
115
|
+
string << '-X'
|
116
|
+
|
117
|
+
if opt_def[:long]
|
118
|
+
string << ' --' + opt_def[:long]
|
119
|
+
end
|
120
|
+
|
121
|
+
case opt_def[:argument]
|
122
|
+
when :required
|
123
|
+
string << '=<value>'
|
124
|
+
when :optional
|
125
|
+
string << '=[<value>]'
|
126
|
+
end
|
127
|
+
|
128
|
+
string.size
|
129
|
+
end.max
|
130
|
+
end
|
131
|
+
|
101
132
|
def append_options(text)
|
102
133
|
groups = { 'options' => @cmd.option_definitions }
|
103
134
|
if @cmd.supercommand
|
104
135
|
groups["options for #{@cmd.supercommand.name}"] = @cmd.supercommand.global_option_definitions
|
105
136
|
end
|
106
|
-
length = groups.values.inject(&:+)
|
137
|
+
length = length_for_opt_defs(groups.values.inject(&:+))
|
107
138
|
groups.keys.sort.each do |name|
|
108
139
|
defs = groups[name]
|
109
140
|
append_option_group(text, name, defs, length)
|
@@ -119,19 +150,70 @@ module Cri
|
|
119
150
|
|
120
151
|
ordered_defs = defs.sort_by { |x| x[:short] || x[:long] }
|
121
152
|
ordered_defs.each do |opt_def|
|
122
|
-
|
123
|
-
|
153
|
+
unless opt_def[:hidden]
|
154
|
+
text << format_opt_def(opt_def, length)
|
155
|
+
text << fmt.wrap_and_indent(opt_def[:desc], LINE_WIDTH, length + OPT_DESC_SPACING + DESC_INDENT, true) << "\n"
|
156
|
+
end
|
124
157
|
end
|
125
158
|
end
|
126
159
|
|
127
|
-
def
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
160
|
+
def short_value_postfix_for(opt_def)
|
161
|
+
value_postfix =
|
162
|
+
case opt_def[:argument]
|
163
|
+
when :required
|
164
|
+
'<value>'
|
165
|
+
when :optional
|
166
|
+
'[<value>]'
|
167
|
+
else
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
|
171
|
+
if value_postfix
|
172
|
+
opt_def[:long] ? '' : ' ' + value_postfix
|
173
|
+
else
|
174
|
+
''
|
175
|
+
end
|
133
176
|
end
|
134
177
|
|
135
|
-
|
178
|
+
def long_value_postfix_for(opt_def)
|
179
|
+
value_postfix =
|
180
|
+
case opt_def[:argument]
|
181
|
+
when :required
|
182
|
+
'=<value>'
|
183
|
+
when :optional
|
184
|
+
'[=<value>]'
|
185
|
+
else
|
186
|
+
nil
|
187
|
+
end
|
188
|
+
|
189
|
+
if value_postfix
|
190
|
+
opt_def[:long] ? value_postfix : ''
|
191
|
+
else
|
192
|
+
''
|
193
|
+
end
|
194
|
+
end
|
136
195
|
|
196
|
+
def format_opt_def(opt_def, length)
|
197
|
+
short_value_postfix = short_value_postfix_for(opt_def)
|
198
|
+
long_value_postfix = long_value_postfix_for(opt_def)
|
199
|
+
|
200
|
+
opt_text = ''
|
201
|
+
opt_text_len = 0
|
202
|
+
if opt_def[:short]
|
203
|
+
opt_text << fmt.format_as_option('-' + opt_def[:short], @io)
|
204
|
+
opt_text << short_value_postfix
|
205
|
+
opt_text << ' '
|
206
|
+
opt_text_len += 1 + opt_def[:short].size + short_value_postfix.size + 1
|
207
|
+
else
|
208
|
+
opt_text << ' '
|
209
|
+
opt_text_len += 3
|
210
|
+
end
|
211
|
+
opt_text << fmt.format_as_option('--' + opt_def[:long], @io) if opt_def[:long]
|
212
|
+
opt_text << long_value_postfix
|
213
|
+
opt_text_len += 2 + opt_def[:long].size if opt_def[:long]
|
214
|
+
opt_text_len += long_value_postfix.size
|
215
|
+
|
216
|
+
' ' + opt_text + ' ' * (length + OPT_DESC_SPACING - opt_text_len)
|
217
|
+
end
|
218
|
+
end
|
137
219
|
end
|