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
@@ -11,19 +11,14 @@ module Commander
|
|
11
11
|
|
12
12
|
module UI
|
13
13
|
|
14
|
-
##
|
15
|
-
# Format used within #log.
|
16
|
-
|
17
|
-
LOG_FORMAT = "%15s %s"
|
18
|
-
|
19
14
|
##
|
20
15
|
# Ask the user for a password. Specify a custom
|
21
|
-
#
|
16
|
+
# +message+ other than 'Password: ' or override the
|
22
17
|
# default +mask+ of '*'.
|
23
18
|
|
24
|
-
def password
|
25
|
-
pass = ask(
|
26
|
-
pass = password
|
19
|
+
def password message = 'Password: ', mask = '*'
|
20
|
+
pass = ask(message) { |q| q.echo = mask }
|
21
|
+
pass = password message, mask if pass.empty?
|
27
22
|
pass
|
28
23
|
end
|
29
24
|
|
@@ -35,11 +30,9 @@ module Commander
|
|
35
30
|
# remove path/to/old_file.rb
|
36
31
|
# remove path/to/old_file2.rb
|
37
32
|
#
|
38
|
-
# To alter this format simply change the Commander::UI::LOG_FORMAT
|
39
|
-
# constant to whatever you like.
|
40
33
|
|
41
34
|
def log action, *args
|
42
|
-
say
|
35
|
+
say '%15s %s' % [action, args.join(' ')]
|
43
36
|
end
|
44
37
|
|
45
38
|
##
|
@@ -48,33 +41,34 @@ module Commander
|
|
48
41
|
# Terminal progress bar utility. In its most basic form
|
49
42
|
# requires that the developer specifies when the bar should
|
50
43
|
# be incremented. Note that a hash of tokens may be passed to
|
51
|
-
# #increment, (or returned when using
|
44
|
+
# #increment, (or returned when using Object#progress).
|
52
45
|
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
46
|
+
# uris = %w(
|
47
|
+
# http://vision-media.ca
|
48
|
+
# http://yahoo.com
|
49
|
+
# http://google.com
|
50
|
+
# )
|
51
|
+
#
|
52
|
+
# bar = Commander::UI::ProgressBar.new uris.length, options
|
53
|
+
# threads = []
|
54
|
+
# uris.each do |uri|
|
55
|
+
# threads << Thread.new do
|
56
|
+
# begin
|
57
|
+
# res = open uri
|
58
|
+
# bar.increment :uri => uri
|
59
|
+
# rescue Exception => e
|
60
|
+
# bar.increment :uri => "#{uri} failed"
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
# threads.each { |t| t.join }
|
58
65
|
#
|
59
|
-
#
|
60
|
-
# threads = []
|
61
|
-
# uris.each do |uri|
|
62
|
-
# threads << Thread.new do
|
63
|
-
# begin
|
64
|
-
# res = open uri
|
65
|
-
# bar.increment :uri => uri
|
66
|
-
# rescue Exception => e
|
67
|
-
# bar.increment :uri => "#{uri} failed"
|
68
|
-
# end
|
69
|
-
# end
|
70
|
-
# end
|
71
|
-
# threads.each { |t| t.join }
|
66
|
+
# The Object method #progress is also available:
|
72
67
|
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
# end
|
68
|
+
# progress uris, :width => 10 do |uri|
|
69
|
+
# res = open uri
|
70
|
+
# { :uri => uri } # Can now use :uri within :format option
|
71
|
+
# end
|
78
72
|
#
|
79
73
|
|
80
74
|
class ProgressBar
|
@@ -171,14 +165,12 @@ module Commander
|
|
171
165
|
# Output the progress bar.
|
172
166
|
|
173
167
|
def show
|
174
|
-
# TODO: shift steps stack instead
|
175
168
|
unless finished?
|
176
169
|
erase_line
|
177
170
|
if completed?
|
178
|
-
|
171
|
+
$terminal.say @complete_message.tokenize(generate_tokens) if @complete_message.is_a? String
|
179
172
|
else
|
180
|
-
|
181
|
-
$stdout.flush
|
173
|
+
$terminal.say @format.tokenize(generate_tokens) << ' '
|
182
174
|
end
|
183
175
|
end
|
184
176
|
end
|
@@ -211,7 +203,8 @@ module Commander
|
|
211
203
|
# Erase previous terminal line.
|
212
204
|
|
213
205
|
def erase_line
|
214
|
-
|
206
|
+
# highline does not expose the output stream
|
207
|
+
$terminal.instance_variable_get('@output').print "\r\e[K"
|
215
208
|
end
|
216
209
|
|
217
210
|
##
|
@@ -226,7 +219,8 @@ module Commander
|
|
226
219
|
#
|
227
220
|
# === See:
|
228
221
|
#
|
229
|
-
# *
|
222
|
+
# * Object#progress
|
223
|
+
#
|
230
224
|
|
231
225
|
def self.progress arr, options = {}, &block
|
232
226
|
bar = ProgressBar.new arr.length, options
|
data/lib/commander/version.rb
CHANGED
@@ -0,0 +1,128 @@
|
|
1
|
+
|
2
|
+
describe Commander::Command do
|
3
|
+
|
4
|
+
before :each do
|
5
|
+
mock_terminal
|
6
|
+
create_test_command
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#seperate_switches_from_description" do
|
10
|
+
it "should seperate switches and description returning both" do
|
11
|
+
switches, description = *@command.seperate_switches_from_description('-h', '--help', 'display help')
|
12
|
+
switches.should == ['-h', '--help']
|
13
|
+
description.should == 'display help'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#option" do
|
18
|
+
it "should add options" do
|
19
|
+
lambda { @command.option '--recursive' }.should change(@command.options, :length).from(2).to(3)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should allow procs as option handlers" do
|
23
|
+
@command.option('--recursive') { |recursive| recursive.should be_true }
|
24
|
+
@command.run '--recursive'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#options" do
|
29
|
+
it "should distinguish between switches and descriptions" do
|
30
|
+
@command.option '-t', '--trace', 'some description'
|
31
|
+
@command.options[2][:description].should == 'some description'
|
32
|
+
@command.options[2][:switches].should == ['-t', '--trace']
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#switch_to_sym" do
|
37
|
+
it "should return a symbol based on the switch name" do
|
38
|
+
@command.switch_to_sym('--trace').should == :trace
|
39
|
+
@command.switch_to_sym('--foo-bar').should == :foo_bar
|
40
|
+
@command.switch_to_sym('--[no-]feature"').should == :feature
|
41
|
+
@command.switch_to_sym('--[no-]feature ARG').should == :feature
|
42
|
+
@command.switch_to_sym('--file [ARG]').should == :file
|
43
|
+
@command.switch_to_sym('--colors colors').should == :colors
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#run" do
|
48
|
+
describe "should invoke #when_called" do
|
49
|
+
it "with arguments seperated from options" do
|
50
|
+
@command.when_called { |args, options| args.join(' ').should == 'just some args' }
|
51
|
+
@command.run '--trace', 'just', 'some', 'args'
|
52
|
+
end
|
53
|
+
|
54
|
+
it "calling the #call method by default when an object is called" do
|
55
|
+
object = mock 'Object'
|
56
|
+
object.should_receive(:call).with(an_instance_of(Array), an_instance_of(Commander::Command::Options)).once
|
57
|
+
@command.when_called object
|
58
|
+
@command.run 'foo'
|
59
|
+
end
|
60
|
+
|
61
|
+
it "calling an arbitrary method when an object is called" do
|
62
|
+
object = mock 'Object'
|
63
|
+
object.should_receive(:foo).with(an_instance_of(Array), an_instance_of(Commander::Command::Options)).once
|
64
|
+
@command.when_called object, :foo
|
65
|
+
@command.run 'foo'
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should raise an error when no handler is present" do
|
69
|
+
lambda { @command.when_called }.should raise_error(ArgumentError)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "should populate options with" do
|
74
|
+
it "boolean values" do
|
75
|
+
@command.option '--[no-]toggle'
|
76
|
+
@command.when_called { |_, options| options.toggle.should be_true }
|
77
|
+
@command.run '--toggle'
|
78
|
+
@command.when_called { |_, options| options.toggle.should be_false }
|
79
|
+
@command.run '--no-toggle'
|
80
|
+
end
|
81
|
+
|
82
|
+
it "manditory arguments" do
|
83
|
+
@command.option '--file FILE'
|
84
|
+
@command.when_called { |_, options| options.file.should == 'foo' }
|
85
|
+
@command.run '--file', 'foo'
|
86
|
+
lambda { @command.run '--file' }.should raise_error(OptionParser::MissingArgument)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "optional arguments" do
|
90
|
+
@command.option '--use-config [file] '
|
91
|
+
@command.when_called { |_, options| options.use_config.should == 'foo' }
|
92
|
+
@command.run '--use-config', 'foo'
|
93
|
+
@command.when_called { |_, options| options.use_config.should be_nil }
|
94
|
+
@command.run '--use-config'
|
95
|
+
@command.option '--interval N', Integer
|
96
|
+
@command.when_called { |_, options| options.interval.should == 5 }
|
97
|
+
@command.run '--interval', '5'
|
98
|
+
lambda { @command.run '--interval', 'invalid' }.should raise_error(OptionParser::InvalidArgument)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "lists" do
|
102
|
+
@command.option '--fav COLORS', Array
|
103
|
+
@command.when_called { |_, options| options.fav.should == ['red', 'green', 'blue'] }
|
104
|
+
@command.run '--fav', 'red,green,blue'
|
105
|
+
end
|
106
|
+
|
107
|
+
it "lists with multi-word items" do
|
108
|
+
@command.option '--fav MOVIES', Array
|
109
|
+
@command.when_called { |_, options| options.fav.should == ['super\ bad', 'nightmare'] }
|
110
|
+
@command.run '--fav', 'super\ bad,nightmare'
|
111
|
+
end
|
112
|
+
|
113
|
+
it "defaults" do
|
114
|
+
@command.option '--files LIST', Array
|
115
|
+
@command.option '--interval N', Integer
|
116
|
+
@command.when_called do |_, options|
|
117
|
+
options.default \
|
118
|
+
:files => ['foo', 'bar'],
|
119
|
+
:interval => 5
|
120
|
+
options.files.should == ['foo', 'bar']
|
121
|
+
options.interval.should == 15
|
122
|
+
end
|
123
|
+
@command.run '--interval', '15'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
describe Array do
|
3
|
+
|
4
|
+
describe "#delete_switches" do
|
5
|
+
it "should delete switches such as -h or --help, returning a new array" do
|
6
|
+
['foo', '-h', 'bar', '--help'].delete_switches.should == ['foo', 'bar']
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#parse" do
|
11
|
+
it "should seperate a list of words into an array" do
|
12
|
+
Array.parse('just a test').should == ['just', 'a', 'test']
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should preserve escaped whitespace" do
|
16
|
+
Array.parse('just a\ test').should == ['just', 'a test']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -1,13 +1,20 @@
|
|
1
|
-
|
1
|
+
|
2
|
+
describe Object do
|
2
3
|
|
4
|
+
describe "#get_binding" do
|
5
|
+
it "should return the objects binding" do
|
6
|
+
lambda {}.get_binding.should be_instance_of(Binding)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
3
10
|
describe "#method_missing" do
|
4
11
|
it "should preserve its original behavior for missing methods" do
|
5
12
|
lambda { i_am_a_missing_method() }.should raise_error(NoMethodError)
|
6
13
|
end
|
7
|
-
|
14
|
+
|
8
15
|
it "should preserve its original behavior for missing variables" do
|
9
16
|
lambda { i_am_a_missing_variable }.should raise_error(NameError)
|
10
17
|
end
|
11
18
|
end
|
12
|
-
|
19
|
+
|
13
20
|
end
|
data/spec/help_formatter_spec.rb
CHANGED
@@ -2,30 +2,30 @@
|
|
2
2
|
describe Commander::HelpFormatter do
|
3
3
|
|
4
4
|
before :each do
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
mock_terminal
|
6
|
+
end
|
7
|
+
|
8
|
+
def run *args
|
9
|
+
new_command_runner *args do
|
10
|
+
program :help_formatter, Commander::HelpFormatter::Base
|
11
|
+
end.run!
|
12
|
+
@output.string
|
8
13
|
end
|
9
14
|
|
10
15
|
it "should display global help using --help switch" do
|
11
|
-
|
12
|
-
program :help_formatter, Commander::HelpFormatter::Base
|
13
|
-
command_runner.run!
|
14
|
-
@output.string.should eql("Implement global help here\n")
|
16
|
+
run('--help').should == "Implement global help here\n"
|
15
17
|
end
|
16
18
|
|
17
19
|
it "should display global help using help command" do
|
18
|
-
|
19
|
-
program :help_formatter, Commander::HelpFormatter::Base
|
20
|
-
command_runner.run!
|
21
|
-
@output.string.should eql("Implement global help here\n")
|
20
|
+
run('help').should == "Implement global help here\n"
|
22
21
|
end
|
23
22
|
|
24
23
|
it "should display command help" do
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
run('help', 'test').should == "Implement help for test here\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should display command help using --help switch" do
|
28
|
+
run('--help', 'test').should == "Implement help for test here\n"
|
29
29
|
end
|
30
30
|
|
31
31
|
end
|
data/spec/runner_spec.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
|
2
|
+
describe Commander do
|
3
|
+
|
4
|
+
before :each do
|
5
|
+
mock_terminal
|
6
|
+
create_test_command
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#program" do
|
10
|
+
it "should set / get program information" do
|
11
|
+
program :name, 'test'
|
12
|
+
program(:name).should == 'test'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should allow arbitrary blocks of global help documentation" do
|
16
|
+
program :help, 'Copyright', 'TJ Holowaychuk'
|
17
|
+
program(:help)['Copyright'].should == 'TJ Holowaychuk'
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should raise an error when required info has not been set" do
|
21
|
+
new_command_runner '--help'
|
22
|
+
program :name, ''
|
23
|
+
lambda { run! }.should raise_error(Commander::Runner::CommandError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#command" do
|
28
|
+
it "should return a command instance when only the name is passed" do
|
29
|
+
command(:test).should be_instance_of(Commander::Command)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should raise InvalidCommandError when the command does not exist" do
|
33
|
+
lambda { command(:im_not_real) }.should raise_error(Commander::Runner::InvalidCommandError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "--version" do
|
38
|
+
it "should output program version" do
|
39
|
+
new_command_runner '--version' do
|
40
|
+
program :help_formatter, Commander::HelpFormatter::Base
|
41
|
+
end.run!
|
42
|
+
@output.string.should == "test 1.2.3\n"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "--help" do
|
47
|
+
it "should not output an invalid command message" do
|
48
|
+
new_command_runner('--help').run!
|
49
|
+
@output.string.should_not == "invalid command. Use --help for more information\n"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "with invalid options" do
|
54
|
+
it "should output an invalid option message" do
|
55
|
+
new_command_runner('test', '--invalid-option').run!
|
56
|
+
@output.string.should == "invalid option: --invalid-option\n"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "with invalid sub-command passed" do
|
61
|
+
it "should output an invalid command message" do
|
62
|
+
new_command_runner('foo').run!
|
63
|
+
@output.string.should == "invalid command. Use --help for more information\n"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "with invalid sub-command passed to help" do
|
68
|
+
it "should output an invalid command message" do
|
69
|
+
new_command_runner('help', 'does_not_exist').run!
|
70
|
+
@output.string.should == "invalid command. Use --help for more information\n"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#valid_command_names_from" do
|
75
|
+
it "should return array of valid command names" do
|
76
|
+
command('foo bar') {}
|
77
|
+
command('foo bar foo') {}
|
78
|
+
command_runner.valid_command_names_from('foo', 'bar', 'foo').should == ['foo bar', 'foo bar foo']
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should return empty array when no possible command names exist" do
|
82
|
+
command_runner.valid_command_names_from('fake', 'command', 'name').should == []
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#command_name_from_args" do
|
87
|
+
it "should locate command within arbitrary arguments passed" do
|
88
|
+
new_command_runner '--help', '--arbitrary', 'test'
|
89
|
+
command_runner.command_name_from_args.should == 'test'
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should support multi-word commands" do
|
93
|
+
new_command_runner '--help', '--arbitrary', 'some', 'long', 'command', 'foo'
|
94
|
+
command('some long command') {}
|
95
|
+
command_runner.command_name_from_args.should == 'some long command'
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should match the longest possible command" do
|
99
|
+
new_command_runner '--help', '--arbitrary', 'foo', 'bar', 'foo'
|
100
|
+
command('foo bar') {}
|
101
|
+
command('foo bar foo') {}
|
102
|
+
command_runner.command_name_from_args.should == 'foo bar foo'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "#active_command" do
|
107
|
+
it "should resolve the active command" do
|
108
|
+
new_command_runner '--help', 'test'
|
109
|
+
command_runner.active_command.should be_instance_of(Commander::Command)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should resolve active command when invalid options are passed" do
|
113
|
+
new_command_runner '--help', 'test', '--arbitrary'
|
114
|
+
command_runner.active_command.should be_instance_of(Commander::Command)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should raise invalid command error when the command is not found" do
|
118
|
+
new_command_runner 'foo'
|
119
|
+
lambda { command_runner.active_command }.should raise_error(Commander::Runner::InvalidCommandError)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "should function correctly" do
|
124
|
+
it "when options are passed before the command name" do
|
125
|
+
new_command_runner '--trace', 'test', 'foo', 'bar' do
|
126
|
+
@command.when_called do |args, options|
|
127
|
+
args.should == ['foo', 'bar']
|
128
|
+
options.trace.should be_true
|
129
|
+
end
|
130
|
+
end.run!
|
131
|
+
end
|
132
|
+
|
133
|
+
it "when options are passed after the command name" do
|
134
|
+
new_command_runner 'test', '--trace', 'foo', 'bar' do
|
135
|
+
@command.when_called do |args, options|
|
136
|
+
args.should == ['foo', 'bar']
|
137
|
+
options.trace.should be_true
|
138
|
+
end
|
139
|
+
end.run!
|
140
|
+
end
|
141
|
+
|
142
|
+
it "when an argument passed is the same name as the command" do
|
143
|
+
new_command_runner 'test', '--trace', 'foo', 'test', 'bar' do
|
144
|
+
@command.when_called do |args, options|
|
145
|
+
args.should == ['foo', 'test', 'bar']
|
146
|
+
options.trace.should be_true
|
147
|
+
end
|
148
|
+
end.run!
|
149
|
+
end
|
150
|
+
|
151
|
+
it "when using multi-word commands" do
|
152
|
+
new_command_runner '--trace', 'my', 'command', 'something', 'foo', 'bar' do
|
153
|
+
command('my command') {}
|
154
|
+
command_runner.command_name_from_args.should == 'my command'
|
155
|
+
command_runner.args_without_command_name.should == ['--trace', 'something', 'foo', 'bar']
|
156
|
+
end.run!
|
157
|
+
end
|
158
|
+
|
159
|
+
it "when using multi-word commands with parts of the command name as arguments" do
|
160
|
+
new_command_runner '--trace', 'my', 'command', 'something', 'my', 'command' do
|
161
|
+
command('my command') {}
|
162
|
+
command_runner.command_name_from_args.should == 'my command'
|
163
|
+
command_runner.args_without_command_name.should == ['--trace', 'something', 'my', 'command']
|
164
|
+
end.run!
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|