deface 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/README.markdown +6 -2
- data/deface.gemspec +6 -5
- data/lib/deface.rb +3 -1
- data/lib/deface/action_view_extensions.rb +35 -3
- data/lib/deface/applicator.rb +192 -0
- data/lib/deface/environment.rb +8 -7
- data/lib/deface/haml_converter.rb +65 -0
- data/lib/deface/original_validator.rb +34 -0
- data/lib/deface/override.rb +56 -183
- data/lib/deface/railtie.rb +18 -8
- data/lib/deface/search.rb +22 -0
- data/lib/deface/template_helper.rb +10 -1
- data/spec/assets/shared/_hello.html.haml +1 -0
- data/spec/assets/shared/pirate.html.haml +5 -0
- data/spec/deface/action_view_template_spec.rb +52 -0
- data/spec/deface/applicator_spec.rb +367 -0
- data/spec/deface/environment_spec.rb +8 -3
- data/spec/deface/haml_converter_spec.rb +62 -0
- data/spec/deface/override_spec.rb +126 -5
- data/spec/deface/template_helper_spec.rb +21 -3
- data/spec/spec_helper.rb +17 -0
- metadata +42 -14
- data/spec/deface/template_spec.rb +0 -286
data/lib/deface/override.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
module Deface
|
2
2
|
class Override
|
3
|
-
include
|
3
|
+
include TemplateHelper
|
4
|
+
include OriginalValidator
|
5
|
+
extend Applicator::ClassMethods
|
6
|
+
extend Search::ClassMethods
|
4
7
|
|
5
8
|
cattr_accessor :actions, :_early
|
6
9
|
attr_accessor :args
|
7
10
|
|
8
11
|
@@_early = []
|
9
|
-
@@actions = [:remove, :replace, :replace_contents, :surround, :surround_contents, :insert_after, :insert_before, :insert_top, :insert_bottom, :set_attributes]
|
10
|
-
@@sources = [:text, :partial, :template]
|
12
|
+
@@actions = [:remove, :replace, :replace_contents, :surround, :surround_contents, :insert_after, :insert_before, :insert_top, :insert_bottom, :set_attributes, :add_to_attributes, :remove_from_attributes]
|
13
|
+
@@sources = [:text, :erb, :haml, :partial, :template]
|
11
14
|
|
12
15
|
# Initializes new override, you must supply only one Target, Action & Source
|
13
16
|
# parameter for each override (and any number of Optional parameters).
|
@@ -29,7 +32,9 @@ module Deface
|
|
29
32
|
# * <tt>:insert_before</tt> - Inserts before all elements that match the supplied selector
|
30
33
|
# * <tt>:insert_top</tt> - Inserts inside all elements that match the supplied selector, before all existing child
|
31
34
|
# * <tt>:insert_bottom</tt> - Inserts inside all elements that match the supplied selector, after all existing child
|
32
|
-
# * <tt>:set_attributes</tt> - Sets
|
35
|
+
# * <tt>:set_attributes</tt> - Sets attributes on all elements that match the supplied selector, replacing existing attribute value if present or adding if not. Expects :attributes option to be passed.
|
36
|
+
# * <tt>:add_to_attributes</tt> - Appends value to attributes on all elements that match the supplied selector, adds attribute if not present. Expects :attributes option to be passed.
|
37
|
+
# * <tt>:remove_from_attributes</tt> - Removes value from attributes on all elements that match the supplied selector. Expects :attributes option to be passed.
|
33
38
|
#
|
34
39
|
# ==== Source
|
35
40
|
#
|
@@ -43,27 +48,32 @@ module Deface
|
|
43
48
|
# This needs to be unique within the same :virtual_path
|
44
49
|
# * <tt>:disabled</tt> - When set to true the override will not be applied.
|
45
50
|
# * <tt>:original</tt> - String containing original markup that is being overridden.
|
46
|
-
# If supplied Deface will log when the original markup changes, which helps highlight overrides that need
|
51
|
+
# If supplied Deface will log when the original markup changes, which helps highlight overrides that need
|
47
52
|
# attention when upgrading versions of the source application. Only really warranted for :replace overrides.
|
48
53
|
# NB: All whitespace is stripped before comparsion.
|
49
|
-
# * <tt>:closing_selector</tt> - A second css selector targeting an end element, allowing you to select a range
|
50
|
-
# of elements to apply an action against. The :closing_selector only supports the :replace, :remove and
|
54
|
+
# * <tt>:closing_selector</tt> - A second css selector targeting an end element, allowing you to select a range
|
55
|
+
# of elements to apply an action against. The :closing_selector only supports the :replace, :remove and
|
51
56
|
# :replace_contents actions, and the end element must be a sibling of the first/starting element. Note the CSS
|
52
57
|
# general sibling selector (~) is used to match the first element after the opening selector.
|
53
58
|
# * <tt>:sequence</tt> - Used to order the application of an override for a specific virtual path, helpful when
|
54
59
|
# an override depends on another override being applied first.
|
55
60
|
# Supports:
|
56
61
|
# :sequence => n - where n is a positive or negative integer (lower numbers get applied first, default 100).
|
57
|
-
# :sequence => {:before => "override_name"} - where "override_name" is the name of an override defined for the
|
58
|
-
# same virutal_path, the current override will be appplied before
|
62
|
+
# :sequence => {:before => "override_name"} - where "override_name" is the name of an override defined for the
|
63
|
+
# same virutal_path, the current override will be appplied before
|
59
64
|
# the named override passed.
|
60
65
|
# :sequence => {:after => "override_name") - the current override will be applied after the named override passed.
|
61
66
|
# * <tt>:attributes</tt> - A hash containing all the attributes to be set on the matched elements, eg: :attributes => {:class => "green", :title => "some string"}
|
62
67
|
#
|
63
68
|
def initialize(args, &content)
|
64
|
-
|
65
|
-
|
66
|
-
|
69
|
+
if Rails.application.try(:config).try(:deface).try(:enabled)
|
70
|
+
unless Rails.application.config.deface.try(:overrides)
|
71
|
+
@@_early << args
|
72
|
+
warn "[WARNING] You no longer need to manually require overrides, remove require for '#{args[:name]}'."
|
73
|
+
return
|
74
|
+
end
|
75
|
+
else
|
76
|
+
warn "[WARNING] You no longer need to manually require overrides, remove require for '#{args[:name]}'."
|
67
77
|
return
|
68
78
|
end
|
69
79
|
|
@@ -73,7 +83,7 @@ module Deface
|
|
73
83
|
args[:text] = content.call if block_given?
|
74
84
|
|
75
85
|
virtual_key = args[:virtual_path].to_sym
|
76
|
-
name_key = args[:name].to_s.parameterize
|
86
|
+
name_key = args[:name].to_s.parameterize
|
77
87
|
|
78
88
|
self.class.all[virtual_key] ||= {}
|
79
89
|
|
@@ -101,6 +111,10 @@ module Deface
|
|
101
111
|
end
|
102
112
|
|
103
113
|
self.class.all[virtual_key][name_key] = self
|
114
|
+
|
115
|
+
expire_compiled_template
|
116
|
+
|
117
|
+
self
|
104
118
|
end
|
105
119
|
|
106
120
|
def selector
|
@@ -138,10 +152,10 @@ module Deface
|
|
138
152
|
end
|
139
153
|
|
140
154
|
else
|
141
|
-
return @args[:sequence].to_i
|
155
|
+
return @args[:sequence].to_i
|
142
156
|
end
|
143
157
|
rescue SystemStackError
|
144
|
-
if defined?(Rails)
|
158
|
+
if defined?(Rails)
|
145
159
|
Rails.logger.error "\e[1;32mDeface: [WARNING]\e[0m Circular sequence dependency includes override named: '#{self.name}' on '#{@args[:virtual_path]}'."
|
146
160
|
end
|
147
161
|
|
@@ -159,32 +173,20 @@ module Deface
|
|
159
173
|
load_template_source(@args[:template], false)
|
160
174
|
elsif @args.key? :text
|
161
175
|
@args[:text]
|
176
|
+
elsif @args.key? :erb
|
177
|
+
@args[:erb]
|
178
|
+
elsif @args.key?(:haml) && Rails.application.config.deface.haml_support
|
179
|
+
haml_engine = Deface::HamlConverter.new(@args[:haml])
|
180
|
+
haml_engine.render
|
162
181
|
end
|
182
|
+
|
183
|
+
erb
|
163
184
|
end
|
164
185
|
|
165
186
|
def source_element
|
166
187
|
Deface::Parser.convert(source.clone)
|
167
188
|
end
|
168
189
|
|
169
|
-
def original_source
|
170
|
-
return nil unless @args[:original].present?
|
171
|
-
|
172
|
-
Deface::Parser.convert(@args[:original].clone)
|
173
|
-
end
|
174
|
-
|
175
|
-
# logs if original source has changed
|
176
|
-
def validate_original(match)
|
177
|
-
return true if self.original_source.nil?
|
178
|
-
|
179
|
-
valid = self.original_source.to_s.gsub(/\s/, '') == match.to_s.gsub(/\s/, '')
|
180
|
-
|
181
|
-
if !valid && defined?(Rails.logger) == "constant"
|
182
|
-
Rails.logger.error "\e[1;32mDeface: [WARNING]\e[0m The original source for '#{self.name}' has changed, this override should be reviewed to ensure it's still valid."
|
183
|
-
end
|
184
|
-
|
185
|
-
valid
|
186
|
-
end
|
187
|
-
|
188
190
|
def disabled?
|
189
191
|
@args.key?(:disabled) ? @args[:disabled] : false
|
190
192
|
end
|
@@ -198,164 +200,35 @@ module Deface
|
|
198
200
|
@args[:attributes] || []
|
199
201
|
end
|
200
202
|
|
201
|
-
|
202
|
-
|
203
|
-
def self.apply(source, details, log=true)
|
204
|
-
overrides = find(details)
|
205
|
-
|
206
|
-
if log
|
207
|
-
log = defined?(Rails.logger)
|
208
|
-
end
|
209
|
-
|
210
|
-
if log && overrides.size > 0
|
211
|
-
Rails.logger.info "\e[1;32mDeface:\e[0m #{overrides.size} overrides found for '#{details[:virtual_path]}'"
|
212
|
-
end
|
213
|
-
|
214
|
-
unless overrides.empty?
|
215
|
-
doc = Deface::Parser.convert(source)
|
216
|
-
|
217
|
-
overrides.each do |override|
|
218
|
-
if override.disabled?
|
219
|
-
Rails.logger.info("\e[1;32mDeface:\e[0m '#{override.name}' is disabled") if log
|
220
|
-
next
|
221
|
-
end
|
222
|
-
|
223
|
-
if override.end_selector.blank?
|
224
|
-
# single css selector
|
225
|
-
|
226
|
-
matches = doc.css(override.selector)
|
227
|
-
|
228
|
-
if log
|
229
|
-
Rails.logger.send(matches.size == 0 ? :error : :info, "\e[1;32mDeface:\e[0m '#{override.name}' matched #{matches.size} times with '#{override.selector}'")
|
230
|
-
end
|
231
|
-
|
232
|
-
matches.each do |match|
|
233
|
-
override.validate_original(match)
|
234
|
-
|
235
|
-
case override.action
|
236
|
-
when :remove
|
237
|
-
match.replace ""
|
238
|
-
when :replace
|
239
|
-
match.replace override.source_element
|
240
|
-
when :replace_contents
|
241
|
-
match.children.remove
|
242
|
-
match.add_child(override.source_element)
|
243
|
-
when :surround, :surround_contents
|
244
|
-
|
245
|
-
new_source = override.source_element.clone(1)
|
246
|
-
|
247
|
-
if original = new_source.css("code:contains('render_original')").first
|
248
|
-
if override.action == :surround
|
249
|
-
original.replace match.clone(1)
|
250
|
-
match.replace new_source
|
251
|
-
elsif override.action == :surround_contents
|
252
|
-
original.replace match.children
|
253
|
-
match.children.remove
|
254
|
-
match.add_child new_source
|
255
|
-
end
|
256
|
-
else
|
257
|
-
#maybe we should log that the original wasn't found.
|
258
|
-
end
|
259
|
-
|
260
|
-
when :insert_before
|
261
|
-
match.before override.source_element
|
262
|
-
when :insert_after
|
263
|
-
match.after override.source_element
|
264
|
-
when :insert_top
|
265
|
-
if match.children.size == 0
|
266
|
-
match.children = override.source_element
|
267
|
-
else
|
268
|
-
match.children.before(override.source_element)
|
269
|
-
end
|
270
|
-
when :insert_bottom
|
271
|
-
if match.children.size == 0
|
272
|
-
match.children = override.source_element
|
273
|
-
else
|
274
|
-
match.children.after(override.source_element)
|
275
|
-
end
|
276
|
-
when :set_attributes
|
277
|
-
override.attributes.each do |name, value|
|
278
|
-
match.set_attribute(name.to_s, value.to_s)
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
end
|
283
|
-
else
|
284
|
-
# targeting range of elements as end_selector is present
|
285
|
-
starting = doc.css(override.selector).first
|
286
|
-
|
287
|
-
if starting && starting.parent
|
288
|
-
ending = starting.parent.css(override.end_selector).first
|
289
|
-
else
|
290
|
-
ending = doc.css(override.end_selector).first
|
291
|
-
end
|
292
|
-
|
293
|
-
if starting && ending
|
294
|
-
if log
|
295
|
-
Rails.logger.info("\e[1;32mDeface:\e[0m '#{override.name}' matched starting with '#{override.selector}' and ending with '#{override.end_selector}'")
|
296
|
-
end
|
297
|
-
|
298
|
-
elements = select_range(starting, ending)
|
299
|
-
|
300
|
-
case override.action
|
301
|
-
when :remove
|
302
|
-
elements.map &:remove
|
303
|
-
when :replace
|
304
|
-
starting.before(override.source_element)
|
305
|
-
elements.map &:remove
|
306
|
-
when :replace_contents
|
307
|
-
elements[1..-2].map &:remove
|
308
|
-
starting.after(override.source_element)
|
309
|
-
end
|
310
|
-
else
|
311
|
-
if starting.nil?
|
312
|
-
Rails.logger.info("\e[1;32mDeface:\e[0m '#{override.name}' failed to match with starting selector '#{override.selector}'")
|
313
|
-
else
|
314
|
-
Rails.logger.info("\e[1;32mDeface:\e[0m '#{override.name}' failed to match with end selector '#{override.end_selector}'")
|
315
|
-
end
|
316
|
-
|
317
|
-
end
|
318
|
-
end
|
319
|
-
|
320
|
-
end
|
321
|
-
|
322
|
-
#prevents any caching by rails in development mode
|
323
|
-
details[:updated_at] = Time.now
|
324
|
-
|
325
|
-
source = doc.to_s
|
326
|
-
|
327
|
-
Deface::Parser.undo_erb_markup!(source)
|
328
|
-
end
|
329
|
-
|
330
|
-
source
|
203
|
+
def digest
|
204
|
+
Digest::MD5.new.update(@args.to_s).hexdigest
|
331
205
|
end
|
332
206
|
|
333
|
-
|
334
|
-
|
335
|
-
def self.find(details)
|
336
|
-
return [] if self.all.empty? || details.empty?
|
337
|
-
|
338
|
-
virtual_path = details[:virtual_path]
|
339
|
-
return [] if virtual_path.nil?
|
340
|
-
|
341
|
-
virtual_path = virtual_path.gsub(/^\//, '')
|
342
|
-
|
343
|
-
result = []
|
344
|
-
result << self.all[virtual_path.to_sym].try(:values)
|
345
|
-
|
346
|
-
result.flatten.compact.sort_by &:sequence
|
207
|
+
def self.all
|
208
|
+
Rails.application.config.deface.overrides.all
|
347
209
|
end
|
348
210
|
|
211
|
+
def self.digest(details)
|
212
|
+
overrides = self.find(details)
|
349
213
|
|
350
|
-
|
351
|
-
Rails.application.config.deface.overrides.all
|
214
|
+
Digest::MD5.new.update(overrides.inject('') { |digest, override| digest << override.digest }).hexdigest
|
352
215
|
end
|
353
216
|
|
354
217
|
private
|
355
|
-
|
218
|
+
|
219
|
+
# check if method is compiled for the current virtual path
|
356
220
|
#
|
357
|
-
def
|
358
|
-
|
221
|
+
def expire_compiled_template
|
222
|
+
if compiled_method_name = ActionView::CompiledTemplates.instance_methods.detect { |name| name =~ /#{args[:virtual_path].gsub(/[^a-z_]/, '_')}/ }
|
223
|
+
#if the compiled method does not contain the current deface digest
|
224
|
+
#then remove the old method - this will allow the template to be
|
225
|
+
#recompiled the next time it is rendered (showing the latest changes)
|
226
|
+
|
227
|
+
unless compiled_method_name =~ /\A_#{self.class.digest(:virtual_path => @args[:virtual_path])}_/
|
228
|
+
ActionView::CompiledTemplates.send :remove_method, compiled_method_name
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
359
232
|
end
|
360
233
|
|
361
234
|
end
|
data/lib/deface/railtie.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Deface
|
2
2
|
class Railtie < Rails::Railtie
|
3
3
|
# include rake tasks.
|
4
|
-
#
|
4
|
+
#
|
5
5
|
rake_tasks do
|
6
6
|
%w{utils precompile}.each { |r| load File.join([File.dirname(__FILE__) , "../../tasks/#{r}.rake"]) }
|
7
7
|
end
|
@@ -50,16 +50,26 @@ module Deface
|
|
50
50
|
# overrides if deface is enabled.
|
51
51
|
#
|
52
52
|
initializer "deface.environment", :after => :load_environment_config do |app|
|
53
|
-
|
53
|
+
if app.config.deface.enabled
|
54
|
+
# only decorate ActionView if deface is enabled
|
55
|
+
require "deface/action_view_extensions"
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
+
# setup real env object
|
58
|
+
app.config.deface = Deface::Environment.new
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
+
# checks if haml is loaded and enables support
|
61
|
+
if defined?(Haml)
|
62
|
+
app.config.deface.haml_support = true
|
63
|
+
require 'deface/haml_converter'
|
64
|
+
end
|
60
65
|
|
61
|
-
|
62
|
-
|
66
|
+
# catchs any overrides that we required manually
|
67
|
+
app.config.deface.overrides.early_check
|
68
|
+
else
|
69
|
+
# deface is disabled so clear any overrides
|
70
|
+
# that might have been manually required
|
71
|
+
# won't get loaded but just in case
|
72
|
+
Deface::Override._early.clear
|
63
73
|
end
|
64
74
|
end
|
65
75
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Deface
|
2
|
+
module Search
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
# finds all applicable overrides for supplied template
|
6
|
+
#
|
7
|
+
def find(details)
|
8
|
+
return [] if self.all.empty? || details.empty?
|
9
|
+
|
10
|
+
virtual_path = details[:virtual_path]
|
11
|
+
return [] if virtual_path.nil?
|
12
|
+
|
13
|
+
[/^\//, /\.\w+\z/].each { |regex| virtual_path.gsub!(regex, '') }
|
14
|
+
|
15
|
+
result = []
|
16
|
+
result << self.all[virtual_path.to_sym].try(:values)
|
17
|
+
|
18
|
+
result.flatten.compact.sort_by &:sequence
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -13,9 +13,18 @@ module Deface
|
|
13
13
|
name = parts.join("/")
|
14
14
|
end
|
15
15
|
|
16
|
+
#this needs to be reviewed for production mode, overrides not present
|
16
17
|
Rails.application.config.deface.enabled = apply_overrides
|
17
18
|
@lookup_context ||= ActionView::LookupContext.new(ActionController::Base.view_paths, {:formats => [:html]})
|
18
|
-
@lookup_context.
|
19
|
+
view = @lookup_context.disable_cache do
|
20
|
+
@lookup_context.find(name, prefix, partial)
|
21
|
+
end
|
22
|
+
|
23
|
+
if view.handler.to_s == "Haml::Plugin"
|
24
|
+
Deface::HamlConverter.new(view.source).result
|
25
|
+
else
|
26
|
+
view.source
|
27
|
+
end
|
19
28
|
end
|
20
29
|
|
21
30
|
#gets source erb for an element
|
@@ -0,0 +1 @@
|
|
1
|
+
%div{:class => @some, :id => "message"}= 'Hello, World!'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
describe Template do
|
5
|
+
include_context "mock Rails.application"
|
6
|
+
|
7
|
+
describe "with no overrides defined" do
|
8
|
+
before(:each) do
|
9
|
+
@updated_at = Time.now - 600
|
10
|
+
@template = ActionView::Template.new("<p>test</p>", "/some/path/to/file.erb", ActionView::Template::Handlers::ERB, {:virtual_path=>"posts/index", :format=>:html, :updated_at => @updated_at})
|
11
|
+
#stub for Rails < 3.1
|
12
|
+
unless defined?(@template.updated_at)
|
13
|
+
@template.stub(:updated_at).and_return(@updated_at)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should initialize new template object" do
|
18
|
+
@template.is_a?(ActionView::Template).should == true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return unmodified source" do
|
22
|
+
@template.source.should == "<p>test</p>"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not change updated_at" do
|
26
|
+
@template.updated_at.should == @updated_at
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "with a single remove override defined" do
|
32
|
+
before(:each) do
|
33
|
+
@updated_at = Time.now - 300
|
34
|
+
Deface::Override.new(:virtual_path => "posts/index", :name => "Posts#index", :remove => "p", :text => "<h1>Argh!</h1>")
|
35
|
+
@template = ActionView::Template.new("<p>test</p><%= raw(text) %>", "/some/path/to/file.erb", ActionView::Template::Handlers::ERB, {:virtual_path=>"posts/index", :format=>:html, :updated_at => @updated_at})
|
36
|
+
#stub for Rails < 3.1
|
37
|
+
unless defined?(@template.updated_at)
|
38
|
+
@template.stub(:updated_at).and_return(@updated_at + 500)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return modified source" do
|
43
|
+
@template.source.should == "<%= raw(text) %>"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should change updated_at" do
|
47
|
+
@template.updated_at.should > @updated_at
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|