thor 0.12.2 → 0.12.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +3 -5
- data/bin/thor +1 -0
- data/lib/thor.rb +35 -37
- data/lib/thor/actions.rb +1 -0
- data/lib/thor/actions/create_file.rb +2 -2
- data/lib/thor/actions/file_manipulation.rb +4 -4
- data/lib/thor/actions/inject_into_file.rb +1 -1
- data/lib/thor/core_ext/file_binary_read.rb +9 -0
- data/lib/thor/group.rb +39 -27
- data/lib/thor/runner.rb +40 -40
- data/lib/thor/shell/color.rb +1 -1
- data/lib/thor/task.rb +13 -14
- data/lib/thor/util.rb +4 -22
- data/lib/thor/version.rb +1 -1
- data/spec/actions/create_file_spec.rb +7 -7
- data/spec/actions/directory_spec.rb +1 -1
- data/spec/actions/file_manipulation_spec.rb +15 -15
- data/spec/actions_spec.rb +14 -13
- data/spec/group_spec.rb +1 -7
- data/spec/runner_spec.rb +30 -34
- data/spec/shell/basic_spec.rb +43 -43
- data/spec/shell/color_spec.rb +6 -6
- data/spec/spec_helper.rb +4 -4
- data/spec/task_spec.rb +11 -9
- data/spec/thor_spec.rb +9 -12
- data/spec/util_spec.rb +7 -31
- metadata +3 -29
data/CHANGELOG.rdoc
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
==
|
2
|
-
|
3
|
-
* Improve spec coverage for Thor::Runner
|
4
|
-
|
5
|
-
== 0.12, released 2009-11-06
|
1
|
+
== 0.12, released 2010-01-02
|
6
2
|
|
3
|
+
* Removed rr in favor to rspec mock framework
|
4
|
+
* Improved output for thor -T
|
7
5
|
* [#7] Do not force white color on status
|
8
6
|
* [#8] Yield a block with the filename on directory
|
9
7
|
|
data/bin/thor
CHANGED
data/lib/thor.rb
CHANGED
@@ -2,6 +2,7 @@ require 'thor/base'
|
|
2
2
|
require 'thor/group'
|
3
3
|
require 'thor/actions'
|
4
4
|
|
5
|
+
# TODO: Update thor to allow for git-style CLI (git bisect run)
|
5
6
|
class Thor
|
6
7
|
class << self
|
7
8
|
# Sets the default task when thor is executed without an explicit task to be called.
|
@@ -139,51 +140,47 @@ class Thor
|
|
139
140
|
end
|
140
141
|
end
|
141
142
|
|
142
|
-
# Prints help information
|
143
|
-
# only about the specific task.
|
143
|
+
# Prints help information for the given task.
|
144
144
|
#
|
145
145
|
# ==== Parameters
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
shell.say
|
160
|
-
class_options_help(shell, nil => task.options.map { |_, o| o })
|
161
|
-
shell.say task.description
|
162
|
-
else
|
163
|
-
list = printable_tasks(!options[:short])
|
164
|
-
|
165
|
-
Thor::Util.thor_classes_in(self).each do |klass|
|
166
|
-
list += klass.printable_tasks(false)
|
167
|
-
end
|
168
|
-
|
169
|
-
list.sort!{ |a,b| a[0] <=> b[0] }
|
146
|
+
# shell<Thor::Shell>
|
147
|
+
# task_name<String>
|
148
|
+
#
|
149
|
+
def task_help(shell, task_name)
|
150
|
+
task = all_tasks[task_name]
|
151
|
+
raise UndefinedTaskError, "task '#{task_name}' could not be found in namespace '#{self.namespace}'" unless task
|
152
|
+
|
153
|
+
shell.say "Usage:"
|
154
|
+
shell.say " #{banner(task)}"
|
155
|
+
shell.say
|
156
|
+
class_options_help(shell, nil => task.options.map { |_, o| o })
|
157
|
+
shell.say task.description
|
158
|
+
end
|
170
159
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
160
|
+
# Prints help information for this class.
|
161
|
+
#
|
162
|
+
# ==== Parameters
|
163
|
+
# shell<Thor::Shell>
|
164
|
+
#
|
165
|
+
def help(shell)
|
166
|
+
list = printable_tasks
|
167
|
+
Thor::Util.thor_classes_in(self).each do |klass|
|
168
|
+
list += klass.printable_tasks(false)
|
179
169
|
end
|
170
|
+
list.sort!{ |a,b| a[0] <=> b[0] }
|
171
|
+
|
172
|
+
shell.say "Tasks:"
|
173
|
+
shell.print_table(list, :ident => 2, :truncate => true)
|
174
|
+
shell.say
|
175
|
+
class_options_help(shell)
|
180
176
|
end
|
181
177
|
|
178
|
+
# Returns tasks ready to be printed.
|
182
179
|
def printable_tasks(all=true)
|
183
180
|
(all ? all_tasks : tasks).map do |_, task|
|
184
181
|
item = []
|
185
182
|
item << banner(task)
|
186
|
-
item << "# #{task.description}"
|
183
|
+
item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
|
187
184
|
item
|
188
185
|
end
|
189
186
|
end
|
@@ -196,7 +193,8 @@ class Thor
|
|
196
193
|
# the namespace should be displayed as arguments.
|
197
194
|
#
|
198
195
|
def banner(task)
|
199
|
-
"thor
|
196
|
+
base = $thor_runner ? "thor" : File.basename($0.split(" ").first)
|
197
|
+
"#{base} #{task.formatted_usage(self, base == "thor")}"
|
200
198
|
end
|
201
199
|
|
202
200
|
def baseclass #:nodoc:
|
@@ -239,6 +237,6 @@ class Thor
|
|
239
237
|
|
240
238
|
desc "help [TASK]", "Describe available tasks or one specific task"
|
241
239
|
def help(task=nil)
|
242
|
-
self.class.
|
240
|
+
task ? self.class.task_help(shell, task) : self.class.help(shell)
|
243
241
|
end
|
244
242
|
end
|
data/lib/thor/actions.rb
CHANGED
@@ -42,7 +42,7 @@ class Thor
|
|
42
42
|
# Boolean:: true if it is identical, false otherwise.
|
43
43
|
#
|
44
44
|
def identical?
|
45
|
-
exists? && File.
|
45
|
+
exists? && File.binread(destination) == render
|
46
46
|
end
|
47
47
|
|
48
48
|
# Holds the content to be added to the file.
|
@@ -58,7 +58,7 @@ class Thor
|
|
58
58
|
def invoke!
|
59
59
|
invoke_with_conflict_check do
|
60
60
|
FileUtils.mkdir_p(File.dirname(destination))
|
61
|
-
File.open(destination, '
|
61
|
+
File.open(destination, 'wb') { |f| f.write render }
|
62
62
|
end
|
63
63
|
given_destination
|
64
64
|
end
|
@@ -23,7 +23,7 @@ class Thor
|
|
23
23
|
source = File.expand_path(find_in_source_paths(source.to_s))
|
24
24
|
|
25
25
|
create_file destination, nil, config do
|
26
|
-
content = File.
|
26
|
+
content = File.binread(source)
|
27
27
|
content = block.call(content) if block
|
28
28
|
content
|
29
29
|
end
|
@@ -48,7 +48,7 @@ class Thor
|
|
48
48
|
#
|
49
49
|
def get(source, destination=nil, config={}, &block)
|
50
50
|
source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^http\:\/\//
|
51
|
-
render =
|
51
|
+
render = File.binread(source)
|
52
52
|
|
53
53
|
destination ||= if block_given?
|
54
54
|
block.arity == 1 ? block.call(render) : block.call
|
@@ -80,7 +80,7 @@ class Thor
|
|
80
80
|
context = instance_eval('binding')
|
81
81
|
|
82
82
|
create_file destination, nil, config do
|
83
|
-
content = ERB.new(::File.
|
83
|
+
content = ERB.new(::File.binread(source), nil, '-').result(context)
|
84
84
|
content = block.call(content) if block
|
85
85
|
content
|
86
86
|
end
|
@@ -193,7 +193,7 @@ class Thor
|
|
193
193
|
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
194
194
|
|
195
195
|
unless options[:pretend]
|
196
|
-
content = File.
|
196
|
+
content = File.binread(path)
|
197
197
|
content.gsub!(flag, *args, &block)
|
198
198
|
File.open(path, 'wb') { |file| file.write(content) }
|
199
199
|
end
|
data/lib/thor/group.rb
CHANGED
@@ -41,16 +41,12 @@ class Thor::Group
|
|
41
41
|
# ==== Options
|
42
42
|
# short:: When true, shows only usage.
|
43
43
|
#
|
44
|
-
def help(shell
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
shell.say ""
|
51
|
-
class_options_help(shell)
|
52
|
-
shell.say self.desc if self.desc
|
53
|
-
end
|
44
|
+
def help(shell)
|
45
|
+
shell.say "Usage:"
|
46
|
+
shell.say " #{banner}\n"
|
47
|
+
shell.say
|
48
|
+
class_options_help(shell)
|
49
|
+
shell.say self.desc if self.desc
|
54
50
|
end
|
55
51
|
|
56
52
|
# Stores invocations for this class merging with superclass values.
|
@@ -214,13 +210,27 @@ class Thor::Group
|
|
214
210
|
end
|
215
211
|
end
|
216
212
|
|
213
|
+
# Returns tasks ready to be printed.
|
214
|
+
def printable_tasks(*)
|
215
|
+
item = []
|
216
|
+
item << banner
|
217
|
+
item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "")
|
218
|
+
[item]
|
219
|
+
end
|
220
|
+
|
217
221
|
protected
|
218
222
|
|
219
223
|
# The banner for this class. You can customize it if you are invoking the
|
220
224
|
# thor class by another ways which is not the Thor::Runner.
|
221
225
|
#
|
222
226
|
def banner
|
223
|
-
|
227
|
+
base = $thor_runner ? "thor" : File.basename($0.split(" ").first)
|
228
|
+
"#{base} #{self_task.formatted_usage(self, false)}"
|
229
|
+
end
|
230
|
+
|
231
|
+
# Represents the whole class as a task.
|
232
|
+
def self_task #:nodoc:
|
233
|
+
Thor::Task::Dynamic.new(self.namespace, class_options)
|
224
234
|
end
|
225
235
|
|
226
236
|
def baseclass #:nodoc:
|
@@ -237,23 +247,25 @@ class Thor::Group
|
|
237
247
|
|
238
248
|
protected
|
239
249
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
shell.padding += 1
|
250
|
+
# Shortcut to invoke with padding and block handling. Use internally by
|
251
|
+
# invoke and invoke_from_option class methods.
|
252
|
+
def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
|
253
|
+
shell.padding += 1
|
245
254
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
255
|
+
result = if block_given?
|
256
|
+
case block.arity
|
257
|
+
when 3
|
258
|
+
block.call(self, klass, task)
|
259
|
+
when 2
|
260
|
+
block.call(self, klass)
|
261
|
+
when 1
|
262
|
+
instance_exec(klass, &block)
|
254
263
|
end
|
255
|
-
|
256
|
-
|
257
|
-
result
|
264
|
+
else
|
265
|
+
invoke klass, task, *args
|
258
266
|
end
|
267
|
+
|
268
|
+
shell.padding -= 1
|
269
|
+
result
|
270
|
+
end
|
259
271
|
end
|
data/lib/thor/runner.rb
CHANGED
@@ -124,11 +124,7 @@ class Thor::Runner < Thor #:nodoc:
|
|
124
124
|
method_options :internal => :boolean
|
125
125
|
def installed
|
126
126
|
initialize_thorfiles(nil, true)
|
127
|
-
|
128
|
-
klasses = Thor::Base.subclasses
|
129
|
-
klasses -= [Thor, Thor::Runner] unless options["internal"]
|
130
|
-
|
131
|
-
display_klasses(true, klasses)
|
127
|
+
display_klasses(true, options["internal"])
|
132
128
|
end
|
133
129
|
|
134
130
|
desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
|
@@ -144,7 +140,7 @@ class Thor::Runner < Thor #:nodoc:
|
|
144
140
|
(options[:all] || k.group == group) && k.namespace =~ search
|
145
141
|
end
|
146
142
|
|
147
|
-
display_klasses(false, klasses)
|
143
|
+
display_klasses(false, false, klasses)
|
148
144
|
end
|
149
145
|
|
150
146
|
private
|
@@ -160,7 +156,7 @@ class Thor::Runner < Thor #:nodoc:
|
|
160
156
|
def thor_yaml
|
161
157
|
@thor_yaml ||= begin
|
162
158
|
yaml_file = File.join(thor_root, "thor.yml")
|
163
|
-
yaml
|
159
|
+
yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
|
164
160
|
yaml || {}
|
165
161
|
end
|
166
162
|
end
|
@@ -219,9 +215,6 @@ class Thor::Runner < Thor #:nodoc:
|
|
219
215
|
# 5. c:\ <-- no Thorfiles found!
|
220
216
|
#
|
221
217
|
def thorfiles(relevant_to=nil, skip_lookup=false)
|
222
|
-
# TODO Remove this dealing with deprecated thor when :namespaces: is available as constants
|
223
|
-
save_yaml(thor_yaml) if Thor::Util.convert_constants_to_namespaces(thor_yaml)
|
224
|
-
|
225
218
|
thorfiles = []
|
226
219
|
|
227
220
|
unless skip_lookup
|
@@ -257,47 +250,54 @@ class Thor::Runner < Thor #:nodoc:
|
|
257
250
|
# Display information about the given klasses. If with_module is given,
|
258
251
|
# it shows a table with information extracted from the yaml file.
|
259
252
|
#
|
260
|
-
def display_klasses(with_modules=false, klasses=Thor.subclasses)
|
261
|
-
klasses -= [Thor, Thor::Runner] unless
|
253
|
+
def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses)
|
254
|
+
klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal
|
255
|
+
|
262
256
|
raise Error, "No Thor tasks available" if klasses.empty?
|
257
|
+
show_modules if with_modules && !thor_yaml.empty?
|
263
258
|
|
264
|
-
|
265
|
-
|
266
|
-
|
259
|
+
# Remove subclasses
|
260
|
+
klasses.dup.each do |klass|
|
261
|
+
klasses -= Thor::Util.thor_classes_in(klass)
|
262
|
+
end
|
267
263
|
|
268
|
-
|
269
|
-
|
264
|
+
list = Hash.new { |h,k| h[k] = [] }
|
265
|
+
groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
|
270
266
|
|
271
|
-
|
272
|
-
|
273
|
-
end
|
267
|
+
# Get classes which inherit from Thor
|
268
|
+
(klasses - groups).each { |k| list[k.namespace] += k.printable_tasks(false) }
|
274
269
|
|
275
|
-
|
276
|
-
|
277
|
-
|
270
|
+
# Get classes which inherit from Thor::Base
|
271
|
+
groups.map! { |k| k.printable_tasks(false).first }
|
272
|
+
list["root"] = groups
|
278
273
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
274
|
+
# Order namespaces with default coming first
|
275
|
+
list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
|
276
|
+
list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
|
277
|
+
end
|
283
278
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
279
|
+
def display_tasks(namespace, list) #:nodoc:
|
280
|
+
list.sort!{ |a,b| a[0] <=> b[0] }
|
281
|
+
|
282
|
+
say shell.set_color(namespace, :blue, true)
|
283
|
+
say "-" * namespace.size
|
284
|
+
|
285
|
+
print_table(list, :truncate => true)
|
286
|
+
say
|
288
287
|
end
|
289
288
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
unless klass.tasks.empty?
|
294
|
-
base = klass.namespace
|
289
|
+
def show_modules #:nodoc:
|
290
|
+
info = []
|
291
|
+
labels = ["Modules", "Namespaces"]
|
295
292
|
|
296
|
-
|
297
|
-
|
293
|
+
info << labels
|
294
|
+
info << [ "-" * labels[0].size, "-" * labels[1].size ]
|
298
295
|
|
299
|
-
|
300
|
-
|
296
|
+
thor_yaml.each do |name, hash|
|
297
|
+
info << [ name, hash[:namespaces].join(", ") ]
|
301
298
|
end
|
299
|
+
|
300
|
+
print_table info
|
301
|
+
say ""
|
302
302
|
end
|
303
303
|
end
|
data/lib/thor/shell/color.rb
CHANGED
@@ -63,7 +63,7 @@ class Thor
|
|
63
63
|
#
|
64
64
|
def show_diff(destination, content) #:nodoc:
|
65
65
|
if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil?
|
66
|
-
actual = File.
|
66
|
+
actual = File.binread(destination).to_s.split("\n")
|
67
67
|
content = content.to_s.split("\n")
|
68
68
|
|
69
69
|
Diff::LCS.sdiff(actual, content).each do |diff|
|
data/lib/thor/task.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
class Thor
|
2
2
|
class Task < Struct.new(:name, :description, :usage, :options)
|
3
|
+
FILE_REGEXP = /^#{Regexp.escape(File.expand_path(__FILE__))}:[\w:]+ `run'$/
|
3
4
|
|
4
5
|
# A dynamic task that handles method missing scenarios.
|
5
|
-
#
|
6
6
|
class Dynamic < Task
|
7
|
-
def initialize(name)
|
8
|
-
super(name.to_s, "A dynamically-generated task", name.to_s)
|
7
|
+
def initialize(name, options=nil)
|
8
|
+
super(name.to_s, "A dynamically-generated task", name.to_s, options)
|
9
9
|
end
|
10
10
|
|
11
11
|
def run(instance, args=[])
|
@@ -27,7 +27,6 @@ class Thor
|
|
27
27
|
|
28
28
|
# By default, a task invokes a method in the thor class. You can change this
|
29
29
|
# implementation to create custom tasks.
|
30
|
-
#
|
31
30
|
def run(instance, args=[])
|
32
31
|
raise UndefinedTaskError, "the '#{name}' task of #{instance.class} is private" unless public_method?(instance)
|
33
32
|
instance.send(name, *args)
|
@@ -41,8 +40,8 @@ class Thor
|
|
41
40
|
|
42
41
|
# Returns the formatted usage by injecting given required arguments
|
43
42
|
# and required options into the given usage.
|
44
|
-
def formatted_usage(klass, namespace=
|
45
|
-
namespace = klass.namespace
|
43
|
+
def formatted_usage(klass, namespace=true)
|
44
|
+
namespace = klass.namespace unless namespace == false
|
46
45
|
|
47
46
|
# Add namespace
|
48
47
|
formatted = if namespace
|
@@ -54,7 +53,7 @@ class Thor
|
|
54
53
|
# Add usage with required arguments
|
55
54
|
formatted << if klass && !klass.arguments.empty?
|
56
55
|
usage.to_s.gsub(/^#{name}/) do |match|
|
57
|
-
match << " " <<
|
56
|
+
match << " " << klass.arguments.map{ |a| a.usage }.compact.join(' ')
|
58
57
|
end
|
59
58
|
else
|
60
59
|
usage.to_s
|
@@ -69,25 +68,25 @@ class Thor
|
|
69
68
|
|
70
69
|
protected
|
71
70
|
|
72
|
-
def required_arguments(klass)
|
73
|
-
klass.arguments.map{ |a| a.usage if a.required? }.compact.join(' ')
|
74
|
-
end
|
75
|
-
|
76
71
|
def required_options
|
77
72
|
@required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ")
|
78
73
|
end
|
79
74
|
|
80
75
|
# Given a target, checks if this class name is not a private/protected method.
|
81
|
-
#
|
82
76
|
def public_method?(instance) #:nodoc:
|
83
77
|
collection = instance.private_methods + instance.protected_methods
|
84
78
|
(collection & [name.to_s, name.to_sym]).empty?
|
85
79
|
end
|
86
80
|
|
87
|
-
|
81
|
+
# For Ruby <= 1.8.7, we have to match the method name that we are trying to call.
|
82
|
+
# In Ruby >= 1.9.1, we have to match the method run in this file.
|
83
|
+
def backtrace_match?(backtrace) #:nodoc:
|
88
84
|
method_name = /`#{Regexp.escape(name.split(':').last)}'/
|
85
|
+
backtrace =~ method_name || backtrace =~ FILE_REGEXP
|
86
|
+
end
|
89
87
|
|
90
|
-
|
88
|
+
def parse_argument_error(instance, e, caller) #:nodoc:
|
89
|
+
if e.message =~ /wrong number of arguments/ && backtrace_match?(e.backtrace.first.to_s)
|
91
90
|
if instance.is_a?(Thor::Group)
|
92
91
|
raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?"
|
93
92
|
else
|