pivotal-erector 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/README.txt +81 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/erect +7 -0
  4. data/lib/erector/erect.rb +148 -0
  5. data/lib/erector/erected.rb +63 -0
  6. data/lib/erector/extensions/object.rb +18 -0
  7. data/lib/erector/indenting.rb +36 -0
  8. data/lib/erector/rails/extensions/action_controller/1.2.5/action_controller.rb +17 -0
  9. data/lib/erector/rails/extensions/action_controller/2.2.0/action_controller.rb +26 -0
  10. data/lib/erector/rails/extensions/action_controller.rb +8 -0
  11. data/lib/erector/rails/extensions/action_view.rb +21 -0
  12. data/lib/erector/rails/extensions/widget/1.2.5/widget.rb +18 -0
  13. data/lib/erector/rails/extensions/widget/2.2.0/widget.rb +36 -0
  14. data/lib/erector/rails/extensions/widget.rb +117 -0
  15. data/lib/erector/rails/supported_rails_versions.rb +14 -0
  16. data/lib/erector/rails/template_handlers/1.2.5/action_view_template_handler.rb +32 -0
  17. data/lib/erector/rails/template_handlers/2.0.0/action_view_template_handler.rb +36 -0
  18. data/lib/erector/rails/template_handlers/2.1.0/action_view_template_handler.rb +31 -0
  19. data/lib/erector/rails/template_handlers/2.2.0/action_view_template_handler.rb +46 -0
  20. data/lib/erector/rails/template_handlers/action_view_template_handler.rb +14 -0
  21. data/lib/erector/rails.rb +6 -0
  22. data/lib/erector/raw_string.rb +8 -0
  23. data/lib/erector/rhtml.treetop +156 -0
  24. data/lib/erector/unicode.rb +18185 -0
  25. data/lib/erector/unicode_builder.rb +67 -0
  26. data/lib/erector/version.rb +10 -0
  27. data/lib/erector/widget.rb +510 -0
  28. data/lib/erector/widgets/table.rb +96 -0
  29. data/lib/erector/widgets.rb +2 -0
  30. data/lib/erector.rb +16 -0
  31. data/spec/core_spec_suite.rb +3 -0
  32. data/spec/erect/erect_spec.rb +145 -0
  33. data/spec/erect/erected_spec.rb +80 -0
  34. data/spec/erect/rhtml_parser_spec.rb +318 -0
  35. data/spec/erector/indentation_spec.rb +136 -0
  36. data/spec/erector/unicode_builder_spec.rb +75 -0
  37. data/spec/erector/widget_spec.rb +657 -0
  38. data/spec/erector/widgets/table_spec.rb +99 -0
  39. data/spec/rails_spec_suite.rb +3 -0
  40. data/spec/spec_helper.rb +54 -0
  41. data/spec/spec_suite.rb +45 -0
  42. 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&amp;lt;4" instead of
222
+ # "2&lt;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 '&#160;' since that works
260
+ # in both HTML and XML (as opposed to '&nbsp;' which only works in HTML).
261
+ def nbsp(value = " ")
262
+ raw(value.html_escape.gsub(/ /,'&#160;'))
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
@@ -0,0 +1,2 @@
1
+ require "#{File.dirname(__FILE__)}/widgets/table"
2
+
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
+