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 CHANGED
@@ -3,4 +3,6 @@ rvm:
3
3
  - 1.8.7
4
4
  - 1.9.2
5
5
  - 1.9.3
6
- - ree
6
+ gemfile:
7
+ - gemfiles/rails3_1.gemfile
8
+ - gemfiles/rails3_2.gemfile
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
- Production & Precompiling
20
- ------------------------
18
+ There are two ways of using Deface:
21
19
 
22
- 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:
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
- bundle exec rake deface:precompile
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
- NOTE: You can also use precompiling in development mode.
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::Override
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
- 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. Note: the source parameter is not required when the "remove" action is specified.
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>: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.
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
- Deface::Override.new(:virtual_path => 'posts/index',
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 above for more.
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.8.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', '>= 3.0.9')
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')
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ gem "rails", "~> 3.1.0"
4
+
5
+ gemspec :path=>"../"
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ gem "rails", "~> 3.2.0"
4
+
5
+ gemspec :path=>"../"
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
- if @compiled && !view.respond_to?(method_name)
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
- alias_method :method_name_without_deface, :method_name
42
+ alias_method :method_name_without_deface, :method_name
35
43
 
36
- # inject deface hash into compiled view method name
37
- # used to determine if recompilation is needed
38
- #
39
- def method_name
40
- deface_hash = Deface::Override.digest(:virtual_path => @virtual_path)
41
- "_#{deface_hash}_#{method_name_without_deface}"
42
- end
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!)
@@ -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
- # targeting range of elements as end_selector is present
122
- starting = doc.css(override.selector).first
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
- private
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