pivotal-erector 0.5.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.txt +81 -0
- data/VERSION.yml +4 -0
- data/bin/erect +7 -0
- data/lib/erector/erect.rb +148 -0
- data/lib/erector/erected.rb +63 -0
- data/lib/erector/extensions/object.rb +18 -0
- data/lib/erector/indenting.rb +36 -0
- data/lib/erector/rails/extensions/action_controller/1.2.5/action_controller.rb +17 -0
- data/lib/erector/rails/extensions/action_controller/2.2.0/action_controller.rb +26 -0
- data/lib/erector/rails/extensions/action_controller.rb +8 -0
- data/lib/erector/rails/extensions/action_view.rb +21 -0
- data/lib/erector/rails/extensions/widget/1.2.5/widget.rb +18 -0
- data/lib/erector/rails/extensions/widget/2.2.0/widget.rb +36 -0
- data/lib/erector/rails/extensions/widget.rb +117 -0
- data/lib/erector/rails/supported_rails_versions.rb +14 -0
- data/lib/erector/rails/template_handlers/1.2.5/action_view_template_handler.rb +32 -0
- data/lib/erector/rails/template_handlers/2.0.0/action_view_template_handler.rb +36 -0
- data/lib/erector/rails/template_handlers/2.1.0/action_view_template_handler.rb +31 -0
- data/lib/erector/rails/template_handlers/2.2.0/action_view_template_handler.rb +46 -0
- data/lib/erector/rails/template_handlers/action_view_template_handler.rb +14 -0
- data/lib/erector/rails.rb +6 -0
- data/lib/erector/raw_string.rb +8 -0
- data/lib/erector/rhtml.treetop +156 -0
- data/lib/erector/unicode.rb +18185 -0
- data/lib/erector/unicode_builder.rb +67 -0
- data/lib/erector/version.rb +10 -0
- data/lib/erector/widget.rb +510 -0
- data/lib/erector/widgets/table.rb +96 -0
- data/lib/erector/widgets.rb +2 -0
- data/lib/erector.rb +16 -0
- data/spec/core_spec_suite.rb +3 -0
- data/spec/erect/erect_spec.rb +145 -0
- data/spec/erect/erected_spec.rb +80 -0
- data/spec/erect/rhtml_parser_spec.rb +318 -0
- data/spec/erector/indentation_spec.rb +136 -0
- data/spec/erector/unicode_builder_spec.rb +75 -0
- data/spec/erector/widget_spec.rb +657 -0
- data/spec/erector/widgets/table_spec.rb +99 -0
- data/spec/rails_spec_suite.rb +3 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/spec_suite.rb +45 -0
- metadata +118 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
# Note that this class is only used in building erector itself
|
2
|
+
# (and even then, only needs to be run when there is a new
|
3
|
+
# UnicodeData.txt file from unicode.org).
|
4
|
+
class Erector::UnicodeBuilder
|
5
|
+
|
6
|
+
def initialize(input, output)
|
7
|
+
@input = input
|
8
|
+
@output = output
|
9
|
+
@first = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate()
|
13
|
+
@output.puts "Erector::CHARACTERS = {"
|
14
|
+
process_file
|
15
|
+
@output.puts "}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_file()
|
19
|
+
while !@input.eof
|
20
|
+
line = @input.gets.strip
|
21
|
+
if (line == "")
|
22
|
+
next;
|
23
|
+
end
|
24
|
+
|
25
|
+
process_line(line)
|
26
|
+
end
|
27
|
+
if (!@first)
|
28
|
+
@output.puts
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def output_line(line)
|
33
|
+
if (!@first)
|
34
|
+
@output.puts(',')
|
35
|
+
end
|
36
|
+
|
37
|
+
@output.print(line)
|
38
|
+
|
39
|
+
@first = false
|
40
|
+
end
|
41
|
+
|
42
|
+
def process_line(line)
|
43
|
+
fields = line.split(';')
|
44
|
+
code_point = fields[0]
|
45
|
+
name = fields[1]
|
46
|
+
alternate_name = fields[10]
|
47
|
+
|
48
|
+
if /^</.match(name)
|
49
|
+
return ""
|
50
|
+
end
|
51
|
+
|
52
|
+
output name, code_point
|
53
|
+
if (!alternate_name.nil? && alternate_name != "")
|
54
|
+
output alternate_name, code_point
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def output(name, code_point)
|
59
|
+
output_line " :#{namify(name)} => 0x#{code_point.downcase}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def namify(name)
|
63
|
+
name.downcase.gsub(/[- ]/, '_')
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
##
|
2
|
+
# Erector view framework
|
3
|
+
module Erector
|
4
|
+
if !Erector.const_defined?(:VERSION)
|
5
|
+
dir = File.dirname(__FILE__)
|
6
|
+
version = YAML.load_file(File.expand_path("#{dir}/../../VERSION.yml"))
|
7
|
+
VERSION = "#{version['major']}.#{version['minor']}.#{version['patch']}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
@@ -0,0 +1,510 @@
|
|
1
|
+
module Erector
|
2
|
+
|
3
|
+
# A Widget is the center of the Erector universe.
|
4
|
+
#
|
5
|
+
# To create a widget, extend Erector::Widget and implement
|
6
|
+
# the +render+ method. Inside this method you may call any of the tag methods like +span+ or +p+ to emit HTML/XML
|
7
|
+
# tags.
|
8
|
+
#
|
9
|
+
# You can also define a widget on the fly by passing a block to +new+. This block will get executed when the widget's
|
10
|
+
# +render+ method is called.
|
11
|
+
#
|
12
|
+
# To render a widget from the outside, instantiate it and call its +to_s+ method.
|
13
|
+
#
|
14
|
+
# To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
|
15
|
+
# its +render_to+ method, passing in +self+ (or self.output if you prefer). This assures that the same output
|
16
|
+
# is used, which gives better performance than using +capture+ or +to_s+.
|
17
|
+
#
|
18
|
+
# In this documentation we've tried to keep the distinction clear between methods that *emit* text and those that
|
19
|
+
# *return* text. "Emit" means that it writes to the output stream; "return" means that it returns a string
|
20
|
+
# like a normal method and leaves it up to the caller to emit that string if it wants.
|
21
|
+
class Widget
|
22
|
+
class << self
|
23
|
+
def all_tags
|
24
|
+
Erector::Widget.full_tags + Erector::Widget.empty_tags
|
25
|
+
end
|
26
|
+
|
27
|
+
# tags which are always self-closing
|
28
|
+
def empty_tags
|
29
|
+
['area', 'base', 'br', 'col', 'frame',
|
30
|
+
'hr', 'img', 'input', 'link', 'meta']
|
31
|
+
end
|
32
|
+
|
33
|
+
# tags which can contain other stuff
|
34
|
+
def full_tags
|
35
|
+
[
|
36
|
+
'a', 'abbr', 'acronym', 'address',
|
37
|
+
'b', 'bdo', 'big', 'blockquote', 'body', 'button',
|
38
|
+
'caption', 'center', 'cite', 'code', 'colgroup',
|
39
|
+
'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em',
|
40
|
+
'fieldset', 'form', 'frameset',
|
41
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'i',
|
42
|
+
'iframe', 'ins', 'kbd', 'label', 'legend', 'li', 'map',
|
43
|
+
'noframes', 'noscript',
|
44
|
+
'object', 'ol', 'optgroup', 'option', 'p', 'param', 'pre',
|
45
|
+
'q', 's',
|
46
|
+
'samp', 'script', 'select', 'small', 'span', 'strike',
|
47
|
+
'strong', 'style', 'sub', 'sup',
|
48
|
+
'table', 'tbody', 'td', 'textarea', 'tfoot',
|
49
|
+
'th', 'thead', 'title', 'tr', 'tt', 'u', 'ul', 'var'
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
def after_initialize(instance=nil, &blk)
|
54
|
+
if blk
|
55
|
+
after_initialize_parts << blk
|
56
|
+
elsif instance
|
57
|
+
if superclass.respond_to?(:after_initialize)
|
58
|
+
superclass.after_initialize instance
|
59
|
+
end
|
60
|
+
after_initialize_parts.each do |part|
|
61
|
+
instance.instance_eval &part
|
62
|
+
end
|
63
|
+
else
|
64
|
+
raise ArgumentError, "You must provide either an instance or a block"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
def after_initialize_parts
|
70
|
+
@after_initialize_parts ||= []
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
@@prettyprint_default = false
|
75
|
+
def prettyprint_default
|
76
|
+
@@prettyprint_default
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.prettyprint_default=(enabled)
|
80
|
+
@@prettyprint_default = enabled
|
81
|
+
end
|
82
|
+
|
83
|
+
NON_NEWLINEY = {'i' => true, 'b' => true, 'small' => true,
|
84
|
+
'img' => true, 'span' => true, 'a' => true,
|
85
|
+
'input' => true, 'textarea' => true, 'button' => true, 'select' => true
|
86
|
+
}
|
87
|
+
|
88
|
+
SPACES_PER_INDENT = 2
|
89
|
+
|
90
|
+
attr_reader :helpers, :assigns, :block, :parent, :output
|
91
|
+
attr_accessor :enable_prettyprint
|
92
|
+
|
93
|
+
def initialize(helpers=nil, assigns={}, output = "", &block)
|
94
|
+
@assigns = assigns
|
95
|
+
assign_locals(assigns)
|
96
|
+
@helpers = helpers
|
97
|
+
@parent = block ? eval("self", block.binding) : nil
|
98
|
+
@output = output
|
99
|
+
@block = block
|
100
|
+
@at_start_of_line = true
|
101
|
+
@indent = 0
|
102
|
+
@enable_prettyprint = prettyprint_default
|
103
|
+
self.class.after_initialize self
|
104
|
+
end
|
105
|
+
|
106
|
+
#-- methods for other classes to call, left public for ease of testing and documentation
|
107
|
+
#++
|
108
|
+
|
109
|
+
def assign_locals(local_assigns)
|
110
|
+
local_assigns.each do |name, value|
|
111
|
+
instance_variable_set("@#{name}", value)
|
112
|
+
metaclass.module_eval do
|
113
|
+
attr_reader name
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Set whether Erector should add newlines and indentation in to_s.
|
119
|
+
# This is an experimental feature and is subject to change
|
120
|
+
# (either in terms of how it is enabled, or in terms of
|
121
|
+
# what decisions Erector makes about where to add whitespace).
|
122
|
+
# This flag should be set prior to any rendering being done
|
123
|
+
# (for example, calls to to_s or to_pretty).
|
124
|
+
def enable_prettyprint(enable)
|
125
|
+
self.enable_prettyprint = enable
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
# Render (like to_s) but adding newlines and indentation.
|
130
|
+
def to_pretty
|
131
|
+
enable_prettyprint(true).to_s
|
132
|
+
end
|
133
|
+
|
134
|
+
# Entry point for rendering a widget (and all its children). This method creates a new output string,
|
135
|
+
# calls this widget's #render method and returns the string.
|
136
|
+
#
|
137
|
+
# If it's called again later
|
138
|
+
# then it returns the earlier rendered string, which may lead to higher performance, but may have confusing
|
139
|
+
# effects if some underlying state has changed. In general we recommend you create a new instance of every
|
140
|
+
# widget for each render, unless you know what you're doing.
|
141
|
+
def to_s(render_method_name=:render, &blk)
|
142
|
+
# The @__to_s variable is used as a cache.
|
143
|
+
# If it's useful we should add a test for it. -ac
|
144
|
+
return @__to_s if @__to_s
|
145
|
+
send(render_method_name, &blk)
|
146
|
+
@__to_s = output.to_s
|
147
|
+
end
|
148
|
+
|
149
|
+
alias_method :inspect, :to_s
|
150
|
+
|
151
|
+
# Template method which must be overridden by all widget subclasses. Inside this method you call the magic
|
152
|
+
# #element methods which emit HTML and text to the output string.
|
153
|
+
def render
|
154
|
+
if @block
|
155
|
+
instance_eval(&@block)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
|
160
|
+
# its +render_to+ method, passing in +self+ (or self.output if you prefer). This assures that the same output string
|
161
|
+
# is used, which gives better performance than using +capture+ or +to_s+.
|
162
|
+
def render_to(output_or_widget)
|
163
|
+
if output_or_widget.is_a?(Widget)
|
164
|
+
@parent = output_or_widget
|
165
|
+
@output = @parent.output
|
166
|
+
else
|
167
|
+
@output = output_or_widget
|
168
|
+
end
|
169
|
+
render
|
170
|
+
end
|
171
|
+
|
172
|
+
# Convenience method for on-the-fly widgets. This is a way of making
|
173
|
+
# a sub-widget which still has access to the methods of the parent class.
|
174
|
+
# This is an experimental erector feature which may disappear in future
|
175
|
+
# versions of erector (see #widget in widget_spec in the Erector tests).
|
176
|
+
def widget(widget_class, assigns={}, &block)
|
177
|
+
child = widget_class.new(helpers, assigns, output, &block)
|
178
|
+
child.render
|
179
|
+
end
|
180
|
+
|
181
|
+
# (Should we make this hidden?)
|
182
|
+
def html_escape
|
183
|
+
return to_s
|
184
|
+
end
|
185
|
+
|
186
|
+
#-- methods for subclasses to call
|
187
|
+
#++
|
188
|
+
|
189
|
+
# Internal method used to emit an HTML/XML element, including an open tag, attributes (optional, via the default hash),
|
190
|
+
# contents (also optional), and close tag.
|
191
|
+
#
|
192
|
+
# Using the arcane powers of Ruby, there are magic methods that call +element+ for all the standard
|
193
|
+
# HTML tags, like +a+, +body+, +p+, and so forth. Look at the source of #full_tags for the full list.
|
194
|
+
# Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
|
195
|
+
# us, they're there.
|
196
|
+
#
|
197
|
+
# When calling one of these magic methods, put attributes in the default hash. If there is a string parameter,
|
198
|
+
# then it is used as the contents. If there is a block, then it is executed (yielded), and the string parameter is ignored.
|
199
|
+
# The block will usually be in the scope of the child widget, which means it has access to all the
|
200
|
+
# methods of Widget, which will eventually end up appending text to the +output+ string. See how
|
201
|
+
# elegant it is? Not confusing at all if you don't think about it.
|
202
|
+
#
|
203
|
+
def element(*args, &block)
|
204
|
+
__element__(*args, &block)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Internal method used to emit a self-closing HTML/XML element, including a tag name and optional attributes
|
208
|
+
# (passed in via the default hash).
|
209
|
+
#
|
210
|
+
# Using the arcane powers of Ruby, there are magic methods that call +empty_element+ for all the standard
|
211
|
+
# HTML tags, like +img+, +br+, and so forth. Look at the source of #empty_tags for the full list.
|
212
|
+
# Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
|
213
|
+
# us, they're there.
|
214
|
+
#
|
215
|
+
def empty_element(*args, &block)
|
216
|
+
__empty_element__(*args, &block)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Returns an HTML-escaped version of its parameter. Leaves the output string untouched. Note that
|
220
|
+
# the #text method automatically HTML-escapes its parameter, so be careful *not* to do something like
|
221
|
+
# text(h("2<4")) since that will double-escape the less-than sign (you'll get "2&lt;4" instead of
|
222
|
+
# "2<4").
|
223
|
+
def h(content)
|
224
|
+
content.html_escape
|
225
|
+
end
|
226
|
+
|
227
|
+
# Emits an open tag, comprising '<', tag name, optional attributes, and '>'
|
228
|
+
def open_tag(tag_name, attributes={})
|
229
|
+
indent_for_open_tag(tag_name)
|
230
|
+
@indent += SPACES_PER_INDENT
|
231
|
+
|
232
|
+
output.concat "<#{tag_name}#{format_attributes(attributes)}>"
|
233
|
+
@at_start_of_line = false
|
234
|
+
end
|
235
|
+
|
236
|
+
# Emits text. If a string is passed in, it will be HTML-escaped.
|
237
|
+
# If a widget or the result of calling methods such as raw
|
238
|
+
# is passed in, the HTML will not be HTML-escaped again.
|
239
|
+
# If another kind of object is passed in, the result of calling
|
240
|
+
# its to_s method will be treated as a string would be.
|
241
|
+
def text(value)
|
242
|
+
output.concat(value.html_escape)
|
243
|
+
@at_start_of_line = false
|
244
|
+
nil
|
245
|
+
end
|
246
|
+
|
247
|
+
# Returns text which will *not* be HTML-escaped.
|
248
|
+
def raw(value)
|
249
|
+
RawString.new(value.to_s)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Emits text which will *not* be HTML-escaped. Same effect as text(raw(s))
|
253
|
+
def rawtext(value)
|
254
|
+
text raw(value)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Returns a copy of value with spaces replaced by non-breaking space characters.
|
258
|
+
# With no arguments, return a single non-breaking space.
|
259
|
+
# The output uses the escaping format ' ' since that works
|
260
|
+
# in both HTML and XML (as opposed to ' ' which only works in HTML).
|
261
|
+
def nbsp(value = " ")
|
262
|
+
raw(value.html_escape.gsub(/ /,' '))
|
263
|
+
end
|
264
|
+
|
265
|
+
# Return a character given its unicode code point or unicode name.
|
266
|
+
def character(code_point_or_name)
|
267
|
+
if code_point_or_name.is_a?(Symbol)
|
268
|
+
found = Erector::CHARACTERS[code_point_or_name]
|
269
|
+
if found.nil?
|
270
|
+
raise "Unrecognized character #{code_point_or_name}"
|
271
|
+
end
|
272
|
+
raw("&#x#{sprintf '%x', found};")
|
273
|
+
elsif code_point_or_name.is_a?(Integer)
|
274
|
+
raw("&#x#{sprintf '%x', code_point_or_name};")
|
275
|
+
else
|
276
|
+
raise "Unrecognized argument to character: #{code_point_or_name}"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# Emits a close tag, consisting of '<', tag name, and '>'
|
281
|
+
def close_tag(tag_name)
|
282
|
+
@indent -= SPACES_PER_INDENT
|
283
|
+
indent()
|
284
|
+
|
285
|
+
output.concat("</#{tag_name}>")
|
286
|
+
|
287
|
+
if newliney(tag_name)
|
288
|
+
output.concat "\n"
|
289
|
+
@at_start_of_line = true
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Emits the result of joining the elements in array with the separator.
|
294
|
+
# The array elements and separator can be Erector::Widget objects,
|
295
|
+
# which are rendered, or strings, which are quoted and output.
|
296
|
+
def join(array, separator)
|
297
|
+
first = true
|
298
|
+
array.each do |widget_or_text|
|
299
|
+
if !first
|
300
|
+
text separator
|
301
|
+
end
|
302
|
+
first = false
|
303
|
+
text widget_or_text
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
308
|
+
def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
|
309
|
+
output.concat "<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>"
|
310
|
+
end
|
311
|
+
|
312
|
+
# Creates a whole new output string, executes the block, then converts the output string to a string and
|
313
|
+
# emits it as raw text. If at all possible you should avoid this method since it hurts performance,
|
314
|
+
# and use #render_to instead.
|
315
|
+
def capture(&block)
|
316
|
+
begin
|
317
|
+
original_output = output
|
318
|
+
@output = ""
|
319
|
+
yield
|
320
|
+
raw(output.to_s)
|
321
|
+
ensure
|
322
|
+
@output = original_output
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
full_tags.each do |tag_name|
|
327
|
+
self.class_eval(
|
328
|
+
"def #{tag_name}(*args, &block)\n" <<
|
329
|
+
" __element__('#{tag_name}', *args, &block)\n" <<
|
330
|
+
"end",
|
331
|
+
__FILE__,
|
332
|
+
__LINE__ - 4
|
333
|
+
)
|
334
|
+
end
|
335
|
+
|
336
|
+
empty_tags.each do |tag_name|
|
337
|
+
self.class_eval(
|
338
|
+
"def #{tag_name}(*args, &block)\n" <<
|
339
|
+
" __empty_element__('#{tag_name}', *args, &block)\n" <<
|
340
|
+
"end",
|
341
|
+
__FILE__,
|
342
|
+
__LINE__ - 4
|
343
|
+
)
|
344
|
+
end
|
345
|
+
|
346
|
+
# Emits a javascript block inside a +script+ tag, wrapped in CDATA doohickeys like all the cool JS kids do.
|
347
|
+
def javascript(*args, &block)
|
348
|
+
if args.length > 2
|
349
|
+
raise ArgumentError, "Cannot accept more than two arguments"
|
350
|
+
end
|
351
|
+
attributes, value = nil, nil
|
352
|
+
arg0 = args[0]
|
353
|
+
if arg0.is_a?(Hash)
|
354
|
+
attributes = arg0
|
355
|
+
else
|
356
|
+
value = arg0
|
357
|
+
arg1 = args[1]
|
358
|
+
if arg1.is_a?(Hash)
|
359
|
+
attributes = arg1
|
360
|
+
end
|
361
|
+
end
|
362
|
+
attributes ||= {}
|
363
|
+
attributes[:type] = "text/javascript"
|
364
|
+
open_tag 'script', attributes
|
365
|
+
|
366
|
+
# Shouldn't this be a "cdata" HtmlPart?
|
367
|
+
# (maybe, but the syntax is specific to javascript; it isn't
|
368
|
+
# really a generic XML CDATA section. Specifically,
|
369
|
+
# ]]> within value is not treated as ending the
|
370
|
+
# CDATA section by Firefox2 when parsing text/html,
|
371
|
+
# although I guess we could refuse to generate ]]>
|
372
|
+
# there, for the benefit of XML/XHTML parsers).
|
373
|
+
rawtext "\n// <![CDATA[\n"
|
374
|
+
if block
|
375
|
+
instance_eval(&block)
|
376
|
+
else
|
377
|
+
rawtext value
|
378
|
+
end
|
379
|
+
rawtext "\n// ]]>\n"
|
380
|
+
|
381
|
+
close_tag 'script'
|
382
|
+
rawtext "\n"
|
383
|
+
end
|
384
|
+
|
385
|
+
# Convenience method to emit a css file link, which looks like this: <link href="erector.css" rel="stylesheet" type="text/css" />
|
386
|
+
# The parameter is the full contents of the href attribute, including any ".css" extension.
|
387
|
+
#
|
388
|
+
# If you want to emit raw CSS inline, use the #script method instead.
|
389
|
+
def css(href)
|
390
|
+
link :rel => 'stylesheet', :type => 'text/css', :href => href
|
391
|
+
end
|
392
|
+
|
393
|
+
# Convenience method to emit an anchor tag whose href and text are the same, e.g. <a href="http://example.com">http://example.com</a>
|
394
|
+
def url(href)
|
395
|
+
a href, :href => href
|
396
|
+
end
|
397
|
+
|
398
|
+
def newliney(tag_name)
|
399
|
+
if @enable_prettyprint
|
400
|
+
!NON_NEWLINEY.include?(tag_name)
|
401
|
+
else
|
402
|
+
false
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
### internal utility methods
|
407
|
+
|
408
|
+
protected
|
409
|
+
|
410
|
+
# This is part of the sub-widget/parent feature (see #widget method).
|
411
|
+
def method_missing(name, *args, &block)
|
412
|
+
block ||= lambda {} # captures self HERE
|
413
|
+
if @parent
|
414
|
+
@parent.send(name, *args, &block)
|
415
|
+
else
|
416
|
+
super
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def __element__(tag_name, *args, &block)
|
421
|
+
if args.length > 2
|
422
|
+
raise ArgumentError, "Cannot accept more than three arguments"
|
423
|
+
end
|
424
|
+
attributes, value = nil, nil
|
425
|
+
arg0 = args[0]
|
426
|
+
if arg0.is_a?(Hash)
|
427
|
+
attributes = arg0
|
428
|
+
else
|
429
|
+
value = arg0
|
430
|
+
arg1 = args[1]
|
431
|
+
if arg1.is_a?(Hash)
|
432
|
+
attributes = arg1
|
433
|
+
end
|
434
|
+
end
|
435
|
+
attributes ||= {}
|
436
|
+
open_tag tag_name, attributes
|
437
|
+
if block
|
438
|
+
instance_eval(&block)
|
439
|
+
else
|
440
|
+
text value
|
441
|
+
end
|
442
|
+
close_tag tag_name
|
443
|
+
end
|
444
|
+
|
445
|
+
def __empty_element__(tag_name, attributes={})
|
446
|
+
indent_for_open_tag(tag_name)
|
447
|
+
|
448
|
+
output.concat "<#{tag_name}#{format_attributes(attributes)} />"
|
449
|
+
|
450
|
+
if newliney(tag_name)
|
451
|
+
output.concat "\n"
|
452
|
+
@at_start_of_line = true
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
def indent_for_open_tag(tag_name)
|
457
|
+
if !@at_start_of_line && newliney(tag_name)
|
458
|
+
output.concat "\n"
|
459
|
+
@at_start_of_line = true
|
460
|
+
end
|
461
|
+
|
462
|
+
indent()
|
463
|
+
end
|
464
|
+
|
465
|
+
def indent()
|
466
|
+
if @at_start_of_line
|
467
|
+
output.concat " " * @indent
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def format_attributes(attributes)
|
472
|
+
if !attributes || attributes.empty?
|
473
|
+
""
|
474
|
+
else
|
475
|
+
format_sorted(sorted(attributes))
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
def format_sorted(sorted)
|
480
|
+
results = ['']
|
481
|
+
sorted.each do |key, value|
|
482
|
+
if value
|
483
|
+
if value.is_a?(Array)
|
484
|
+
value = [value].flatten.join(' ')
|
485
|
+
end
|
486
|
+
results << "#{key}=\"#{value.html_escape}\""
|
487
|
+
end
|
488
|
+
end
|
489
|
+
return results.join(' ')
|
490
|
+
end
|
491
|
+
|
492
|
+
def sorted(attributes)
|
493
|
+
stringized = []
|
494
|
+
attributes.each do |key, value|
|
495
|
+
stringized << [key.to_s, value]
|
496
|
+
end
|
497
|
+
return stringized.sort
|
498
|
+
end
|
499
|
+
|
500
|
+
def sort_for_xml_declaration(attributes)
|
501
|
+
# correct order is "version, encoding, standalone" (XML 1.0 section 2.8).
|
502
|
+
# But we only try to put version before encoding for now.
|
503
|
+
stringized = []
|
504
|
+
attributes.each do |key, value|
|
505
|
+
stringized << [key.to_s, value]
|
506
|
+
end
|
507
|
+
return stringized.sort{|a, b| b <=> a}
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Erector
|
2
|
+
module Widgets #:nodoc:
|
3
|
+
# The Table widget provides the ability to render a table from a
|
4
|
+
# list of objects (one for each row).
|
5
|
+
#
|
6
|
+
# Because the default for the column titles utilizes the ActiveSupport
|
7
|
+
# Inflector#titleize method, this widget requires active_support to be loaded.
|
8
|
+
#
|
9
|
+
# class UsersTable < Erector::Widgets::Table
|
10
|
+
# column :first_name
|
11
|
+
# column :last_name
|
12
|
+
# column :email
|
13
|
+
# row_classes :even, :odd
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# render_widget UsersTable, :row_objects => [user_1, user_2, user_3]
|
17
|
+
class Table < Erector::Widget
|
18
|
+
ColumnDefinition = Struct.new(:id, :name, :cell_proc)
|
19
|
+
class << self
|
20
|
+
# Define a column, optionally specifying the name (the heading
|
21
|
+
# that the user sees) and a block which renders the cell given
|
22
|
+
# a row object. If the block is not specified, the cell contains
|
23
|
+
# the result of calling a method whose name is id.
|
24
|
+
#
|
25
|
+
# The name can be a string or a proc.
|
26
|
+
def column(id, name=id.to_s.titleize, &cell_proc)
|
27
|
+
cell_proc ||= proc {|object| text object.__send__(id)}
|
28
|
+
column_definitions << ColumnDefinition.new(id, name, cell_proc)
|
29
|
+
end
|
30
|
+
|
31
|
+
def column_definitions #:nodoc:
|
32
|
+
@column_definitions ||= []
|
33
|
+
end
|
34
|
+
|
35
|
+
# A list of HTML classes to apply to the rows in turn. After the
|
36
|
+
# list is exhausted, start again at the start. The most
|
37
|
+
# common use for this is to specify one class for odd rows
|
38
|
+
# and a different class for even rows.
|
39
|
+
def row_classes(*row_classes)
|
40
|
+
@row_class_list = row_classes
|
41
|
+
end
|
42
|
+
attr_reader :row_class_list
|
43
|
+
end
|
44
|
+
|
45
|
+
# The standard erector render method.
|
46
|
+
def render
|
47
|
+
table do
|
48
|
+
thead do
|
49
|
+
tr do
|
50
|
+
column_definitions.each do |column_def|
|
51
|
+
th do
|
52
|
+
if column_def.name.is_a?(Proc)
|
53
|
+
self.instance_exec(column_def.id, &column_def.name)
|
54
|
+
else
|
55
|
+
text column_def.name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
tbody do
|
62
|
+
@row_objects.each_with_index do |object, index|
|
63
|
+
row object, index
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
def row(object, index) #:nodoc:
|
71
|
+
tr(:class => row_css_class(object, index)) do
|
72
|
+
column_definitions.each do |column_def|
|
73
|
+
td do
|
74
|
+
self.instance_exec(object, &column_def.cell_proc)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# You can override this method to provide a class for a row
|
81
|
+
# (as an alternative to calling row_classes).
|
82
|
+
def row_css_class(object, index)
|
83
|
+
cycle(index)
|
84
|
+
end
|
85
|
+
|
86
|
+
def column_definitions #:nodoc:
|
87
|
+
self.class.column_definitions
|
88
|
+
end
|
89
|
+
|
90
|
+
def cycle(index) #:nodoc:
|
91
|
+
list = self.class.row_class_list
|
92
|
+
list ? list[index % list.length] : ''
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/erector.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
dir = File.dirname(__FILE__)
|
3
|
+
require 'cgi'
|
4
|
+
require 'yaml'
|
5
|
+
require "active_support/inflector"
|
6
|
+
require "active_support/inflections"
|
7
|
+
require "#{dir}/erector/extensions/object"
|
8
|
+
require "#{dir}/erector/raw_string"
|
9
|
+
require "#{dir}/erector/widget"
|
10
|
+
require "#{dir}/erector/unicode"
|
11
|
+
require "#{dir}/erector/widgets"
|
12
|
+
require "#{dir}/erector/version"
|
13
|
+
if Object.const_defined?(:RAILS_ROOT)
|
14
|
+
require "#{dir}/erector/rails"
|
15
|
+
end
|
16
|
+
|