Class: Deface::Override

Inherits:
Object
  • Object
show all
Includes:
TemplateHelper
Defined in:
lib/deface/override.rb

Constant Summary collapse

@@_early =
[]
@@actions =
[:remove, :replace, :replace_contents, :surround, :surround_contents, :insert_after, :insert_before, :insert_top, :insert_bottom, :set_attributes]
@@sources =
[:text, :partial, :template]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from TemplateHelper

#element_source, #load_template_source

Constructor Details

#initialize(args) ⇒ Override

Initializes new override, you must supply only one Target, Action & Source parameter for each override (and any number of Optional parameters).

Target

  • :virtual_path - The path of the template / partial where the override should take effect eg: “shared/_person”, “admin/posts/new” this will apply to all controller actions that use the specified template

Action

  • :remove - Removes all elements that match the supplied selector

  • :replace - Replaces all elements that match the supplied selector

  • :replace_contents - Replaces the contents of all elements that match the supplied selector

  • :surround - Surrounds all elements that match the supplied selector, expects replacement markup to contain <%= render_original %> placeholder

  • :surround_contents - Surrounds the contents of all elements that match the supplied selector, expects replacement markup to contain <%= render_original %> placeholder

  • :insert_after - Inserts after all elements that match the supplied selector

  • :insert_before - Inserts before all elements that match the supplied selector

  • :insert_top - Inserts inside all elements that match the supplied selector, before all existing child

  • :insert_bottom - Inserts inside all elements that match the supplied selector, after all existing child

  • :set_attributes - Sets (or adds) attributes to all elements that match the supplied selector, expects :attributes option to be passed.

Source

  • :text - String containing markup

  • :partial - Relative path to partial

  • :template - Relative path to template

Optional

  • :name - Unique name for override so it can be identified and modified later. This needs to be unique within the same :virtual_path

  • :disabled - When set to true the override will not be applied.

  • :original - String containing original markup that is being overridden. If supplied Deface will log when the original markup changes, which helps highlight overrides that need attention when upgrading versions of the source application. Only really warranted for :replace overrides. NB: All whitespace is stripped before comparsion.

  • :closing_selector - A second css selector targeting an end element, allowing you to select a range of elements to apply an action against. The :closing_selector only supports the :replace, :remove and :replace_contents actions, and the end element must be a sibling of the first/starting element. Note the CSS general sibling selector (~) is used to match the first element after the opening selector.

  • :sequence - Used to order the application of an override for a specific virtual path, helpful when an override depends on another override being applied first. Supports: :sequence => n - where n is a positive or negative integer (lower numbers get applied first, default 100). :sequence => => “override_name” - where “override_name” is the name of an override defined for the

    same virutal_path, the current override will be appplied before 
    the named override passed.
    

    :sequence => => “override_name”) - the current override will be applied after the named override passed.

  • :attributes - A hash containing all the attributes to be set on the matched elements, eg: :attributes => {:class => “green”, :title => “some string”

Raises:

  • (ArgumentError)


63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/deface/override.rb', line 63

def initialize(args)
  unless Rails.application.try(:config).respond_to?(:deface) and Rails.application.try(:config).deface.try(:overrides)
    @@_early << args
    warn "[WARNING] Deface railtie has not initialized yet, override '#{args[:name]}' is being declared too early."
    return
  end

  raise(ArgumentError, ":name must be defined") unless args.key? :name
  raise(ArgumentError, ":virtual_path must be defined") if args[:virtual_path].blank?

  virtual_key = args[:virtual_path].to_sym
  name_key = args[:name].to_s.parameterize 

  self.class.all[virtual_key] ||= {}

  if self.class.all[virtual_key].has_key? name_key
    #updating exisiting override

    @args = self.class.all[virtual_key][name_key].args

    #check if the action is being redefined, and reject old action
    if (@@actions & args.keys).present?
      @args.reject!{|key, value| (@@actions & @args.keys).include? key }
    end

    #check if the source is being redefined, and reject old action
    if (@@sources & args.keys).present?
      @args.reject!{|key, value| (@@sources & @args.keys).include? key }
    end

    @args.merge!(args)
  else
    #initializing new override
    @args = args

    raise(ArgumentError, ":action is invalid") if self.action.nil?
  end

  self.class.all[virtual_key][name_key] = self
end

Instance Attribute Details

#argsObject

Returns the value of attribute args.



6
7
8
# File 'lib/deface/override.rb', line 6

def args
  @args
end

Class Method Details

.allObject



348
349
350
# File 'lib/deface/override.rb', line 348

def self.all
  Rails.application.config.deface.overrides.all
end

.apply(source, details, log = true) ⇒ Object

applies all applicable overrides to given source



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/deface/override.rb', line 201

def self.apply(source, details, log=true)
  overrides = find(details)

  if log
    log = defined?(Rails.logger) 
  end

  if log && overrides.size > 0
    Rails.logger.info "\e[1;32mDeface:\e[0m #{overrides.size} overrides found for '#{details[:virtual_path]}'"
  end

  unless overrides.empty?
    doc = Deface::Parser.convert(source)

    overrides.each do |override|
      if override.disabled?
        Rails.logger.info("\e[1;32mDeface:\e[0m '#{override.name}' is disabled") if log
        next
      end

      if override.end_selector.blank?
        # single css selector

        matches = doc.css(override.selector)

        if log
          Rails.logger.send(matches.size == 0 ? :error : :info, "\e[1;32mDeface:\e[0m '#{override.name}' matched #{matches.size} times with '#{override.selector}'")
        end

        matches.each do |match|
          override.validate_original(match)

          case override.action
            when :remove
              match.replace ""
            when :replace
              match.replace override.source_element
            when :replace_contents
              match.children.remove 
              match.add_child(override.source_element)
            when :surround, :surround_contents

              new_source = override.source_element.clone(1)

              if original = new_source.css("code:contains('render_original')").first
                if override.action == :surround
                  original.replace match.clone(1)
                  match.replace new_source
                elsif override.action == :surround_contents
                  original.replace match.children
                  match.children.remove
                  match.add_child new_source
                end
              else
                #maybe we should log that the original wasn't found.
              end

            when :insert_before
              match.before override.source_element
            when :insert_after
              match.after override.source_element
            when :insert_top
              if match.children.size == 0
                match.children = override.source_element
              else
                match.children.before(override.source_element)
              end
            when :insert_bottom
              if match.children.size == 0
                match.children = override.source_element
              else
                match.children.after(override.source_element)
              end
            when :set_attributes
              override.attributes.each do |name, value|
                match.set_attribute(name.to_s, value.to_s)
              end
          end

        end
      else
        # targeting range of elements as end_selector is present
        starting    = doc.css(override.selector).first

        if starting && starting.parent
          ending = starting.parent.css(override.end_selector).first
        else
          ending = doc.css(override.end_selector).first
        end

        if starting && ending
          if log
            Rails.logger.info("\e[1;32mDeface:\e[0m '#{override.name}' matched starting with '#{override.selector}' and ending with '#{override.end_selector}'")
          end

          elements = select_range(starting, ending)

          case override.action
            when :remove
              elements.map &:remove
            when :replace
              starting.before(override.source_element)
              elements.map &:remove
            when :replace_contents
              elements[1..-2].map &:remove
              starting.after(override.source_element)
          end
        else
          if starting.nil?
            Rails.logger.info("\e[1;32mDeface:\e[0m '#{override.name}' failed to match with starting selector '#{override.selector}'")
          else
            Rails.logger.info("\e[1;32mDeface:\e[0m '#{override.name}' failed to match with end selector '#{override.end_selector}'")
          end

        end
      end

    end

    #prevents any caching by rails in development mode
    details[:updated_at] = Time.now

    source = doc.to_s

    Deface::Parser.undo_erb_markup!(source)
  end

  source
end

.find(details) ⇒ Object

finds all applicable overrides for supplied template



333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/deface/override.rb', line 333

def self.find(details)
  return [] if self.all.empty? || details.empty?

  virtual_path = details[:virtual_path]
  return [] if virtual_path.nil?

  virtual_path = virtual_path[1..-1] if virtual_path.first == '/'

  result = []
  result << self.all[virtual_path.to_sym].try(:values)

  result.flatten.compact.sort_by &:sequence
end

Instance Method Details

#actionObject



149
150
151
# File 'lib/deface/override.rb', line 149

def action
  (@@actions & @args.keys).first
end

#attributesObject



195
196
197
# File 'lib/deface/override.rb', line 195

def attributes
  @args[:attributes] || []
end

#disabled?Boolean

Returns:

  • (Boolean)


186
187
188
# File 'lib/deface/override.rb', line 186

def disabled?
  @args.key?(:disabled) ? @args[:disabled] : false
end

#end_selectorObject



190
191
192
193
# File 'lib/deface/override.rb', line 190

def end_selector
  return nil if @args[:closing_selector].blank?
  "#{self.selector} ~ #{@args[:closing_selector]}"
end

#nameObject



108
109
110
# File 'lib/deface/override.rb', line 108

def name
  @args[:name]
end

#original_sourceObject



167
168
169
170
171
# File 'lib/deface/override.rb', line 167

def original_source
  return nil unless @args[:original].present?

  Deface::Parser.convert(@args[:original].clone)
end

#selectorObject



104
105
106
# File 'lib/deface/override.rb', line 104

def selector
  @args[self.action]
end

#sequenceObject



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/deface/override.rb', line 112

def sequence
  return 100 unless @args.key?(:sequence)
  if @args[:sequence].is_a? Hash
    key = @args[:virtual_path].to_sym

    if @args[:sequence].key? :before
      ref_name = @args[:sequence][:before]

      if self.class.all[key].key? ref_name.to_s
        return self.class.all[key][ref_name.to_s].sequence - 1
      else
        return 100
      end
    elsif @args[:sequence].key? :after
      ref_name = @args[:sequence][:after]

      if self.class.all[key].key? ref_name.to_s
        return self.class.all[key][ref_name.to_s].sequence + 1
      else
        return 100
      end
    else
      #should never happen.. tut tut!
      return 100
    end

  else
    return @args[:sequence].to_i 
  end
rescue SystemStackError
  if defined?(Rails) 
    Rails.logger.error "\e[1;32mDeface: [WARNING]\e[0m Circular sequence dependency includes override named: '#{self.name}' on '#{@args[:virtual_path]}'."
  end

  return 100
end

#sourceObject



153
154
155
156
157
158
159
160
161
# File 'lib/deface/override.rb', line 153

def source
  erb = if @args.key? :partial
    load_template_source(@args[:partial], true)
  elsif @args.key? :template
    load_template_source(@args[:template], false)
  elsif @args.key? :text
    @args[:text]
  end
end

#source_elementObject



163
164
165
# File 'lib/deface/override.rb', line 163

def source_element
  Deface::Parser.convert(source.clone)
end

#validate_original(match) ⇒ Object

logs if original source has changed



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/deface/override.rb', line 174

def validate_original(match)
  return true if self.original_source.nil?

  valid = self.original_source.to_s.gsub(/\s/, '') == match.to_s.gsub(/\s/, '')

  if !valid && defined?(Rails.logger) == "constant"
    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."
  end

  valid
end