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