millisami-thor 0.14.6

Sign up to get free protection for your applications and to get access to all the features.
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,270 @@
1
+ require 'erb'
2
+ require 'open-uri'
3
+
4
+ class Thor
5
+ module Actions
6
+
7
+ # Copies the file from the relative source to the relative destination. If
8
+ # the destination is not given it's assumed to be equal to the source.
9
+ #
10
+ # ==== Parameters
11
+ # source<String>:: the relative path to the source root.
12
+ # destination<String>:: the relative path to the destination root.
13
+ # config<Hash>:: give :verbose => false to not log the status.
14
+ #
15
+ # ==== Examples
16
+ #
17
+ # copy_file "README", "doc/README"
18
+ #
19
+ # copy_file "doc/README"
20
+ #
21
+ def copy_file(source, *args, &block)
22
+ config = args.last.is_a?(Hash) ? args.pop : {}
23
+ destination = args.first || source
24
+ source = File.expand_path(find_in_source_paths(source.to_s))
25
+
26
+ create_file destination, nil, config do
27
+ content = File.binread(source)
28
+ content = block.call(content) if block
29
+ content
30
+ end
31
+ end
32
+
33
+ # Links the file from the relative source to the relative destination. If
34
+ # the destination is not given it's assumed to be equal to the source.
35
+ #
36
+ # ==== Parameters
37
+ # source<String>:: the relative path to the source root.
38
+ # destination<String>:: the relative path to the destination root.
39
+ # config<Hash>:: give :verbose => false to not log the status.
40
+ #
41
+ # ==== Examples
42
+ #
43
+ # link_file "README", "doc/README"
44
+ #
45
+ # link_file "doc/README"
46
+ #
47
+ def link_file(source, *args, &block)
48
+ config = args.last.is_a?(Hash) ? args.pop : {}
49
+ destination = args.first || source
50
+ source = File.expand_path(find_in_source_paths(source.to_s))
51
+
52
+ create_link destination, source, config
53
+ end
54
+
55
+ # Gets the content at the given address and places it at the given relative
56
+ # destination. If a block is given instead of destination, the content of
57
+ # the url is yielded and used as location.
58
+ #
59
+ # ==== Parameters
60
+ # source<String>:: the address of the given content.
61
+ # destination<String>:: the relative path to the destination root.
62
+ # config<Hash>:: give :verbose => false to not log the status.
63
+ #
64
+ # ==== Examples
65
+ #
66
+ # get "http://gist.github.com/103208", "doc/README"
67
+ #
68
+ # get "http://gist.github.com/103208" do |content|
69
+ # content.split("\n").first
70
+ # end
71
+ #
72
+ def get(source, *args, &block)
73
+ config = args.last.is_a?(Hash) ? args.pop : {}
74
+ destination = args.first
75
+
76
+ source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^https?\:\/\//
77
+ render = open(source) {|input| input.binmode.read }
78
+
79
+ destination ||= if block_given?
80
+ block.arity == 1 ? block.call(render) : block.call
81
+ else
82
+ File.basename(source)
83
+ end
84
+
85
+ create_file destination, render, config
86
+ end
87
+
88
+ # Gets an ERB template at the relative source, executes it and makes a copy
89
+ # at the relative destination. If the destination is not given it's assumed
90
+ # to be equal to the source removing .tt from the filename.
91
+ #
92
+ # ==== Parameters
93
+ # source<String>:: the relative path to the source root.
94
+ # destination<String>:: the relative path to the destination root.
95
+ # config<Hash>:: give :verbose => false to not log the status.
96
+ #
97
+ # ==== Examples
98
+ #
99
+ # template "README", "doc/README"
100
+ #
101
+ # template "doc/README"
102
+ #
103
+ def template(source, *args, &block)
104
+ config = args.last.is_a?(Hash) ? args.pop : {}
105
+ destination = args.first || source.sub(/\.tt$/, '')
106
+
107
+ source = File.expand_path(find_in_source_paths(source.to_s))
108
+ context = instance_eval('binding')
109
+
110
+ create_file destination, nil, config do
111
+ content = ERB.new(::File.binread(source), nil, '-', '@output_buffer').result(context)
112
+ content = block.call(content) if block
113
+ content
114
+ end
115
+ end
116
+
117
+ # Changes the mode of the given file or directory.
118
+ #
119
+ # ==== Parameters
120
+ # mode<Integer>:: the file mode
121
+ # path<String>:: the name of the file to change mode
122
+ # config<Hash>:: give :verbose => false to not log the status.
123
+ #
124
+ # ==== Example
125
+ #
126
+ # chmod "script/*", 0755
127
+ #
128
+ def chmod(path, mode, config={})
129
+ return unless behavior == :invoke
130
+ path = File.expand_path(path, destination_root)
131
+ say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
132
+ FileUtils.chmod_R(mode, path) unless options[:pretend]
133
+ end
134
+
135
+ # Prepend text to a file. Since it depends on insert_into_file, it's reversible.
136
+ #
137
+ # ==== Parameters
138
+ # path<String>:: path of the file to be changed
139
+ # data<String>:: the data to prepend to the file, can be also given as a block.
140
+ # config<Hash>:: give :verbose => false to not log the status.
141
+ #
142
+ # ==== Example
143
+ #
144
+ # prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"'
145
+ #
146
+ # prepend_to_file 'config/environments/test.rb' do
147
+ # 'config.gem "rspec"'
148
+ # end
149
+ #
150
+ def prepend_to_file(path, *args, &block)
151
+ config = args.last.is_a?(Hash) ? args.pop : {}
152
+ config.merge!(:after => /\A/)
153
+ insert_into_file(path, *(args << config), &block)
154
+ end
155
+ alias_method :prepend_file, :prepend_to_file
156
+
157
+ # Append text to a file. Since it depends on insert_into_file, it's reversible.
158
+ #
159
+ # ==== Parameters
160
+ # path<String>:: path of the file to be changed
161
+ # data<String>:: the data to append to the file, can be also given as a block.
162
+ # config<Hash>:: give :verbose => false to not log the status.
163
+ #
164
+ # ==== Example
165
+ #
166
+ # append_to_file 'config/environments/test.rb', 'config.gem "rspec"'
167
+ #
168
+ # append_to_file 'config/environments/test.rb' do
169
+ # 'config.gem "rspec"'
170
+ # end
171
+ #
172
+ def append_to_file(path, *args, &block)
173
+ config = args.last.is_a?(Hash) ? args.pop : {}
174
+ config.merge!(:before => /\z/)
175
+ insert_into_file(path, *(args << config), &block)
176
+ end
177
+ alias_method :append_file, :append_to_file
178
+
179
+ # Injects text right after the class definition. Since it depends on
180
+ # insert_into_file, it's reversible.
181
+ #
182
+ # ==== Parameters
183
+ # path<String>:: path of the file to be changed
184
+ # klass<String|Class>:: the class to be manipulated
185
+ # data<String>:: the data to append to the class, can be also given as a block.
186
+ # config<Hash>:: give :verbose => false to not log the status.
187
+ #
188
+ # ==== Examples
189
+ #
190
+ # inject_into_class "app/controllers/application_controller.rb", " filter_parameter :password\n"
191
+ #
192
+ # inject_into_class "app/controllers/application_controller.rb", ApplicationController do
193
+ # " filter_parameter :password\n"
194
+ # end
195
+ #
196
+ def inject_into_class(path, klass, *args, &block)
197
+ config = args.last.is_a?(Hash) ? args.pop : {}
198
+ config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/)
199
+ insert_into_file(path, *(args << config), &block)
200
+ end
201
+
202
+ # Run a regular expression replacement on a file.
203
+ #
204
+ # ==== Parameters
205
+ # path<String>:: path of the file to be changed
206
+ # flag<Regexp|String>:: the regexp or string to be replaced
207
+ # replacement<String>:: the replacement, can be also given as a block
208
+ # config<Hash>:: give :verbose => false to not log the status.
209
+ #
210
+ # ==== Example
211
+ #
212
+ # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
213
+ #
214
+ # gsub_file 'README', /rake/, :green do |match|
215
+ # match << " no more. Use thor!"
216
+ # end
217
+ #
218
+ def gsub_file(path, flag, *args, &block)
219
+ return unless behavior == :invoke
220
+ config = args.last.is_a?(Hash) ? args.pop : {}
221
+
222
+ path = File.expand_path(path, destination_root)
223
+ say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
224
+
225
+ unless options[:pretend]
226
+ content = File.binread(path)
227
+ content.gsub!(flag, *args, &block)
228
+ File.open(path, 'wb') { |file| file.write(content) }
229
+ end
230
+ end
231
+
232
+ # Removes a file at the given location.
233
+ #
234
+ # ==== Parameters
235
+ # path<String>:: path of the file to be changed
236
+ # config<Hash>:: give :verbose => false to not log the status.
237
+ #
238
+ # ==== Example
239
+ #
240
+ # remove_file 'README'
241
+ # remove_file 'app/controllers/application_controller.rb'
242
+ #
243
+ def remove_file(path, config={})
244
+ return unless behavior == :invoke
245
+ path = File.expand_path(path, destination_root)
246
+
247
+ say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
248
+ ::FileUtils.rm_rf(path) if !options[:pretend] && File.exists?(path)
249
+ end
250
+ alias :remove_dir :remove_file
251
+
252
+ private
253
+ attr_accessor :output_buffer
254
+ def concat(string)
255
+ @output_buffer.concat(string)
256
+ end
257
+
258
+ def capture(*args, &block)
259
+ with_output_buffer { block.call(*args) }
260
+ end
261
+
262
+ def with_output_buffer(buf = '') #:nodoc:
263
+ self.output_buffer, old_buffer = buf, output_buffer
264
+ yield
265
+ output_buffer
266
+ ensure
267
+ self.output_buffer = old_buffer
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,109 @@
1
+ require 'thor/actions/empty_directory'
2
+
3
+ class Thor
4
+ module Actions
5
+
6
+ # Injects the given content into a file. Different from gsub_file, this
7
+ # method is reversible.
8
+ #
9
+ # ==== Parameters
10
+ # destination<String>:: Relative path to the destination root
11
+ # data<String>:: Data to add to the file. Can be given as a block.
12
+ # config<Hash>:: give :verbose => false to not log the status and the flag
13
+ # for injection (:after or :before) or :force => true for
14
+ # insert two or more times the same content.
15
+ #
16
+ # ==== Examples
17
+ #
18
+ # insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
19
+ #
20
+ # insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
21
+ # gems = ask "Which gems would you like to add?"
22
+ # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
23
+ # end
24
+ #
25
+ def insert_into_file(destination, *args, &block)
26
+ if block_given?
27
+ data, config = block, args.shift
28
+ else
29
+ data, config = args.shift, args.shift
30
+ end
31
+ action InjectIntoFile.new(self, destination, data, config)
32
+ end
33
+ alias_method :inject_into_file, :insert_into_file
34
+
35
+ class InjectIntoFile < EmptyDirectory #:nodoc:
36
+ attr_reader :replacement, :flag, :behavior
37
+
38
+ def initialize(base, destination, data, config)
39
+ super(base, destination, { :verbose => true }.merge(config))
40
+
41
+ @behavior, @flag = if @config.key?(:after)
42
+ [:after, @config.delete(:after)]
43
+ else
44
+ [:before, @config.delete(:before)]
45
+ end
46
+
47
+ @replacement = data.is_a?(Proc) ? data.call : data
48
+ @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
49
+ end
50
+
51
+ def invoke!
52
+ say_status :invoke
53
+
54
+ content = if @behavior == :after
55
+ '\0' + replacement
56
+ else
57
+ replacement + '\0'
58
+ end
59
+
60
+ replace!(/#{flag}/, content, config[:force])
61
+ end
62
+
63
+ def revoke!
64
+ say_status :revoke
65
+
66
+ regexp = if @behavior == :after
67
+ content = '\1\2'
68
+ /(#{flag})(.*)(#{Regexp.escape(replacement)})/m
69
+ else
70
+ content = '\2\3'
71
+ /(#{Regexp.escape(replacement)})(.*)(#{flag})/m
72
+ end
73
+
74
+ replace!(regexp, content, true)
75
+ end
76
+
77
+ protected
78
+
79
+ def say_status(behavior)
80
+ status = if behavior == :invoke
81
+ if flag == /\A/
82
+ :prepend
83
+ elsif flag == /\z/
84
+ :append
85
+ else
86
+ :insert
87
+ end
88
+ else
89
+ :subtract
90
+ end
91
+
92
+ super(status, config[:verbose])
93
+ end
94
+
95
+ # Adds the content to the file.
96
+ #
97
+ def replace!(regexp, string, force)
98
+ unless base.options[:pretend]
99
+ content = File.binread(destination)
100
+ if force || !content.include?(replacement)
101
+ content.gsub!(regexp, string)
102
+ File.open(destination, 'wb') { |file| file.write(content) }
103
+ end
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,314 @@
1
+ require 'fileutils'
2
+ require 'uri'
3
+ require 'thor/core_ext/file_binary_read'
4
+ require 'thor/actions/create_file'
5
+ require 'thor/actions/create_link'
6
+ require 'thor/actions/directory'
7
+ require 'thor/actions/empty_directory'
8
+ require 'thor/actions/file_manipulation'
9
+ require 'thor/actions/inject_into_file'
10
+
11
+ class Thor
12
+ module Actions
13
+ attr_accessor :behavior
14
+
15
+ def self.included(base) #:nodoc:
16
+ base.extend ClassMethods
17
+ end
18
+
19
+ module ClassMethods
20
+ # Hold source paths for one Thor instance. source_paths_for_search is the
21
+ # method responsible to gather source_paths from this current class,
22
+ # inherited paths and the source root.
23
+ #
24
+ def source_paths
25
+ @_source_paths ||= []
26
+ end
27
+
28
+ # Stores and return the source root for this class
29
+ def source_root(path=nil)
30
+ @_source_root = path if path
31
+ @_source_root
32
+ end
33
+
34
+ # Returns the source paths in the following order:
35
+ #
36
+ # 1) This class source paths
37
+ # 2) Source root
38
+ # 3) Parents source paths
39
+ #
40
+ def source_paths_for_search
41
+ paths = []
42
+ paths += self.source_paths
43
+ paths << self.source_root if self.source_root
44
+ paths += from_superclass(:source_paths, [])
45
+ paths
46
+ end
47
+
48
+ # Add runtime options that help actions execution.
49
+ #
50
+ def add_runtime_options!
51
+ class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
52
+ :desc => "Overwrite files that already exist"
53
+
54
+ class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
55
+ :desc => "Run but do not make any changes"
56
+
57
+ class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime,
58
+ :desc => "Supress status output"
59
+
60
+ class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
61
+ :desc => "Skip files that already exist"
62
+ end
63
+ end
64
+
65
+ # Extends initializer to add more configuration options.
66
+ #
67
+ # ==== Configuration
68
+ # behavior<Symbol>:: The actions default behavior. Can be :invoke or :revoke.
69
+ # It also accepts :force, :skip and :pretend to set the behavior
70
+ # and the respective option.
71
+ #
72
+ # destination_root<String>:: The root directory needed for some actions.
73
+ #
74
+ def initialize(args=[], options={}, config={})
75
+ self.behavior = case config[:behavior].to_s
76
+ when "force", "skip"
77
+ _cleanup_options_and_set(options, config[:behavior])
78
+ :invoke
79
+ when "revoke"
80
+ :revoke
81
+ else
82
+ :invoke
83
+ end
84
+
85
+ super
86
+ self.destination_root = config[:destination_root]
87
+ end
88
+
89
+ # Wraps an action object and call it accordingly to the thor class behavior.
90
+ #
91
+ def action(instance) #:nodoc:
92
+ if behavior == :revoke
93
+ instance.revoke!
94
+ else
95
+ instance.invoke!
96
+ end
97
+ end
98
+
99
+ # Returns the root for this thor class (also aliased as destination root).
100
+ #
101
+ def destination_root
102
+ @destination_stack.last
103
+ end
104
+
105
+ # Sets the root for this thor class. Relatives path are added to the
106
+ # directory where the script was invoked and expanded.
107
+ #
108
+ def destination_root=(root)
109
+ @destination_stack ||= []
110
+ @destination_stack[0] = File.expand_path(root || '')
111
+ end
112
+
113
+ # Returns the given path relative to the absolute root (ie, root where
114
+ # the script started).
115
+ #
116
+ def relative_to_original_destination_root(path, remove_dot=true)
117
+ path = path.gsub(@destination_stack[0], '.')
118
+ remove_dot ? (path[2..-1] || '') : path
119
+ end
120
+
121
+ # Holds source paths in instance so they can be manipulated.
122
+ #
123
+ def source_paths
124
+ @source_paths ||= self.class.source_paths_for_search
125
+ end
126
+
127
+ # Receives a file or directory and search for it in the source paths.
128
+ #
129
+ def find_in_source_paths(file)
130
+ relative_root = relative_to_original_destination_root(destination_root, false)
131
+
132
+ source_paths.each do |source|
133
+ source_file = File.expand_path(file, File.join(source, relative_root))
134
+ return source_file if File.exists?(source_file)
135
+ end
136
+
137
+ message = "Could not find #{file.inspect} in any of your source paths. "
138
+
139
+ unless self.class.source_root
140
+ message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. "
141
+ end
142
+
143
+ if source_paths.empty?
144
+ message << "Currently you have no source paths."
145
+ else
146
+ message << "Your current source paths are: \n#{source_paths.join("\n")}"
147
+ end
148
+
149
+ raise Error, message
150
+ end
151
+
152
+ # Do something in the root or on a provided subfolder. If a relative path
153
+ # is given it's referenced from the current root. The full path is yielded
154
+ # to the block you provide. The path is set back to the previous path when
155
+ # the method exits.
156
+ #
157
+ # ==== Parameters
158
+ # dir<String>:: the directory to move to.
159
+ # config<Hash>:: give :verbose => true to log and use padding.
160
+ #
161
+ def inside(dir='', config={}, &block)
162
+ verbose = config.fetch(:verbose, false)
163
+ pretend = options[:pretend]
164
+
165
+ say_status :inside, dir, verbose
166
+ shell.padding += 1 if verbose
167
+ @destination_stack.push File.expand_path(dir, destination_root)
168
+
169
+ # If the directory doesnt exist and we're not pretending
170
+ if !File.exist?(destination_root) && !pretend
171
+ FileUtils.mkdir_p(destination_root)
172
+ end
173
+
174
+ if pretend
175
+ # In pretend mode, just yield down to the block
176
+ block.arity == 1 ? yield(destination_root) : yield
177
+ else
178
+ FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
179
+ end
180
+
181
+ @destination_stack.pop
182
+ shell.padding -= 1 if verbose
183
+ end
184
+
185
+ # Goes to the root and execute the given block.
186
+ #
187
+ def in_root
188
+ inside(@destination_stack.first) { yield }
189
+ end
190
+
191
+ # Loads an external file and execute it in the instance binding.
192
+ #
193
+ # ==== Parameters
194
+ # path<String>:: The path to the file to execute. Can be a web address or
195
+ # a relative path from the source root.
196
+ #
197
+ # ==== Examples
198
+ #
199
+ # apply "http://gist.github.com/103208"
200
+ #
201
+ # apply "recipes/jquery.rb"
202
+ #
203
+ def apply(path, config={})
204
+ verbose = config.fetch(:verbose, true)
205
+ is_uri = path =~ /^https?\:\/\//
206
+ path = find_in_source_paths(path) unless is_uri
207
+
208
+ say_status :apply, path, verbose
209
+ shell.padding += 1 if verbose
210
+
211
+ if is_uri
212
+ contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read }
213
+ else
214
+ contents = open(path) {|io| io.read }
215
+ end
216
+
217
+ instance_eval(contents, path)
218
+ shell.padding -= 1 if verbose
219
+ end
220
+
221
+ # Executes a command returning the contents of the command.
222
+ #
223
+ # ==== Parameters
224
+ # command<String>:: the command to be executed.
225
+ # config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output. Specify :with
226
+ # to append an executable to command executation.
227
+ #
228
+ # ==== Example
229
+ #
230
+ # inside('vendor') do
231
+ # run_cmd('ln -s ~/edge rails')
232
+ # end
233
+ #
234
+ def run_cmd(command, config={})
235
+ return unless behavior == :invoke
236
+
237
+ destination = relative_to_original_destination_root(destination_root, false)
238
+ desc = "#{command} from #{destination.inspect}"
239
+
240
+ if config[:with]
241
+ desc = "#{File.basename(config[:with].to_s)} #{desc}"
242
+ command = "#{config[:with]} #{command}"
243
+ end
244
+
245
+ say_status :run_cmd, desc, config.fetch(:verbose, true)
246
+
247
+ unless options[:pretend]
248
+ config[:capture] ? `#{command}` : system("#{command}")
249
+ end
250
+ end
251
+
252
+ # Executes a ruby script (taking into account WIN32 platform quirks).
253
+ #
254
+ # ==== Parameters
255
+ # command<String>:: the command to be executed.
256
+ # config<Hash>:: give :verbose => false to not log the status.
257
+ #
258
+ def run_ruby_script(command, config={})
259
+ return unless behavior == :invoke
260
+ run_cmd command, config.merge(:with => Thor::Util.ruby_command)
261
+ end
262
+
263
+ # Run a thor command. A hash of options can be given and it's converted to
264
+ # switches.
265
+ #
266
+ # ==== Parameters
267
+ # task<String>:: the task to be invoked
268
+ # args<Array>:: arguments to the task
269
+ # config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output.
270
+ # Other options are given as parameter to Thor.
271
+ #
272
+ #
273
+ # ==== Examples
274
+ #
275
+ # thor :install, "http://gist.github.com/103208"
276
+ # #=> thor install http://gist.github.com/103208
277
+ #
278
+ # thor :list, :all => true, :substring => 'rails'
279
+ # #=> thor list --all --substring=rails
280
+ #
281
+ def thor(task, *args)
282
+ config = args.last.is_a?(Hash) ? args.pop : {}
283
+ verbose = config.key?(:verbose) ? config.delete(:verbose) : true
284
+ pretend = config.key?(:pretend) ? config.delete(:pretend) : false
285
+ capture = config.key?(:capture) ? config.delete(:capture) : false
286
+
287
+ args.unshift task
288
+ args.push Thor::Options.to_switches(config)
289
+ command = args.join(' ').strip
290
+
291
+ run_cmd command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture
292
+ end
293
+
294
+ protected
295
+
296
+ # Allow current root to be shared between invocations.
297
+ #
298
+ def _shared_configuration #:nodoc:
299
+ super.merge!(:destination_root => self.destination_root)
300
+ end
301
+
302
+ def _cleanup_options_and_set(options, key) #:nodoc:
303
+ case options
304
+ when Array
305
+ %w(--force -f --skip -s).each { |i| options.delete(i) }
306
+ options << "--#{key}"
307
+ when Hash
308
+ [:force, :skip, "force", "skip"].each { |i| options.delete(i) }
309
+ options.merge!(key => true)
310
+ end
311
+ end
312
+
313
+ end
314
+ end