visionmedia-commander 2.5.7 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +30 -0
- data/Manifest +6 -5
- data/README.rdoc +7 -20
- data/Todo.rdoc +24 -19
- data/bin/commander +30 -29
- data/commander.gemspec +4 -4
- data/lib/commander.rb +3 -14
- data/lib/commander/command.rb +105 -120
- data/lib/commander/core_ext.rb +0 -1
- data/lib/commander/core_ext/array.rb +9 -4
- data/lib/commander/core_ext/object.rb +37 -2
- data/lib/commander/core_ext/string.rb +2 -14
- data/lib/commander/help_formatters.rb +4 -2
- data/lib/commander/help_formatters/base.rb +3 -23
- data/lib/commander/help_formatters/terminal.rb +1 -8
- data/lib/commander/help_formatters/terminal/command_help.erb +8 -8
- data/lib/commander/help_formatters/terminal/help.erb +4 -4
- data/lib/commander/runner.rb +83 -83
- data/lib/commander/user_interaction.rb +36 -42
- data/lib/commander/version.rb +1 -1
- data/spec/command_spec.rb +128 -0
- data/spec/core_ext/array_spec.rb +20 -0
- data/spec/{import_spec.rb → core_ext/object_spec.rb} +10 -3
- data/spec/help_formatter_spec.rb +15 -15
- data/spec/runner_spec.rb +168 -0
- data/spec/spec_helper.rb +19 -4
- metadata +8 -10
- data/lib/commander/core_ext/kernel.rb +0 -12
- data/lib/commander/fileutils.rb +0 -30
- data/lib/commander/import.rb +0 -31
- data/spec/commander_spec.rb +0 -241
data/lib/commander/core_ext.rb
CHANGED
@@ -2,23 +2,28 @@
|
|
2
2
|
class Array
|
3
3
|
|
4
4
|
##
|
5
|
-
# Split +string+ into an array.
|
5
|
+
# Split +string+ into an array. Used in
|
6
|
+
# conjunection with Highline's ask, or ask_for_array
|
7
|
+
# methods, which must respond to #parse.
|
6
8
|
#
|
7
9
|
# === Highline example:
|
8
10
|
#
|
9
11
|
# # ask invokes Array#parse
|
10
12
|
# list = ask 'Favorite cookies:', Array
|
11
13
|
#
|
14
|
+
# # or use ask_for_CLASS
|
15
|
+
# list = ask_for_array 'Favorite cookies: '
|
16
|
+
#
|
12
17
|
|
13
18
|
def self.parse string
|
14
|
-
string
|
19
|
+
eval "%w(#{string})"
|
15
20
|
end
|
16
21
|
|
17
22
|
##
|
18
|
-
# Delete switches such as -h or --help.
|
23
|
+
# Delete switches such as -h or --help. Mutative.
|
19
24
|
|
20
25
|
def delete_switches
|
21
|
-
self.
|
26
|
+
self.delete_if { |value| value.to_s =~ /^-/ }
|
22
27
|
end
|
23
28
|
|
24
29
|
end
|
@@ -1,13 +1,48 @@
|
|
1
1
|
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
##
|
5
|
+
# Delegates the following methods:
|
6
|
+
#
|
7
|
+
# * Commander::Runner#add_command
|
8
|
+
# * Commander::Runner#command
|
9
|
+
# * Commander::Runner#commands
|
10
|
+
# * Commander::Runner#program
|
11
|
+
# * Commander::UI::ProgressBar#progress
|
12
|
+
#
|
13
|
+
|
2
14
|
class Object
|
3
15
|
|
16
|
+
extend Forwardable
|
4
17
|
include Commander::UI
|
5
18
|
|
19
|
+
def_delegators :$command_runner, :add_command, :command, :program, :run!, :commands
|
20
|
+
def_delegators Commander::UI::ProgressBar, :progress
|
21
|
+
|
6
22
|
##
|
7
|
-
#
|
23
|
+
# Return the current binding.
|
8
24
|
|
9
25
|
def get_binding
|
10
26
|
binding
|
11
27
|
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Return the current command runner.
|
12
31
|
|
13
|
-
|
32
|
+
def command_runner
|
33
|
+
$command_runner
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Implement #ask_for_CLASS.
|
38
|
+
|
39
|
+
include Module.new {
|
40
|
+
def method_missing meth, *args, &block
|
41
|
+
case meth.to_s
|
42
|
+
when /^ask_for_([\w]+)/ ; $terminal.ask(args.first, eval($1.capitalize))
|
43
|
+
else super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
}
|
47
|
+
|
48
|
+
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
class String
|
3
3
|
|
4
4
|
##
|
5
|
-
# Replace +hash+ keys with associated values.
|
5
|
+
# Replace +hash+ keys with associated values.
|
6
6
|
|
7
7
|
def tokenize! hash = {}
|
8
|
-
hash.each { |
|
8
|
+
hash.each { |key, value| gsub! /:#{key}/, value.to_s }
|
9
9
|
self
|
10
10
|
end
|
11
11
|
|
@@ -16,16 +16,4 @@ class String
|
|
16
16
|
self.dup.tokenize! hash
|
17
17
|
end
|
18
18
|
|
19
|
-
##
|
20
|
-
# Converts a string to camelcase.
|
21
|
-
|
22
|
-
def camelcase upcase_first_letter = true
|
23
|
-
up = upcase_first_letter
|
24
|
-
s = dup
|
25
|
-
s.gsub!(/\/(.?)/){ "::#{$1.upcase}" }
|
26
|
-
s.gsub!(/(?:_+|-+)([a-z])/){ $1.upcase }
|
27
|
-
s.gsub!(/(\A|\s)([a-z])/){ $1 + $2.upcase } if up
|
28
|
-
s
|
29
|
-
end
|
30
|
-
|
31
19
|
end
|
@@ -9,30 +9,10 @@ module Commander
|
|
9
9
|
# The default formatter is Commander::HelpFormatter::Terminal.
|
10
10
|
|
11
11
|
module HelpFormatter
|
12
|
-
|
13
12
|
class Base
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
def initialize runner
|
19
|
-
@runner = runner
|
20
|
-
end
|
21
|
-
|
22
|
-
##
|
23
|
-
# Renders global help.
|
24
|
-
|
25
|
-
def render
|
26
|
-
"Implement global help here"
|
27
|
-
end
|
28
|
-
|
29
|
-
##
|
30
|
-
# Renders +command+ specific help.
|
31
|
-
|
32
|
-
def render_command command
|
33
|
-
"Implement help for #{command.name} here"
|
34
|
-
end
|
35
|
-
|
13
|
+
def initialize runner; @runner = runner end
|
14
|
+
def render; 'Implement global help here' end
|
15
|
+
def render_command command; "Implement help for #{command.name} here" end
|
36
16
|
end
|
37
17
|
end
|
38
18
|
end
|
@@ -3,13 +3,6 @@ require 'erb'
|
|
3
3
|
|
4
4
|
module Commander
|
5
5
|
module HelpFormatter
|
6
|
-
|
7
|
-
##
|
8
|
-
# = Terminal
|
9
|
-
#
|
10
|
-
# Outputs help in a terminal friendly format,
|
11
|
-
# utilizing asni formatting via the highline gem.
|
12
|
-
|
13
6
|
class Terminal < Base
|
14
7
|
def render
|
15
8
|
template(:help).result @runner.get_binding
|
@@ -20,7 +13,7 @@ module Commander
|
|
20
13
|
end
|
21
14
|
|
22
15
|
def template name
|
23
|
-
ERB.new(File.read(File.expand_path(File.dirname(__FILE__)) + "/terminal/#{name}.erb"), nil,
|
16
|
+
ERB.new(File.read(File.expand_path(File.dirname(__FILE__)) + "/terminal/#{name}.erb"), nil, '-')
|
24
17
|
end
|
25
18
|
end
|
26
19
|
end
|
@@ -1,31 +1,31 @@
|
|
1
1
|
|
2
|
-
<%= color "NAME", :bold %>:
|
2
|
+
<%= $terminal.color "NAME", :bold %>:
|
3
3
|
|
4
4
|
<%= @name %>
|
5
5
|
|
6
|
-
<%= color "DESCRIPTION", :bold %>:
|
6
|
+
<%= $terminal.color "DESCRIPTION", :bold %>:
|
7
7
|
|
8
8
|
<%= @description || @summary || 'No description.' -%>
|
9
9
|
|
10
10
|
<% if @syntax -%>
|
11
11
|
|
12
|
-
<%= color "SYNOPSIS", :bold %>:
|
12
|
+
<%= $terminal.color "SYNOPSIS", :bold %>:
|
13
13
|
|
14
14
|
<%= @syntax -%>
|
15
15
|
|
16
16
|
<% end -%>
|
17
17
|
<% unless @examples.empty? -%>
|
18
18
|
|
19
|
-
<%= color "EXAMPLES", :bold %>:
|
20
|
-
<% @examples.each do |
|
19
|
+
<%= $terminal.color "EXAMPLES", :bold %>:
|
20
|
+
<% @examples.each do |description, command| -%>
|
21
21
|
|
22
|
-
# <%=
|
23
|
-
<%=
|
22
|
+
# <%= description %>
|
23
|
+
<%= command %>
|
24
24
|
<% end -%>
|
25
25
|
<% end -%>
|
26
26
|
<% unless @options.empty? -%>
|
27
27
|
|
28
|
-
<%= color "OPTIONS", :bold %>:
|
28
|
+
<%= $terminal.color "OPTIONS", :bold %>:
|
29
29
|
<% @options.each do |option| -%>
|
30
30
|
|
31
31
|
<%= option[:switches].join ', ' %>
|
@@ -1,19 +1,19 @@
|
|
1
1
|
|
2
|
-
<%= color "NAME", :bold %>:
|
2
|
+
<%= $terminal.color "NAME", :bold %>:
|
3
3
|
|
4
4
|
<%= program :name %>
|
5
5
|
|
6
|
-
<%= color "DESCRIPTION", :bold %>:
|
6
|
+
<%= $terminal.color "DESCRIPTION", :bold %>:
|
7
7
|
|
8
8
|
<%= program :description %>
|
9
9
|
|
10
|
-
<%= color "SUB-COMMANDS", :bold %>:
|
10
|
+
<%= $terminal.color "SUB-COMMANDS", :bold %>:
|
11
11
|
<% @commands.each_pair do |name, command| %>
|
12
12
|
<%= "%-20s %s" % [command.name, command.summary || command.description] -%>
|
13
13
|
<% end %>
|
14
14
|
<% if program :help -%>
|
15
15
|
<% program(:help).each_pair do |title, body| %>
|
16
|
-
<%= color title.to_s.upcase, :bold %>:
|
16
|
+
<%= $terminal.color title.to_s.upcase, :bold %>:
|
17
17
|
|
18
18
|
<%= body %>
|
19
19
|
<% end %>
|
data/lib/commander/runner.rb
CHANGED
@@ -22,31 +22,30 @@ module Commander
|
|
22
22
|
attr_reader :options
|
23
23
|
|
24
24
|
##
|
25
|
-
# Initialize a new command runner.
|
25
|
+
# Initialize a new command runner. Optionally
|
26
|
+
# supplying +args+ for mocking, or arbitrary usage.
|
26
27
|
|
27
28
|
def initialize args = ARGV
|
28
|
-
@args = args
|
29
|
-
@
|
30
|
-
:help_formatter => Commander::HelpFormatter::Terminal,
|
31
|
-
:int_message => "\nProcess interrupted",
|
32
|
-
}
|
29
|
+
@args, @commands, @options = args, {}, {}
|
30
|
+
@program = program_defaults
|
33
31
|
create_default_commands
|
34
32
|
parse_global_options
|
35
33
|
end
|
36
34
|
|
37
35
|
##
|
38
|
-
# Run
|
36
|
+
# Run command parsing and execution process.
|
39
37
|
|
40
38
|
def run!
|
41
|
-
|
39
|
+
require_program :name, :version, :description
|
42
40
|
case
|
43
|
-
when options[:version]
|
44
|
-
when options[:help]
|
45
|
-
else
|
41
|
+
when options[:version] ; $terminal.say "#{program(:name)} #{program(:version)}"
|
42
|
+
when options[:help] ; command(:help).run(*@args[1..@args.length])
|
43
|
+
else active_command.run *args_without_command_name
|
46
44
|
end
|
47
45
|
rescue InvalidCommandError
|
48
|
-
$terminal.say
|
49
|
-
rescue
|
46
|
+
$terminal.say 'invalid command. Use --help for more information'
|
47
|
+
rescue \
|
48
|
+
OptionParser::InvalidOption,
|
50
49
|
OptionParser::InvalidArgument,
|
51
50
|
OptionParser::MissingArgument => e
|
52
51
|
$terminal.say e
|
@@ -57,25 +56,25 @@ module Commander
|
|
57
56
|
#
|
58
57
|
# === Examples:
|
59
58
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
59
|
+
# # Set data
|
60
|
+
# program :name, 'Commander'
|
61
|
+
# program :version, Commander::VERSION
|
62
|
+
# program :description, 'Commander utility program.'
|
63
|
+
# program :help, 'Copyright', '2008 TJ Holowaychuk'
|
64
|
+
# program :help, 'Anything', 'You want'
|
65
|
+
# program :int_message 'Bye bye!'
|
66
|
+
#
|
67
|
+
# # Get data
|
68
|
+
# program :name # => 'Commander'
|
70
69
|
#
|
71
70
|
# === Keys:
|
72
71
|
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
72
|
+
# :name (required) Program name
|
73
|
+
# :version (required) Program version triple, ex: '0.0.1'
|
74
|
+
# :description (required) Program description
|
75
|
+
# :help_formatter Defaults to Commander::HelpFormatter::Terminal
|
76
|
+
# :help Allows addition of arbitrary global help blocks
|
77
|
+
# :int_message Message to display when interrupted (CTRL + C)
|
79
78
|
#
|
80
79
|
|
81
80
|
def program key, *args
|
@@ -84,31 +83,30 @@ module Commander
|
|
84
83
|
@program[:help][args.first] = args[1]
|
85
84
|
else
|
86
85
|
@program[key] = *args unless args.empty?
|
87
|
-
@program[key]
|
86
|
+
@program[key]
|
88
87
|
end
|
89
88
|
end
|
90
89
|
|
91
90
|
##
|
92
|
-
#
|
93
|
-
#
|
91
|
+
# Creates and yields a command instance when a block is passed.
|
92
|
+
# Otherise attempts to return the command, raising InvalidCommandError when
|
93
|
+
# it does not exist.
|
94
94
|
#
|
95
95
|
# === Examples:
|
96
96
|
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
# === See:
|
104
|
-
#
|
105
|
-
# * Commander::Command
|
106
|
-
# * Commander::Runner#add_command
|
97
|
+
# command :my_command do |c|
|
98
|
+
# c.when_called do |args|
|
99
|
+
# # Code
|
100
|
+
# end
|
101
|
+
# end
|
107
102
|
#
|
108
103
|
|
109
104
|
def command name, &block
|
110
|
-
|
111
|
-
|
105
|
+
if block
|
106
|
+
yield add_command(Commander::Command.new(name))
|
107
|
+
else
|
108
|
+
@commands[name.to_s] or raise InvalidCommandError, "invalid command '#{ name || 'nil' }'", caller
|
109
|
+
end
|
112
110
|
end
|
113
111
|
|
114
112
|
##
|
@@ -119,14 +117,7 @@ module Commander
|
|
119
117
|
end
|
120
118
|
|
121
119
|
##
|
122
|
-
#
|
123
|
-
|
124
|
-
def get_command name
|
125
|
-
@commands[name.to_s] or raise InvalidCommandError, "invalid command '#{ name || 'nil' }'", caller
|
126
|
-
end
|
127
|
-
|
128
|
-
##
|
129
|
-
# Check if a command exists.
|
120
|
+
# Check if a command +name+ exists.
|
130
121
|
|
131
122
|
def command_exists? name
|
132
123
|
@commands[name.to_s]
|
@@ -134,60 +125,70 @@ module Commander
|
|
134
125
|
|
135
126
|
##
|
136
127
|
# Get active command within arguments passed to this runner.
|
137
|
-
#
|
138
|
-
# === See:
|
139
|
-
#
|
140
|
-
# * Commander::Runner#parse_global_options
|
141
|
-
#
|
142
128
|
|
143
129
|
def active_command
|
144
|
-
@
|
130
|
+
@__active_command ||= command(command_name_from_args)
|
145
131
|
end
|
146
132
|
|
147
133
|
##
|
148
|
-
# Attemps to locate command from
|
149
|
-
#
|
134
|
+
# Attemps to locate a command name from within the arguments.
|
135
|
+
# Supports multi-word commands, using the largest possible match.
|
150
136
|
|
151
137
|
def command_name_from_args
|
152
|
-
@
|
153
|
-
|
154
|
-
|
155
|
-
|
138
|
+
@__command_name_from_args ||= valid_command_names_from(*@args.dup).last
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# Returns array of valid command names found within +args+.
|
143
|
+
|
144
|
+
def valid_command_names_from *args
|
145
|
+
arg_string = args.delete_switches.join ' '
|
146
|
+
commands.keys.map { |key| key if arg_string.include? key }.compact
|
156
147
|
end
|
157
148
|
|
158
149
|
##
|
159
150
|
# Help formatter instance.
|
160
151
|
|
161
152
|
def help_formatter
|
162
|
-
@
|
153
|
+
@__help_formatter ||= program(:help_formatter).new self
|
163
154
|
end
|
164
155
|
|
165
156
|
##
|
166
157
|
# Return arguments without the command name.
|
167
158
|
|
168
|
-
def
|
169
|
-
|
159
|
+
def args_without_command_name
|
160
|
+
removed = []
|
161
|
+
parts = command_name_from_args.split
|
162
|
+
@args.dup.delete_if do |arg|
|
163
|
+
removed << arg if parts.include?(arg) and not removed.include?(arg)
|
164
|
+
end
|
170
165
|
end
|
171
166
|
|
172
167
|
private
|
173
168
|
|
169
|
+
##
|
170
|
+
# Returns hash of program defaults.
|
171
|
+
|
172
|
+
def program_defaults
|
173
|
+
return :help_formatter => HelpFormatter::Terminal, :int_message => "\nProcess interrupted"
|
174
|
+
end
|
175
|
+
|
174
176
|
##
|
175
177
|
# Creates default commands such as 'help' which is
|
176
178
|
# essentially the same as using the --help switch.
|
177
179
|
|
178
180
|
def create_default_commands
|
179
181
|
command :help do |c|
|
180
|
-
c.syntax =
|
181
|
-
c.summary =
|
182
|
-
c.description =
|
183
|
-
c.example
|
184
|
-
c.example "Display help for 'foo'",
|
182
|
+
c.syntax = 'command help <sub_command>'
|
183
|
+
c.summary = 'Display help documentation for <sub_command>'
|
184
|
+
c.description = 'Display help documentation for the global or sub commands'
|
185
|
+
c.example 'Display global help', 'command help'
|
186
|
+
c.example "Display help for 'foo'", 'command help foo'
|
185
187
|
c.when_called do |args, options|
|
186
|
-
gen = help_formatter
|
187
188
|
if args.empty?
|
188
|
-
$terminal.say
|
189
|
+
$terminal.say help_formatter.render
|
189
190
|
else
|
190
|
-
$terminal.say
|
191
|
+
$terminal.say help_formatter.render_command(command(args.join(' ')))
|
191
192
|
end
|
192
193
|
end
|
193
194
|
end
|
@@ -199,14 +200,11 @@ module Commander
|
|
199
200
|
# These options are used by commander itself
|
200
201
|
# as well as allowing your program to specify
|
201
202
|
# global commands such as '--verbose'.
|
202
|
-
#
|
203
|
-
# TODO: allow 'option' method for global program
|
204
|
-
#
|
205
203
|
|
206
204
|
def parse_global_options
|
207
205
|
opts = OptionParser.new
|
208
|
-
opts.on(
|
209
|
-
opts.on(
|
206
|
+
opts.on('--help') { @options[:help] = true }
|
207
|
+
opts.on('--version') { @options[:version] = true }
|
210
208
|
opts.parse! @args.dup
|
211
209
|
rescue OptionParser::InvalidOption
|
212
210
|
# Ignore invalid options since options will be further
|
@@ -214,10 +212,12 @@ module Commander
|
|
214
212
|
end
|
215
213
|
|
216
214
|
##
|
217
|
-
# Raises a CommandError when the program +
|
215
|
+
# Raises a CommandError when the program any of the +keys+ are not present, or empty.
|
218
216
|
|
219
|
-
def
|
220
|
-
|
217
|
+
def require_program *keys
|
218
|
+
keys.each do |key|
|
219
|
+
raise CommandError, "program #{key} required" if program(key).nil? or program(key).empty?
|
220
|
+
end
|
221
221
|
end
|
222
222
|
|
223
223
|
end
|