gli 2.11.0 → 2.20.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 -19
- 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 +31 -8
- data/lib/gli/app_support.rb +15 -3
- data/lib/gli/command.rb +24 -2
- data/lib/gli/command_finder.rb +42 -25
- data/lib/gli/command_support.rb +7 -6
- 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 +2 -2
- data/lib/gli/commands/help_modules/command_help_format.rb +19 -1
- data/lib/gli/commands/help_modules/full_synopsis_formatter.rb +3 -2
- 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 +2 -1
- data/lib/gli/flag.rb +23 -2
- data/lib/gli/gli_option_parser.rb +66 -15
- data/lib/gli/option_parser_factory.rb +9 -2
- 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 +1 -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 +12 -6
- data/test/apps/todo/lib/todo/commands/create.rb +42 -41
- data/test/apps/todo/lib/todo/commands/list.rb +48 -36
- 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} +78 -50
- data/test/{tc_help.rb → unit/help_test.rb} +54 -113
- 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/{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 +85 -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 -100
- data/features/support/env.rb +0 -55
- data/features/todo.feature +0 -546
- 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
- /data/test/{init_simplecov.rb → unit/init_simplecov.rb} +0 -0
- /data/test/{fake_std_out.rb → unit/support/fake_std_out.rb} +0 -0
@@ -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
@@ -50,7 +50,7 @@ module GLI
|
|
50
50
|
# command :pack do ...
|
51
51
|
#
|
52
52
|
# Produces the synopsis:
|
53
|
-
# app.rb [global options] pack output input
|
53
|
+
# app.rb [global options] pack output input...
|
54
54
|
def arg(name, options=[])
|
55
55
|
@next_arguments ||= []
|
56
56
|
@next_arguments << Argument.new(name, Array(options).flatten)
|
@@ -75,6 +75,7 @@ module GLI
|
|
75
75
|
# +:arg_name+:: the arg name, instead of using #arg_name
|
76
76
|
# +:must_match+:: A regexp that the flag's value must match or an array of allowable values
|
77
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
|
78
79
|
#
|
79
80
|
# Example:
|
80
81
|
#
|
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,7 +45,7 @@ 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
|
@@ -43,6 +58,38 @@ module GLI
|
|
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
|
84
|
+
|
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
|
46
93
|
|
47
94
|
def verify_required_options!(flags, command, options)
|
48
95
|
missing_required_options = flags.values.
|
@@ -65,12 +112,12 @@ module GLI
|
|
65
112
|
end
|
66
113
|
|
67
114
|
def error_handler
|
68
|
-
lambda { |message,extra_error_context|
|
115
|
+
lambda { |message,extra_error_context|
|
69
116
|
raise UnknownCommandArgument.new(message,extra_error_context)
|
70
117
|
}
|
71
118
|
end
|
72
119
|
|
73
|
-
def parse!(parsing_result)
|
120
|
+
def parse!(parsing_result,argument_handling_strategy,autocomplete)
|
74
121
|
parsed_command_options = {}
|
75
122
|
command = parsing_result.command
|
76
123
|
arguments = nil
|
@@ -84,7 +131,7 @@ module GLI
|
|
84
131
|
arguments = option_block_parser.parse!(arguments)
|
85
132
|
|
86
133
|
parsed_command_options[command] = option_parser_factory.options_hash_with_defaults_set!
|
87
|
-
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)
|
88
135
|
next_command_name = arguments.shift
|
89
136
|
|
90
137
|
verify_required_options!(command.flags, command, parsed_command_options[command])
|
@@ -121,13 +168,17 @@ module GLI
|
|
121
168
|
parsing_result.command_options = command_options
|
122
169
|
parsing_result.command = command
|
123
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
|
+
|
124
175
|
parsing_result
|
125
176
|
end
|
126
177
|
|
127
178
|
end
|
128
179
|
|
129
180
|
class LegacyCommandOptionParser < NormalCommandOptionParser
|
130
|
-
def parse!(parsing_result)
|
181
|
+
def parse!(parsing_result,argument_handling_strategy,autocomplete)
|
131
182
|
command = parsing_result.command
|
132
183
|
option_parser_factory = OptionParserFactory.for_command(command,@accepts)
|
133
184
|
option_block_parser = LegacyCommandOptionBlockParser.new(option_parser_factory, self.error_handler)
|
@@ -136,7 +187,7 @@ module GLI
|
|
136
187
|
parsing_result.arguments = option_block_parser.parse!(parsing_result.arguments)
|
137
188
|
parsing_result.command_options = option_parser_factory.options_hash_with_defaults_set!
|
138
189
|
|
139
|
-
subcommand,args = find_subcommand(command,parsing_result.arguments)
|
190
|
+
subcommand,args = find_subcommand(command,parsing_result.arguments,autocomplete)
|
140
191
|
parsing_result.command = subcommand
|
141
192
|
parsing_result.arguments = args
|
142
193
|
verify_required_options!(command.flags, parsing_result.command, parsing_result.command_options)
|
@@ -144,7 +195,7 @@ module GLI
|
|
144
195
|
|
145
196
|
private
|
146
197
|
|
147
|
-
def find_subcommand(command,arguments)
|
198
|
+
def find_subcommand(command,arguments,autocomplete)
|
148
199
|
arguments = Array(arguments)
|
149
200
|
command_name = if arguments.empty?
|
150
201
|
nil
|
@@ -153,15 +204,15 @@ module GLI
|
|
153
204
|
end
|
154
205
|
|
155
206
|
default_command = command.get_default_command
|
156
|
-
finder = CommandFinder.new(command.commands,default_command.to_s)
|
207
|
+
finder = CommandFinder.new(command.commands, :default_command => default_command.to_s, :autocomplete => autocomplete)
|
157
208
|
|
158
209
|
begin
|
159
210
|
results = [finder.find_command(command_name),arguments[1..-1]]
|
160
|
-
find_subcommand(results[0],results[1])
|
211
|
+
find_subcommand(results[0],results[1],autocomplete)
|
161
212
|
rescue UnknownCommand, AmbiguousCommand
|
162
213
|
begin
|
163
214
|
results = [finder.find_command(default_command.to_s),arguments]
|
164
|
-
find_subcommand(results[0],results[1])
|
215
|
+
find_subcommand(results[0],results[1],autocomplete)
|
165
216
|
rescue UnknownCommand, AmbiguousCommand
|
166
217
|
[command,arguments]
|
167
218
|
end
|
@@ -58,8 +58,15 @@ module GLI
|
|
58
58
|
tokens.each do |ignore,token|
|
59
59
|
opts.on(*token.arguments_for_option_parser) do |arg|
|
60
60
|
token.names_and_aliases.each do |name|
|
61
|
-
|
62
|
-
|
61
|
+
if token.kind_of?(Flag) && token.multiple?
|
62
|
+
options[name] ||= []
|
63
|
+
options[name.to_sym] ||= []
|
64
|
+
options[name] << arg
|
65
|
+
options[name.to_sym] << arg
|
66
|
+
else
|
67
|
+
options[name] = arg
|
68
|
+
options[name.to_sym] = arg
|
69
|
+
end
|
63
70
|
end
|
64
71
|
end
|
65
72
|
end
|
data/lib/gli/options.rb
CHANGED
@@ -7,12 +7,12 @@ module GLI
|
|
7
7
|
|
8
8
|
# Return the value of an attribute
|
9
9
|
def[](k)
|
10
|
-
|
10
|
+
self.send(k.to_sym)
|
11
11
|
end
|
12
12
|
|
13
13
|
# Set the value of an attribute
|
14
14
|
def[]=(k, v)
|
15
|
-
|
15
|
+
self.send("#{k.to_sym}=",v)
|
16
16
|
end
|
17
17
|
|
18
18
|
def map(&block)
|
data/lib/gli/switch.rb
CHANGED
data/lib/gli/terminal.rb
CHANGED
@@ -43,7 +43,7 @@ module GLI
|
|
43
43
|
#
|
44
44
|
# +command+:: The command, as a String, to check for, without any path information.
|
45
45
|
def self.command_exists?(command)
|
46
|
-
ENV['PATH'].split(File::PATH_SEPARATOR).any? {|dir| File.
|
46
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).any? {|dir| File.exist? File.join(dir, command) }
|
47
47
|
end
|
48
48
|
|
49
49
|
def command_exists?(command)
|
@@ -78,7 +78,11 @@ module GLI
|
|
78
78
|
#
|
79
79
|
# Returns an Array of size two Ints representing the terminal width and height
|
80
80
|
def size
|
81
|
-
SIZE_DETERMINERS.
|
81
|
+
SIZE_DETERMINERS.each do |predicate, get_size|
|
82
|
+
next unless predicate.call
|
83
|
+
size = get_size.call
|
84
|
+
return size unless size == [0, 0]
|
85
|
+
end
|
82
86
|
rescue Exception => ex
|
83
87
|
raise ex if @unsafe
|
84
88
|
Terminal.default_size
|
data/lib/gli/version.rb
CHANGED
data/lib/gli.rb
CHANGED
data/object-model.dot
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
digraph G {
|
2
|
+
|
3
|
+
rankdir="BT"
|
4
|
+
nodesep=0.5
|
5
|
+
|
6
|
+
node[shape=record fontname=courier fontsize=18]
|
7
|
+
edge[fontname=avenir fontsize=12]
|
8
|
+
|
9
|
+
CommandLineToken [ label="{ CommandLineToken | #name\l | #description\l | #long_description\l | #aliases\l}"]
|
10
|
+
CommandLineOption [ label="{ CommandLineOption | #default_value \l }"]
|
11
|
+
DSL
|
12
|
+
Command
|
13
|
+
Flag [ label="{ Flag | #argument_name\l }"]
|
14
|
+
Switch
|
15
|
+
App
|
16
|
+
TopLevel [ label="top level?" shape=diamond fontname=avenir fontsize=12]
|
17
|
+
|
18
|
+
Command -> DSL [ arrowhead=oarrow label=" includes" minlen=3]
|
19
|
+
Command -> CommandLineToken [ arrowhead=oarrow label="inherits"]
|
20
|
+
CommandLineOption -> CommandLineToken [ arrowhead=oarrow label="inherits"]
|
21
|
+
Flag -> CommandLineOption [ arrowhead=oarrow label="inherits"]
|
22
|
+
Switch -> CommandLineOption [ arrowhead=oarrow label="inherits"]
|
23
|
+
Command -> TopLevel [ arrowhead=none label="parent" style=dotted]
|
24
|
+
TopLevel -> App [ arrowhead=odiamond label="YES" style=dotted ]
|
25
|
+
TopLevel -> Command [ arrowhead=odiamond label="NO" style=dotted ]
|
26
|
+
CommandLineOption -> Command [ arrowhead=odiamond style=dotted label="associated_command"]
|
27
|
+
|
28
|
+
{ rank=same; DSL; App }
|
29
|
+
}
|
data/object-model.png
ADDED
Binary file
|