wtch 0.0.1

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.
@@ -0,0 +1,134 @@
1
+ class Thor
2
+ module Actions
3
+
4
+ # Creates an empty directory.
5
+ #
6
+ # ==== Parameters
7
+ # destination<String>:: the relative path to the destination root.
8
+ # config<Hash>:: give :verbose => false to not log the status.
9
+ #
10
+ # ==== Examples
11
+ #
12
+ # empty_directory "doc"
13
+ #
14
+ def empty_directory(destination, config={})
15
+ action EmptyDirectory.new(self, destination, config)
16
+ end
17
+
18
+ # Class which holds create directory logic. This is the base class for
19
+ # other actions like create_file and directory.
20
+ #
21
+ # This implementation is based in Templater actions, created by Jonas Nicklas
22
+ # and Michael S. Klishin under MIT LICENSE.
23
+ #
24
+ class EmptyDirectory #:nodoc:
25
+ attr_reader :base, :destination, :given_destination, :relative_destination, :config
26
+
27
+ # Initializes given the source and destination.
28
+ #
29
+ # ==== Parameters
30
+ # base<Thor::Base>:: A Thor::Base instance
31
+ # source<String>:: Relative path to the source of this file
32
+ # destination<String>:: Relative path to the destination of this file
33
+ # config<Hash>:: give :verbose => false to not log the status.
34
+ #
35
+ def initialize(base, destination, config={})
36
+ @base, @config = base, { :verbose => true }.merge(config)
37
+ self.destination = destination
38
+ end
39
+
40
+ # Checks if the destination file already exists.
41
+ #
42
+ # ==== Returns
43
+ # Boolean:: true if the file exists, false otherwise.
44
+ #
45
+ def exists?
46
+ ::File.exists?(destination)
47
+ end
48
+
49
+ def invoke!
50
+ invoke_with_conflict_check do
51
+ ::FileUtils.mkdir_p(destination)
52
+ end
53
+ end
54
+
55
+ def revoke!
56
+ say_status :remove, :red
57
+ ::FileUtils.rm_rf(destination) if !pretend? && exists?
58
+ given_destination
59
+ end
60
+
61
+ protected
62
+
63
+ # Shortcut for pretend.
64
+ #
65
+ def pretend?
66
+ base.options[:pretend]
67
+ end
68
+
69
+ # Sets the absolute destination value from a relative destination value.
70
+ # It also stores the given and relative destination. Let's suppose our
71
+ # script is being executed on "dest", it sets the destination root to
72
+ # "dest". The destination, given_destination and relative_destination
73
+ # are related in the following way:
74
+ #
75
+ # inside "bar" do
76
+ # empty_directory "baz"
77
+ # end
78
+ #
79
+ # destination #=> dest/bar/baz
80
+ # relative_destination #=> bar/baz
81
+ # given_destination #=> baz
82
+ #
83
+ def destination=(destination)
84
+ if destination
85
+ @given_destination = convert_encoded_instructions(destination.to_s)
86
+ @destination = ::File.expand_path(@given_destination, base.destination_root)
87
+ @relative_destination = base.relative_to_original_destination_root(@destination)
88
+ end
89
+ end
90
+
91
+ # Filenames in the encoded form are converted. If you have a file:
92
+ #
93
+ # %class_name%.rb
94
+ #
95
+ # It gets the class name from the base and replace it:
96
+ #
97
+ # user.rb
98
+ #
99
+ def convert_encoded_instructions(filename)
100
+ filename.gsub(/%(.*?)%/) do |string|
101
+ instruction = $1.strip
102
+ base.respond_to?(instruction) ? base.send(instruction) : string
103
+ end
104
+ end
105
+
106
+ # Receives a hash of options and just execute the block if some
107
+ # conditions are met.
108
+ #
109
+ def invoke_with_conflict_check(&block)
110
+ if exists?
111
+ on_conflict_behavior(&block)
112
+ else
113
+ say_status :create, :green
114
+ block.call unless pretend?
115
+ end
116
+
117
+ destination
118
+ end
119
+
120
+ # What to do when the destination file already exists.
121
+ #
122
+ def on_conflict_behavior(&block)
123
+ say_status :exist, :blue
124
+ end
125
+
126
+ # Shortcut to say_status shell method.
127
+ #
128
+ def say_status(status, color)
129
+ base.shell.say_status status, relative_destination, color if config[:verbose]
130
+ end
131
+
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,229 @@
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
+ # Gets the content at the given address and places it at the given relative
34
+ # destination. If a block is given instead of destination, the content of
35
+ # the url is yielded and used as location.
36
+ #
37
+ # ==== Parameters
38
+ # source<String>:: the address of the given content.
39
+ # destination<String>:: the relative path to the destination root.
40
+ # config<Hash>:: give :verbose => false to not log the status.
41
+ #
42
+ # ==== Examples
43
+ #
44
+ # get "http://gist.github.com/103208", "doc/README"
45
+ #
46
+ # get "http://gist.github.com/103208" do |content|
47
+ # content.split("\n").first
48
+ # end
49
+ #
50
+ def get(source, *args, &block)
51
+ config = args.last.is_a?(Hash) ? args.pop : {}
52
+ destination = args.first
53
+
54
+ source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^http\:\/\//
55
+ render = open(source) {|input| input.binmode.read }
56
+
57
+ destination ||= if block_given?
58
+ block.arity == 1 ? block.call(render) : block.call
59
+ else
60
+ File.basename(source)
61
+ end
62
+
63
+ create_file destination, render, config
64
+ end
65
+
66
+ # Gets an ERB template at the relative source, executes it and makes a copy
67
+ # at the relative destination. If the destination is not given it's assumed
68
+ # to be equal to the source removing .tt from the filename.
69
+ #
70
+ # ==== Parameters
71
+ # source<String>:: the relative path to the source root.
72
+ # destination<String>:: the relative path to the destination root.
73
+ # config<Hash>:: give :verbose => false to not log the status.
74
+ #
75
+ # ==== Examples
76
+ #
77
+ # template "README", "doc/README"
78
+ #
79
+ # template "doc/README"
80
+ #
81
+ def template(source, *args, &block)
82
+ config = args.last.is_a?(Hash) ? args.pop : {}
83
+ destination = args.first || source
84
+
85
+ source = File.expand_path(find_in_source_paths(source.to_s))
86
+ context = instance_eval('binding')
87
+
88
+ create_file destination, nil, config do
89
+ content = ERB.new(::File.binread(source), nil, '-').result(context)
90
+ content = block.call(content) if block
91
+ content
92
+ end
93
+ end
94
+
95
+ # Changes the mode of the given file or directory.
96
+ #
97
+ # ==== Parameters
98
+ # mode<Integer>:: the file mode
99
+ # path<String>:: the name of the file to change mode
100
+ # config<Hash>:: give :verbose => false to not log the status.
101
+ #
102
+ # ==== Example
103
+ #
104
+ # chmod "script/*", 0755
105
+ #
106
+ def chmod(path, mode, config={})
107
+ return unless behavior == :invoke
108
+ path = File.expand_path(path, destination_root)
109
+ say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
110
+ FileUtils.chmod_R(mode, path) unless options[:pretend]
111
+ end
112
+
113
+ # Prepend text to a file. Since it depends on inject_into_file, it's reversible.
114
+ #
115
+ # ==== Parameters
116
+ # path<String>:: path of the file to be changed
117
+ # data<String>:: the data to prepend to the file, can be also given as a block.
118
+ # config<Hash>:: give :verbose => false to not log the status.
119
+ #
120
+ # ==== Example
121
+ #
122
+ # prepend_file 'config/environments/test.rb', 'config.gem "rspec"'
123
+ #
124
+ # prepend_file 'config/environments/test.rb' do
125
+ # 'config.gem "rspec"'
126
+ # end
127
+ #
128
+ def prepend_file(path, *args, &block)
129
+ config = args.last.is_a?(Hash) ? args.pop : {}
130
+ config.merge!(:after => /\A/)
131
+ inject_into_file(path, *(args << config), &block)
132
+ end
133
+
134
+ # Append text to a file. Since it depends on inject_into_file, it's reversible.
135
+ #
136
+ # ==== Parameters
137
+ # path<String>:: path of the file to be changed
138
+ # data<String>:: the data to append to the file, can be also given as a block.
139
+ # config<Hash>:: give :verbose => false to not log the status.
140
+ #
141
+ # ==== Example
142
+ #
143
+ # append_file 'config/environments/test.rb', 'config.gem "rspec"'
144
+ #
145
+ # append_file 'config/environments/test.rb' do
146
+ # 'config.gem "rspec"'
147
+ # end
148
+ #
149
+ def append_file(path, *args, &block)
150
+ config = args.last.is_a?(Hash) ? args.pop : {}
151
+ config.merge!(:before => /\z/)
152
+ inject_into_file(path, *(args << config), &block)
153
+ end
154
+
155
+ # Injects text right after the class definition. Since it depends on
156
+ # inject_into_file, it's reversible.
157
+ #
158
+ # ==== Parameters
159
+ # path<String>:: path of the file to be changed
160
+ # klass<String|Class>:: the class to be manipulated
161
+ # data<String>:: the data to append to the class, can be also given as a block.
162
+ # config<Hash>:: give :verbose => false to not log the status.
163
+ #
164
+ # ==== Examples
165
+ #
166
+ # inject_into_class "app/controllers/application_controller.rb", " filter_parameter :password\n"
167
+ #
168
+ # inject_into_class "app/controllers/application_controller.rb", ApplicationController do
169
+ # " filter_parameter :password\n"
170
+ # end
171
+ #
172
+ def inject_into_class(path, klass, *args, &block)
173
+ config = args.last.is_a?(Hash) ? args.pop : {}
174
+ config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/)
175
+ inject_into_file(path, *(args << config), &block)
176
+ end
177
+
178
+ # Run a regular expression replacement on a file.
179
+ #
180
+ # ==== Parameters
181
+ # path<String>:: path of the file to be changed
182
+ # flag<Regexp|String>:: the regexp or string to be replaced
183
+ # replacement<String>:: the replacement, can be also given as a block
184
+ # config<Hash>:: give :verbose => false to not log the status.
185
+ #
186
+ # ==== Example
187
+ #
188
+ # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
189
+ #
190
+ # gsub_file 'README', /rake/, :green do |match|
191
+ # match << " no more. Use thor!"
192
+ # end
193
+ #
194
+ def gsub_file(path, flag, *args, &block)
195
+ return unless behavior == :invoke
196
+ config = args.last.is_a?(Hash) ? args.pop : {}
197
+
198
+ path = File.expand_path(path, destination_root)
199
+ say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
200
+
201
+ unless options[:pretend]
202
+ content = File.binread(path)
203
+ content.gsub!(flag, *args, &block)
204
+ File.open(path, 'wb') { |file| file.write(content) }
205
+ end
206
+ end
207
+
208
+ # Removes a file at the given location.
209
+ #
210
+ # ==== Parameters
211
+ # path<String>:: path of the file to be changed
212
+ # config<Hash>:: give :verbose => false to not log the status.
213
+ #
214
+ # ==== Example
215
+ #
216
+ # remove_file 'README'
217
+ # remove_file 'app/controllers/application_controller.rb'
218
+ #
219
+ def remove_file(path, config={})
220
+ return unless behavior == :invoke
221
+ path = File.expand_path(path, destination_root)
222
+
223
+ say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
224
+ ::FileUtils.rm_rf(path) if !options[:pretend] && File.exists?(path)
225
+ end
226
+ alias :remove_dir :remove_file
227
+
228
+ end
229
+ end
@@ -0,0 +1,104 @@
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
+ # inject_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
19
+ #
20
+ # inject_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 inject_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
+
34
+ class InjectIntoFile < EmptyDirectory #:nodoc:
35
+ attr_reader :replacement, :flag, :behavior
36
+
37
+ def initialize(base, destination, data, config)
38
+ super(base, destination, { :verbose => true }.merge(config))
39
+
40
+ @behavior, @flag = if @config.key?(:after)
41
+ [:after, @config.delete(:after)]
42
+ else
43
+ [:before, @config.delete(:before)]
44
+ end
45
+
46
+ @replacement = data.is_a?(Proc) ? data.call : data
47
+ @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
48
+ end
49
+
50
+ def invoke!
51
+ say_status :invoke
52
+
53
+ content = if @behavior == :after
54
+ '\0' + replacement
55
+ else
56
+ replacement + '\0'
57
+ end
58
+
59
+ replace!(/#{flag}/, content, config[:force])
60
+ end
61
+
62
+ def revoke!
63
+ say_status :revoke
64
+
65
+ regexp = if @behavior == :after
66
+ content = '\1\2'
67
+ /(#{flag})(.*)(#{Regexp.escape(replacement)})/m
68
+ else
69
+ content = '\2\3'
70
+ /(#{Regexp.escape(replacement)})(.*)(#{flag})/m
71
+ end
72
+
73
+ replace!(regexp, content, true)
74
+ end
75
+
76
+ protected
77
+
78
+ def say_status(behavior)
79
+ status = if flag == /\A/
80
+ behavior == :invoke ? :prepend : :unprepend
81
+ elsif flag == /\z/
82
+ behavior == :invoke ? :append : :unappend
83
+ else
84
+ behavior == :invoke ? :inject : :deinject
85
+ end
86
+
87
+ super(status, config[:verbose])
88
+ end
89
+
90
+ # Adds the content to the file.
91
+ #
92
+ def replace!(regexp, string, force)
93
+ unless base.options[:pretend]
94
+ content = File.binread(destination)
95
+ if force || !content.include?(replacement)
96
+ content.gsub!(regexp, string)
97
+ File.open(destination, 'wb') { |file| file.write(content) }
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+ end
104
+ end