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