gli 2.0.0 → 2.1.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.
@@ -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