gli 2.11.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 -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
|