visionmedia-commander 2.5.7 → 3.0.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.
- 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
|