deface 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/.travis.yml
CHANGED
data/README.markdown
CHANGED
@@ -7,40 +7,40 @@
|
|
7
7
|
Deface
|
8
8
|
======
|
9
9
|
|
10
|
-
Deface is a library that allows you to customize HTML ERB views in a Rails application without editing the underlying view.
|
10
|
+
Deface is a library that allows you to customize HTML (ERB and HAML) views in a Rails application without editing the underlying view.
|
11
11
|
|
12
12
|
It allows you to easily target html & erb elements as the hooks for customization using CSS selectors as supported by Nokogiri.
|
13
13
|
|
14
|
-
Demo & Testing
|
15
|
-
--------------
|
16
|
-
You can play with Deface and see its parsing in action at [deface.heroku.com](http://deface.heroku.com)
|
17
14
|
|
15
|
+
Usage
|
16
|
+
-----
|
18
17
|
|
19
|
-
|
20
|
-
------------------------
|
18
|
+
There are two ways of using Deface:
|
21
19
|
|
22
|
-
|
20
|
+
- Using `Deface::Override` - this is the traditional method whereby you define instances of the Deface::Override class directly.
|
21
|
+
- Using the Deface DSL (.deface files) - the DSL provides a terser syntax, and better organization of the individual override files.
|
23
22
|
|
24
|
-
|
23
|
+
Both methods are interoperable, so you can use a mix, and redefine overrides defined one-way using the other.
|
25
24
|
|
26
|
-
It's important to disable Deface once precompiling is used to prevent overrides getting applied twice. To disable add the following line to your application's `production.rb` file:
|
27
25
|
|
28
|
-
config.deface.enabled = false
|
29
26
|
|
30
|
-
|
27
|
+
Using Deface::Override
|
28
|
+
----------------------
|
29
|
+
|
30
|
+
A new instance of the Deface::Override class is initialized for each customization you wish to define. When initializing a new override you must supply only one Target, Action & Source parameter and any number of Optional parameters.
|
31
31
|
|
32
|
+
**Note:** the source parameter is not required when the ````:remove, :set_attributes, :add_to_attributes, :remove_from_attributes```` actions are specified.
|
32
33
|
|
33
|
-
Deface
|
34
|
-
================
|
34
|
+
You should save your overrides in the ````app/overrides````, normally one override per file using the same file name as specified in the :name argument. Deface will automatically require these from your application, and any engines installed.
|
35
35
|
|
36
|
-
|
36
|
+
**Note:** You should NOT manually require override files, as this can cause problems for precompiling.
|
37
|
+
|
38
|
+
### Target
|
37
39
|
|
38
|
-
Target
|
39
|
-
------
|
40
40
|
* <tt>:virtual_path</tt> - The template / partial / layout where the override should take effect eg: *"shared/_person"*, *"admin/posts/new"* this will apply to all controller actions that use the specified template.
|
41
41
|
|
42
|
-
Action
|
43
|
-
|
42
|
+
### Action
|
43
|
+
|
44
44
|
* <tt>:remove</tt> - Removes all elements that match the supplied selector
|
45
45
|
|
46
46
|
* <tt>:replace</tt> - Replaces all elements that match the supplied selector
|
@@ -59,23 +59,30 @@ Action
|
|
59
59
|
|
60
60
|
* <tt>:insert_bottom</tt> - Inserts inside all elements that match the supplied selector, as the last child.
|
61
61
|
|
62
|
-
* <tt>:
|
62
|
+
* <tt>:set_</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.
|
63
63
|
|
64
64
|
* <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.
|
65
65
|
|
66
66
|
* <tt>:remove_from_attributes</tt> - Removes value from attributes on all elements that match the supplied selector. Expects :attributes option to be passed.
|
67
67
|
|
68
|
-
Source
|
69
|
-
|
68
|
+
### Source
|
69
|
+
|
70
70
|
* <tt>:text</tt> - String containing markup
|
71
71
|
|
72
72
|
* <tt>:partial</tt> - Relative path to a partial
|
73
73
|
|
74
74
|
* <tt>:template</tt> - Relative path to a template
|
75
75
|
|
76
|
+
* <tt>:cut</tt> - Cuts (i.e. copies and removes) an element or a range of elements from the current template as the source, using css selector(s). Supports two versions:
|
77
|
+
* <tt>selector</tt> - A single string css selector (first match is used).
|
78
|
+
* <tt>{:start => 'selector_a', :end => 'selector_b'}</tt> - select a range of elements using :start and :end css selectors. The end element must be a sibling of the first/starting element.
|
79
|
+
|
80
|
+
* <tt>:copy</tt> - Copies an element or a range of elements from the current template as the source, using css selector(s). Supports two versions:
|
81
|
+
* <tt>selector</tt> - A single string css selector (first match is used).
|
82
|
+
* <tt>{:start => 'selector_a', :end => 'selector_b'}</tt> - select a range of elements using :start and :end css selectors. The end element must be a sibling of the first/starting element.
|
83
|
+
|
84
|
+
### Optional
|
76
85
|
|
77
|
-
Optional
|
78
|
-
--------
|
79
86
|
* <tt>:name</tt> - Unique name for override so it can be identified and modified later. This needs to be unique within the same `:virtual_path`
|
80
87
|
|
81
88
|
* <tt>:disabled</tt> - When set to true the override will not be applied.
|
@@ -93,8 +100,7 @@ Optional
|
|
93
100
|
|
94
101
|
* <tt>:attributes</tt> - A hash containing all the attributes to be set on the matched elements, eg: :attributes => {:class => "green", :title => "some string"}
|
95
102
|
|
96
|
-
Examples
|
97
|
-
========
|
103
|
+
### Examples
|
98
104
|
|
99
105
|
Replaces all instances of `h1` in the `posts/_form.html.erb` partial with `<h1>New Post</h1>`
|
100
106
|
|
@@ -153,23 +159,84 @@ Remove an entire ERB if statement (and all it's contents) in the 'admin/products
|
|
153
159
|
:remove => "code[erb-silent]:contains('if @product.sold?')",
|
154
160
|
:closing_selector => "code[erb-silent]:contains('end')"
|
155
161
|
|
156
|
-
Scope
|
157
|
-
=====
|
162
|
+
### Scope
|
158
163
|
|
159
164
|
Deface scopes overrides by virtual_path (or partial / template file), that means all override names only need to be unique within that single file.
|
160
165
|
|
161
|
-
Redefining Overrides
|
162
|
-
====================
|
166
|
+
### Redefining Overrides
|
163
167
|
|
164
168
|
You can redefine an existing override by simply declaring a new override with the same <tt>:virtual_path</tt> and <tt>:name</tt> that was originally used.
|
165
169
|
You do not need to resupply all the values originally used, just the ones you want to change:
|
166
170
|
|
167
|
-
|
171
|
+
Deface::Override.new(:virtual_path => 'posts/index',
|
168
172
|
:name => 'add_attrs_to_a_link',
|
169
173
|
:disabled => true)
|
170
174
|
|
175
|
+
|
176
|
+
Using the Deface DSL (.deface files)
|
177
|
+
------------------------------------
|
178
|
+
|
179
|
+
Instead of defining Deface::Override instances directly, you can alternatively add `.deface` files to the `app/overrides` folder and Deface will automatically them pick up.
|
180
|
+
The path of each override should match the path of the view template you want to modify, say for example if you have a template at:
|
181
|
+
|
182
|
+
app/views/posts/_post.html.erb
|
183
|
+
|
184
|
+
Then you can override it by adding a .deface file at:
|
185
|
+
|
186
|
+
app/overrides/posts/_post/my_override.html.erb.deface
|
187
|
+
|
188
|
+
The format of a .deface file is a comment showing the action to be performed, followed by any markup that would be normally passed to the :erb, :text, :haml arguments:
|
189
|
+
|
190
|
+
<!-- insert_after 'h1' -->
|
191
|
+
<h2>These robots are awesome.</h2>
|
192
|
+
|
193
|
+
The same effect can also be achieved with haml, by changing the overrides filename to:
|
194
|
+
|
195
|
+
app/overrides/posts/_post/my_override.html.haml.deface
|
196
|
+
|
197
|
+
and including haml source:
|
198
|
+
|
199
|
+
/
|
200
|
+
insert_after 'h1'
|
201
|
+
%h2 These robots are awesome.
|
202
|
+
|
203
|
+
|
204
|
+
#### Additional Options
|
205
|
+
|
206
|
+
You can include all the additional options you can normally use when defining a Deface::Override manually, a more complex example:
|
207
|
+
|
208
|
+
<!-- replace_contents 'h1'
|
209
|
+
closing_selector 'div#intro'
|
210
|
+
disabled -->
|
211
|
+
<p>This is a complicated example</p>
|
212
|
+
|
213
|
+
#### Disabled / Enabled
|
214
|
+
|
215
|
+
The DSL does not accept the instance style ````:disabled => boolean```` instead you can simply include either:
|
216
|
+
|
217
|
+
<!-- enabled -->
|
218
|
+
|
219
|
+
or
|
220
|
+
|
221
|
+
<!-- disabled -->
|
222
|
+
|
223
|
+
### DSL usage for overrides that do not include markup
|
224
|
+
|
225
|
+
If your override does not require any markup, for example actions including ````:remove, :set_attributes, :remove_from_attributes, :add_to_attrbiutes```` you can exclude the "html.erb" or "html.haml" from the file name and you do not need to wrap the arguments in a comment.
|
226
|
+
|
227
|
+
So the override filename becomes simply:
|
228
|
+
|
229
|
+
app/overrides/posts/_post/my_override.deface
|
230
|
+
|
231
|
+
And the contents:
|
232
|
+
|
233
|
+
add_to_attributes 'a#search'
|
234
|
+
attributes :alt => 'Click here to search'
|
235
|
+
|
236
|
+
|
237
|
+
|
171
238
|
Rake Tasks
|
172
|
-
|
239
|
+
----------
|
173
240
|
|
174
241
|
Deface includes a couple of rake tasks that can be helpful when defining or debugging overrides.
|
175
242
|
|
@@ -185,10 +252,28 @@ Deface includes a couple of rake tasks that can be helpful when defining or debu
|
|
185
252
|
|
186
253
|
rake deface:test_selector['admin/products/index','div.toolbar']
|
187
254
|
|
188
|
-
**deface:precompile** - Generates compiled views that contain all overrides applied. See `Production & Precompiling` section
|
255
|
+
**deface:precompile** - Generates compiled views that contain all overrides applied. See `Production & Precompiling` section below for more.
|
189
256
|
|
190
257
|
rake deface:precompile
|
191
258
|
|
259
|
+
Production & Precompiling
|
260
|
+
-------------------------
|
261
|
+
|
262
|
+
Deface now supports precompiling where all overrides are loaded and applied to the original views and the resulting templates are then saved to your application's `app/compiled_views` directory. To precompile run:
|
263
|
+
|
264
|
+
bundle exec rake deface:precompile
|
265
|
+
|
266
|
+
It's important to disable Deface once precompiling is used to prevent overrides getting applied twice. To disable add the following line to your application's `production.rb` file:
|
267
|
+
|
268
|
+
config.deface.enabled = false
|
269
|
+
|
270
|
+
NOTE: You can also use precompiling in development mode.
|
271
|
+
|
272
|
+
|
273
|
+
Demo & Testing
|
274
|
+
--------------
|
275
|
+
You can play with Deface and see its parsing in action at [deface.heroku.com](http://deface.heroku.com)
|
276
|
+
|
192
277
|
|
193
278
|
Implementation
|
194
279
|
==============
|
@@ -219,6 +304,7 @@ ERB that is contained inside a HTML tag definition is converted slightly differe
|
|
219
304
|
|
220
305
|
Deface overrides have full access to all variables accessible to the view being customized.
|
221
306
|
|
307
|
+
|
222
308
|
Caveats
|
223
309
|
======
|
224
310
|
Deface uses the amazing Nokogiri library (and in turn libxml) for parsing HTML / view files, in some circumstances either Deface's own pre-parser or libxml's will fail to correctly parse a template. You can avoid such issues by ensuring your templates contain valid HTML. Some other caveats include:
|
data/deface.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "deface"
|
3
|
-
s.version = "0.
|
3
|
+
s.version = "0.9.0"
|
4
4
|
|
5
5
|
s.authors = ["Brian D Quinn"]
|
6
6
|
s.description = "Deface is a library that allows you to customize ERB & HAML views in a Rails application without editing the underlying view."
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.summary = "Deface is a library that allows you to customize ERB & HAML views in Rails"
|
17
17
|
|
18
18
|
s.add_dependency('nokogiri', '~> 1.5.0')
|
19
|
-
s.add_dependency('rails', '
|
19
|
+
s.add_dependency('rails', '~> 3.1')
|
20
20
|
|
21
21
|
s.add_development_dependency('rspec', '>= 2.8.0')
|
22
22
|
s.add_development_dependency('haml', '>= 3.1.4')
|
data/lib/deface.rb
CHANGED
@@ -7,9 +7,16 @@ require "deface/search"
|
|
7
7
|
require "deface/override"
|
8
8
|
require "deface/parser"
|
9
9
|
require "deface/environment"
|
10
|
+
require "deface/dsl/loader"
|
10
11
|
|
11
12
|
module Deface
|
12
13
|
if defined?(Rails)
|
13
14
|
require "deface/railtie"
|
14
15
|
end
|
16
|
+
|
17
|
+
# Exceptions
|
18
|
+
class DefaceError < StandardError; end
|
19
|
+
|
20
|
+
class NotSupportedError < DefaceError; end
|
21
|
+
|
15
22
|
end
|
@@ -23,23 +23,33 @@ ActionView::Template.class_eval do
|
|
23
23
|
# view needs to be recompiled
|
24
24
|
#
|
25
25
|
def render(view, locals, buffer=nil, &block)
|
26
|
-
|
26
|
+
|
27
|
+
if view.is_a?(ActionView::CompiledTemplates)
|
28
|
+
mod = ActionView::CompiledTemplates
|
29
|
+
else
|
30
|
+
mod = view.singleton_class
|
31
|
+
end
|
32
|
+
|
33
|
+
if @compiled && !mod.instance_methods.map(&:to_s).include?(method_name)
|
27
34
|
@compiled = false
|
28
35
|
@source = refresh(view).source
|
29
36
|
end
|
30
37
|
render_without_deface(view, locals, buffer, &block)
|
31
38
|
end
|
32
39
|
|
40
|
+
protected
|
33
41
|
|
34
|
-
|
42
|
+
alias_method :method_name_without_deface, :method_name
|
35
43
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
# inject deface hash into compiled view method name
|
45
|
+
# used to determine if recompilation is needed
|
46
|
+
#
|
47
|
+
def method_name
|
48
|
+
deface_hash = Deface::Override.digest(:virtual_path => @virtual_path)
|
49
|
+
|
50
|
+
#we digest the whole method name as if it gets too long there's problems
|
51
|
+
"_#{Digest::MD5.new.update("#{deface_hash}_#{method_name_without_deface}").hexdigest}"
|
52
|
+
end
|
43
53
|
end
|
44
54
|
|
45
55
|
#fix for Rails 3.1 not setting virutal_path anymore (BOO!)
|
data/lib/deface/applicator.rb
CHANGED
@@ -24,6 +24,8 @@ module Deface
|
|
24
24
|
next
|
25
25
|
end
|
26
26
|
|
27
|
+
override.parsed_document = doc
|
28
|
+
|
27
29
|
if override.end_selector.blank?
|
28
30
|
# single css selector
|
29
31
|
|
@@ -118,14 +120,11 @@ module Deface
|
|
118
120
|
|
119
121
|
end
|
120
122
|
else
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
if starting && starting.parent
|
125
|
-
ending = starting.parent.css(override.end_selector).first
|
126
|
-
else
|
127
|
-
ending = doc.css(override.end_selector).first
|
123
|
+
unless [:remove, :replace, :replace_contents, :surround, :surround_contents].include? override.action
|
124
|
+
raise Deface::NotSupportedError, ":#{override.action} action does not support :closing_selector"
|
128
125
|
end
|
126
|
+
# targeting range of elements as end_selector is present
|
127
|
+
starting, ending = select_endpoints(doc, override.selector, override.end_selector)
|
129
128
|
|
130
129
|
if starting && ending
|
131
130
|
if log
|
@@ -143,6 +142,43 @@ module Deface
|
|
143
142
|
when :replace_contents
|
144
143
|
elements[1..-2].map &:remove
|
145
144
|
starting.after(override.source_element)
|
145
|
+
when :surround, :surround_contents
|
146
|
+
|
147
|
+
new_source = override.source_element.clone(1)
|
148
|
+
|
149
|
+
if original = new_source.css("code:contains('render_original')").first
|
150
|
+
|
151
|
+
if override.action == :surround
|
152
|
+
start = elements[0].clone(1)
|
153
|
+
original.replace start
|
154
|
+
|
155
|
+
elements[1..-1].each do |element|
|
156
|
+
element = element.clone(1)
|
157
|
+
start.after element
|
158
|
+
start = element
|
159
|
+
end
|
160
|
+
|
161
|
+
starting.before(new_source)
|
162
|
+
elements.map &:remove
|
163
|
+
|
164
|
+
|
165
|
+
elsif override.action == :surround_contents
|
166
|
+
|
167
|
+
start = elements[1].clone(1)
|
168
|
+
original.replace start
|
169
|
+
|
170
|
+
elements[2...-1].each do |element|
|
171
|
+
element = element.clone(1)
|
172
|
+
start.after element
|
173
|
+
start = element
|
174
|
+
end
|
175
|
+
|
176
|
+
starting.after(new_source)
|
177
|
+
elements[1...-1].map &:remove
|
178
|
+
end
|
179
|
+
else
|
180
|
+
#maybe we should log that the original wasn't found.
|
181
|
+
end
|
146
182
|
end
|
147
183
|
else
|
148
184
|
if starting.nil?
|
@@ -167,13 +203,31 @@ module Deface
|
|
167
203
|
source
|
168
204
|
end
|
169
205
|
|
170
|
-
|
206
|
+
|
207
|
+
def select_endpoints(doc, start, finish)
|
208
|
+
# targeting range of elements as end_selector is present
|
209
|
+
#
|
210
|
+
finish = "#{start} ~ #{finish}"
|
211
|
+
starting = doc.css(start).first
|
212
|
+
|
213
|
+
ending = if starting && starting.parent
|
214
|
+
starting.parent.css(finish).first
|
215
|
+
else
|
216
|
+
doc.css(finish).first
|
217
|
+
end
|
218
|
+
|
219
|
+
return starting, ending
|
220
|
+
|
221
|
+
end
|
222
|
+
|
171
223
|
# finds all elements upto closing sibling in nokgiri document
|
172
224
|
#
|
173
225
|
def select_range(first, last)
|
174
226
|
first == last ? [first] : [first, *select_range(first.next, last)]
|
175
227
|
end
|
176
228
|
|
229
|
+
private
|
230
|
+
|
177
231
|
def normalize_attribute_name(name)
|
178
232
|
name = name.to_s.gsub /"|'/, ''
|
179
233
|
|
@@ -183,10 +237,6 @@ module Deface
|
|
183
237
|
|
184
238
|
name
|
185
239
|
end
|
186
|
-
|
187
|
-
def set_attributes(match, name, value)
|
188
|
-
|
189
|
-
end
|
190
240
|
end
|
191
241
|
end
|
192
242
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Deface
|
2
|
+
module DSL
|
3
|
+
class Context
|
4
|
+
def initialize(name)
|
5
|
+
@name = name
|
6
|
+
@options = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_override
|
10
|
+
options = {
|
11
|
+
:name => @name,
|
12
|
+
:virtual_path => @virtual_path,
|
13
|
+
}.merge(@action || {}).merge(@source || {}).merge(@options)
|
14
|
+
|
15
|
+
Deface::Override.new(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def virtual_path(name)
|
19
|
+
@virtual_path = name
|
20
|
+
end
|
21
|
+
|
22
|
+
Deface::Override.actions.each do |action_name|
|
23
|
+
define_method(action_name) do |selector|
|
24
|
+
if @action.present?
|
25
|
+
Rails.logger.error "\e[1;32mDeface: [WARNING]\e[0m Multiple action methods have been called. The last one will be used."
|
26
|
+
end
|
27
|
+
|
28
|
+
@action = { action_name => selector }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Deface::Override.sources.each do |source_name|
|
33
|
+
define_method(source_name) do |value|
|
34
|
+
if @source.present?
|
35
|
+
Rails.logger.error "\e[1;32mDeface: [WARNING]\e[0m Multiple source methods have been called. The last one will be used."
|
36
|
+
end
|
37
|
+
|
38
|
+
@source = { source_name => value }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def original(markup)
|
43
|
+
@options[:original] = markup
|
44
|
+
end
|
45
|
+
|
46
|
+
def closing_selector(selector)
|
47
|
+
@options[:closing_selector] = selector
|
48
|
+
end
|
49
|
+
|
50
|
+
def sequence(value)
|
51
|
+
@options[:sequence] = value
|
52
|
+
end
|
53
|
+
|
54
|
+
def attributes(values)
|
55
|
+
@options[:attributes] = values
|
56
|
+
end
|
57
|
+
|
58
|
+
def enabled
|
59
|
+
@options[:disabled] = false
|
60
|
+
end
|
61
|
+
|
62
|
+
def disabled
|
63
|
+
@options[:disabled] = true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|