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.
- data/.travis.yml +3 -1
- data/README.markdown +118 -32
- data/deface.gemspec +2 -2
- data/gemfiles/rails3_1.gemfile +5 -0
- data/gemfiles/rails3_2.gemfile +5 -0
- data/lib/deface.rb +7 -0
- data/lib/deface/action_view_extensions.rb +19 -9
- data/lib/deface/applicator.rb +62 -12
- data/lib/deface/dsl/context.rb +67 -0
- data/lib/deface/dsl/loader.rb +125 -0
- data/lib/deface/environment.rb +22 -11
- data/lib/deface/haml_converter.rb +23 -11
- data/lib/deface/override.rb +78 -65
- data/lib/deface/railtie.rb +4 -0
- data/lib/deface/search.rb +10 -0
- data/spec/assets/dummy_app/overrides/posts/index/app_override.html.erb.deface +2 -0
- data/spec/assets/dummy_engine/overrides/users/index/engine_override.html.erb.deface +2 -0
- data/spec/deface/action_view_template_spec.rb +16 -0
- data/spec/deface/applicator_spec.rb +79 -0
- data/spec/deface/dsl/context_spec.rb +149 -0
- data/spec/deface/dsl/loader_spec.rb +197 -0
- data/spec/deface/environment_spec.rb +39 -9
- data/spec/deface/haml_converter_spec.rb +27 -0
- data/spec/deface/override_spec.rb +140 -16
- data/spec/deface/search_spec.rb +41 -0
- data/spec/spec_helper.rb +13 -0
- data/tasks/utils.rake +1 -1
- metadata +86 -87
@@ -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
|
data/lib/deface/environment.rb
CHANGED
@@ -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
|
28
|
-
|
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
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
66
|
+
if key[-1] == ?:
|
67
|
+
attrs << "#{key} #{value}"
|
68
|
+
else
|
69
|
+
attrs << "#{key} => #{value}"
|
70
|
+
end
|
59
71
|
end
|
60
72
|
|
61
|
-
attrs.
|
73
|
+
attrs.join(', ')
|
62
74
|
end
|
63
75
|
|
64
76
|
end
|
data/lib/deface/override.rb
CHANGED
@@ -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
|
-
#
|
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 =
|
129
|
+
erb = case source_argument
|
130
|
+
when :partial
|
171
131
|
load_template_source(@args[:partial], true)
|
172
|
-
|
132
|
+
when :template
|
173
133
|
load_template_source(@args[:template], false)
|
174
|
-
|
134
|
+
when :text
|
175
135
|
@args[:text]
|
176
|
-
|
136
|
+
when :erb
|
177
137
|
@args[:erb]
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
194
|
+
@args[:closing_selector]
|
197
195
|
end
|
198
196
|
|
199
197
|
def attributes
|
200
198
|
@args[:attributes] || []
|
201
199
|
end
|
202
200
|
|
203
|
-
|
204
|
-
|
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
|
-
|
208
|
-
|
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
|
data/lib/deface/railtie.rb
CHANGED
@@ -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
|