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
@@ -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
|