effigy 0.2.0 → 0.2.1
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/README.textile +41 -3
- data/Rakefile +2 -0
- data/TODO.textile +5 -0
- data/VERSION +1 -1
- data/lib/effigy.rb +5 -0
- data/lib/effigy/class_list.rb +11 -0
- data/lib/effigy/core_ext/hash.rb +11 -0
- data/lib/effigy/core_ext/object.rb +12 -0
- data/lib/effigy/errors.rb +5 -0
- data/lib/effigy/selection.rb +39 -0
- data/lib/effigy/view.rb +191 -15
- data/spec/effigy/core_ext/hash_spec.rb +10 -0
- data/spec/effigy/core_ext/object_spec.rb +10 -0
- data/spec/effigy/rails/template_handler_spec.rb +1 -1
- data/spec/effigy/selection_spec.rb +39 -0
- data/spec/effigy/view_spec.rb +38 -12
- metadata +23 -4
data/README.textile
CHANGED
@@ -36,10 +36,10 @@ class PostView < Effigy::View
|
|
36
36
|
text('h1', post.title)
|
37
37
|
text('title', "#{post.title} - Site title")
|
38
38
|
text('p.body', post.body)
|
39
|
-
|
39
|
+
replace_each('.comment', post.comments) do |comment|
|
40
40
|
text('h2', comment.title)
|
41
41
|
text('p', comment.summary)
|
42
|
-
|
42
|
+
attr('a', :href => url_for(comment))
|
43
43
|
end
|
44
44
|
remove('#no-comments') if post.comments.empty?
|
45
45
|
end
|
@@ -72,6 +72,34 @@ document = view.render(template)
|
|
72
72
|
|
73
73
|
See the documentation for more information on available transformations.
|
74
74
|
|
75
|
+
h2. Chaining
|
76
|
+
|
77
|
+
If you prefer, you can select elements and then apply tranformations in a chain. The previous example could have been written like this:
|
78
|
+
|
79
|
+
<pre>
|
80
|
+
class PostView < Effigy::View
|
81
|
+
attr_reader :post
|
82
|
+
|
83
|
+
def initialize(post)
|
84
|
+
@post = post
|
85
|
+
end
|
86
|
+
|
87
|
+
def transform
|
88
|
+
find('h1').text(post.title)
|
89
|
+
find('title').text("#{post.title} - Site title")
|
90
|
+
find('p.body').text(post.body)
|
91
|
+
find('.comment').replace_each(post.comments) do |comment|
|
92
|
+
find('h2').text(comment.title)
|
93
|
+
find('p').text(comment.summary)
|
94
|
+
find('a').attr(:href => url_for(comment))
|
95
|
+
end
|
96
|
+
find('#no-comments').remove if post.comments.empty?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
</pre>
|
100
|
+
|
101
|
+
#find is also aliased as #f for brevity, if you're into that sort of thing.
|
102
|
+
|
75
103
|
h2. Rails
|
76
104
|
|
77
105
|
Effigy integrates with Rails. It provides a view subclass that copies instance variables from the controller, a template handler to find Effigy views and templates, and a generator to create skeleton view files.
|
@@ -103,11 +131,21 @@ end
|
|
103
131
|
|
104
132
|
View this example in your browser and you'll see "hocus pocus."
|
105
133
|
|
134
|
+
h2. Install
|
135
|
+
|
136
|
+
Effigy is distributed as a gem through gemcutter:
|
137
|
+
|
138
|
+
<pre>
|
139
|
+
sudo gem install effigy -s http://gemcutter.org
|
140
|
+
</pre>
|
141
|
+
|
142
|
+
Effigy requires Nokogiri.
|
143
|
+
|
106
144
|
h2. Why?
|
107
145
|
|
108
146
|
Effigy is based on the idea that putting behavior in your templates is confusing and makes them difficult to maintain, and that the closer an ERB template gets to 50% Ruby, 50% HTML, the closer it gets to total chaos. Complicated views require unintuitive concepts (ERB buffers, capture blocks, etc). ERB also has the constant threat of unescaped user input slipping into a view.
|
109
147
|
|
110
|
-
Effigy was created because I have never liked interpolation-based templating languages like ERB and because XSLT
|
148
|
+
Effigy was created because I have never liked interpolation-based templating languages like ERB and because XSLT requires introducing another language (and I like Ruby just fine).
|
111
149
|
|
112
150
|
h2. Author
|
113
151
|
|
data/Rakefile
CHANGED
data/TODO.textile
ADDED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.1
|
data/lib/effigy.rb
CHANGED
data/lib/effigy/class_list.rb
CHANGED
@@ -1,17 +1,26 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
|
3
3
|
module Effigy
|
4
|
+
# Internal use only.
|
5
|
+
#
|
6
|
+
# Used when parsing and manipulating lists of CSS class names.
|
4
7
|
class ClassList
|
8
|
+
# @param element [Nokogiri::XML::Element] the element whose class names
|
9
|
+
# should be manipulated
|
5
10
|
def initialize(element)
|
6
11
|
@element = element
|
7
12
|
read_class_names
|
8
13
|
end
|
9
14
|
|
15
|
+
# Appends a class name to the list.
|
16
|
+
# @param [String] class_name the class name to append
|
10
17
|
def <<(class_name)
|
11
18
|
@class_names << class_name
|
12
19
|
write_class_names
|
13
20
|
end
|
14
21
|
|
22
|
+
# Removes a class name from the list.
|
23
|
+
# @param [String] class_name the class name to remove
|
15
24
|
def remove(class_name)
|
16
25
|
@class_names.delete(class_name)
|
17
26
|
write_class_names
|
@@ -19,10 +28,12 @@ module Effigy
|
|
19
28
|
|
20
29
|
private
|
21
30
|
|
31
|
+
# Parses and caches the list of class names. Called after initialization.
|
22
32
|
def read_class_names
|
23
33
|
@class_names = (@element['class'] || '').split(' ')
|
24
34
|
end
|
25
35
|
|
36
|
+
# Writes the transformed list of classes to the element's class attribute.
|
26
37
|
def write_class_names
|
27
38
|
@element['class'] = @class_names.join(' ')
|
28
39
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Internal use only.
|
2
|
+
#
|
3
|
+
# Extends Hash to easily convert a hash or key, value pair to a hash.
|
4
|
+
class Hash
|
5
|
+
# @return [Hash] self
|
6
|
+
# @param [Object] value ignored
|
7
|
+
# @see Object#to_effigy_attributes
|
8
|
+
def to_effigy_attributes(value)
|
9
|
+
self
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Internal use only.
|
2
|
+
#
|
3
|
+
# Extends Object to easily convert a hash or key, value pair to a hash.
|
4
|
+
class Object
|
5
|
+
# @return [Hash] a hash composed of the object as the key and the given value
|
6
|
+
# as the value.
|
7
|
+
# @param [Object] value the value to use in the generated hash
|
8
|
+
# @see Hash#to_effigy_attributes
|
9
|
+
def to_effigy_attributes(value)
|
10
|
+
{ self => value }
|
11
|
+
end
|
12
|
+
end
|
data/lib/effigy/errors.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
module Effigy
|
2
|
+
# Raised when attempting to select an element that isn't on the source
|
3
|
+
# template.
|
2
4
|
class ElementNotFound < StandardError
|
5
|
+
# [String] The selector that didn't match any elements
|
3
6
|
attr_accessor :selector
|
4
7
|
|
8
|
+
# @param [String] the selector that didn't match any elements
|
5
9
|
def initialize(selector)
|
6
10
|
@selector = selector
|
7
11
|
end
|
8
12
|
|
13
|
+
# @return [String] a description of the failed search
|
9
14
|
def message
|
10
15
|
"No element matched the given selector: #{selector.inspect}"
|
11
16
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Effigy
|
2
|
+
# Proxy class for performing transformations with a set selection.
|
3
|
+
#
|
4
|
+
# Selections are created and returned when calling {View#find} without a
|
5
|
+
# block. Any methods called on the Selection will be forwarded to the given
|
6
|
+
# view with the given selector inserted at the front of the argument list.
|
7
|
+
#
|
8
|
+
# All methods should return the Selection itself, so transformation methods
|
9
|
+
# can be chained on a selection.
|
10
|
+
#
|
11
|
+
# Normally, Selections are not instantiated directly, but by calling
|
12
|
+
# {View#find}.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# view = Effigy::View.new
|
16
|
+
# # same as calling view.remove_class('.active', 'inactive')
|
17
|
+
# view.find('.active').remove_class('inactive')
|
18
|
+
class Selection
|
19
|
+
|
20
|
+
# Creates a selection over the given view using the given selector.
|
21
|
+
# @param view [View] the view to which transformations should be forwarded
|
22
|
+
# @param selector [String] the selector that should be used for forwarded
|
23
|
+
# transformation methods
|
24
|
+
def initialize(view, selector)
|
25
|
+
@view = view
|
26
|
+
@selector = selector
|
27
|
+
end
|
28
|
+
|
29
|
+
# Undefined methods are forwarded to the view with the selector inserted at
|
30
|
+
# the beginning of the argument list. The Selection is then returned for
|
31
|
+
# chaining.
|
32
|
+
#
|
33
|
+
# @return [Selection] self
|
34
|
+
def method_missing(method, *args, &block)
|
35
|
+
@view.send(method, @selector, *args, &block)
|
36
|
+
self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/effigy/view.rb
CHANGED
@@ -1,21 +1,72 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
require 'effigy/class_list'
|
3
3
|
require 'effigy/errors'
|
4
|
+
require 'effigy/selection'
|
4
5
|
|
5
6
|
module Effigy
|
7
|
+
# Accepts a template to be transformed.
|
8
|
+
#
|
9
|
+
# For most common cases, creating a subclass makes the most sense, but this
|
10
|
+
# class can be used directly by passing a block to {#render}.
|
11
|
+
#
|
12
|
+
# @see #transform
|
13
|
+
# @see #render
|
14
|
+
# @example
|
15
|
+
# view = Effigy::View.new
|
16
|
+
# view.render(template) do
|
17
|
+
# view.find('h1').text('the title')
|
18
|
+
# end
|
19
|
+
#
|
6
20
|
class View
|
21
|
+
# Replaces the text content of the selected element.
|
22
|
+
#
|
23
|
+
# Markup in the given content is escaped. Use {#html} if you want to
|
24
|
+
# replace the contents with live markup.
|
25
|
+
#
|
26
|
+
# @param [String] selector a CSS or XPath string describing the element to
|
27
|
+
# transform
|
28
|
+
# @param [String] content the text that should be the new element contents
|
29
|
+
# @example
|
30
|
+
# text('h1', 'a title')
|
31
|
+
# find('h1').text('a title')
|
32
|
+
# text('p', '<b>title</b>') # <p><b>title</title></p>
|
7
33
|
def text(selector, content)
|
8
34
|
select(selector).content = content
|
9
35
|
end
|
10
36
|
|
11
|
-
|
37
|
+
# Adds or updates the given attribute or attributes of the selected element.
|
38
|
+
#
|
39
|
+
# @param [String] selector a CSS or XPath string describing the element to
|
40
|
+
# transform
|
41
|
+
# @param [String,Hash] attributes_or_attribute_name if a String, replaces
|
42
|
+
# that attribute with the given value. If a Hash, uses the keys as
|
43
|
+
# attribute names and values as attribute values
|
44
|
+
# @param [String] value the value for the replaced attribute. Used only if
|
45
|
+
# attributes_or_attribute_name is a String
|
46
|
+
# @example
|
47
|
+
# attr('p', :id => 'an_id', :style => 'display: none')
|
48
|
+
# attr('p', :id, 'an_id')
|
49
|
+
# find('p').attr(:id, 'an_id')
|
50
|
+
def attr(selector, attributes_or_attribute_name, value = nil)
|
12
51
|
element = select(selector)
|
52
|
+
attributes = attributes_or_attribute_name.to_effigy_attributes(value)
|
13
53
|
attributes.each do |attribute, value|
|
14
54
|
element[attribute.to_s] = value
|
15
55
|
end
|
16
56
|
end
|
17
57
|
|
18
|
-
|
58
|
+
# Replaces the selected element with a clone for each item in the collection.
|
59
|
+
#
|
60
|
+
# @param [String] selector a CSS or XPath string describing the element to
|
61
|
+
# transform
|
62
|
+
# @param [Enumerable] collection the items that are the base for each
|
63
|
+
# cloned element
|
64
|
+
# @example
|
65
|
+
# titles = %w(one two three)
|
66
|
+
# find('.post').replace_each(titles) do |title|
|
67
|
+
# text('h1', title)
|
68
|
+
# end
|
69
|
+
def replace_each(selector, collection, &block)
|
19
70
|
original_element = select(selector)
|
20
71
|
collection.inject(original_element) do |sibling, item|
|
21
72
|
item_element = clone_element_with_item(original_element, item, &block)
|
@@ -24,6 +75,13 @@ module Effigy
|
|
24
75
|
original_element.unlink
|
25
76
|
end
|
26
77
|
|
78
|
+
# Perform transformations on the given template.
|
79
|
+
#
|
80
|
+
# @yield inside the given block, transformation methods such as #text and
|
81
|
+
# #html can be used on the template. Using a subclass, you can instead
|
82
|
+
# override the #transform method, which is the preferred approach.
|
83
|
+
#
|
84
|
+
# @return [String] the resulting document
|
27
85
|
def render(template)
|
28
86
|
@current_context = Nokogiri::XML.parse(template)
|
29
87
|
yield if block_given?
|
@@ -31,44 +89,141 @@ module Effigy
|
|
31
89
|
output
|
32
90
|
end
|
33
91
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
92
|
+
# Removes the selected elements from the template.
|
93
|
+
#
|
94
|
+
# @param [String] selector a CSS or XPath string describing the element to
|
95
|
+
# transform
|
96
|
+
# @example
|
97
|
+
# remove('.post')
|
98
|
+
# find('.post').remove
|
41
99
|
def remove(selector)
|
42
100
|
select_all(selector).each { |element| element.unlink }
|
43
101
|
end
|
44
102
|
|
45
|
-
|
103
|
+
# Adds the given class names to the selected element.
|
104
|
+
#
|
105
|
+
# @param [String] selector a CSS or XPath string describing the element to
|
106
|
+
# transform
|
107
|
+
# @param [String] class_names a CSS class name that should be added
|
108
|
+
# @example
|
109
|
+
# add_class('a#home', 'selected')
|
110
|
+
# find('a#home').add_class('selected')
|
111
|
+
def add_class(selector, *class_names)
|
46
112
|
element = select(selector)
|
47
113
|
class_list = ClassList.new(element)
|
48
114
|
class_names.each { |class_name| class_list << class_name }
|
49
115
|
end
|
50
116
|
|
51
|
-
|
117
|
+
# Removes the given class names from the selected element.
|
118
|
+
#
|
119
|
+
# Ignores class names that are not present.
|
120
|
+
#
|
121
|
+
# @param [String] selector a CSS or XPath string describing the element to
|
122
|
+
# transform
|
123
|
+
# @param [String] class_names a CSS class name that should be removed
|
124
|
+
# @example
|
125
|
+
# remove_class('a#home', 'selected')
|
126
|
+
# find('a#home').remove_class('selected')
|
127
|
+
def remove_class(selector, *class_names)
|
52
128
|
element = select(selector)
|
53
129
|
class_list = ClassList.new(element)
|
54
130
|
class_names.each { |class_name| class_list.remove(class_name) }
|
55
131
|
end
|
56
132
|
|
57
|
-
|
133
|
+
# Replaces the contents of the selected element with live markup.
|
134
|
+
#
|
135
|
+
# @param [String] selector a CSS or XPath string describing the element to
|
136
|
+
# transform
|
137
|
+
# @param [String] xml the new contents of the selected element. Markup is
|
138
|
+
# not escaped.
|
139
|
+
# @example
|
140
|
+
# html('p', '<b>Welcome!</b>')
|
141
|
+
# find('p').html('<b>Welcome!</b>')
|
142
|
+
def html(selector, xml)
|
58
143
|
select(selector).inner_html = xml
|
59
144
|
end
|
60
145
|
|
61
|
-
|
146
|
+
# Replaces the selected element with live markup.
|
147
|
+
#
|
148
|
+
# The "outer HTML" for the selected tag itself is also replaced.
|
149
|
+
#
|
150
|
+
# @param [String] selector a CSS or XPath string describing the element to
|
151
|
+
# transform
|
152
|
+
# @param [String] xml the new markup to replace the selected element. Markup is
|
153
|
+
# not escaped.
|
154
|
+
def replace_with(selector, xml)
|
62
155
|
select(selector).after(xml).unlink
|
63
156
|
end
|
64
157
|
|
65
|
-
|
158
|
+
# Selects an element or elements for chained transformation.
|
159
|
+
#
|
160
|
+
# If given a block, the selection will be in effect during the block.
|
161
|
+
#
|
162
|
+
# If not given a block, a {Selection} will be returned on which
|
163
|
+
# transformation methods can be called. Any methods called on the
|
164
|
+
# Selection will be delegated back to the view with the selector inserted
|
165
|
+
# into the parameter list.
|
166
|
+
#
|
167
|
+
# @param [String] selector a CSS or XPath string describing the element to
|
168
|
+
# transform
|
169
|
+
# @return [Selection] a proxy object on which transformation methods can be
|
170
|
+
# called
|
171
|
+
# @example
|
172
|
+
# find('.post') do
|
173
|
+
# text('h1', post.title)
|
174
|
+
# text('p', post.body)
|
175
|
+
# end
|
176
|
+
# find('h1').text(post.title).add_class('active')
|
177
|
+
def find(selector)
|
178
|
+
if block_given?
|
179
|
+
old_context = @current_context
|
180
|
+
@current_context = select(selector)
|
181
|
+
yield
|
182
|
+
@current_context = old_context
|
183
|
+
else
|
184
|
+
Selection.new(self, selector)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
alias_method :f, :find
|
66
188
|
|
189
|
+
# Called by {#render} to perform transformations on the source template.
|
190
|
+
#
|
191
|
+
# Override this method in subclasses to perform the transformations
|
192
|
+
# specific to your view.
|
193
|
+
#
|
194
|
+
# @example
|
195
|
+
# class PostView < Effigy::View
|
196
|
+
# def initialize(post)
|
197
|
+
# @post = post
|
198
|
+
# end
|
199
|
+
#
|
200
|
+
# def transform
|
201
|
+
# find('.post') do
|
202
|
+
# find('h2').text(post.title)
|
203
|
+
# find('p').text(post.body)
|
204
|
+
# end
|
205
|
+
# end
|
206
|
+
# end
|
67
207
|
def transform
|
68
208
|
end
|
69
209
|
|
210
|
+
private
|
211
|
+
|
212
|
+
# The current set of nodes on which transformations are performed.
|
213
|
+
#
|
214
|
+
# This is usually the entire document, but will be a subset of child nodes
|
215
|
+
# during {#find} blocks.
|
70
216
|
attr_reader :current_context
|
71
217
|
|
218
|
+
# Returns the first node that matches the given selection, or the nodes
|
219
|
+
# themselves if given a set of nodes.
|
220
|
+
#
|
221
|
+
# @param nodes [String,Nokogiri::XML::NodeSet] if a String, the selector to
|
222
|
+
# use when determining the current context. When a NodeSet, the set of
|
223
|
+
# nodes that should be returned.
|
224
|
+
# @return [Nokogiri::XML::NodeSet] the nodes selected by the given selector
|
225
|
+
# or node set.
|
226
|
+
# @raise [ElementNotFound] if no nodes match the given selector
|
72
227
|
def select(nodes)
|
73
228
|
if nodes.respond_to?(:search)
|
74
229
|
nodes
|
@@ -78,18 +233,39 @@ module Effigy
|
|
78
233
|
end
|
79
234
|
end
|
80
235
|
|
236
|
+
# Returns a set of nodes matching the given selector.
|
237
|
+
#
|
238
|
+
# @param selector [String] the selctor to use when finding nodes
|
239
|
+
# @return [Nokogiri::XML::NodeSet] the nodes selected by the given selector
|
240
|
+
# @raise [ElementNotFound] if no nodes match the given selector
|
81
241
|
def select_all(selector)
|
82
242
|
result = current_context.search(selector)
|
83
243
|
raise ElementNotFound, selector if result.empty?
|
84
244
|
result
|
85
245
|
end
|
86
246
|
|
247
|
+
# Clones an element, sets it as the current context, and yields to the
|
248
|
+
# given block with the given item.
|
249
|
+
#
|
250
|
+
# @param [Nokogiri::XML::Element] the element to clone
|
251
|
+
# @param [Object] item the item that should be yielded to the block
|
252
|
+
# @yield [Object] the item passed as item
|
253
|
+
# @return [Nokogiri::XML::Element] the clone of the original element
|
87
254
|
def clone_element_with_item(original_element, item, &block)
|
88
255
|
item_element = original_element.dup
|
89
|
-
|
256
|
+
find(item_element) { yield(item) }
|
90
257
|
item_element
|
91
258
|
end
|
92
259
|
|
260
|
+
# Converts the transformed document to a string.
|
261
|
+
#
|
262
|
+
# Called by {#render} after transforming the document using a passed block
|
263
|
+
# and {#transform}.
|
264
|
+
#
|
265
|
+
# Override this in subclasses if you wish to return something besides an
|
266
|
+
# XHTML string representation of the transformed document.
|
267
|
+
#
|
268
|
+
# @return [String] the transformed document as a string
|
93
269
|
def output
|
94
270
|
current_context.to_xhtml
|
95
271
|
end
|
@@ -34,7 +34,7 @@ describe "a controller with an effigy view and template" do
|
|
34
34
|
create_rails_file 'app/views/layouts/application.html.effigy', <<-RUBY
|
35
35
|
class LayoutsApplicationView < Effigy::Rails::View
|
36
36
|
def transform
|
37
|
-
|
37
|
+
html('body', content_for(:layout))
|
38
38
|
end
|
39
39
|
end
|
40
40
|
RUBY
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'effigy/selection'
|
3
|
+
|
4
|
+
module Effigy
|
5
|
+
describe Selection do
|
6
|
+
%w(text).each do |method|
|
7
|
+
it "should delegate #{method} to its view" do
|
8
|
+
view = stub('a view')
|
9
|
+
selector = '.findme'
|
10
|
+
arguments = [1, 2, 3]
|
11
|
+
view.should_receive(method).with(selector, *arguments)
|
12
|
+
selection = Selection.new(view, selector)
|
13
|
+
selection.send(method, *arguments)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return itself after delegating" do
|
18
|
+
view = stub('a view')
|
19
|
+
view.stub!(:text)
|
20
|
+
selection = Selection.new(view, '.findme')
|
21
|
+
selection.text.should == selection
|
22
|
+
end
|
23
|
+
|
24
|
+
class BlockRecorder
|
25
|
+
attr_reader :block
|
26
|
+
def run(*args, &block)
|
27
|
+
@block = block
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should pass blocks when delegating" do
|
32
|
+
view = BlockRecorder.new
|
33
|
+
block = lambda { 'hello' }
|
34
|
+
selection = Selection.new(view, '.findme')
|
35
|
+
selection.run(&block)
|
36
|
+
view.block.should == block
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/spec/effigy/view_spec.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'effigy/view'
|
3
3
|
require 'effigy/errors'
|
4
|
+
require 'effigy/core_ext/hash'
|
5
|
+
require 'effigy/core_ext/object'
|
4
6
|
|
5
7
|
module Effigy
|
6
8
|
describe View do
|
7
|
-
it "should replace element
|
9
|
+
it "should replace element text" do
|
8
10
|
template = %{<test><element one="abc">something</element></test>}
|
9
11
|
|
10
12
|
view = Effigy::View.new
|
@@ -20,18 +22,29 @@ module Effigy
|
|
20
22
|
|
21
23
|
view = Effigy::View.new
|
22
24
|
xml = view.render(template) do
|
23
|
-
view.
|
25
|
+
view.attr 'element', :one => '123', :two => '234'
|
24
26
|
end
|
25
27
|
|
26
28
|
xml.should have_selector(:element, :contents => 'something', :one => '123', :two => '234')
|
27
29
|
end
|
28
30
|
|
31
|
+
it "should replace one attribute" do
|
32
|
+
template = %{<test><element one="abc">something</element></test>}
|
33
|
+
|
34
|
+
view = Effigy::View.new
|
35
|
+
xml = view.render(template) do
|
36
|
+
view.attr 'element', :one, '123'
|
37
|
+
end
|
38
|
+
|
39
|
+
xml.should have_selector(:element, :contents => 'something', :one => '123')
|
40
|
+
end
|
41
|
+
|
29
42
|
it "should replace an element with a clone for each item in a collection" do
|
30
43
|
template = %{<test><element><value>original</value></element></test>}
|
31
44
|
|
32
45
|
view = Effigy::View.new
|
33
46
|
xml = view.render(template) do
|
34
|
-
view.
|
47
|
+
view.replace_each('element', %w(one two)) do |value|
|
35
48
|
view.text('value', value)
|
36
49
|
end
|
37
50
|
end
|
@@ -47,7 +60,7 @@ module Effigy
|
|
47
60
|
|
48
61
|
view = Effigy::View.new
|
49
62
|
xml = view.render(template) do
|
50
|
-
view.
|
63
|
+
view.find('element') do
|
51
64
|
view.text('value', 'expected')
|
52
65
|
end
|
53
66
|
end
|
@@ -72,7 +85,7 @@ module Effigy
|
|
72
85
|
|
73
86
|
view = Effigy::View.new
|
74
87
|
xml = view.render(template) do
|
75
|
-
view.
|
88
|
+
view.add_class('test', 'one', 'two')
|
76
89
|
end
|
77
90
|
|
78
91
|
xml.should have_selector('test.original')
|
@@ -85,7 +98,7 @@ module Effigy
|
|
85
98
|
|
86
99
|
view = Effigy::View.new
|
87
100
|
xml = view.render(template) do
|
88
|
-
view.
|
101
|
+
view.remove_class('test', 'one', 'two')
|
89
102
|
end
|
90
103
|
|
91
104
|
xml.should have_selector('test.three')
|
@@ -98,7 +111,7 @@ module Effigy
|
|
98
111
|
|
99
112
|
view = Effigy::View.new
|
100
113
|
xml = view.render(template) do
|
101
|
-
view.
|
114
|
+
view.html 'test', '<new>replaced</new>'
|
102
115
|
end
|
103
116
|
|
104
117
|
xml.should have_selector('test new', :contents => 'replaced')
|
@@ -110,7 +123,7 @@ module Effigy
|
|
110
123
|
|
111
124
|
view = Effigy::View.new
|
112
125
|
xml = view.render(template) do
|
113
|
-
view.
|
126
|
+
view.replace_with 'test', '<new>replaced</new>'
|
114
127
|
end
|
115
128
|
|
116
129
|
xml.should have_selector('new', :contents => 'replaced')
|
@@ -120,7 +133,20 @@ module Effigy
|
|
120
133
|
it "should render xhtml by default" do
|
121
134
|
template = %{<html/>}
|
122
135
|
xml = Effigy::View.new.render(template)
|
123
|
-
xml.should_not include('xml')
|
136
|
+
xml.should_not include('<?xml')
|
137
|
+
end
|
138
|
+
|
139
|
+
%w(find f).each do |chain_method|
|
140
|
+
it "should allow chains using #{chain_method}" do
|
141
|
+
template = %{<test><element one="abc">something</element></test>}
|
142
|
+
|
143
|
+
view = Effigy::View.new
|
144
|
+
xml = view.render(template) do
|
145
|
+
view.send(chain_method, 'element').text('expected')
|
146
|
+
end
|
147
|
+
|
148
|
+
xml.should have_selector(:element, :contents => 'expected', :one => 'abc')
|
149
|
+
end
|
124
150
|
end
|
125
151
|
|
126
152
|
describe "given a template without .find" do
|
@@ -136,12 +162,12 @@ module Effigy
|
|
136
162
|
end
|
137
163
|
|
138
164
|
it "should raise when updating attributes for .find" do
|
139
|
-
render { |view| view.
|
165
|
+
render { |view| view.attr('.find', :attr => 'value') }.
|
140
166
|
should raise_error(Effigy::ElementNotFound)
|
141
167
|
end
|
142
168
|
|
143
169
|
it "should raise when replacing an element matching .find" do
|
144
|
-
render { |view| view.
|
170
|
+
render { |view| view.replace_each('.find', []) }.
|
145
171
|
should raise_error(Effigy::ElementNotFound)
|
146
172
|
end
|
147
173
|
|
@@ -151,7 +177,7 @@ module Effigy
|
|
151
177
|
end
|
152
178
|
|
153
179
|
it "should raise when setting the context to .find" do
|
154
|
-
render { |view| view.
|
180
|
+
render { |view| view.find('.find') {} }.
|
155
181
|
should raise_error(Effigy::ElementNotFound)
|
156
182
|
end
|
157
183
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: effigy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Ferris
|
@@ -9,10 +9,19 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-04 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: nokogiri
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
16
25
|
description: Define views in Ruby and templates in HTML. Avoid code interpolation or ugly templating languages. Use ids, class names, and semantic structures already present in your documents to produce content.
|
17
26
|
email: jferris@thoughtbot.com
|
18
27
|
executables: []
|
@@ -26,17 +35,24 @@ files:
|
|
26
35
|
- LICENSE
|
27
36
|
- README.textile
|
28
37
|
- Rakefile
|
38
|
+
- TODO.textile
|
29
39
|
- VERSION
|
30
40
|
- lib/effigy.rb
|
31
41
|
- lib/effigy/class_list.rb
|
42
|
+
- lib/effigy/core_ext/hash.rb
|
43
|
+
- lib/effigy/core_ext/object.rb
|
32
44
|
- lib/effigy/errors.rb
|
33
45
|
- lib/effigy/rails.rb
|
34
46
|
- lib/effigy/rails/template_handler.rb
|
35
47
|
- lib/effigy/rails/view.rb
|
48
|
+
- lib/effigy/selection.rb
|
36
49
|
- lib/effigy/view.rb
|
37
50
|
- spec/effigy/class_list_spec.rb
|
51
|
+
- spec/effigy/core_ext/hash_spec.rb
|
52
|
+
- spec/effigy/core_ext/object_spec.rb
|
38
53
|
- spec/effigy/errors_spec.rb
|
39
54
|
- spec/effigy/rails/template_handler_spec.rb
|
55
|
+
- spec/effigy/selection_spec.rb
|
40
56
|
- spec/effigy/view_spec.rb
|
41
57
|
- spec/rails/generators/effigy_view_spec.rb
|
42
58
|
- spec/spec_helper.rb
|
@@ -71,7 +87,10 @@ specification_version: 3
|
|
71
87
|
summary: Effigy provides a view and template framework without a templating language.
|
72
88
|
test_files:
|
73
89
|
- spec/effigy/class_list_spec.rb
|
90
|
+
- spec/effigy/core_ext/hash_spec.rb
|
91
|
+
- spec/effigy/core_ext/object_spec.rb
|
74
92
|
- spec/effigy/errors_spec.rb
|
75
93
|
- spec/effigy/rails/template_handler_spec.rb
|
94
|
+
- spec/effigy/selection_spec.rb
|
76
95
|
- spec/effigy/view_spec.rb
|
77
96
|
- spec/rails/generators/effigy_view_spec.rb
|