millisami-thor 0.14.6

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.
Files changed (82) hide show
  1. data/.autotest +8 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +1 -0
  5. data/CHANGELOG.rdoc +103 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE.md +20 -0
  8. data/README.md +26 -0
  9. data/Thorfile +13 -0
  10. data/bin/rake2thor +86 -0
  11. data/bin/thor +6 -0
  12. data/lib/thor/actions/create_file.rb +105 -0
  13. data/lib/thor/actions/create_link.rb +57 -0
  14. data/lib/thor/actions/directory.rb +93 -0
  15. data/lib/thor/actions/empty_directory.rb +134 -0
  16. data/lib/thor/actions/file_manipulation.rb +270 -0
  17. data/lib/thor/actions/inject_into_file.rb +109 -0
  18. data/lib/thor/actions.rb +314 -0
  19. data/lib/thor/base.rb +598 -0
  20. data/lib/thor/core_ext/file_binary_read.rb +9 -0
  21. data/lib/thor/core_ext/hash_with_indifferent_access.rb +75 -0
  22. data/lib/thor/core_ext/ordered_hash.rb +100 -0
  23. data/lib/thor/error.rb +30 -0
  24. data/lib/thor/group.rb +276 -0
  25. data/lib/thor/invocation.rb +168 -0
  26. data/lib/thor/parser/argument.rb +67 -0
  27. data/lib/thor/parser/arguments.rb +165 -0
  28. data/lib/thor/parser/option.rb +120 -0
  29. data/lib/thor/parser/options.rb +181 -0
  30. data/lib/thor/parser.rb +4 -0
  31. data/lib/thor/rake_compat.rb +70 -0
  32. data/lib/thor/runner.rb +309 -0
  33. data/lib/thor/shell/basic.rb +302 -0
  34. data/lib/thor/shell/color.rb +108 -0
  35. data/lib/thor/shell/html.rb +121 -0
  36. data/lib/thor/shell.rb +88 -0
  37. data/lib/thor/task.rb +129 -0
  38. data/lib/thor/util.rb +229 -0
  39. data/lib/thor/version.rb +3 -0
  40. data/lib/thor.rb +336 -0
  41. data/spec/actions/create_file_spec.rb +170 -0
  42. data/spec/actions/directory_spec.rb +136 -0
  43. data/spec/actions/empty_directory_spec.rb +98 -0
  44. data/spec/actions/file_manipulation_spec.rb +317 -0
  45. data/spec/actions/inject_into_file_spec.rb +135 -0
  46. data/spec/actions_spec.rb +322 -0
  47. data/spec/base_spec.rb +274 -0
  48. data/spec/core_ext/hash_with_indifferent_access_spec.rb +43 -0
  49. data/spec/core_ext/ordered_hash_spec.rb +115 -0
  50. data/spec/fixtures/application.rb +2 -0
  51. data/spec/fixtures/bundle/execute.rb +6 -0
  52. data/spec/fixtures/bundle/main.thor +1 -0
  53. data/spec/fixtures/doc/%file_name%.rb.tt +1 -0
  54. data/spec/fixtures/doc/README +3 -0
  55. data/spec/fixtures/doc/block_helper.rb +3 -0
  56. data/spec/fixtures/doc/components/.empty_directory +0 -0
  57. data/spec/fixtures/doc/config.rb +1 -0
  58. data/spec/fixtures/doc/config.yaml.tt +1 -0
  59. data/spec/fixtures/group.thor +114 -0
  60. data/spec/fixtures/invoke.thor +112 -0
  61. data/spec/fixtures/path with spaces +0 -0
  62. data/spec/fixtures/script.thor +184 -0
  63. data/spec/fixtures/task.thor +10 -0
  64. data/spec/group_spec.rb +216 -0
  65. data/spec/invocation_spec.rb +100 -0
  66. data/spec/parser/argument_spec.rb +47 -0
  67. data/spec/parser/arguments_spec.rb +65 -0
  68. data/spec/parser/option_spec.rb +202 -0
  69. data/spec/parser/options_spec.rb +329 -0
  70. data/spec/rake_compat_spec.rb +72 -0
  71. data/spec/register_spec.rb +92 -0
  72. data/spec/runner_spec.rb +210 -0
  73. data/spec/shell/basic_spec.rb +223 -0
  74. data/spec/shell/color_spec.rb +41 -0
  75. data/spec/shell/html_spec.rb +27 -0
  76. data/spec/shell_spec.rb +47 -0
  77. data/spec/spec_helper.rb +54 -0
  78. data/spec/task_spec.rb +74 -0
  79. data/spec/thor_spec.rb +362 -0
  80. data/spec/util_spec.rb +163 -0
  81. data/thor.gemspec +25 -0
  82. metadata +241 -0
@@ -0,0 +1,309 @@
1
+ require 'thor'
2
+ require 'thor/group'
3
+ require 'thor/core_ext/file_binary_read'
4
+
5
+ require 'fileutils'
6
+ require 'open-uri'
7
+ require 'yaml'
8
+ require 'digest/md5'
9
+ require 'pathname'
10
+
11
+ class Thor::Runner < Thor #:nodoc:
12
+ map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
13
+
14
+ # Override Thor#help so it can give information about any class and any method.
15
+ #
16
+ def help(meth = nil)
17
+ if meth && !self.respond_to?(meth)
18
+ initialize_thorfiles(meth)
19
+ klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
20
+ klass.start(["-h", task].compact, :shell => self.shell)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ # If a task is not found on Thor::Runner, method missing is invoked and
27
+ # Thor::Runner is then responsable for finding the task in all classes.
28
+ #
29
+ def method_missing(meth, *args)
30
+ meth = meth.to_s
31
+ initialize_thorfiles(meth)
32
+ klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
33
+ args.unshift(task) if task
34
+ klass.start(args, :shell => self.shell)
35
+ end
36
+
37
+ desc "install NAME", "Install an optionally named Thor file into your system tasks"
38
+ method_options :as => :string, :relative => :boolean, :force => :boolean
39
+ def install(name)
40
+ initialize_thorfiles
41
+
42
+ # If a directory name is provided as the argument, look for a 'main.thor'
43
+ # task in said directory.
44
+ begin
45
+ if File.directory?(File.expand_path(name))
46
+ base, package = File.join(name, "main.thor"), :directory
47
+ contents = open(base) {|input| input.read }
48
+ else
49
+ base, package = name, :file
50
+ contents = open(name) {|input| input.read }
51
+ end
52
+ rescue OpenURI::HTTPError
53
+ raise Error, "Error opening URI '#{name}'"
54
+ rescue Errno::ENOENT
55
+ raise Error, "Error opening file '#{name}'"
56
+ end
57
+
58
+ say "Your Thorfile contains:"
59
+ say contents
60
+
61
+ unless options["force"]
62
+ return false if no?("Do you wish to continue [y/N]?")
63
+ end
64
+
65
+ as = options["as"] || begin
66
+ first_line = contents.split("\n")[0]
67
+ (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
68
+ end
69
+
70
+ unless as
71
+ basename = File.basename(name)
72
+ as = ask("Please specify a name for #{name} in the system repository [#{basename}]:")
73
+ as = basename if as.empty?
74
+ end
75
+
76
+ location = if options[:relative] || name =~ /^https?:\/\//
77
+ name
78
+ else
79
+ File.expand_path(name)
80
+ end
81
+
82
+ thor_yaml[as] = {
83
+ :filename => Digest::MD5.hexdigest(name + as),
84
+ :location => location,
85
+ :namespaces => Thor::Util.namespaces_in_content(contents, base)
86
+ }
87
+
88
+ save_yaml(thor_yaml)
89
+ say "Storing thor file in your system repository"
90
+ destination = File.join(thor_root, thor_yaml[as][:filename])
91
+
92
+ if package == :file
93
+ File.open(destination, "w") { |f| f.puts contents }
94
+ else
95
+ FileUtils.cp_r(name, destination)
96
+ end
97
+
98
+ thor_yaml[as][:filename] # Indicate success
99
+ end
100
+
101
+ desc "version", "Show Thor version"
102
+ def version
103
+ require 'thor/version'
104
+ say "Thor #{Thor::VERSION}"
105
+ end
106
+
107
+ desc "uninstall NAME", "Uninstall a named Thor module"
108
+ def uninstall(name)
109
+ raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
110
+ say "Uninstalling #{name}."
111
+ FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}"))
112
+
113
+ thor_yaml.delete(name)
114
+ save_yaml(thor_yaml)
115
+
116
+ puts "Done."
117
+ end
118
+
119
+ desc "update NAME", "Update a Thor file from its original location"
120
+ def update(name)
121
+ raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location]
122
+
123
+ say "Updating '#{name}' from #{thor_yaml[name][:location]}"
124
+
125
+ old_filename = thor_yaml[name][:filename]
126
+ self.options = self.options.merge("as" => name)
127
+ filename = install(thor_yaml[name][:location])
128
+
129
+ unless filename == old_filename
130
+ File.delete(File.join(thor_root, old_filename))
131
+ end
132
+ end
133
+
134
+ desc "installed", "List the installed Thor modules and tasks"
135
+ method_options :internal => :boolean
136
+ def installed
137
+ initialize_thorfiles(nil, true)
138
+ display_klasses(true, options["internal"])
139
+ end
140
+
141
+ desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
142
+ method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
143
+ def list(search="")
144
+ initialize_thorfiles
145
+
146
+ search = ".*#{search}" if options["substring"]
147
+ search = /^#{search}.*/i
148
+ group = options[:group] || "standard"
149
+
150
+ klasses = Thor::Base.subclasses.select do |k|
151
+ (options[:all] || k.group == group) && k.namespace =~ search
152
+ end
153
+
154
+ display_klasses(false, false, klasses)
155
+ end
156
+
157
+ private
158
+
159
+ def self.banner(task, all = false, subcommand = false)
160
+ "thor " + task.formatted_usage(self, all, subcommand)
161
+ end
162
+
163
+ def thor_root
164
+ Thor::Util.thor_root
165
+ end
166
+
167
+ def thor_yaml
168
+ @thor_yaml ||= begin
169
+ yaml_file = File.join(thor_root, "thor.yml")
170
+ yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
171
+ yaml || {}
172
+ end
173
+ end
174
+
175
+ # Save the yaml file. If none exists in thor root, creates one.
176
+ #
177
+ def save_yaml(yaml)
178
+ yaml_file = File.join(thor_root, "thor.yml")
179
+
180
+ unless File.exists?(yaml_file)
181
+ FileUtils.mkdir_p(thor_root)
182
+ yaml_file = File.join(thor_root, "thor.yml")
183
+ FileUtils.touch(yaml_file)
184
+ end
185
+
186
+ File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml }
187
+ end
188
+
189
+ def self.exit_on_failure?
190
+ true
191
+ end
192
+
193
+ # Load the thorfiles. If relevant_to is supplied, looks for specific files
194
+ # in the thor_root instead of loading them all.
195
+ #
196
+ # By default, it also traverses the current path until find Thor files, as
197
+ # described in thorfiles. This look up can be skipped by suppliying
198
+ # skip_lookup true.
199
+ #
200
+ def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
201
+ thorfiles(relevant_to, skip_lookup).each do |f|
202
+ Thor::Util.load_thorfile(f, nil, options[:debug]) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f))
203
+ end
204
+ end
205
+
206
+ # Finds Thorfiles by traversing from your current directory down to the root
207
+ # directory of your system. If at any time we find a Thor file, we stop.
208
+ #
209
+ # We also ensure that system-wide Thorfiles are loaded first, so local
210
+ # Thorfiles can override them.
211
+ #
212
+ # ==== Example
213
+ #
214
+ # If we start at /Users/wycats/dev/thor ...
215
+ #
216
+ # 1. /Users/wycats/dev/thor
217
+ # 2. /Users/wycats/dev
218
+ # 3. /Users/wycats <-- we find a Thorfile here, so we stop
219
+ #
220
+ # Suppose we start at c:\Documents and Settings\james\dev\thor ...
221
+ #
222
+ # 1. c:\Documents and Settings\james\dev\thor
223
+ # 2. c:\Documents and Settings\james\dev
224
+ # 3. c:\Documents and Settings\james
225
+ # 4. c:\Documents and Settings
226
+ # 5. c:\ <-- no Thorfiles found!
227
+ #
228
+ def thorfiles(relevant_to=nil, skip_lookup=false)
229
+ thorfiles = []
230
+
231
+ unless skip_lookup
232
+ Pathname.pwd.ascend do |path|
233
+ thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten
234
+ break unless thorfiles.empty?
235
+ end
236
+ end
237
+
238
+ files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob)
239
+ files += thorfiles
240
+ files -= ["#{thor_root}/thor.yml"]
241
+
242
+ files.map! do |file|
243
+ File.directory?(file) ? File.join(file, "main.thor") : file
244
+ end
245
+ end
246
+
247
+ # Load thorfiles relevant to the given method. If you provide "foo:bar" it
248
+ # will load all thor files in the thor.yaml that has "foo" e "foo:bar"
249
+ # namespaces registered.
250
+ #
251
+ def thorfiles_relevant_to(meth)
252
+ lookup = [ meth, meth.split(":")[0...-1].join(":") ]
253
+
254
+ files = thor_yaml.select do |k, v|
255
+ v[:namespaces] && !(v[:namespaces] & lookup).empty?
256
+ end
257
+
258
+ files.map { |k, v| File.join(thor_root, "#{v[:filename]}") }
259
+ end
260
+
261
+ # Display information about the given klasses. If with_module is given,
262
+ # it shows a table with information extracted from the yaml file.
263
+ #
264
+ def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses)
265
+ klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal
266
+
267
+ raise Error, "No Thor tasks available" if klasses.empty?
268
+ show_modules if with_modules && !thor_yaml.empty?
269
+
270
+ list = Hash.new { |h,k| h[k] = [] }
271
+ groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
272
+
273
+ # Get classes which inherit from Thor
274
+ (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_tasks(false) }
275
+
276
+ # Get classes which inherit from Thor::Base
277
+ groups.map! { |k| k.printable_tasks(false).first }
278
+ list["root"] = groups
279
+
280
+ # Order namespaces with default coming first
281
+ list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
282
+ list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
283
+ end
284
+
285
+ def display_tasks(namespace, list) #:nodoc:
286
+ list.sort!{ |a,b| a[0] <=> b[0] }
287
+
288
+ say shell.set_color(namespace, :blue, true)
289
+ say "-" * namespace.size
290
+
291
+ print_table(list, :truncate => true)
292
+ say
293
+ end
294
+
295
+ def show_modules #:nodoc:
296
+ info = []
297
+ labels = ["Modules", "Namespaces"]
298
+
299
+ info << labels
300
+ info << [ "-" * labels[0].size, "-" * labels[1].size ]
301
+
302
+ thor_yaml.each do |name, hash|
303
+ info << [ name, hash[:namespaces].join(", ") ]
304
+ end
305
+
306
+ print_table info
307
+ say ""
308
+ end
309
+ end
@@ -0,0 +1,302 @@
1
+ require 'tempfile'
2
+
3
+ class Thor
4
+ module Shell
5
+ class Basic
6
+ attr_accessor :base, :padding
7
+
8
+ # Initialize base, mute and padding to nil.
9
+ #
10
+ def initialize #:nodoc:
11
+ @base, @mute, @padding = nil, false, 0
12
+ end
13
+
14
+ # Mute everything that's inside given block
15
+ #
16
+ def mute
17
+ @mute = true
18
+ yield
19
+ ensure
20
+ @mute = false
21
+ end
22
+
23
+ # Check if base is muted
24
+ #
25
+ def mute?
26
+ @mute
27
+ end
28
+
29
+ # Sets the output padding, not allowing less than zero values.
30
+ #
31
+ def padding=(value)
32
+ @padding = [0, value].max
33
+ end
34
+
35
+ # Ask something to the user and receives a response.
36
+ #
37
+ # ==== Example
38
+ # ask("What is your name?")
39
+ #
40
+ def ask(statement, color=nil)
41
+ say("#{statement} ", color)
42
+ stdin.gets.strip
43
+ end
44
+
45
+ # Say (print) something to the user. If the sentence ends with a whitespace
46
+ # or tab character, a new line is not appended (print + flush). Otherwise
47
+ # are passed straight to puts (behavior got from Highline).
48
+ #
49
+ # ==== Example
50
+ # say("I know you knew that.")
51
+ #
52
+ def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
53
+ message = message.to_s
54
+ message = set_color(message, color) if color
55
+
56
+ spaces = " " * padding
57
+
58
+ if force_new_line
59
+ stdout.puts(spaces + message)
60
+ else
61
+ stdout.print(spaces + message)
62
+ end
63
+ stdout.flush
64
+ end
65
+
66
+ # Say a status with the given color and appends the message. Since this
67
+ # method is used frequently by actions, it allows nil or false to be given
68
+ # in log_status, avoiding the message from being shown. If a Symbol is
69
+ # given in log_status, it's used as the color.
70
+ #
71
+ def say_status(status, message, log_status=true)
72
+ return if quiet? || log_status == false
73
+ spaces = " " * (padding + 1)
74
+ color = log_status.is_a?(Symbol) ? log_status : :green
75
+
76
+ status = status.to_s.rjust(12)
77
+ status = set_color status, color, true if color
78
+
79
+ stdout.puts "#{status}#{spaces}#{message}"
80
+ stdout.flush
81
+ end
82
+
83
+ # Make a question the to user and returns true if the user replies "y" or
84
+ # "yes".
85
+ #
86
+ def yes?(statement, color=nil)
87
+ !!(ask(statement, color) =~ is?(:yes))
88
+ end
89
+
90
+ # Make a question the to user and returns true if the user replies "n" or
91
+ # "no".
92
+ #
93
+ def no?(statement, color=nil)
94
+ !yes?(statement, color)
95
+ end
96
+
97
+ # Prints a table.
98
+ #
99
+ # ==== Parameters
100
+ # Array[Array[String, String, ...]]
101
+ #
102
+ # ==== Options
103
+ # ident<Integer>:: Indent the first column by ident value.
104
+ # colwidth<Integer>:: Force the first column to colwidth spaces wide.
105
+ #
106
+ def print_table(table, options={})
107
+ return if table.empty?
108
+
109
+ formats, ident, colwidth = [], options[:ident].to_i, options[:colwidth]
110
+ options[:truncate] = terminal_width if options[:truncate] == true
111
+
112
+ formats << "%-#{colwidth + 2}s" if colwidth
113
+ start = colwidth ? 1 : 0
114
+
115
+ start.upto(table.first.length - 2) do |i|
116
+ maxima ||= table.max{|a,b| a[i].size <=> b[i].size }[i].size
117
+ formats << "%-#{maxima + 2}s"
118
+ end
119
+
120
+ formats[0] = formats[0].insert(0, " " * ident)
121
+ formats << "%s"
122
+
123
+ table.each do |row|
124
+ sentence = ""
125
+
126
+ row.each_with_index do |column, i|
127
+ sentence << formats[i] % column.to_s
128
+ end
129
+
130
+ sentence = truncate(sentence, options[:truncate]) if options[:truncate]
131
+ stdout.puts sentence
132
+ end
133
+ end
134
+
135
+ # Prints a long string, word-wrapping the text to the current width of the
136
+ # terminal display. Ideal for printing heredocs.
137
+ #
138
+ # ==== Parameters
139
+ # String
140
+ #
141
+ # ==== Options
142
+ # ident<Integer>:: Indent each line of the printed paragraph by ident value.
143
+ #
144
+ def print_wrapped(message, options={})
145
+ ident = options[:ident] || 0
146
+ width = terminal_width - ident
147
+ paras = message.split("\n\n")
148
+
149
+ paras.map! do |unwrapped|
150
+ unwrapped.strip.gsub(/\n/, " ").squeeze(" ").
151
+ gsub(/.{1,#{width}}(?:\s|\Z)/){($& + 5.chr).
152
+ gsub(/\n\005/,"\n").gsub(/\005/,"\n")}
153
+ end
154
+
155
+ paras.each do |para|
156
+ para.split("\n").each do |line|
157
+ stdout.puts line.insert(0, " " * ident)
158
+ end
159
+ stdout.puts unless para == paras.last
160
+ end
161
+ end
162
+
163
+ # Deals with file collision and returns true if the file should be
164
+ # overwriten and false otherwise. If a block is given, it uses the block
165
+ # response as the content for the diff.
166
+ #
167
+ # ==== Parameters
168
+ # destination<String>:: the destination file to solve conflicts
169
+ # block<Proc>:: an optional block that returns the value to be used in diff
170
+ #
171
+ def file_collision(destination)
172
+ return true if @always_force
173
+ options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
174
+
175
+ while true
176
+ answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
177
+
178
+ case answer
179
+ when is?(:yes), is?(:force), ""
180
+ return true
181
+ when is?(:no), is?(:skip)
182
+ return false
183
+ when is?(:always)
184
+ return @always_force = true
185
+ when is?(:quit)
186
+ say 'Aborting...'
187
+ raise SystemExit
188
+ when is?(:diff)
189
+ show_diff(destination, yield) if block_given?
190
+ say 'Retrying...'
191
+ else
192
+ say file_collision_help
193
+ end
194
+ end
195
+ end
196
+
197
+ # Called if something goes wrong during the execution. This is used by Thor
198
+ # internally and should not be used inside your scripts. If something went
199
+ # wrong, you can always raise an exception. If you raise a Thor::Error, it
200
+ # will be rescued and wrapped in the method below.
201
+ #
202
+ def error(statement)
203
+ stderr.puts statement
204
+ end
205
+
206
+ # Apply color to the given string with optional bold. Disabled in the
207
+ # Thor::Shell::Basic class.
208
+ #
209
+ def set_color(string, color, bold=false) #:nodoc:
210
+ string
211
+ end
212
+
213
+ protected
214
+
215
+ def stdout
216
+ $stdout
217
+ end
218
+
219
+ def stdin
220
+ $stdin
221
+ end
222
+
223
+ def stderr
224
+ $stderr
225
+ end
226
+
227
+ def is?(value) #:nodoc:
228
+ value = value.to_s
229
+
230
+ if value.size == 1
231
+ /\A#{value}\z/i
232
+ else
233
+ /\A(#{value}|#{value[0,1]})\z/i
234
+ end
235
+ end
236
+
237
+ def file_collision_help #:nodoc:
238
+ <<HELP
239
+ Y - yes, overwrite
240
+ n - no, do not overwrite
241
+ a - all, overwrite this and all others
242
+ q - quit, abort
243
+ d - diff, show the differences between the old and the new
244
+ h - help, show this help
245
+ HELP
246
+ end
247
+
248
+ def show_diff(destination, content) #:nodoc:
249
+ diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u'
250
+
251
+ Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
252
+ temp.write content
253
+ temp.rewind
254
+ system %(#{diff_cmd} "#{destination}" "#{temp.path}")
255
+ end
256
+ end
257
+
258
+ def quiet? #:nodoc:
259
+ mute? || (base && base.options[:quiet])
260
+ end
261
+
262
+ # This code was copied from Rake, available under MIT-LICENSE
263
+ # Copyright (c) 2003, 2004 Jim Weirich
264
+ def terminal_width
265
+ if ENV['THOR_COLUMNS']
266
+ result = ENV['THOR_COLUMNS'].to_i
267
+ else
268
+ result = unix? ? dynamic_width : 80
269
+ end
270
+ (result < 10) ? 80 : result
271
+ rescue
272
+ 80
273
+ end
274
+
275
+ # Calculate the dynamic width of the terminal
276
+ def dynamic_width
277
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
278
+ end
279
+
280
+ def dynamic_width_stty
281
+ %x{stty size 2>/dev/null}.split[1].to_i
282
+ end
283
+
284
+ def dynamic_width_tput
285
+ %x{tput cols 2>/dev/null}.to_i
286
+ end
287
+
288
+ def unix?
289
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
290
+ end
291
+
292
+ def truncate(string, width)
293
+ if string.length <= width
294
+ string
295
+ else
296
+ ( string[0, width-3] || "" ) + "..."
297
+ end
298
+ end
299
+
300
+ end
301
+ end
302
+ end