thor 0.12.2 → 0.12.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,7 @@
1
- == TODO
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
@@ -4,4 +4,5 @@
4
4
  require 'thor'
5
5
  require 'thor/runner'
6
6
 
7
+ $thor_runner = true
7
8
  Thor::Runner.start
@@ -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. If a task name is given, it shows information
143
- # only about the specific task.
143
+ # Prints help information for the given task.
144
144
  #
145
145
  # ==== Parameters
146
- # meth<String>:: An optional task name to print usage information about.
147
- #
148
- # ==== Options
149
- # namespace:: When true, shows the namespace in the output before the usage.
150
- # skip_inherited:: When true, does not show tasks from superclass.
151
- #
152
- def help(shell, options={})
153
- if options[:task]
154
- task = all_tasks[options[:task]]
155
- raise UndefinedTaskError, "task '#{options[:task]}' could not be found in namespace '#{self.namespace}'" unless task
156
-
157
- shell.say "Usage:"
158
- shell.say " #{banner(task)}"
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
- if options[:short]
172
- shell.print_table(list, :truncate => true)
173
- else
174
- shell.say "Tasks:"
175
- shell.print_table(list, :ident => 2, :truncate => true)
176
- shell.say
177
- class_options_help(shell)
178
- end
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}" if 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 " + task.formatted_usage(self)
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.help(shell, :task => task)
240
+ task ? self.class.task_help(shell, task) : self.class.help(shell)
243
241
  end
244
242
  end
@@ -1,4 +1,5 @@
1
1
  require 'fileutils'
2
+ require 'thor/core_ext/file_binary_read'
2
3
 
3
4
  Dir[File.join(File.dirname(__FILE__), "actions", "*.rb")].each do |action|
4
5
  require action
@@ -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.read(destination) == render
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, 'w'){ |f| f.write render }
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.read(source)
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 = open(source).read
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.read(source), nil, '-').result(context)
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.read(path)
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
@@ -90,7 +90,7 @@ class Thor
90
90
  #
91
91
  def replace!(regexp, string)
92
92
  unless base.options[:pretend]
93
- content = File.read(destination)
93
+ content = File.binread(destination)
94
94
  content.gsub!(regexp, string)
95
95
  File.open(destination, 'wb') { |file| file.write(content) }
96
96
  end
@@ -0,0 +1,9 @@
1
+ class File #:nodoc:
2
+
3
+ unless File.respond_to?(:binread)
4
+ def self.binread(file)
5
+ File.open(file, 'rb') { |f| f.read }
6
+ end
7
+ end
8
+
9
+ end
@@ -41,16 +41,12 @@ class Thor::Group
41
41
  # ==== Options
42
42
  # short:: When true, shows only usage.
43
43
  #
44
- def help(shell, options={})
45
- if options[:short]
46
- shell.say banner
47
- else
48
- shell.say "Usage:"
49
- shell.say " #{banner}\n"
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
- "thor #{self.namespace} #{self.arguments.map {|a| a.usage }.join(' ')}"
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
- # Shortcut to invoke with padding and block handling. Use internally by
241
- # invoke and invoke_from_option class methods.
242
- #
243
- def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
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
- result = if block_given?
247
- if block.arity == 2
248
- block.call(self, klass)
249
- else
250
- block.call(self, klass, task)
251
- end
252
- else
253
- invoke klass, task, *args
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
- shell.padding -= 1
257
- result
264
+ else
265
+ invoke klass, task, *args
258
266
  end
267
+
268
+ shell.padding -= 1
269
+ result
270
+ end
259
271
  end
@@ -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 = YAML.load_file(yaml_file) if File.exists?(yaml_file)
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 with_modules
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
- if with_modules && !thor_yaml.empty?
265
- info = []
266
- labels = ["Modules", "Namespaces"]
259
+ # Remove subclasses
260
+ klasses.dup.each do |klass|
261
+ klasses -= Thor::Util.thor_classes_in(klass)
262
+ end
267
263
 
268
- info << labels
269
- info << [ "-" * labels[0].size, "-" * labels[1].size ]
264
+ list = Hash.new { |h,k| h[k] = [] }
265
+ groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
270
266
 
271
- thor_yaml.each do |name, hash|
272
- info << [ name, hash[:namespaces].join(", ") ]
273
- end
267
+ # Get classes which inherit from Thor
268
+ (klasses - groups).each { |k| list[k.namespace] += k.printable_tasks(false) }
274
269
 
275
- print_table info
276
- say ""
277
- end
270
+ # Get classes which inherit from Thor::Base
271
+ groups.map! { |k| k.printable_tasks(false).first }
272
+ list["root"] = groups
278
273
 
279
- unless klasses.empty?
280
- klasses.dup.each do |klass|
281
- klasses -= Thor::Util.thor_classes_in(klass)
282
- end
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
- klasses.each { |k| display_tasks(k) }
285
- else
286
- say "\033[1;34mNo Thor tasks available\033[0m"
287
- end
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
- # Display tasks from the given Thor class.
291
- #
292
- def display_tasks(klass)
293
- unless klass.tasks.empty?
294
- base = klass.namespace
289
+ def show_modules #:nodoc:
290
+ info = []
291
+ labels = ["Modules", "Namespaces"]
295
292
 
296
- say shell.set_color(base, :blue, true)
297
- say "-" * base.length
293
+ info << labels
294
+ info << [ "-" * labels[0].size, "-" * labels[1].size ]
298
295
 
299
- klass.help(shell, :short => true)
300
- say
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
@@ -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.read(destination).to_s.split("\n")
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|
@@ -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=nil)
45
- namespace = klass.namespace if namespace.nil?
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 << " " << required_arguments(klass)
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
- def parse_argument_error(instance, e, caller) #:nodoc:
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
- if e.message =~ /wrong number of arguments/ && e.backtrace.first.to_s =~ method_name
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