deface 0.8.0 → 0.9.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.
@@ -0,0 +1,125 @@
1
+ require 'polyglot'
2
+
3
+ require 'deface/dsl/context'
4
+
5
+ module Deface
6
+ module DSL
7
+ class Loader
8
+ def self.load(filename, options = nil, &block)
9
+ unless File.basename(filename) =~ /^[^\.]+(.html.(erb|haml)){0,1}.deface$/
10
+ raise "Deface::DSL does not know how to read '#{filename}'. Override files should end with just .deface, .html.erb.deface, or .html.haml.deface"
11
+ end
12
+
13
+ unless file_in_dir_below_overrides?(filename)
14
+ raise "Deface::DSL overrides must be in a sub-directory that matches the views virtual path. Move '#{filename}' into a sub-directory."
15
+ end
16
+
17
+ File.open(filename) do |file|
18
+ context_name = File.basename(filename).gsub('.deface', '')
19
+
20
+ file_contents = file.read
21
+
22
+ if context_name.end_with?('.html.erb')
23
+ dsl_commands, the_rest = extract_dsl_commands_from_erb(file_contents)
24
+
25
+ context_name = context_name.gsub('.html.erb', '')
26
+ context = Context.new(context_name)
27
+ context.virtual_path(determine_virtual_path(filename))
28
+ context.instance_eval(dsl_commands)
29
+ context.erb(the_rest)
30
+ context.create_override
31
+ elsif context_name.end_with?('.html.haml')
32
+ dsl_commands, the_rest = extract_dsl_commands_from_haml(file_contents)
33
+
34
+ context_name = context_name.gsub('.html.haml', '')
35
+ context = Context.new(context_name)
36
+ context.virtual_path(determine_virtual_path(filename))
37
+ context.instance_eval(dsl_commands)
38
+ context.haml(the_rest)
39
+ context.create_override
40
+ else
41
+ context = Context.new(context_name)
42
+ context.virtual_path(determine_virtual_path(filename))
43
+ context.instance_eval(file_contents)
44
+ context.create_override
45
+ end
46
+ end
47
+ end
48
+
49
+ def self.register
50
+ Polyglot.register('deface', Deface::DSL::Loader)
51
+ end
52
+
53
+ def self.extract_dsl_commands_from_erb(html_file_contents)
54
+ dsl_commands = ''
55
+
56
+ while starts_with_html_comment?(html_file_contents)
57
+ first_open_comment_index = html_file_contents.lstrip.index('<!--')
58
+ first_close_comment_index = html_file_contents.index('-->')
59
+
60
+ unless first_close_comment_index.nil?
61
+ comment = html_file_contents[first_open_comment_index..first_close_comment_index+2]
62
+ end
63
+
64
+ dsl_commands << comment.gsub('<!--', '').gsub('-->', '').strip + "\n"
65
+
66
+ html_file_contents = html_file_contents.gsub(comment, '')
67
+ end
68
+
69
+ [dsl_commands, html_file_contents]
70
+ end
71
+
72
+ def self.extract_dsl_commands_from_haml(file_contents)
73
+ dsl_commands = ''
74
+
75
+ while starts_with_haml_comment?(file_contents)
76
+ first_open_comment_index = file_contents.lstrip.index('/')
77
+ first_close_comment_index = file_contents.index("\n")
78
+
79
+ unless first_close_comment_index.nil?
80
+ comment = file_contents[first_open_comment_index..first_close_comment_index]
81
+ end
82
+
83
+ dsl_commands << comment.gsub('/', '').strip + "\n"
84
+
85
+ file_contents = file_contents.gsub(comment, '')
86
+
87
+ while file_contents.start_with?(' ')
88
+ first_newline_index = file_contents.index("\n")
89
+ comment = file_contents[0..first_newline_index]
90
+ dsl_commands << comment.gsub('/', '').strip + "\n"
91
+ file_contents = file_contents.gsub(comment, '')
92
+ end
93
+ end
94
+
95
+ [dsl_commands, file_contents]
96
+ end
97
+
98
+ private
99
+
100
+ def self.starts_with_html_comment?(line)
101
+ line.lstrip.index('<!--') == 0
102
+ end
103
+
104
+ def self.starts_with_haml_comment?(line)
105
+ line.lstrip.index('/') == 0
106
+ end
107
+
108
+ def self.file_in_dir_below_overrides?(filename)
109
+ File.fnmatch?("**/overrides/**/#{File.basename(filename)}", filename)
110
+ end
111
+
112
+ def self.determine_virtual_path(filename)
113
+ result = ''
114
+ pathname = Pathname.new(filename)
115
+ pathname.ascend do |parent|
116
+ if parent.basename.to_s == 'overrides'
117
+ result = pathname.sub(parent.to_s + '/', '').dirname.to_s
118
+ break
119
+ end
120
+ end
121
+ result
122
+ end
123
+ end
124
+ end
125
+ end
@@ -23,18 +23,14 @@ module Deface
23
23
  def load_all(app)
24
24
  #clear overrides before reloading them
25
25
  app.config.deface.overrides.all.clear
26
+ Deface::DSL::Loader.register
26
27
 
27
- # check application for specified overrides paths
28
- override_paths = app.paths["app/overrides"]
29
- enumerate_and_load(override_paths, app.root)
30
-
31
- # check all railties / engines / extensions for overrides
32
- app.railties.all.each do |railtie|
28
+ # check all railties / engines / extensions / application for overrides
29
+ app.railties.all.dup.push(app).each do |railtie|
33
30
  next unless railtie.respond_to? :root
34
-
35
- override_paths = railtie.respond_to?(:paths) ? railtie.paths["app/overrides"] : nil
36
- enumerate_and_load(override_paths, railtie.root)
31
+ load_overrides(railtie)
37
32
  end
33
+
38
34
  end
39
35
 
40
36
  def early_check
@@ -46,15 +42,30 @@ module Deface
46
42
  end
47
43
 
48
44
  private
45
+
46
+ def load_overrides(railtie)
47
+ Override.current_railtie = railtie
48
+ paths = railtie.respond_to?(:paths) ? railtie.paths["app/overrides"] : nil
49
+ enumerate_and_load(paths, railtie.root)
50
+ end
51
+
49
52
  def enumerate_and_load(paths, root)
50
53
  paths ||= ["app/overrides"]
51
54
 
52
55
  paths.each do |path|
53
- Dir.glob(root.join path, "*.rb") do |c|
56
+ if Rails.version[0..2] == "3.2"
57
+ # add path to watchable_dir so Rails will call to_prepare on file changes
58
+ # allowing overrides to be updated / reloaded in development mode.
59
+ Rails.application.config.watchable_dirs[root.join(path).to_s] = [:rb, :deface]
60
+ end
61
+
62
+ Dir.glob(root.join path, "**/*.rb") do |c|
54
63
  Rails.application.config.cache_classes ? require(c) : load(c)
55
64
  end
65
+ Dir.glob(root.join path, "**/*.deface") do |c|
66
+ Rails.application.config.cache_classes ? require(c) : Deface::DSL::Loader.load(c)
67
+ end
56
68
  end
57
-
58
69
  end
59
70
  end
60
71
  end
@@ -40,25 +40,37 @@ module Deface
40
40
  # coverts { attributes into deface compatibily attributes
41
41
  def deface_attributes(attrs)
42
42
  return if attrs.nil?
43
+
43
44
  attrs.gsub! /\{|\}/, ''
44
- attrs = attrs.split(',')
45
45
 
46
- if attrs.join.include? "=>"
47
- attrs.map!{|a| a.split("=>").map(&:strip) }
48
- else
49
- attrs.map!{|a| a.split(": ").map(&:strip) }
46
+ attributes = {}
47
+ scanner = StringScanner.new(attrs)
48
+ scanner.scan(/\s+/)
49
+
50
+ until scanner.eos?
51
+ return unless key = scanner.scan(/:(\w*)|(["'])((?![\\#]|\2).|\\.)*\2|(\w*):/) #matches :key, 'key', "key" or key:
52
+ return unless scanner.scan(/\s*(=>)?\s*/) #match => or just white space
53
+ return unless value = scanner.scan(/(["'])((?![\\#]|\1).|\\.)*\1|[^\s,]*/) #match 'value', "value", value, @value, some-value
54
+ return unless scanner.scan(/\s*(?:,|$)\s*/)
55
+ attributes[key.to_s] = value
50
56
  end
51
57
 
52
- attrs.map! do |a|
53
- if a[1][0] != ?' && a[1][0] != ?"
54
- a[0] = %Q{"data-erb-#{a[0].gsub(/:|'|"/,'')}"}
55
- a[1] = %Q{"<%= #{a[1]} %>"}
58
+ attrs = []
59
+ attributes.each do |key, value|
60
+ #only need to convert non-literal values
61
+ if value[0] != ?' && value[0] != ?" && value[0] != ?:
62
+ key = %Q{"data-erb-#{key.gsub(/:|'|"/,'')}"}
63
+ value = %Q{"<%= #{value} %>"}
56
64
  end
57
65
 
58
- a
66
+ if key[-1] == ?:
67
+ attrs << "#{key} #{value}"
68
+ else
69
+ attrs << "#{key} => #{value}"
70
+ end
59
71
  end
60
72
 
61
- attrs.map{ |a| a.join " => " }.join(', ')
73
+ attrs.join(', ')
62
74
  end
63
75
 
64
76
  end
@@ -5,66 +5,17 @@ module Deface
5
5
  extend Applicator::ClassMethods
6
6
  extend Search::ClassMethods
7
7
 
8
- cattr_accessor :actions, :_early
9
- attr_accessor :args
8
+ cattr_accessor :actions, :sources, :_early, :current_railtie
9
+ attr_accessor :args, :parsed_document
10
10
 
11
11
  @@_early = []
12
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]
13
+ @@sources = [:text, :erb, :haml, :partial, :template, :cut, :copy]
14
14
 
15
15
  # Initializes new override, you must supply only one Target, Action & Source
16
16
  # parameter for each override (and any number of Optional parameters).
17
17
  #
18
- # ==== Target
19
- #
20
- # * <tt>:virtual_path</tt> - The path of the template / partial where
21
- # the override should take effect eg: "shared/_person", "admin/posts/new"
22
- # this will apply to all controller actions that use the specified template
23
- #
24
- # ==== Action
25
- #
26
- # * <tt>:remove</tt> - Removes all elements that match the supplied selector
27
- # * <tt>:replace</tt> - Replaces all elements that match the supplied selector
28
- # * <tt>:replace_contents</tt> - Replaces the contents of all elements that match the supplied selector
29
- # * <tt>:surround</tt> - Surrounds all elements that match the supplied selector, expects replacement markup to contain <%= render_original %> placeholder
30
- # * <tt>:surround_contents</tt> - Surrounds the contents of all elements that match the supplied selector, expects replacement markup to contain <%= render_original %> placeholder
31
- # * <tt>:insert_after</tt> - Inserts after all elements that match the supplied selector
32
- # * <tt>:insert_before</tt> - Inserts before all elements that match the supplied selector
33
- # * <tt>:insert_top</tt> - Inserts inside all elements that match the supplied selector, before all existing child
34
- # * <tt>:insert_bottom</tt> - Inserts inside all elements that match the supplied selector, after all existing child
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.
38
- #
39
- # ==== Source
40
- #
41
- # * <tt>:text</tt> - String containing markup
42
- # * <tt>:partial</tt> - Relative path to partial
43
- # * <tt>:template</tt> - Relative path to template
44
- #
45
- # ==== Optional
46
- #
47
- # * <tt>:name</tt> - Unique name for override so it can be identified and modified later.
48
- # This needs to be unique within the same :virtual_path
49
- # * <tt>:disabled</tt> - When set to true the override will not be applied.
50
- # * <tt>:original</tt> - String containing original markup that is being overridden.
51
- # If supplied Deface will log when the original markup changes, which helps highlight overrides that need
52
- # attention when upgrading versions of the source application. Only really warranted for :replace overrides.
53
- # NB: All whitespace is stripped before comparsion.
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
56
- # :replace_contents actions, and the end element must be a sibling of the first/starting element. Note the CSS
57
- # general sibling selector (~) is used to match the first element after the opening selector.
58
- # * <tt>:sequence</tt> - Used to order the application of an override for a specific virtual path, helpful when
59
- # an override depends on another override being applied first.
60
- # Supports:
61
- # :sequence => n - where n is a positive or negative integer (lower numbers get applied first, default 100).
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
64
- # the named override passed.
65
- # :sequence => {:after => "override_name") - the current override will be applied after the named override passed.
66
- # * <tt>:attributes</tt> - A hash containing all the attributes to be set on the matched elements, eg: :attributes => {:class => "green", :title => "some string"}
67
- #
18
+ # See READme for more!
68
19
  def initialize(args, &content)
69
20
  if Rails.application.try(:config).try(:deface).try(:enabled)
70
21
  unless Rails.application.config.deface.try(:overrides)
@@ -110,6 +61,10 @@ module Deface
110
61
  raise(ArgumentError, ":action is invalid") if self.action.nil?
111
62
  end
112
63
 
64
+ #set loaded time (if not already present) for hash invalidation
65
+ @args[:updated_at] ||= Time.zone.now.to_f
66
+ @args[:railtie] = self.class.current_railtie
67
+
113
68
  self.class.all[virtual_key][name_key] = self
114
69
 
115
70
  expire_compiled_template
@@ -125,6 +80,10 @@ module Deface
125
80
  @args[:name]
126
81
  end
127
82
 
83
+ def railtie
84
+ @args[:railtie]
85
+ end
86
+
128
87
  def sequence
129
88
  return 100 unless @args.key?(:sequence)
130
89
  if @args[:sequence].is_a? Hash
@@ -167,22 +126,61 @@ module Deface
167
126
  end
168
127
 
169
128
  def source
170
- erb = if @args.key? :partial
129
+ erb = case source_argument
130
+ when :partial
171
131
  load_template_source(@args[:partial], true)
172
- elsif @args.key? :template
132
+ when :template
173
133
  load_template_source(@args[:template], false)
174
- elsif @args.key? :text
134
+ when :text
175
135
  @args[:text]
176
- elsif @args.key? :erb
136
+ when :erb
177
137
  @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
138
+ when :cut
139
+ cut = @args[:cut]
140
+
141
+ if cut.is_a? Hash
142
+ starting, ending = self.class.select_endpoints(self.parsed_document, cut[:start], cut[:end])
143
+
144
+ range = self.class.select_range(starting, ending)
145
+ range.map &:remove
146
+
147
+ Deface::Parser.undo_erb_markup! range.map(&:to_s).join
148
+
149
+ else
150
+ Deface::Parser.undo_erb_markup! self.parsed_document.css(cut).first.remove.to_s.clone
151
+ end
152
+
153
+ when :copy
154
+ copy = @args[:copy]
155
+
156
+ if copy.is_a? Hash
157
+ starting, ending = self.class.select_endpoints(self.parsed_document, copy[:start], copy[:end])
158
+
159
+ range = self.class.select_range(starting, ending)
160
+
161
+ Deface::Parser.undo_erb_markup! range.map(&:to_s).join
162
+ else
163
+ Deface::Parser.undo_erb_markup! parsed_document.css(copy).first.to_s.clone
164
+ end
165
+
166
+ when :haml
167
+ if Rails.application.config.deface.haml_support
168
+ haml_engine = Deface::HamlConverter.new(@args[:haml])
169
+ haml_engine.render
170
+ else
171
+ raise Deface::NotSupportedError, "`#{self.name}` supplies :haml source, but haml_support is not detected."
172
+ end
181
173
  end
182
174
 
183
175
  erb
184
176
  end
185
177
 
178
+ # Returns a :symbol for the source argument present
179
+ #
180
+ def source_argument
181
+ @@sources.detect { |source| @args.key? source }
182
+ end
183
+
186
184
  def source_element
187
185
  Deface::Parser.convert(source.clone)
188
186
  end
@@ -193,27 +191,42 @@ module Deface
193
191
 
194
192
  def end_selector
195
193
  return nil if @args[:closing_selector].blank?
196
- "#{self.selector} ~ #{@args[:closing_selector]}"
194
+ @args[:closing_selector]
197
195
  end
198
196
 
199
197
  def attributes
200
198
  @args[:attributes] || []
201
199
  end
202
200
 
203
- def digest
204
- Digest::MD5.new.update(@args.to_s).hexdigest
201
+ # Alters digest of override to force view method
202
+ # recompilation (when source template/partial changes)
203
+ #
204
+ def touch
205
+ @args[:updated_at] = Time.zone.now.to_f
205
206
  end
206
207
 
207
- def self.all
208
- Rails.application.config.deface.overrides.all
208
+ # Creates MD5 hash of args sorted keys and values
209
+ # used to determine if an override has changed
210
+ #
211
+ def digest
212
+ Digest::MD5.new.update(@args.keys.map(&:to_s).sort.concat(@args.values.map(&:to_s).sort).join).hexdigest
209
213
  end
210
214
 
215
+ # Creates MD5 of all overrides that apply to a particular
216
+ # virtual_path, used in CompiledTemplates method name
217
+ # so we can support re-compiling of compiled method
218
+ # when overrides change. Only of use in production mode.
219
+ #
211
220
  def self.digest(details)
212
221
  overrides = self.find(details)
213
222
 
214
223
  Digest::MD5.new.update(overrides.inject('') { |digest, override| digest << override.digest }).hexdigest
215
224
  end
216
225
 
226
+ def self.all
227
+ Rails.application.config.deface.overrides.all
228
+ end
229
+
217
230
  private
218
231
 
219
232
  # check if method is compiled for the current virtual path
@@ -65,6 +65,10 @@ module Deface
65
65
 
66
66
  # catchs any overrides that we required manually
67
67
  app.config.deface.overrides.early_check
68
+
69
+ if Dir.glob(app.root.join("app/compiled_views", "**/*.erb")).present?
70
+ puts "[WARNING] Precompiled views present and Deface is enabled, this can result in overrides being applied twice."
71
+ end
68
72
  else
69
73
  # deface is disabled so clear any overrides
70
74
  # that might have been manually required