gli 2.9.0 → 2.20.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 +5 -5
- data/.circleci/config.yml +28 -0
- data/.gitignore +3 -3
- data/.tool-versions +1 -0
- data/Gemfile +0 -2
- data/README.rdoc +29 -18
- data/Rakefile +15 -37
- data/bin/ci +29 -0
- data/bin/gli +24 -54
- data/bin/rake +29 -0
- data/bin/setup +5 -0
- data/exe/gli +68 -0
- data/gli.gemspec +20 -24
- data/gli.rdoc +9 -9
- data/lib/gli/app.rb +42 -8
- data/lib/gli/app_support.rb +17 -5
- data/lib/gli/argument.rb +20 -0
- data/lib/gli/command.rb +27 -2
- data/lib/gli/command_finder.rb +42 -25
- data/lib/gli/command_support.rb +13 -7
- data/lib/gli/commands/doc.rb +9 -3
- data/lib/gli/commands/help.rb +2 -1
- data/lib/gli/commands/help_modules/arg_name_formatter.rb +29 -2
- data/lib/gli/commands/help_modules/command_help_format.rb +19 -1
- data/lib/gli/commands/help_modules/full_synopsis_formatter.rb +5 -4
- data/lib/gli/commands/help_modules/global_help_format.rb +1 -1
- data/lib/gli/commands/help_modules/options_formatter.rb +4 -6
- data/lib/gli/commands/initconfig.rb +3 -6
- data/lib/gli/commands/rdoc_document_listener.rb +2 -1
- data/lib/gli/commands/scaffold.rb +71 -142
- data/lib/gli/dsl.rb +25 -1
- data/lib/gli/exceptions.rb +26 -0
- data/lib/gli/flag.rb +23 -2
- data/lib/gli/gli_option_parser.rb +73 -21
- data/lib/gli/option_parser_factory.rb +10 -3
- data/lib/gli/options.rb +2 -2
- data/lib/gli/switch.rb +4 -0
- data/lib/gli/terminal.rb +6 -2
- data/lib/gli/version.rb +1 -1
- data/lib/gli.rb +2 -0
- data/object-model.dot +29 -0
- data/object-model.png +0 -0
- data/test/apps/todo/Gemfile +1 -1
- data/test/apps/todo/bin/todo +16 -6
- data/test/apps/todo/lib/todo/commands/create.rb +48 -18
- data/test/apps/todo/lib/todo/commands/list.rb +48 -35
- data/test/apps/todo/lib/todo/commands/ls.rb +25 -24
- data/test/apps/todo/lib/todo/commands/make.rb +42 -39
- data/test/apps/todo/todo.gemspec +1 -2
- data/test/apps/todo_legacy/todo.gemspec +1 -2
- data/test/apps/todo_plugins/commands/third.rb +2 -0
- data/test/integration/gli_cli_test.rb +69 -0
- data/test/integration/gli_powered_app_test.rb +52 -0
- data/test/integration/scaffold_test.rb +30 -0
- data/test/integration/test_helper.rb +52 -0
- data/test/unit/command_finder_test.rb +54 -0
- data/test/{tc_command.rb → unit/command_test.rb} +20 -7
- data/test/unit/compound_command_test.rb +17 -0
- data/test/{tc_doc.rb → unit/doc_test.rb} +38 -51
- data/test/{tc_flag.rb → unit/flag_test.rb} +19 -25
- data/test/{tc_gli.rb → unit/gli_test.rb} +92 -49
- data/test/{tc_help.rb → unit/help_test.rb} +54 -113
- data/test/{init_simplecov.rb → unit/init_simplecov.rb} +0 -0
- data/test/{tc_options.rb → unit/options_test.rb} +4 -4
- data/test/unit/subcommand_parsing_test.rb +263 -0
- data/test/unit/subcommands_test.rb +245 -0
- data/test/{fake_std_out.rb → unit/support/fake_std_out.rb} +0 -0
- data/test/{config.yaml → unit/support/gli_test_config.yml} +1 -0
- data/test/unit/switch_test.rb +49 -0
- data/test/{tc_terminal.rb → unit/terminal_test.rb} +28 -3
- data/test/unit/test_helper.rb +13 -0
- data/test/unit/verbatim_wrapper_test.rb +24 -0
- metadata +86 -141
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -12
- data/ObjectModel.graffle +0 -1191
- data/bin/report_on_rake_results +0 -10
- data/bin/test_all_rubies.sh +0 -6
- data/features/gli_executable.feature +0 -90
- data/features/gli_init.feature +0 -232
- data/features/step_definitions/gli_executable_steps.rb +0 -18
- data/features/step_definitions/gli_init_steps.rb +0 -11
- data/features/step_definitions/todo_steps.rb +0 -96
- data/features/support/env.rb +0 -54
- data/features/todo.feature +0 -449
- data/features/todo_legacy.feature +0 -128
- data/test/option_test_helper.rb +0 -13
- data/test/tc_compound_command.rb +0 -22
- data/test/tc_subcommand_parsing.rb +0 -104
- data/test/tc_subcommands.rb +0 -259
- data/test/tc_switch.rb +0 -55
- data/test/tc_verbatim_wrapper.rb +0 -36
- data/test/test_helper.rb +0 -20
@@ -31,6 +31,7 @@ module GLI
|
|
31
31
|
end
|
32
32
|
puts "Created #{rvmrc}"
|
33
33
|
end
|
34
|
+
init_git(root_dir, project_name)
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
@@ -44,7 +45,7 @@ module GLI
|
|
44
45
|
puts "Created #{root_dir}/#{project_name}/README.rdoc"
|
45
46
|
File.open("#{root_dir}/#{project_name}/#{project_name}.rdoc",'w') do |file|
|
46
47
|
file << "= #{project_name}\n\n"
|
47
|
-
file << "Generate this with\n #{project_name}
|
48
|
+
file << "Generate this with\n #{project_name} _doc\nAfter you have described your command line interface"
|
48
49
|
end
|
49
50
|
puts "Created #{root_dir}/#{project_name}/#{project_name}.rdoc"
|
50
51
|
end
|
@@ -55,7 +56,7 @@ module GLI
|
|
55
56
|
file.puts <<EOS
|
56
57
|
# Ensure we require the local version and not one we might have installed already
|
57
58
|
require File.join([File.dirname(__FILE__),'lib','#{project_name}','version.rb'])
|
58
|
-
spec = Gem::Specification.new do |s|
|
59
|
+
spec = Gem::Specification.new do |s|
|
59
60
|
s.name = '#{project_name}'
|
60
61
|
s.version = #{project_name_as_module_name(project_name)}::VERSION
|
61
62
|
s.author = 'Your Name Here'
|
@@ -65,15 +66,14 @@ spec = Gem::Specification.new do |s|
|
|
65
66
|
s.summary = 'A description of your project'
|
66
67
|
s.files = `git ls-files`.split("\n")
|
67
68
|
s.require_paths << 'lib'
|
68
|
-
s.has_rdoc = true
|
69
69
|
s.extra_rdoc_files = ['README.rdoc','#{project_name}.rdoc']
|
70
70
|
s.rdoc_options << '--title' << '#{project_name}' << '--main' << 'README.rdoc' << '-ri'
|
71
71
|
s.bindir = 'bin'
|
72
72
|
s.executables << '#{project_name}'
|
73
|
-
s.add_development_dependency('rake')
|
74
|
-
s.add_development_dependency('rdoc')
|
75
|
-
s.add_development_dependency('
|
76
|
-
s.add_runtime_dependency('gli','#{GLI::VERSION}')
|
73
|
+
s.add_development_dependency('rake','~> 0.9.2')
|
74
|
+
s.add_development_dependency('rdoc', '~> 4.3')
|
75
|
+
s.add_development_dependency('minitest', '~> 5.14')
|
76
|
+
s.add_runtime_dependency('gli','~> #{GLI::VERSION}')
|
77
77
|
end
|
78
78
|
EOS
|
79
79
|
end
|
@@ -114,12 +114,6 @@ require 'rubygems'
|
|
114
114
|
require 'rubygems/package_task'
|
115
115
|
require 'rdoc/task'
|
116
116
|
EOS
|
117
|
-
if create_test_dir
|
118
|
-
file.puts <<EOS
|
119
|
-
require 'cucumber'
|
120
|
-
require 'cucumber/rake/task'
|
121
|
-
EOS
|
122
|
-
end
|
123
117
|
file.puts <<EOS
|
124
118
|
Rake::RDocTask.new do |rd|
|
125
119
|
rd.main = "README.rdoc"
|
@@ -134,44 +128,19 @@ end
|
|
134
128
|
EOS
|
135
129
|
if create_test_dir
|
136
130
|
file.puts <<EOS
|
137
|
-
CUKE_RESULTS = 'results.html'
|
138
|
-
CLEAN << CUKE_RESULTS
|
139
|
-
desc 'Run features'
|
140
|
-
Cucumber::Rake::Task.new(:features) do |t|
|
141
|
-
opts = "features --format html -o \#{CUKE_RESULTS} --format progress -x"
|
142
|
-
opts += " --tags \#{ENV['TAGS']}" if ENV['TAGS']
|
143
|
-
t.cucumber_opts = opts
|
144
|
-
t.fork = false
|
145
|
-
end
|
146
|
-
|
147
|
-
desc 'Run features tagged as work-in-progress (@wip)'
|
148
|
-
Cucumber::Rake::Task.new('features:wip') do |t|
|
149
|
-
tag_opts = ' --tags ~@pending'
|
150
|
-
tag_opts = ' --tags @wip'
|
151
|
-
t.cucumber_opts = "features --format html -o \#{CUKE_RESULTS} --format pretty -x -s\#{tag_opts}"
|
152
|
-
t.fork = false
|
153
|
-
end
|
154
|
-
|
155
|
-
task :cucumber => :features
|
156
|
-
task 'cucumber:wip' => 'features:wip'
|
157
|
-
task :wip => 'features:wip'
|
158
|
-
EOS
|
159
|
-
end
|
160
|
-
if create_test_dir
|
161
|
-
file.puts <<EOS
|
162
131
|
require 'rake/testtask'
|
163
132
|
Rake::TestTask.new do |t|
|
164
133
|
t.libs << "test"
|
165
134
|
t.test_files = FileList['test/*_test.rb']
|
166
135
|
end
|
167
136
|
|
168
|
-
task :default =>
|
137
|
+
task :default => :test
|
169
138
|
EOS
|
170
139
|
File.open("#{root_dir}/#{project_name}/test/default_test.rb",'w') do |test_file|
|
171
140
|
test_file.puts <<EOS
|
172
|
-
|
141
|
+
require_relative "test_helper"
|
173
142
|
|
174
|
-
class DefaultTest < Test
|
143
|
+
class DefaultTest < Minitest::Test
|
175
144
|
|
176
145
|
def setup
|
177
146
|
end
|
@@ -188,15 +157,10 @@ EOS
|
|
188
157
|
puts "Created #{root_dir}/#{project_name}/test/default_test.rb"
|
189
158
|
File.open("#{root_dir}/#{project_name}/test/test_helper.rb",'w') do |test_file|
|
190
159
|
test_file.puts <<EOS
|
191
|
-
require
|
160
|
+
require "minitest/autorun"
|
192
161
|
|
193
162
|
# Add test libraries you want to use here, e.g. mocha
|
194
|
-
|
195
|
-
class Test::Unit::TestCase
|
196
|
-
|
197
|
-
# Add global extensions to the test case class here
|
198
|
-
|
199
|
-
end
|
163
|
+
# Add helper classes or methods here, too
|
200
164
|
EOS
|
201
165
|
end
|
202
166
|
puts "Created #{root_dir}/#{project_name}/test/test_helper.rb"
|
@@ -210,54 +174,6 @@ EOS
|
|
210
174
|
bundler_file.puts "gemspec"
|
211
175
|
end
|
212
176
|
puts "Created #{root_dir}/#{project_name}/Gemfile"
|
213
|
-
if create_test_dir
|
214
|
-
features_dir = File.join(root_dir,project_name,'features')
|
215
|
-
FileUtils.mkdir features_dir
|
216
|
-
FileUtils.mkdir File.join(features_dir,"step_definitions")
|
217
|
-
FileUtils.mkdir File.join(features_dir,"support")
|
218
|
-
File.open(File.join(features_dir,"#{project_name}.feature"),'w') do |file|
|
219
|
-
file.puts <<EOS
|
220
|
-
Feature: My bootstrapped app kinda works
|
221
|
-
In order to get going on coding my awesome app
|
222
|
-
I want to have aruba and cucumber setup
|
223
|
-
So I don't have to do it myself
|
224
|
-
|
225
|
-
Scenario: App just runs
|
226
|
-
When I get help for "#{project_name}"
|
227
|
-
Then the exit status should be 0
|
228
|
-
EOS
|
229
|
-
end
|
230
|
-
File.open(File.join(features_dir,"step_definitions","#{project_name}_steps.rb"),'w') do |file|
|
231
|
-
file.puts <<EOS
|
232
|
-
When /^I get help for "([^"]*)"$/ do |app_name|
|
233
|
-
@app_name = app_name
|
234
|
-
step %(I run `\#{app_name} help`)
|
235
|
-
end
|
236
|
-
|
237
|
-
# Add more step definitions here
|
238
|
-
EOS
|
239
|
-
end
|
240
|
-
File.open(File.join(features_dir,"support","env.rb"),'w') do |file|
|
241
|
-
file.puts <<EOS
|
242
|
-
require 'aruba/cucumber'
|
243
|
-
|
244
|
-
ENV['PATH'] = "\#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}\#{File::PATH_SEPARATOR}\#{ENV['PATH']}"
|
245
|
-
LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
|
246
|
-
|
247
|
-
Before do
|
248
|
-
# Using "announce" causes massive warnings on 1.9.2
|
249
|
-
@puts = true
|
250
|
-
@original_rubylib = ENV['RUBYLIB']
|
251
|
-
ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
|
252
|
-
end
|
253
|
-
|
254
|
-
After do
|
255
|
-
ENV['RUBYLIB'] = @original_rubylib
|
256
|
-
end
|
257
|
-
EOS
|
258
|
-
end
|
259
|
-
puts "Created #{features_dir}"
|
260
|
-
end
|
261
177
|
end
|
262
178
|
|
263
179
|
def self.mk_binfile(root_dir,create_ext_dir,force,dry_run,project_name,commands)
|
@@ -278,82 +194,87 @@ rescue LoadError
|
|
278
194
|
exit 64
|
279
195
|
end
|
280
196
|
|
281
|
-
|
197
|
+
class App
|
198
|
+
extend GLI::App
|
199
|
+
|
200
|
+
program_desc 'Describe your application here'
|
282
201
|
|
283
|
-
|
202
|
+
version #{project_name_as_module_name(project_name)}::VERSION
|
284
203
|
|
285
|
-
|
204
|
+
subcommand_option_handling :normal
|
205
|
+
arguments :strict
|
286
206
|
|
287
|
-
desc 'Describe some switch here'
|
288
|
-
switch [:s,:switch]
|
207
|
+
desc 'Describe some switch here'
|
208
|
+
switch [:s,:switch]
|
289
209
|
|
290
|
-
desc 'Describe some flag here'
|
291
|
-
default_value 'the default'
|
292
|
-
arg_name 'The name of the argument'
|
293
|
-
flag [:f,:flagname]
|
210
|
+
desc 'Describe some flag here'
|
211
|
+
default_value 'the default'
|
212
|
+
arg_name 'The name of the argument'
|
213
|
+
flag [:f,:flagname]
|
294
214
|
EOS
|
295
215
|
first = true
|
296
216
|
commands.each do |command|
|
297
217
|
file.puts <<EOS
|
298
218
|
|
299
|
-
desc 'Describe #{command} here'
|
300
|
-
arg_name 'Describe arguments to #{command} here'
|
219
|
+
desc 'Describe #{command} here'
|
220
|
+
arg_name 'Describe arguments to #{command} here'
|
301
221
|
EOS
|
302
222
|
if first
|
303
223
|
file.puts <<EOS
|
304
|
-
command :#{command} do |c|
|
305
|
-
|
306
|
-
|
224
|
+
command :#{command} do |c|
|
225
|
+
c.desc 'Describe a switch to #{command}'
|
226
|
+
c.switch :s
|
307
227
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
228
|
+
c.desc 'Describe a flag to #{command}'
|
229
|
+
c.default_value 'default'
|
230
|
+
c.flag :f
|
231
|
+
c.action do |global_options,options,args|
|
312
232
|
|
313
|
-
|
314
|
-
|
315
|
-
# If you have any errors, just raise them
|
316
|
-
# raise "that command made no sense"
|
233
|
+
# Your command logic here
|
317
234
|
|
318
|
-
|
235
|
+
# If you have any errors, just raise them
|
236
|
+
# raise "that command made no sense"
|
237
|
+
|
238
|
+
puts "#{command} command ran"
|
239
|
+
end
|
319
240
|
end
|
320
|
-
end
|
321
241
|
EOS
|
322
242
|
else
|
323
243
|
file.puts <<EOS
|
324
|
-
command :#{command} do |c|
|
325
|
-
|
326
|
-
|
244
|
+
command :#{command} do |c|
|
245
|
+
c.action do |global_options,options,args|
|
246
|
+
puts "#{command} command ran"
|
247
|
+
end
|
327
248
|
end
|
328
|
-
end
|
329
249
|
EOS
|
330
250
|
end
|
331
251
|
first = false
|
332
252
|
end
|
333
253
|
file.puts <<EOS
|
334
254
|
|
335
|
-
pre do |global,command,options,args|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
end
|
255
|
+
pre do |global,command,options,args|
|
256
|
+
# Pre logic here
|
257
|
+
# Return true to proceed; false to abort and not call the
|
258
|
+
# chosen command
|
259
|
+
# Use skips_pre before a command to skip this block
|
260
|
+
# on that command only
|
261
|
+
true
|
262
|
+
end
|
343
263
|
|
344
|
-
post do |global,command,options,args|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
end
|
264
|
+
post do |global,command,options,args|
|
265
|
+
# Post logic here
|
266
|
+
# Use skips_post before a command to skip this
|
267
|
+
# block on that command only
|
268
|
+
end
|
349
269
|
|
350
|
-
on_error do |exception|
|
351
|
-
|
352
|
-
|
353
|
-
|
270
|
+
on_error do |exception|
|
271
|
+
# Error logic here
|
272
|
+
# return false to skip default error handling
|
273
|
+
true
|
274
|
+
end
|
354
275
|
end
|
355
276
|
|
356
|
-
exit run(ARGV)
|
277
|
+
exit App.run(ARGV)
|
357
278
|
EOS
|
358
279
|
puts "Created #{bin_file}"
|
359
280
|
end
|
@@ -365,6 +286,14 @@ EOS
|
|
365
286
|
true
|
366
287
|
end
|
367
288
|
|
289
|
+
def self.init_git(root_dir, project_name)
|
290
|
+
project_dir = "#{root_dir}/#{project_name}"
|
291
|
+
|
292
|
+
unless system("git", "init", "--quiet", project_dir)
|
293
|
+
puts "There was a problem initializing Git. Your gemspec may not work until you have done a successful `git init`"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
368
297
|
def self.mkdirs(dirs,force,dry_run)
|
369
298
|
exists = false
|
370
299
|
if !force
|
data/lib/gli/dsl.rb
CHANGED
@@ -36,6 +36,26 @@ module GLI
|
|
36
36
|
@next_arg_options = options
|
37
37
|
end
|
38
38
|
|
39
|
+
# Describes one of the arguments of the next command
|
40
|
+
#
|
41
|
+
# +name+:: A String that *briefly* describes the argument given to the following command.
|
42
|
+
# +options+:: Symbol or array of symbols to annotate this argument. This doesn't affect parsing, just
|
43
|
+
# the help output. Values recognized are:
|
44
|
+
# +:optional+:: indicates this argument is optional; will format it with square brackets
|
45
|
+
# +:multiple+:: indicates multiple values are accepted; will format appropriately
|
46
|
+
#
|
47
|
+
# Example:
|
48
|
+
# arg :output
|
49
|
+
# arg :input, :multiple
|
50
|
+
# command :pack do ...
|
51
|
+
#
|
52
|
+
# Produces the synopsis:
|
53
|
+
# app.rb [global options] pack output input...
|
54
|
+
def arg(name, options=[])
|
55
|
+
@next_arguments ||= []
|
56
|
+
@next_arguments << Argument.new(name, Array(options).flatten)
|
57
|
+
end
|
58
|
+
|
39
59
|
# set the default value of the next flag or switch
|
40
60
|
#
|
41
61
|
# +val+:: The default value to be used for the following flag if the user doesn't specify one
|
@@ -53,8 +73,9 @@ module GLI
|
|
53
73
|
# +:long_desc+:: the long_description, instead of using #long_desc
|
54
74
|
# +:default_value+:: the default value, instead of using #default_value
|
55
75
|
# +:arg_name+:: the arg name, instead of using #arg_name
|
56
|
-
# +:must_match+:: A regexp that the flag's value must match
|
76
|
+
# +:must_match+:: A regexp that the flag's value must match or an array of allowable values
|
57
77
|
# +:type+:: A Class (or object you passed to GLI::App#accept) to trigger type coversion
|
78
|
+
# +:multiple+:: if true, flag may be used multiple times and values are stored in an array
|
58
79
|
#
|
59
80
|
# Example:
|
60
81
|
#
|
@@ -152,10 +173,12 @@ module GLI
|
|
152
173
|
:description => @next_desc,
|
153
174
|
:arguments_name => @next_arg_name,
|
154
175
|
:arguments_options => @next_arg_options,
|
176
|
+
:arguments => @next_arguments,
|
155
177
|
:long_desc => @next_long_desc,
|
156
178
|
:skips_pre => @skips_pre,
|
157
179
|
:skips_post => @skips_post,
|
158
180
|
:skips_around => @skips_around,
|
181
|
+
:hide_commands_without_desc => @hide_commands_without_desc,
|
159
182
|
}
|
160
183
|
@commands_declaration_order ||= []
|
161
184
|
if names.first.kind_of? Hash
|
@@ -177,6 +200,7 @@ module GLI
|
|
177
200
|
yield command
|
178
201
|
end
|
179
202
|
clear_nexts
|
203
|
+
@next_arguments = []
|
180
204
|
end
|
181
205
|
alias :c :command
|
182
206
|
|
data/lib/gli/exceptions.rb
CHANGED
@@ -5,6 +5,21 @@ module GLI
|
|
5
5
|
module StandardException
|
6
6
|
def exit_code; 1; end
|
7
7
|
end
|
8
|
+
|
9
|
+
# Hack to request help from within a command
|
10
|
+
# Will *not* be rethrown when GLI_DEBUG is ON
|
11
|
+
class RequestHelp < StandardError
|
12
|
+
include StandardException
|
13
|
+
def exit_code; 0; end
|
14
|
+
|
15
|
+
# The command for which the argument was unknown
|
16
|
+
attr_reader :command_in_context
|
17
|
+
|
18
|
+
def initialize(command_in_context)
|
19
|
+
@command_in_context = command_in_context
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
8
23
|
# Indicates that the command line invocation was bad
|
9
24
|
class BadCommandLine < StandardError
|
10
25
|
include StandardException
|
@@ -44,6 +59,17 @@ module GLI
|
|
44
59
|
end
|
45
60
|
end
|
46
61
|
|
62
|
+
class MissingRequiredArgumentsException < BadCommandLine
|
63
|
+
# The command for which the argument was unknown
|
64
|
+
attr_reader :command_in_context
|
65
|
+
# +message+:: the error message to show the user
|
66
|
+
# +command+:: the command we were using to parse command-specific options
|
67
|
+
def initialize(message,command)
|
68
|
+
super(message)
|
69
|
+
@command_in_context = command
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
47
73
|
# Indicates the bad command line was an unknown command argument
|
48
74
|
class UnknownCommandArgument < CommandException
|
49
75
|
end
|
data/lib/gli/flag.rb
CHANGED
@@ -24,17 +24,18 @@ module GLI
|
|
24
24
|
# :must_match:: a regexp that the flag's value must match
|
25
25
|
# :type:: a class to convert the value to
|
26
26
|
# :required:: if true, this flag must be specified on the command line
|
27
|
+
# :multiple:: if true, flag may be used multiple times and values are stored in an array
|
27
28
|
# :mask:: if true, the default value of this flag will not be output in the help.
|
28
29
|
# This is useful for password flags where you might not want to show it
|
29
30
|
# on the command-line.
|
30
31
|
def initialize(names,options)
|
31
32
|
super(names,options)
|
32
33
|
@argument_name = options[:arg_name] || "arg"
|
33
|
-
@default_value = options[:default_value]
|
34
34
|
@must_match = options[:must_match]
|
35
35
|
@type = options[:type]
|
36
36
|
@mask = options[:mask]
|
37
37
|
@required = options[:required]
|
38
|
+
@multiple = options[:multiple]
|
38
39
|
end
|
39
40
|
|
40
41
|
# True if this flag is required on the command line
|
@@ -42,12 +43,32 @@ module GLI
|
|
42
43
|
@required
|
43
44
|
end
|
44
45
|
|
46
|
+
# True if the flag may be used multiple times.
|
47
|
+
def multiple?
|
48
|
+
@multiple
|
49
|
+
end
|
45
50
|
|
46
51
|
def safe_default_value
|
47
52
|
if @mask
|
48
53
|
"********"
|
49
54
|
else
|
50
|
-
default_value
|
55
|
+
# This uses @default_value instead of the `default_value` method because
|
56
|
+
# this method is only used for display, and for flags that may be passed
|
57
|
+
# multiple times, we want to display whatever is set in the code as the
|
58
|
+
# the default, or the string "none" rather than displaying an empty
|
59
|
+
# array.
|
60
|
+
@default_value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# The default value for this flag. Uses the value passed if one is set;
|
65
|
+
# otherwise uses `[]` if the flag support multiple arguments and `nil` if
|
66
|
+
# it does not.
|
67
|
+
def default_value
|
68
|
+
if @default_value
|
69
|
+
@default_value
|
70
|
+
elsif @multiple
|
71
|
+
[]
|
51
72
|
end
|
52
73
|
end
|
53
74
|
|
@@ -1,20 +1,35 @@
|
|
1
1
|
module GLI
|
2
2
|
# Parses the command-line options using an actual +OptionParser+
|
3
3
|
class GLIOptionParser
|
4
|
-
|
5
|
-
|
4
|
+
attr_accessor :options
|
5
|
+
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
:default_command => nil,
|
8
|
+
:autocomplete => true,
|
9
|
+
:subcommand_option_handling_strategy => :legacy,
|
10
|
+
:argument_handling_strategy => :loose
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(commands,flags,switches,accepts, options={})
|
14
|
+
self.options = DEFAULT_OPTIONS.merge(options)
|
15
|
+
|
16
|
+
command_finder = CommandFinder.new(commands,
|
17
|
+
:default_command => (options[:default_command] || :help),
|
18
|
+
:autocomplete => options[:autocomplete])
|
6
19
|
@global_option_parser = GlobalOptionParser.new(OptionParserFactory.new(flags,switches,accepts),command_finder,flags)
|
7
20
|
@accepts = accepts
|
8
|
-
|
21
|
+
if options[:argument_handling_strategy] == :strict && options[:subcommand_option_handling_strategy] != :normal
|
22
|
+
raise ArgumentError, "To use strict argument handling, you must enable normal subcommand_option_handling, e.g. subcommand_option_handling :normal"
|
23
|
+
end
|
9
24
|
end
|
10
25
|
|
11
26
|
# Given the command-line argument array, returns an OptionParsingResult
|
12
27
|
def parse_options(args) # :nodoc:
|
13
|
-
option_parser_class = self.class.const_get("#{
|
28
|
+
option_parser_class = self.class.const_get("#{options[:subcommand_option_handling_strategy].to_s.capitalize}CommandOptionParser")
|
14
29
|
OptionParsingResult.new.tap { |parsing_result|
|
15
30
|
parsing_result.arguments = args
|
16
31
|
parsing_result = @global_option_parser.parse!(parsing_result)
|
17
|
-
option_parser_class.new(@accepts).parse!(parsing_result)
|
32
|
+
option_parser_class.new(@accepts).parse!(parsing_result, options[:argument_handling_strategy], options[:autocomplete])
|
18
33
|
}
|
19
34
|
end
|
20
35
|
|
@@ -30,30 +45,63 @@ module GLI
|
|
30
45
|
def parse!(parsing_result)
|
31
46
|
parsing_result.arguments = GLIOptionBlockParser.new(@option_parser_factory,UnknownGlobalArgument).parse!(parsing_result.arguments)
|
32
47
|
parsing_result.global_options = @option_parser_factory.options_hash_with_defaults_set!
|
33
|
-
command_name = if parsing_result.global_options[:help]
|
48
|
+
command_name = if parsing_result.global_options[:help] || parsing_result.global_options[:version]
|
34
49
|
"help"
|
35
50
|
else
|
36
51
|
parsing_result.arguments.shift
|
37
52
|
end
|
38
53
|
parsing_result.command = @command_finder.find_command(command_name)
|
39
54
|
unless command_name == 'help'
|
40
|
-
verify_required_options!(@flags,parsing_result.global_options)
|
55
|
+
verify_required_options!(@flags, parsing_result.command, parsing_result.global_options)
|
41
56
|
end
|
42
57
|
parsing_result
|
43
58
|
end
|
44
59
|
|
45
60
|
protected
|
61
|
+
def verify_arguments!(arguments, command)
|
62
|
+
# Lets assume that if the user sets a 'arg_name' for the command it is for a complex scenario
|
63
|
+
# and we should not validate the arguments
|
64
|
+
return unless command.arguments_description.empty?
|
65
|
+
|
66
|
+
# Go through all declared arguments for the command, counting the min and max number
|
67
|
+
# of arguments
|
68
|
+
min_number_of_arguments = 0
|
69
|
+
max_number_of_arguments = 0
|
70
|
+
command.arguments.each do |arg|
|
71
|
+
if arg.optional?
|
72
|
+
max_number_of_arguments = max_number_of_arguments + 1
|
73
|
+
else
|
74
|
+
min_number_of_arguments = min_number_of_arguments + 1
|
75
|
+
max_number_of_arguments = max_number_of_arguments + 1
|
76
|
+
end
|
77
|
+
|
78
|
+
# Special case, as soon as we have a 'multiple' arguments, all bets are off for the
|
79
|
+
# maximum number of arguments !
|
80
|
+
if arg.multiple?
|
81
|
+
max_number_of_arguments = 99999
|
82
|
+
end
|
83
|
+
end
|
46
84
|
|
47
|
-
|
85
|
+
# Now validate the number of arguments
|
86
|
+
if arguments.size < min_number_of_arguments
|
87
|
+
raise MissingRequiredArgumentsException.new("Not enough arguments for command", command)
|
88
|
+
end
|
89
|
+
if arguments.size > max_number_of_arguments
|
90
|
+
raise MissingRequiredArgumentsException.new("Too many arguments for command", command)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def verify_required_options!(flags, command, options)
|
48
95
|
missing_required_options = flags.values.
|
49
96
|
select(&:required?).
|
50
97
|
reject { |option|
|
51
98
|
options[option.name] != nil
|
52
99
|
}
|
53
100
|
unless missing_required_options.empty?
|
54
|
-
|
101
|
+
missing_required_options.sort!
|
102
|
+
raise MissingRequiredArgumentsException.new(missing_required_options.map { |option|
|
55
103
|
"#{option.name} is required"
|
56
|
-
}.join(' ')
|
104
|
+
}.join(', '), command)
|
57
105
|
end
|
58
106
|
end
|
59
107
|
end
|
@@ -64,12 +112,12 @@ module GLI
|
|
64
112
|
end
|
65
113
|
|
66
114
|
def error_handler
|
67
|
-
lambda { |message,extra_error_context|
|
115
|
+
lambda { |message,extra_error_context|
|
68
116
|
raise UnknownCommandArgument.new(message,extra_error_context)
|
69
117
|
}
|
70
118
|
end
|
71
119
|
|
72
|
-
def parse!(parsing_result)
|
120
|
+
def parse!(parsing_result,argument_handling_strategy,autocomplete)
|
73
121
|
parsed_command_options = {}
|
74
122
|
command = parsing_result.command
|
75
123
|
arguments = nil
|
@@ -83,10 +131,10 @@ module GLI
|
|
83
131
|
arguments = option_block_parser.parse!(arguments)
|
84
132
|
|
85
133
|
parsed_command_options[command] = option_parser_factory.options_hash_with_defaults_set!
|
86
|
-
command_finder = CommandFinder.new(command.commands,command.get_default_command)
|
134
|
+
command_finder = CommandFinder.new(command.commands, :default_command => command.get_default_command, :autocomplete => autocomplete)
|
87
135
|
next_command_name = arguments.shift
|
88
136
|
|
89
|
-
verify_required_options!(command.flags,parsed_command_options[command])
|
137
|
+
verify_required_options!(command.flags, command, parsed_command_options[command])
|
90
138
|
|
91
139
|
begin
|
92
140
|
command = command_finder.find_command(next_command_name)
|
@@ -120,13 +168,17 @@ module GLI
|
|
120
168
|
parsing_result.command_options = command_options
|
121
169
|
parsing_result.command = command
|
122
170
|
parsing_result.arguments = Array(arguments.compact)
|
171
|
+
|
172
|
+
# Lets validate the arguments now that we know for sure the command that is invoked
|
173
|
+
verify_arguments!(parsing_result.arguments, parsing_result.command) if argument_handling_strategy == :strict
|
174
|
+
|
123
175
|
parsing_result
|
124
176
|
end
|
125
177
|
|
126
178
|
end
|
127
179
|
|
128
180
|
class LegacyCommandOptionParser < NormalCommandOptionParser
|
129
|
-
def parse!(parsing_result)
|
181
|
+
def parse!(parsing_result,argument_handling_strategy,autocomplete)
|
130
182
|
command = parsing_result.command
|
131
183
|
option_parser_factory = OptionParserFactory.for_command(command,@accepts)
|
132
184
|
option_block_parser = LegacyCommandOptionBlockParser.new(option_parser_factory, self.error_handler)
|
@@ -135,15 +187,15 @@ module GLI
|
|
135
187
|
parsing_result.arguments = option_block_parser.parse!(parsing_result.arguments)
|
136
188
|
parsing_result.command_options = option_parser_factory.options_hash_with_defaults_set!
|
137
189
|
|
138
|
-
subcommand,args = find_subcommand(command,parsing_result.arguments)
|
190
|
+
subcommand,args = find_subcommand(command,parsing_result.arguments,autocomplete)
|
139
191
|
parsing_result.command = subcommand
|
140
192
|
parsing_result.arguments = args
|
141
|
-
verify_required_options!(command.flags,parsing_result.command_options)
|
193
|
+
verify_required_options!(command.flags, parsing_result.command, parsing_result.command_options)
|
142
194
|
end
|
143
195
|
|
144
196
|
private
|
145
197
|
|
146
|
-
def find_subcommand(command,arguments)
|
198
|
+
def find_subcommand(command,arguments,autocomplete)
|
147
199
|
arguments = Array(arguments)
|
148
200
|
command_name = if arguments.empty?
|
149
201
|
nil
|
@@ -152,15 +204,15 @@ module GLI
|
|
152
204
|
end
|
153
205
|
|
154
206
|
default_command = command.get_default_command
|
155
|
-
finder = CommandFinder.new(command.commands,default_command.to_s)
|
207
|
+
finder = CommandFinder.new(command.commands, :default_command => default_command.to_s, :autocomplete => autocomplete)
|
156
208
|
|
157
209
|
begin
|
158
210
|
results = [finder.find_command(command_name),arguments[1..-1]]
|
159
|
-
find_subcommand(results[0],results[1])
|
211
|
+
find_subcommand(results[0],results[1],autocomplete)
|
160
212
|
rescue UnknownCommand, AmbiguousCommand
|
161
213
|
begin
|
162
214
|
results = [finder.find_command(default_command.to_s),arguments]
|
163
|
-
find_subcommand(results[0],results[1])
|
215
|
+
find_subcommand(results[0],results[1],autocomplete)
|
164
216
|
rescue UnknownCommand, AmbiguousCommand
|
165
217
|
[command,arguments]
|
166
218
|
end
|