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.
Files changed (92) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +28 -0
  3. data/.gitignore +3 -3
  4. data/.tool-versions +1 -0
  5. data/Gemfile +0 -2
  6. data/README.rdoc +29 -19
  7. data/Rakefile +15 -37
  8. data/bin/ci +29 -0
  9. data/bin/gli +24 -54
  10. data/bin/rake +29 -0
  11. data/bin/setup +5 -0
  12. data/exe/gli +68 -0
  13. data/gli.gemspec +20 -24
  14. data/gli.rdoc +9 -9
  15. data/lib/gli/app.rb +31 -8
  16. data/lib/gli/app_support.rb +15 -3
  17. data/lib/gli/command.rb +24 -2
  18. data/lib/gli/command_finder.rb +42 -25
  19. data/lib/gli/command_support.rb +7 -6
  20. data/lib/gli/commands/doc.rb +9 -3
  21. data/lib/gli/commands/help.rb +2 -1
  22. data/lib/gli/commands/help_modules/arg_name_formatter.rb +2 -2
  23. data/lib/gli/commands/help_modules/command_help_format.rb +19 -1
  24. data/lib/gli/commands/help_modules/full_synopsis_formatter.rb +3 -2
  25. data/lib/gli/commands/help_modules/global_help_format.rb +1 -1
  26. data/lib/gli/commands/help_modules/options_formatter.rb +4 -6
  27. data/lib/gli/commands/initconfig.rb +3 -6
  28. data/lib/gli/commands/rdoc_document_listener.rb +2 -1
  29. data/lib/gli/commands/scaffold.rb +71 -142
  30. data/lib/gli/dsl.rb +2 -1
  31. data/lib/gli/flag.rb +23 -2
  32. data/lib/gli/gli_option_parser.rb +66 -15
  33. data/lib/gli/option_parser_factory.rb +9 -2
  34. data/lib/gli/options.rb +2 -2
  35. data/lib/gli/switch.rb +4 -0
  36. data/lib/gli/terminal.rb +6 -2
  37. data/lib/gli/version.rb +1 -1
  38. data/lib/gli.rb +1 -0
  39. data/object-model.dot +29 -0
  40. data/object-model.png +0 -0
  41. data/test/apps/todo/Gemfile +1 -1
  42. data/test/apps/todo/bin/todo +12 -6
  43. data/test/apps/todo/lib/todo/commands/create.rb +42 -41
  44. data/test/apps/todo/lib/todo/commands/list.rb +48 -36
  45. data/test/apps/todo/lib/todo/commands/ls.rb +25 -24
  46. data/test/apps/todo/lib/todo/commands/make.rb +42 -39
  47. data/test/apps/todo/todo.gemspec +1 -2
  48. data/test/apps/todo_legacy/todo.gemspec +1 -2
  49. data/test/apps/todo_plugins/commands/third.rb +2 -0
  50. data/test/integration/gli_cli_test.rb +69 -0
  51. data/test/integration/gli_powered_app_test.rb +52 -0
  52. data/test/integration/scaffold_test.rb +30 -0
  53. data/test/integration/test_helper.rb +52 -0
  54. data/test/unit/command_finder_test.rb +54 -0
  55. data/test/{tc_command.rb → unit/command_test.rb} +20 -7
  56. data/test/unit/compound_command_test.rb +17 -0
  57. data/test/{tc_doc.rb → unit/doc_test.rb} +38 -51
  58. data/test/{tc_flag.rb → unit/flag_test.rb} +19 -25
  59. data/test/{tc_gli.rb → unit/gli_test.rb} +78 -50
  60. data/test/{tc_help.rb → unit/help_test.rb} +54 -113
  61. data/test/{tc_options.rb → unit/options_test.rb} +4 -4
  62. data/test/unit/subcommand_parsing_test.rb +263 -0
  63. data/test/unit/subcommands_test.rb +245 -0
  64. data/test/{config.yaml → unit/support/gli_test_config.yml} +1 -0
  65. data/test/unit/switch_test.rb +49 -0
  66. data/test/{tc_terminal.rb → unit/terminal_test.rb} +28 -3
  67. data/test/unit/test_helper.rb +13 -0
  68. data/test/unit/verbatim_wrapper_test.rb +24 -0
  69. metadata +85 -141
  70. data/.ruby-gemset +0 -1
  71. data/.ruby-version +0 -1
  72. data/.travis.yml +0 -12
  73. data/ObjectModel.graffle +0 -1191
  74. data/bin/report_on_rake_results +0 -10
  75. data/bin/test_all_rubies.sh +0 -6
  76. data/features/gli_executable.feature +0 -90
  77. data/features/gli_init.feature +0 -232
  78. data/features/step_definitions/gli_executable_steps.rb +0 -18
  79. data/features/step_definitions/gli_init_steps.rb +0 -11
  80. data/features/step_definitions/todo_steps.rb +0 -100
  81. data/features/support/env.rb +0 -55
  82. data/features/todo.feature +0 -546
  83. data/features/todo_legacy.feature +0 -128
  84. data/test/option_test_helper.rb +0 -13
  85. data/test/tc_compound_command.rb +0 -22
  86. data/test/tc_subcommand_parsing.rb +0 -104
  87. data/test/tc_subcommands.rb +0 -259
  88. data/test/tc_switch.rb +0 -55
  89. data/test/tc_verbatim_wrapper.rb +0 -36
  90. data/test/test_helper.rb +0 -20
  91. /data/test/{init_simplecov.rb → unit/init_simplecov.rb} +0 -0
  92. /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} rdoc\nAfter you have described your command line interface"
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('aruba')
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 => [:test,:features]
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
- require 'test_helper'
141
+ require_relative "test_helper"
173
142
 
174
- class DefaultTest < Test::Unit::TestCase
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 'test/unit'
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
- include GLI::App
197
+ class App
198
+ extend GLI::App
199
+
200
+ program_desc 'Describe your application here'
282
201
 
283
- program_desc 'Describe your application here'
202
+ version #{project_name_as_module_name(project_name)}::VERSION
284
203
 
285
- version #{project_name_as_module_name(project_name)}::VERSION
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
- c.desc 'Describe a switch to #{command}'
306
- c.switch :s
224
+ command :#{command} do |c|
225
+ c.desc 'Describe a switch to #{command}'
226
+ c.switch :s
307
227
 
308
- c.desc 'Describe a flag to #{command}'
309
- c.default_value 'default'
310
- c.flag :f
311
- c.action do |global_options,options,args|
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
- # Your command logic here
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
- puts "#{command} command ran"
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
- c.action do |global_options,options,args|
326
- puts "#{command} command ran"
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
- # Pre logic here
337
- # Return true to proceed; false to abort and not call the
338
- # chosen command
339
- # Use skips_pre before a command to skip this block
340
- # on that command only
341
- true
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
- # Post logic here
346
- # Use skips_post before a command to skip this
347
- # block on that command only
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
- # Error logic here
352
- # return false to skip default error handling
353
- true
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[, 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
- def initialize(commands,flags,switches,accepts,default_command = nil,subcommand_option_handling_strategy=:legacy)
5
- command_finder = CommandFinder.new(commands,default_command || "help")
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
- @subcommand_option_handling_strategy = subcommand_option_handling_strategy
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("#{@subcommand_option_handling_strategy.to_s.capitalize}CommandOptionParser")
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
- options[name] = arg
62
- options[name.to_sym] = arg
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
- @table[k.to_sym]
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
- @table[k.to_sym] = v
15
+ self.send("#{k.to_sym}=",v)
16
16
  end
17
17
 
18
18
  def map(&block)
data/lib/gli/switch.rb CHANGED
@@ -31,5 +31,9 @@ module GLI
31
31
  def negatable?
32
32
  @negatable
33
33
  end
34
+
35
+ def required?
36
+ false
37
+ end
34
38
  end
35
39
  end
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.exists? File.join(dir, command) }
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.select { |(predicate,ignore)| predicate.call }.first[1].call
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
@@ -1,5 +1,5 @@
1
1
  module GLI
2
2
  unless const_defined? :VERSION
3
- VERSION = '2.11.0'
3
+ VERSION = '2.20.0'
4
4
  end
5
5
  end
data/lib/gli.rb CHANGED
@@ -24,6 +24,7 @@ require 'gli/commands/doc'
24
24
 
25
25
  module GLI
26
26
  include GLI::App
27
+
27
28
  def self.included(klass)
28
29
  warn "You should include GLI::App instead"
29
30
  end
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