gli 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,6 @@
1
1
  = Git-Like Interface Command Line Parser
2
2
 
3
- <b>GLI2 is currently in Release Candidate, so this might change before the official release</b>
4
-
5
- The best way to make a "command-suite" command-line application (for the best way to make a
3
+ GLI is the best way to make a "command-suite" command-line application, e.g. one like <tt>git</tt> (for the best way to make a
6
4
  simpler command-line application, check out methadone[http://www.github.com/davetron5000/methadone]).
7
5
 
8
6
  GLI allows you to make a very polished, easy-to-maintain command-line application without a lot
@@ -44,6 +44,47 @@ Feature: The todo app has a nice user interface
44
44
  | help |
45
45
  | --version |
46
46
 
47
+ Scenario: Help completion mode
48
+ When I successfully run `todo help -c`
49
+ Then the output should contain:
50
+ """
51
+ ch2
52
+ chained
53
+ chained2
54
+ create
55
+ first
56
+ help
57
+ initconfig
58
+ list
59
+ ls
60
+ new
61
+ second
62
+ """
63
+
64
+ Scenario: Help completion mode for partial match
65
+ When I successfully run `todo help -c ch`
66
+ Then the output should contain:
67
+ """
68
+ ch2
69
+ chained
70
+ chained2
71
+ """
72
+
73
+ Scenario: Help completion mode for subcommands
74
+ When I successfully run `todo help -c list`
75
+ Then the output should contain:
76
+ """
77
+ contexts
78
+ tasks
79
+ """
80
+
81
+ Scenario: Help completion mode partial match for subcommands
82
+ When I successfully run `todo help -c list con`
83
+ Then the output should contain:
84
+ """
85
+ contexts
86
+ """
87
+
47
88
  Scenario: Getting Help for a top level command of todo
48
89
  When I successfully run `todo help list`
49
90
  Then the output should contain:
@@ -98,8 +139,8 @@ Feature: The todo app has a nice user interface
98
139
 
99
140
  SYNOPSIS
100
141
  todo [global options] create [command options]
101
- todo [global options] create [command options] contexts
102
- todo [global options] create [command options] tasks
142
+ todo [global options] create [command options] contexts [context_name]
143
+ todo [global options] create [command options] tasks task_name[, task_name]*
103
144
 
104
145
  COMMANDS
105
146
  <default> - Makes a new task
@@ -141,6 +141,14 @@ module GLI
141
141
  switch :version, :negatable => false
142
142
  end
143
143
 
144
+ # By default, GLI mutates the argument passed to it. This is
145
+ # consistent with +OptionParser+, but be less than ideal. Since
146
+ # that value, for scaffolded apps, is +ARGV+, you might want to
147
+ # refer to the entire command-line via +ARGV+ and thus not want it mutated.
148
+ def preserve_argv(preserve=true)
149
+ @preserve_argv = preserve
150
+ end
151
+
144
152
  # Call this with +true+ will cause the +global_options+ and
145
153
  # +options+ passed to your code to be wrapped in
146
154
  # Options, which is a subclass of +OpenStruct+ that adds
@@ -37,12 +37,13 @@ module GLI
37
37
  @default_command
38
38
  end
39
39
 
40
- # Runs whatever command is needed based on the arguments.
40
+ # Runs whatever command is needed based on the arguments.
41
41
  #
42
42
  # +args+:: the command line ARGV array
43
43
  #
44
44
  # Returns a number that would be a reasonable exit code
45
45
  def run(args) #:nodoc:
46
+ args = args.dup if @preserve_argv
46
47
  command = nil
47
48
  begin
48
49
  override_defaults_based_on_config(parse_config)
@@ -169,7 +170,7 @@ module GLI
169
170
  stderr.puts error_message(ex)
170
171
  if ex.kind_of?(OptionParser::ParseError) || ex.kind_of?(BadCommandLine)
171
172
  stderr.puts
172
- commands[:help] and commands[:help].execute([],[],command.nil? ? [] : [command.name.to_s])
173
+ commands[:help] and commands[:help].execute({},{},command.nil? ? [] : [command.name.to_s])
173
174
  end
174
175
  end
175
176
 
@@ -46,6 +46,7 @@ module GLI
46
46
  def initialize(options)
47
47
  super(options[:names],options[:description],options[:long_desc])
48
48
  @arguments_description = options[:arguments_name] || ''
49
+ @arguments_options = Array(options[:arguments_options]).flatten
49
50
  @skips_pre = options[:skips_pre]
50
51
  @skips_post = options[:skips_post]
51
52
  @skips_around = options[:skips_around]
@@ -20,6 +20,10 @@ module GLI
20
20
  @arguments_description
21
21
  end
22
22
 
23
+ def arguments_options
24
+ @arguments_options
25
+ end
26
+
23
27
  # If true, this command doesn't want the pre block run before it executes
24
28
  def skips_pre
25
29
  @skips_pre
@@ -76,11 +80,11 @@ module GLI
76
80
  end
77
81
  end
78
82
 
79
- def arg_name(d)
83
+ def arg_name(d,options=[])
80
84
  if parent.kind_of? Command
81
- parent.arg_name(d)
85
+ parent.arg_name(d,options)
82
86
  else
83
- super(d)
87
+ super(d,options)
84
88
  end
85
89
  end
86
90
 
@@ -6,6 +6,8 @@ require 'gli/commands/help_modules/text_wrapper'
6
6
  require 'gli/commands/help_modules/options_formatter'
7
7
  require 'gli/commands/help_modules/global_help_format'
8
8
  require 'gli/commands/help_modules/command_help_format'
9
+ require 'gli/commands/help_modules/help_completion_format'
10
+ require 'gli/commands/help_modules/command_finder'
9
11
 
10
12
  module GLI
11
13
  module Commands
@@ -20,6 +22,9 @@ module GLI
20
22
  :skips_post => true,
21
23
  :skips_around => true)
22
24
  @app = app
25
+ desc 'List commands one per line, to assist with shell completion'
26
+ switch :c
27
+
23
28
  action do |global_options,options,arguments|
24
29
  show_help(global_options,options,arguments,output,error)
25
30
  end
@@ -28,37 +33,21 @@ module GLI
28
33
  private
29
34
 
30
35
  def show_help(global_options,options,arguments,out,error)
31
- if arguments.empty?
36
+ command_finder = HelpModules::CommandFinder.new(@app,arguments,error)
37
+ if options[:c]
38
+ help_output = HelpModules::HelpCompletionFormat.new(@app,command_finder,arguments).format
39
+ out.puts help_output unless help_output.nil?
40
+ elsif arguments.empty? || options[:c]
32
41
  out.puts HelpModules::GlobalHelpFormat.new(@app).format
33
42
  else
34
43
  name = arguments.shift
35
- command = find_command(name,@app)
36
- return if unknown_command(command,name,error)
37
- while !arguments.empty?
38
- name = arguments.shift
39
- command = find_command(name,command)
40
- return if unknown_command(command,name,error)
44
+ command = command_finder.find_command(name)
45
+ unless command.nil?
46
+ out.puts HelpModules::CommandHelpFormat.new(command,@app,File.basename($0).to_s).format
41
47
  end
42
- out.puts HelpModules::CommandHelpFormat.new(command,@app,File.basename($0).to_s).format
43
48
  end
44
49
  end
45
50
 
46
- def unknown_command(command,name,error)
47
- if command.nil?
48
- error.puts "error: Unknown command '#{name}'. Use 'gli help' for a list of commands."
49
- true
50
- else
51
- false
52
- end
53
- end
54
-
55
- def find_command(command_name,base)
56
- base.commands.values.select { |command|
57
- if [command.name,Array(command.aliases)].flatten.map(&:to_s).any? { |_| _ == command_name }
58
- command
59
- end
60
- }.first
61
- end
62
51
  end
63
52
  end
64
53
  end
@@ -0,0 +1,60 @@
1
+ module GLI
2
+ module Commands
3
+ module HelpModules
4
+ # Finds commands from the application/command data structures
5
+ class CommandFinder
6
+
7
+ attr_reader :last_unknown_command
8
+ attr_reader :last_found_command
9
+ attr_writer :squelch_stderr
10
+
11
+ def initialize(app,arguments,error)
12
+ @app = app
13
+ @arguments = arguments
14
+ @error = error
15
+ @squelch_stderr = false
16
+ @last_unknown_command = nil
17
+ end
18
+
19
+ def find_command(name)
20
+ command = find_command_from_base(name,@app)
21
+ return if unknown_command?(command,name,@error)
22
+ @last_found_command = command
23
+ while !@arguments.empty?
24
+ name = @arguments.shift
25
+ command = find_command_from_base(name,command)
26
+ return if unknown_command?(command,name,@error)
27
+ @last_found_command = command
28
+ end
29
+ command
30
+ end
31
+
32
+ private
33
+
34
+ # Given the name of a command to find, and a base, either the app or another command, returns
35
+ # the command object or nil.
36
+ def find_command_from_base(command_name,base)
37
+ base.commands.values.select { |command|
38
+ if [command.name,Array(command.aliases)].flatten.map(&:to_s).any? { |_| _ == command_name }
39
+ command
40
+ end
41
+ }.first
42
+ end
43
+
44
+ # Checks if the return from find_command was unknown and, if so, prints an error
45
+ # for the user on the error device, returning true or false if the command was unknown.
46
+ def unknown_command?(command,name,error)
47
+ if command.nil?
48
+ @last_unknown_command = name
49
+ unless @squelch_stderr
50
+ error.puts "error: Unknown command '#{name}'. Use 'gli help' for a list of commands."
51
+ end
52
+ true
53
+ else
54
+ false
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -72,8 +72,21 @@ COMMANDS
72
72
  else
73
73
  usage << sub.name.to_s
74
74
  end
75
+ usage << format_arg_name(sub)
75
76
  usage
76
77
  end
78
+
79
+ def format_arg_name(command)
80
+ return '' if String(command.arguments_description).strip == ''
81
+ desc = command.arguments_description
82
+ if command.arguments_options.include? :optional
83
+ desc = "[#{desc}]"
84
+ end
85
+ if command.arguments_options.include? :multiple
86
+ desc = "#{desc}[, #{desc}]*"
87
+ end
88
+ " " + desc
89
+ end
77
90
 
78
91
  def basic_usage(flags_and_switches)
79
92
  usage = @basic_invocation.dup
@@ -0,0 +1,31 @@
1
+ module GLI
2
+ module Commands
3
+ module HelpModules
4
+ class HelpCompletionFormat
5
+ def initialize(app,command_finder,args)
6
+ @app = app
7
+ @command_finder = command_finder
8
+ @command_finder.squelch_stderr = true
9
+ @args = args
10
+ end
11
+
12
+ def format
13
+ name = @args.shift
14
+
15
+ base = @command_finder.find_command(name)
16
+ base = @command_finder.last_found_command if base.nil?
17
+ base = @app if base.nil?
18
+
19
+ prefix_to_match = @command_finder.last_unknown_command
20
+
21
+ base.commands.values.map { |command|
22
+ [command.name,command.aliases]
23
+ }.flatten.compact.map(&:to_s).sort.select { |command_name|
24
+ prefix_to_match.nil? || command_name =~ /^#{prefix_to_match}/
25
+ }.join("\n")
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -19,6 +19,10 @@ module GLI
19
19
  # this VERY short and, ideally, without any spaces (see Example).
20
20
  #
21
21
  # +name+:: A String that *briefly* describes the argument given to the following command or flag.
22
+ # +options+:: Symbol or array of symbols to annotate this argument. This doesn't affect parsing, just
23
+ # the help output. Values recognized are:
24
+ # +:optional+:: indicates this argument is optional; will format it with square brackets
25
+ # +:multiple+:: indicates multiple values are accepted; will format appropriately
22
26
  #
23
27
  # Example:
24
28
  # desc 'Set the filename'
@@ -27,7 +31,10 @@ module GLI
27
31
  #
28
32
  # Produces:
29
33
  # -f, --filename=file_name Set the filename
30
- def arg_name(name); @next_arg_name = name; end
34
+ def arg_name(name,options=[])
35
+ @next_arg_name = name
36
+ @next_arg_options = options
37
+ end
31
38
 
32
39
  # set the default value of the next flag
33
40
  #
@@ -94,6 +101,7 @@ module GLI
94
101
  def clear_nexts # :nodoc:
95
102
  @next_desc = nil
96
103
  @next_arg_name = nil
104
+ @next_arg_options = nil
97
105
  @next_default_value = nil
98
106
  @next_long_desc = nil
99
107
  end
@@ -137,6 +145,7 @@ module GLI
137
145
  command_options = {
138
146
  :description => @next_desc,
139
147
  :arguments_name => @next_arg_name,
148
+ :arguments_options => @next_arg_options,
140
149
  :long_desc => @next_long_desc,
141
150
  :skips_pre => @skips_pre,
142
151
  :skips_post => @skips_post,
@@ -23,7 +23,7 @@ module GLI
23
23
  remaining_args = nil
24
24
 
25
25
  global_options,command_name,args = parse_global_options(OptionParserFactory.new(@flags,@switches,@accepts), args)
26
- @flags.each do |name,flag|
26
+ @flags.each do |name,flag|
27
27
  global_options[name] = flag.default_value unless global_options[name]
28
28
  end
29
29
 
@@ -39,11 +39,11 @@ module GLI
39
39
  command,
40
40
  args)
41
41
 
42
- command.flags.each do |name,flag|
42
+ command.flags.each do |name,flag|
43
43
  command_options[name] = flag.default_value unless command_options[name]
44
44
  end
45
- command.switches.each do |name,switch|
46
- command_options[name] = switch.default_value unless command_options[name]
45
+ command.switches.each do |name,switch|
46
+ command_options[name] = switch.default_value unless command_options[name]
47
47
  end
48
48
 
49
49
  [global_options,command,command_options,args]
@@ -1,5 +1,5 @@
1
1
  module GLI
2
2
  unless const_defined? :VERSION
3
- VERSION = '2.0.0' #:nodoc:
3
+ VERSION = '2.1.0' #:nodoc:
4
4
  end
5
5
  end
@@ -1,6 +1,7 @@
1
1
  desc "Create a new task or context"
2
2
  command [:create,:new] do |c|
3
3
  c.desc "Make a new task"
4
+ c.arg_name 'task_name', :multiple
4
5
  c.command :tasks do |tasks|
5
6
  tasks.action do |global,options,args|
6
7
  puts "#{args}"
@@ -8,6 +9,7 @@ command [:create,:new] do |c|
8
9
  end
9
10
 
10
11
  c.desc "Make a new context"
12
+ c.arg_name 'context_name', :optional
11
13
  c.command :contexts do |contexts|
12
14
  contexts.action do |global,options,args|
13
15
  puts "#{args}"
@@ -15,7 +17,7 @@ command [:create,:new] do |c|
15
17
  end
16
18
 
17
19
  c.default_desc "Makes a new task"
18
- c.action do
20
+ c.action do
19
21
  puts "default action"
20
22
  end
21
23
  end
@@ -21,11 +21,12 @@ command [:list] do |c|
21
21
  )
22
22
  c.command :tasks do |tasks|
23
23
  tasks.desc "blah blah crud x whatever"
24
- tasks.flag [:x]
24
+ tasks.flag [:x], :must_match => Array
25
25
 
26
26
  tasks.flag :flag
27
27
 
28
28
  tasks.action do |global,options,args|
29
+ puts options[:x].inspect
29
30
  puts "list tasks: #{args.join(',')}"
30
31
  end
31
32
  end
@@ -306,6 +306,9 @@ command: help
306
306
  desc: Shows a list of commands or help for one command
307
307
  long_desc: Gets help for the application or its commands. Can also list the commands in a way helpful to creating a bash-style completion function
308
308
  arg_name: command
309
+ switch: c
310
+ desc: List commands one per line, to assist with shell completion
311
+ negatable: true
309
312
  default_command:
310
313
  end help
311
314
  default_command:
@@ -558,6 +558,37 @@ class TC_testGLI < Clean::Test::TestCase
558
558
  assert_equal 'crud',@baz.value
559
559
  end
560
560
 
561
+ def test_that_we_mutate_ARGV_by_default
562
+ @app.reset
563
+ @app.flag :f
564
+ @app.command :foo do |c|
565
+ c.action do |*args|
566
+ end
567
+ end
568
+
569
+ argv = %w(-f some_flag foo bar blah)
570
+
571
+ @app.run(argv)
572
+
573
+ assert_equal %w(bar blah),argv
574
+ end
575
+
576
+ def test_that_we_can_avoid_mutating_ARGV
577
+ @app.reset
578
+ @app.flag :f
579
+ @app.command :foo do |c|
580
+ c.action do |*args|
581
+ end
582
+ end
583
+ @app.preserve_argv
584
+
585
+ argv = %w(-f some_flag foo bar blah)
586
+
587
+ @app.run(argv)
588
+
589
+ assert_equal %w(-f some_flag foo bar blah),argv
590
+ end
591
+
561
592
  private
562
593
 
563
594
  def do_test_flag_create(object)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-14 00:00:00.000000000 Z
12
+ date: 2012-09-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -213,12 +213,18 @@ files:
213
213
  bGliL2dsaS9jb21tYW5kcy9kb2MucmI=
214
214
  - !binary |-
215
215
  bGliL2dsaS9jb21tYW5kcy9oZWxwLnJi
216
+ - !binary |-
217
+ bGliL2dsaS9jb21tYW5kcy9oZWxwX21vZHVsZXMvY29tbWFuZF9maW5kZXIu
218
+ cmI=
216
219
  - !binary |-
217
220
  bGliL2dsaS9jb21tYW5kcy9oZWxwX21vZHVsZXMvY29tbWFuZF9oZWxwX2Zv
218
221
  cm1hdC5yYg==
219
222
  - !binary |-
220
223
  bGliL2dsaS9jb21tYW5kcy9oZWxwX21vZHVsZXMvZ2xvYmFsX2hlbHBfZm9y
221
224
  bWF0LnJi
225
+ - !binary |-
226
+ bGliL2dsaS9jb21tYW5kcy9oZWxwX21vZHVsZXMvaGVscF9jb21wbGV0aW9u
227
+ X2Zvcm1hdC5yYg==
222
228
  - !binary |-
223
229
  bGliL2dsaS9jb21tYW5kcy9oZWxwX21vZHVsZXMvbGlzdF9mb3JtYXR0ZXIu
224
230
  cmI=
@@ -329,7 +335,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
329
335
  version: '0'
330
336
  segments:
331
337
  - 0
332
- hash: -1554108449042083355
338
+ hash: 2667779553904162412
333
339
  required_rubygems_version: !ruby/object:Gem::Requirement
334
340
  none: false
335
341
  requirements:
@@ -338,7 +344,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
338
344
  version: '0'
339
345
  segments:
340
346
  - 0
341
- hash: -1554108449042083355
347
+ hash: 2667779553904162412
342
348
  requirements: []
343
349
  rubyforge_project: gli
344
350
  rubygems_version: 1.8.24