pry 0.8.3-java → 0.8.4pre1-java
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/.document +2 -0
- data/.gitignore +8 -0
- data/.yardopts +1 -0
- data/README.markdown +10 -6
- data/Rakefile +15 -23
- data/TODO +62 -0
- data/bin/pry +3 -1
- data/lib/pry.rb +6 -7
- data/lib/pry/command_context.rb +29 -0
- data/lib/pry/command_processor.rb +15 -28
- data/lib/pry/command_set.rb +234 -0
- data/lib/pry/commands.rb +15 -861
- data/lib/pry/core_extensions.rb +40 -48
- data/lib/pry/default_commands/context.rb +127 -0
- data/lib/pry/default_commands/documentation.rb +145 -0
- data/lib/pry/default_commands/easter_eggs.rb +71 -0
- data/lib/pry/default_commands/gems.rb +59 -0
- data/lib/pry/default_commands/input.rb +38 -0
- data/lib/pry/default_commands/introspection.rb +190 -0
- data/lib/pry/default_commands/ls.rb +199 -0
- data/lib/pry/default_commands/shell.rb +90 -0
- data/lib/pry/helpers.rb +2 -0
- data/lib/pry/{command_base_helpers.rb → helpers/base_helpers.rb} +46 -21
- data/lib/pry/{command_helpers.rb → helpers/command_helpers.rb} +34 -36
- data/lib/pry/pry_class.rb +17 -11
- data/lib/pry/pry_instance.rb +59 -2
- data/lib/pry/version.rb +1 -1
- data/test/{test_helper.rb → helper.rb} +8 -2
- data/test/test_command_helpers.rb +77 -0
- data/test/test_commandset.rb +184 -0
- data/test/{test.rb → test_pry.rb} +164 -132
- data/wiki/Customizing-pry.md +397 -0
- data/wiki/Home.md +4 -0
- metadata +61 -41
- data/lib/pry/command_base.rb +0 -202
data/lib/pry/core_extensions.rb
CHANGED
@@ -1,55 +1,47 @@
|
|
1
|
-
class
|
2
|
-
|
1
|
+
class Object
|
2
|
+
# Start a Pry REPL.
|
3
|
+
# This method differs from `Pry.start` in that it does not
|
4
|
+
# support an options hash. Also, when no parameter is provided, the Pry
|
5
|
+
# session will start on the implied receiver rather than on
|
6
|
+
# top-level (as in the case of `Pry.start`).
|
7
|
+
# It has two forms of invocation. In the first form no parameter
|
8
|
+
# should be provided and it will start a pry session on the
|
9
|
+
# receiver. In the second form it should be invoked without an
|
10
|
+
# explicit receiver and one parameter; this will start a Pry
|
11
|
+
# session on the parameter.
|
12
|
+
# @param [Object, Binding] target The receiver of the Pry session.
|
13
|
+
# @example First form
|
14
|
+
# "dummy".pry
|
15
|
+
# @example Second form
|
16
|
+
# pry "dummy"
|
17
|
+
# @example Start a Pry session on current self (whatever that is)
|
18
|
+
# pry
|
19
|
+
def pry(target=self)
|
20
|
+
Pry.start(target)
|
21
|
+
end
|
3
22
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
# top-level (as in the case of `Pry.start`).
|
9
|
-
# It has two forms of invocation. In the first form no parameter
|
10
|
-
# should be provided and it will start a pry session on the
|
11
|
-
# receiver. In the second form it should be invoked without an
|
12
|
-
# explicit receiver and one parameter; this will start a Pry
|
13
|
-
# session on the parameter.
|
14
|
-
# @param [Object, Binding] target The receiver of the Pry session.
|
15
|
-
# @example First form
|
16
|
-
# "dummy".pry
|
17
|
-
# @example Second form
|
18
|
-
# pry "dummy"
|
19
|
-
# @example Start a Pry session on current self (whatever that is)
|
20
|
-
# pry
|
21
|
-
def pry(target=self)
|
22
|
-
Pry.start(target)
|
23
|
+
# Return a binding object for the receiver.
|
24
|
+
def __binding__
|
25
|
+
if is_a?(Module)
|
26
|
+
return class_eval "binding"
|
23
27
|
end
|
24
28
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
+
unless respond_to? :__binding_impl__
|
30
|
+
begin
|
31
|
+
instance_eval %{
|
32
|
+
def __binding_impl__
|
33
|
+
binding
|
34
|
+
end
|
35
|
+
}
|
36
|
+
rescue TypeError
|
37
|
+
self.class.class_eval %{
|
38
|
+
def __binding_impl__
|
39
|
+
binding
|
40
|
+
end
|
41
|
+
}
|
29
42
|
end
|
30
|
-
|
31
|
-
unless respond_to? :__binding_impl__
|
32
|
-
begin
|
33
|
-
instance_eval %{
|
34
|
-
def __binding_impl__
|
35
|
-
binding
|
36
|
-
end
|
37
|
-
}
|
38
|
-
rescue TypeError
|
39
|
-
self.class.class_eval %{
|
40
|
-
def __binding_impl__
|
41
|
-
binding
|
42
|
-
end
|
43
|
-
}
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
__binding_impl__
|
48
43
|
end
|
49
|
-
end
|
50
|
-
end
|
51
44
|
|
52
|
-
|
53
|
-
|
54
|
-
include Pry::ObjectExtensions
|
45
|
+
__binding_impl__
|
46
|
+
end
|
55
47
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require "pry/default_commands/ls"
|
2
|
+
|
3
|
+
class Pry
|
4
|
+
module DefaultCommands
|
5
|
+
|
6
|
+
Context = Pry::CommandSet.new :context do
|
7
|
+
import Ls
|
8
|
+
|
9
|
+
command "cd", "Start a Pry session on VAR (use `cd ..` to go back and `cd /` to return to Pry top-level)", :keep_retval => true do |obj|
|
10
|
+
if !obj
|
11
|
+
output.puts "Must provide an object."
|
12
|
+
next
|
13
|
+
end
|
14
|
+
|
15
|
+
throw(:breakout, opts[:nesting].level) if obj == ".."
|
16
|
+
|
17
|
+
if obj == "/"
|
18
|
+
throw(:breakout, 1) if opts[:nesting].level > 0
|
19
|
+
next
|
20
|
+
end
|
21
|
+
|
22
|
+
Pry.start target.eval("#{obj}")
|
23
|
+
end
|
24
|
+
|
25
|
+
command "nesting", "Show nesting information." do
|
26
|
+
nesting = opts[:nesting]
|
27
|
+
|
28
|
+
output.puts "Nesting status:"
|
29
|
+
output.puts "--"
|
30
|
+
nesting.each do |level, obj|
|
31
|
+
if level == 0
|
32
|
+
output.puts "#{level}. #{Pry.view_clip(obj)} (Pry top level)"
|
33
|
+
else
|
34
|
+
output.puts "#{level}. #{Pry.view_clip(obj)}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
command "jump-to", "Jump to a Pry session further up the stack, exiting all sessions below." do |break_level|
|
40
|
+
break_level = break_level.to_i
|
41
|
+
nesting = opts[:nesting]
|
42
|
+
|
43
|
+
case break_level
|
44
|
+
when nesting.level
|
45
|
+
output.puts "Already at nesting level #{nesting.level}"
|
46
|
+
when (0...nesting.level)
|
47
|
+
throw(:breakout, break_level + 1)
|
48
|
+
else
|
49
|
+
max_nest_level = nesting.level - 1
|
50
|
+
output.puts "Invalid nest level. Must be between 0 and #{max_nest_level}. Got #{break_level}."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
command "exit", "End the current Pry session. Accepts optional return value. Aliases: quit, back" do
|
55
|
+
str = remove_first_word(opts[:val])
|
56
|
+
throw(:breakout, [opts[:nesting].level, target.eval(str)])
|
57
|
+
end
|
58
|
+
|
59
|
+
alias_command "quit", "exit", ""
|
60
|
+
alias_command "back", "exit", ""
|
61
|
+
|
62
|
+
command "exit-all", "End all nested Pry sessions. Accepts optional return value. Aliases: !!@" do
|
63
|
+
str = remove_first_word(opts[:val])
|
64
|
+
throw(:breakout, [0, target.eval(str)])
|
65
|
+
end
|
66
|
+
|
67
|
+
alias_command "!!@", "exit-all", ""
|
68
|
+
|
69
|
+
command "exit-program", "End the current program. Aliases: quit-program, !!!" do
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
|
73
|
+
alias_command "quit-program", "exit-program", ""
|
74
|
+
alias_command "!!!", "exit-program", ""
|
75
|
+
|
76
|
+
command "!pry", "Start a Pry session on current self; this even works mid-expression." do
|
77
|
+
Pry.start(target)
|
78
|
+
end
|
79
|
+
|
80
|
+
command "whereami", "Show the code context for the session. (whereami <n> shows <n> extra lines of code around the invocation line. Default: 5)" do |num|
|
81
|
+
file = target.eval('__FILE__')
|
82
|
+
line_num = target.eval('__LINE__')
|
83
|
+
klass = target.eval('self.class')
|
84
|
+
|
85
|
+
if num
|
86
|
+
i_num = num.to_i
|
87
|
+
else
|
88
|
+
i_num = 5
|
89
|
+
end
|
90
|
+
|
91
|
+
meth_name = meth_name_from_binding(target)
|
92
|
+
meth_name = "N/A" if !meth_name
|
93
|
+
|
94
|
+
if file =~ /(\(.*\))|<.*>/ || file == "" || file == "-e"
|
95
|
+
output.puts "Cannot find local context. Did you use `binding.pry` ?"
|
96
|
+
next
|
97
|
+
end
|
98
|
+
|
99
|
+
set_file_and_dir_locals(file)
|
100
|
+
output.puts "\n#{bold('From:')} #{file} @ line #{line_num} in #{klass}##{meth_name}:\n\n"
|
101
|
+
|
102
|
+
# This method inspired by http://rubygems.org/gems/ir_b
|
103
|
+
File.open(file).each_with_index do |line, index|
|
104
|
+
line_n = index + 1
|
105
|
+
next unless line_n > (line_num - i_num - 1)
|
106
|
+
break if line_n > (line_num + i_num)
|
107
|
+
if line_n == line_num
|
108
|
+
code =" =>#{line_n.to_s.rjust(3)}: #{line.chomp}"
|
109
|
+
if Pry.color
|
110
|
+
code = CodeRay.scan(code, :ruby).term
|
111
|
+
end
|
112
|
+
output.puts code
|
113
|
+
code
|
114
|
+
else
|
115
|
+
code = "#{line_n.to_s.rjust(6)}: #{line.chomp}"
|
116
|
+
if Pry.color
|
117
|
+
code = CodeRay.scan(code, :ruby).term
|
118
|
+
end
|
119
|
+
output.puts code
|
120
|
+
code
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
class Pry
|
2
|
+
module DefaultCommands
|
3
|
+
|
4
|
+
Documentation = Pry::CommandSet.new :gems do
|
5
|
+
|
6
|
+
command "ri", "View ri documentation. e.g `ri Array#each`" do |*args|
|
7
|
+
run ".ri", *args
|
8
|
+
end
|
9
|
+
|
10
|
+
command "show-doc", "Show the comments above METH. Type `show-doc --help` for more info. Aliases: \?" do |*args|
|
11
|
+
target = target()
|
12
|
+
|
13
|
+
opts = Slop.parse!(args) do |opts|
|
14
|
+
opts.banner %{Usage: show-doc [OPTIONS] [METH]
|
15
|
+
Show the comments above method METH. Tries instance methods first and then methods by default.
|
16
|
+
e.g show-doc hello_method
|
17
|
+
--
|
18
|
+
}
|
19
|
+
opts.on :M, "instance-methods", "Operate on instance methods."
|
20
|
+
opts.on :m, :methods, "Operate on methods."
|
21
|
+
opts.on :c, :context, "Select object context to run under.", true do |context|
|
22
|
+
target = Pry.binding_for(target.eval(context))
|
23
|
+
end
|
24
|
+
opts.on :f, :flood, "Do not use a pager to view text longer than one screen."
|
25
|
+
opts.on :h, :help, "This message." do
|
26
|
+
output.puts opts
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
next if opts.help?
|
31
|
+
|
32
|
+
meth_name = args.shift
|
33
|
+
if (meth = get_method_object(meth_name, target, opts.to_hash(true))).nil?
|
34
|
+
output.puts "Invalid method name: #{meth_name}. Type `show-doc --help` for help"
|
35
|
+
next
|
36
|
+
end
|
37
|
+
|
38
|
+
doc, code_type = doc_and_code_type_for(meth)
|
39
|
+
next if !doc
|
40
|
+
|
41
|
+
next output.puts("No documentation found.") if doc.empty?
|
42
|
+
doc = process_comment_markup(doc, code_type)
|
43
|
+
output.puts make_header(meth, code_type, doc)
|
44
|
+
render_output(opts.flood?, false, doc)
|
45
|
+
doc
|
46
|
+
end
|
47
|
+
|
48
|
+
alias_command "?", "show-doc", ""
|
49
|
+
|
50
|
+
|
51
|
+
command "stat", "View method information and set _file_ and _dir_ locals. Type `stat --help` for more info." do |*args|
|
52
|
+
target = target()
|
53
|
+
|
54
|
+
opts = Slop.parse!(args) do |opts|
|
55
|
+
opts.banner %{Usage: stat [OPTIONS] [METH]
|
56
|
+
Show method information for method METH and set _file_ and _dir_ locals.
|
57
|
+
e.g: stat hello_method
|
58
|
+
--
|
59
|
+
}
|
60
|
+
opts.on :M, "instance-methods", "Operate on instance methods."
|
61
|
+
opts.on :m, :methods, "Operate on methods."
|
62
|
+
opts.on :c, :context, "Select object context to run under.", true do |context|
|
63
|
+
target = Pry.binding_for(target.eval(context))
|
64
|
+
end
|
65
|
+
opts.on :h, :help, "This message" do
|
66
|
+
output.puts opts
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
next if opts.help?
|
71
|
+
|
72
|
+
meth_name = args.shift
|
73
|
+
if (meth = get_method_object(meth_name, target, opts.to_hash(true))).nil?
|
74
|
+
output.puts "Invalid method name: #{meth_name}. Type `stat --help` for help"
|
75
|
+
next
|
76
|
+
end
|
77
|
+
|
78
|
+
code, code_type = code_and_code_type_for(meth)
|
79
|
+
next if !code
|
80
|
+
doc, code_type = doc_and_code_type_for(meth)
|
81
|
+
|
82
|
+
output.puts make_header(meth, code_type, code)
|
83
|
+
output.puts bold("Method Name: ") + meth_name
|
84
|
+
output.puts bold("Method Owner: ") + (meth.owner.to_s ? meth.owner.to_s : "Unknown")
|
85
|
+
output.puts bold("Method Language: ") + code_type.to_s.capitalize
|
86
|
+
output.puts bold("Method Type: ") + (meth.is_a?(Method) ? "Bound" : "Unbound")
|
87
|
+
output.puts bold("Method Arity: ") + meth.arity.to_s
|
88
|
+
|
89
|
+
name_map = { :req => "Required:", :opt => "Optional:", :rest => "Rest:" }
|
90
|
+
if meth.respond_to?(:parameters)
|
91
|
+
output.puts bold("Method Parameters: ") + meth.parameters.group_by(&:first).
|
92
|
+
map { |k, v| "#{name_map[k]} #{v.map { |kk, vv| vv ? vv.to_s : "noname" }.join(", ")}" }.join(". ")
|
93
|
+
end
|
94
|
+
output.puts bold("Comment length: ") + (doc.empty? ? 'No comment.' : (doc.lines.count.to_s + ' lines.'))
|
95
|
+
end
|
96
|
+
|
97
|
+
command "gist-method", "Gist a method to github. Type `gist-method --help` for more info.", :requires_gem => "gist" do |*args|
|
98
|
+
target = target()
|
99
|
+
|
100
|
+
opts = Slop.parse!(args) do |opts|
|
101
|
+
opts.banner = %{Usage: gist-method [OPTIONS] [METH]
|
102
|
+
Gist the method (doc or source) to github.
|
103
|
+
Ensure the `gist` gem is properly working before use. http://github.com/defunkt/gist for instructions.
|
104
|
+
e.g: gist -m my_method
|
105
|
+
e.g: gist -d my_method
|
106
|
+
--
|
107
|
+
}
|
108
|
+
opts.on :m, :method, "Gist a method's source."
|
109
|
+
opts.on :d, :doc, "Gist a method's documentation."
|
110
|
+
opts.on :p, :private, "Create a private gist (default: true)", :default => true
|
111
|
+
opts.on :h, :help, "This message" do
|
112
|
+
output.puts opts
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
next if opts.help?
|
117
|
+
|
118
|
+
# This needs to be extracted into its own method as it's shared
|
119
|
+
# by show-method and show-doc and stat commands
|
120
|
+
meth_name = args.shift
|
121
|
+
if (meth = get_method_object(meth_name, target, opts.to_hash(true))).nil?
|
122
|
+
output.puts "Invalid method name: #{meth_name}. Type `gist-method --help` for help"
|
123
|
+
next
|
124
|
+
end
|
125
|
+
|
126
|
+
type_map = { :ruby => "rb", :c => "c", :plain => "plain" }
|
127
|
+
if !opts.doc?
|
128
|
+
content, code_type = code_and_code_type_for(meth)
|
129
|
+
else
|
130
|
+
content, code_type = doc_and_code_type_for(meth)
|
131
|
+
no_color do
|
132
|
+
content = process_comment_markup(content, code_type)
|
133
|
+
end
|
134
|
+
code_type = :plain
|
135
|
+
end
|
136
|
+
|
137
|
+
IO.popen("gist#{' -p' if opts.p?} -t #{type_map[code_type]} -", "w") do |gist|
|
138
|
+
gist.puts content
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Pry
|
2
|
+
module DefaultCommands
|
3
|
+
|
4
|
+
EasterEggs = Pry::CommandSet.new :easter_eggs do
|
5
|
+
|
6
|
+
command "game", "" do |highest|
|
7
|
+
highest = highest ? highest.to_i : 100
|
8
|
+
num = rand(highest)
|
9
|
+
output.puts "Guess the number between 0-#{highest}: ('.' to quit)"
|
10
|
+
count = 0
|
11
|
+
while(true)
|
12
|
+
count += 1
|
13
|
+
str = Readline.readline("game > ", true)
|
14
|
+
break if str == "." || !str
|
15
|
+
val = str.to_i
|
16
|
+
output.puts "Too large!" if val > num
|
17
|
+
output.puts "Too small!" if val < num
|
18
|
+
if val == num
|
19
|
+
output.puts "Well done! You guessed right! It took you #{count} guesses."
|
20
|
+
break
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
command "east-coker", "" do
|
26
|
+
text = %{
|
27
|
+
--
|
28
|
+
Now the light falls
|
29
|
+
Across the open field, leaving the deep lane
|
30
|
+
Shuttered with branches, dark in the afternoon,
|
31
|
+
Where you lean against a bank while a van passes,
|
32
|
+
And the deep lane insists on the direction
|
33
|
+
Into the village, in the electric heat
|
34
|
+
Hypnotised. In a warm haze the sultry light
|
35
|
+
Is absorbed, not refracted, by grey stone.
|
36
|
+
The dahlias sleep in the empty silence.
|
37
|
+
Wait for the early owl.
|
38
|
+
-- T.S Eliot
|
39
|
+
}
|
40
|
+
output.puts text
|
41
|
+
text
|
42
|
+
end
|
43
|
+
|
44
|
+
command "cohen-poem", "" do
|
45
|
+
text = %{
|
46
|
+
--
|
47
|
+
When this American woman,
|
48
|
+
whose thighs are bound in casual red cloth,
|
49
|
+
comes thundering past my sitting place
|
50
|
+
like a forest-burning Mongol tribe,
|
51
|
+
the city is ravished
|
52
|
+
and brittle buildings of a hundred years
|
53
|
+
splash into the street;
|
54
|
+
and my eyes are burnt
|
55
|
+
for the embroidered Chinese girls,
|
56
|
+
already old,
|
57
|
+
and so small between the thin pines
|
58
|
+
on these enormous landscapes,
|
59
|
+
that if you turn your head
|
60
|
+
they are lost for hours.
|
61
|
+
-- Leonard Cohen
|
62
|
+
}
|
63
|
+
output.puts text
|
64
|
+
text
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Pry
|
2
|
+
module DefaultCommands
|
3
|
+
|
4
|
+
Gems = Pry::CommandSet.new :gems do
|
5
|
+
|
6
|
+
command "gem-install", "Install a gem and refresh the gem cache." do |gem_name|
|
7
|
+
gem_home = Gem.instance_variable_get(:@gem_home)
|
8
|
+
output.puts "Attempting to install gem: #{bold(gem_name)}"
|
9
|
+
|
10
|
+
begin
|
11
|
+
if File.writable?(gem_home)
|
12
|
+
Gem::DependencyInstaller.new.install(gem_name)
|
13
|
+
output.puts "Gem #{bold(gem_name)} successfully installed."
|
14
|
+
else
|
15
|
+
if system("sudo gem install #{gem_name}")
|
16
|
+
output.puts "Gem #{bold(gem_name)} successfully installed."
|
17
|
+
else
|
18
|
+
output.puts "Gem #{bold(gem_name)} could not be installed."
|
19
|
+
next
|
20
|
+
end
|
21
|
+
end
|
22
|
+
rescue Gem::GemNotFoundException
|
23
|
+
output.puts "Required Gem: #{bold(gem_name)} not found."
|
24
|
+
next
|
25
|
+
end
|
26
|
+
|
27
|
+
Gem.refresh
|
28
|
+
output.puts "Refreshed gem cache."
|
29
|
+
end
|
30
|
+
|
31
|
+
command "gem-cd", "Change working directory to specified gem's directory." do |gem_name|
|
32
|
+
require 'rubygems'
|
33
|
+
gem_spec = Gem.source_index.find_name(gem_name).first
|
34
|
+
next output.puts("Gem `#{gem_name}` not found.") if !gem_spec
|
35
|
+
Dir.chdir(File.expand_path(gem_spec.full_gem_path))
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
command "gem-list", "List/search installed gems. (Optional parameter: a regexp to limit the search)" do |arg|
|
40
|
+
gems = Gem.source_index.gems.values.group_by(&:name)
|
41
|
+
if arg
|
42
|
+
query = Regexp.new(arg, Regexp::IGNORECASE)
|
43
|
+
gems = gems.select { |gemname, specs| gemname =~ query }
|
44
|
+
end
|
45
|
+
|
46
|
+
gems.each do |gemname, specs|
|
47
|
+
versions = specs.map(&:version).sort.reverse.map(&:to_s)
|
48
|
+
versions = ["<bright_green>#{versions.first}</bright_green>"] +
|
49
|
+
versions[1..-1].map{|v| "<green>#{v}</green>" }
|
50
|
+
|
51
|
+
gemname = highlight(gemname, query) if query
|
52
|
+
result = "<white>#{gemname} <grey>(#{versions.join ', '})</grey>"
|
53
|
+
output.puts colorize(result)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|