deface 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,16 @@
1
1
  module Deface
2
2
  class Override
3
- include Deface::TemplateHelper
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 (or adds) attributes to all elements that match the supplied selector, expects :attributes option to be passed.
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
- unless Rails.application.try(:config).respond_to?(:deface) and Rails.application.try(:config).deface.try(:overrides)
65
- @@_early << args
66
- warn "[WARNING] Deface railtie has not initialized yet, override '#{args[:name]}' is being declared too early."
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
- # applies all applicable overrides to given source
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
- # finds all applicable overrides for supplied template
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
- def self.all
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
- # finds all elements upto closing sibling in nokgiri document
218
+
219
+ # check if method is compiled for the current virtual path
356
220
  #
357
- def self.select_range(first, last)
358
- first == last ? [first] : [first, *select_range(first.next, last)]
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
@@ -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
- next unless app.config.deface.enabled
53
+ if app.config.deface.enabled
54
+ # only decorate ActionView if deface is enabled
55
+ require "deface/action_view_extensions"
54
56
 
55
- #setup real env object
56
- app.config.deface = Deface::Environment.new
57
+ # setup real env object
58
+ app.config.deface = Deface::Environment.new
57
59
 
58
- #catchs any overrides that we required manually
59
- app.config.deface.overrides.early_check
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
- if Dir.glob(app.root.join("app/compiled_views", "**/*.erb")).present?
62
- puts "[WARNING] Precompiled views present and Deface is enabled, this can result in overrides being applied twice."
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.find(name, prefix, partial).source
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,5 @@
1
+ #content
2
+ .left
3
+ %p= print_information
4
+ .right(id=@right)
5
+ = render :partial => "sidebar"
@@ -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