thor 0.12.2 → 0.12.3
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/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
|