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