pivotal-erector 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|