deface 0.7.2 → 0.8.0

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.
@@ -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