<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>config.yml</filename>
    </added>
    <added>
      <filename>test/border_test.rb</filename>
    </added>
    <added>
      <filename>test/cell_test.rb</filename>
    </added>
    <added>
      <filename>test/collection_table_test.rb</filename>
    </added>
    <added>
      <filename>test/row_test.rb</filename>
    </added>
    <added>
      <filename>test/test_helper.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,4 +1,28 @@
-TableHelper
-===========
+= table_helper
+
+table_helper adds a helper method for generating HTML tables from collections.
+
+== Resources
+
+Wiki
+
+* http://wiki.pluginaweek.org/Table_helper
+
+Announcement
+
+* http://www.pluginaweek.org/
+
+Source
+
+* http://svn.pluginaweek.org/trunk/plugins/action_pack/table_helper
+
+Development
+
+* http://dev.pluginaweek.org/browser/trunk/plugins/action_pack/table_helper
+
+== Description
+
+
+
+== Testing
 
-Description goes here
\ No newline at end of file</diff>
      <filename>README</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,12 @@
-require 'rake'
 require 'rake/testtask'
 require 'rake/rdoctask'
+require 'rake/gempackagetask'
+require 'rake/contrib/sshpublisher'
+
+PKG_NAME           = 'table_helper'
+PKG_VERSION        = '0.0.1'
+PKG_FILE_NAME      = &quot;#{PKG_NAME}-#{PKG_VERSION}&quot;
+RUBY_FORGE_PROJECT = 'pluginaweek'
 
 desc 'Default: run unit tests.'
 task :default =&gt; :test
@@ -20,3 +26,56 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
   rdoc.rdoc_files.include('README')
   rdoc.rdoc_files.include('lib/**/*.rb')
 end
+
+spec = Gem::Specification.new do |s|
+  s.name            = PKG_NAME
+  s.version         = PKG_VERSION
+  s.platform        = Gem::Platform::RUBY
+  s.summary         = 'Adds a helper method for generating HTML tables from collections'
+  
+  s.files           = FileList['{lib,tasks,test}/**/*'].to_a + %w(init.rb MIT-LICENSE Rakefile README)
+  s.require_path    = 'lib'
+  s.autorequire     = 'table_helper'
+  s.has_rdoc        = true
+  s.test_files      = Dir['test/**/*_test.rb']
+  
+  s.author          = 'Aaron Pfeifer and Neil Abraham'
+  s.email           = 'info@pluginaweek.org'
+  s.homepage        = 'http://www.pluginaweek.org'
+end
+  
+Rake::GemPackageTask.new(spec) do |p|
+  p.gem_spec = spec
+  p.need_tar = true
+  p.need_zip = true
+end
+
+desc 'Publish the beta gem'
+task :pgem =&gt; [:package] do
+  Rake::SshFilePublisher.new('pluginaweek@pluginaweek.org', '/home/pluginaweek/gems.pluginaweek.org/gems', 'pkg', &quot;#{PKG_FILE_NAME}.gem&quot;).upload
+end
+
+desc 'Publish the API documentation'
+task :pdoc =&gt; [:rdoc] do
+  Rake::SshDirPublisher.new('pluginaweek@pluginaweek.org', &quot;/home/pluginaweek/api.pluginaweek.org/#{PKG_NAME}&quot;, 'rdoc').upload
+end
+
+desc 'Publish the API docs and gem'
+task :publish =&gt; [:pdoc, :release]
+
+desc 'Publish the release files to RubyForge.'
+task :release =&gt; [:gem, :package] do
+  require 'rubyforge'
+
+  options = {'cookie_jar' =&gt; RubyForge::COOKIE_F}
+  options['password'] = ENV['RUBY_FORGE_PASSWORD'] if ENV['RUBY_FORGE_PASSWORD']
+  ruby_forge = RubyForge.new(&quot;./config.yml&quot;, options)
+  ruby_forge.login
+
+  %w( gem tgz zip ).each do |ext|
+    file = &quot;pkg/#{PKG_FILE_NAME}.#{ext}&quot;
+    puts &quot;Releasing #{File.basename(file)}...&quot;
+    
+    ruby_forge.add_release(RUBY_FORGE_PROJECT, PKG_NAME, PKG_VERSION, file)
+  end
+end
\ No newline at end of file</diff>
      <filename>Rakefile</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1 @@
-require 'set_or_append'
-
 require 'table_helper'
\ No newline at end of file</diff>
      <filename>init.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,5 @@
+require 'set_or_append'
+
 module PluginAWeek #:nodoc:
   module Helpers #:nodoc:
     # Provides a set of methods for working with tables and especially tables
@@ -25,7 +27,7 @@ module PluginAWeek #:nodoc:
     # 
     # ...is compiled to (formatted here for the sake of sanity):
     # 
-    #  &lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;summary alternate&quot; id=&quot;posts&quot;&gt;
+    #  &lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;alternate summary&quot; id=&quot;posts&quot;&gt;
     #  &lt;thead&gt;
     #    &lt;tr class=&quot;header row&quot;&gt;
     #      &lt;th class=&quot;title&quot; scope=&quot;col&quot;&gt;Title&lt;/th&gt;&lt;th class=&quot;category&quot; scope=&quot;col&quot;&gt;Category&lt;/th&gt;
@@ -44,7 +46,7 @@ module PluginAWeek #:nodoc:
     #      &lt;td class=&quot;num_trackbacks&quot;&gt;-&lt;/td&gt;
     #    &lt;/tr&gt;
     #    &lt;tr&gt;&lt;td colspan=&quot;6&quot;&gt;&lt;div class=&quot;horizontal_border&quot;&gt;&lt;!-- --&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
-    #    &lt;tr class=&quot;alt_row&quot;&gt;
+    #    &lt;tr class=&quot;row alternate&quot;&gt;
     #      &lt;td class=&quot;title&quot;&gt;&lt;div class=&quot;wrapped&quot;&gt;5 reasons you should care about Rails&lt;/div&gt;&lt;/td&gt;
     #      &lt;td class=&quot;category&quot;&gt;Rails&lt;/td&gt;&lt;td class=&quot;author&quot;&gt;John Q. Public&lt;/td&gt;
     #      &lt;td class=&quot;publish_date&quot;&gt;21 days&lt;/td&gt;
@@ -61,7 +63,7 @@ module PluginAWeek #:nodoc:
     #      &lt;td class=&quot;num_trackbacks&quot;&gt;-&lt;/td&gt;
     #    &lt;/tr&gt;
     #    &lt;tr&gt;&lt;td colspan=&quot;6&quot;&gt;&lt;div class=&quot;horizontal_border&quot;&gt;&lt;!-- --&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
-    #    &lt;tr class=&quot;alt_row&quot;&gt;
+    #    &lt;tr class=&quot;row alternate&quot;&gt;
     #      &lt;td class=&quot;title&quot;&gt;&lt;div class=&quot;wrapped&quot;&gt;Jumpstart your Rails career at RailsConf 2007&lt;/div&gt;&lt;/td&gt;
     #      &lt;td class=&quot;category&quot;&gt;Conferences&lt;/td&gt;
     #      &lt;td class=&quot;author&quot;&gt;Jane Doe&lt;/td&gt;
@@ -84,7 +86,7 @@ module PluginAWeek #:nodoc:
       # 
       # 
       # Configuration options:
-      # * &lt;tt&gt;alternate&lt;/tt&gt; - If set to :odd or :even, every odd or even-numbered row will have the class 'alt_row' appended to its html attributes, respectively.  Default is nil.
+      # * &lt;tt&gt;alternate&lt;/tt&gt; - If set to :odd or :even, every odd or even-numbered row will have the class 'alternate' appended to its html attributes, respectively.  Default is nil.
       # * &lt;tt&gt;row_border&lt;/tt&gt; - If set to :before, border rows will be generated before each normal row.  If set to :after or true, border rows will be generated after each normal row.  Default is :after.
       # * &lt;tt&gt;header&lt;/tt&gt; - Specify if a header (thead) should be built into the table.  Default is true.
       # * &lt;tt&gt;footer&lt;/tt&gt; - Specify if a footer (tfoot) should be built into the table.  Default is false.
@@ -118,30 +120,36 @@ module PluginAWeek #:nodoc:
         
         class &lt;&lt; self
           # An empty data cell
-          def empty_data
-            @empty_data ||= new('td', :empty, '')
+          def empty_data(class_name = :empty)
+            empty_cell('td', class_name)
           end
           
           # An empty header cell
-          def empty_header
-            @empty_header ||= new('th', :empty, '')
+          def empty_header(class_name = :empty)
+            empty_cell('th', class_name)
           end
-        end
-        
-        def initialize(tag_name, class_name, content = class_name.titleize, html_options = {}) #:nodoc
-          html_options.set_or_append(:class, class_name.to_s)
           
-          @tag_name, @content, @html_options = tag_name, content, html_options
+          private
+          def empty_cell(tag_name, class_name) #:nodoc:
+            html_options = {}
+            html_options[:class] = 'empty' if class_name.to_sym != :empty
+            
+            new(tag_name, class_name, '', html_options)
+          end
         end
         
-        # Gets the html option for the cell
-        def [](option)
-          @html_options[option.to_sym]
-        end
+        # The collection of options to use in the cell's html
+        attr_reader :html_options
+        
+        delegate    :[],
+                    :[]=,
+                      :to =&gt; :html_options
         
-        # Sets the html option for the cell
-        def []=(option, value)
-          @html_options[option.to_sym] = value
+        def initialize(tag_name, class_name, content = class_name.to_s.titleize, html_options = {}) #:nodoc
+          @html_options = html_options.symbolize_keys
+          @html_options.set_or_prepend(:class, class_name.to_s)
+          
+          @tag_name, @content = tag_name, content
         end
         
         # Builds the html representation of the cell
@@ -161,31 +169,29 @@ module PluginAWeek #:nodoc:
         # The collection of cells in the order that they will be rendered
         attr_reader :cells
         
+        # The collection of options to use in the row's html
+        attr_reader :html_options
+        
+        delegate    :[],
+                    :[]=,
+                      :to =&gt; :html_options
+        
         # Configuration options:
         # &lt;tt&gt;alternate&lt;/tt&gt; - Specify if this is an alternating row.  Default is false.
         # &lt;tt&gt;border&lt;/tt&gt; - Specify whether a border should be used.  Default is nil (no border).
-        def initialize(options = {}) #:nodoc:
+        def initialize(options = {}, html_options = {}) #:nodoc:
           options.assert_valid_keys(
             :alternate,
             :border
           )
+          
           @options = {
             :alternate =&gt; false
           }.update(options)
           
           @border = Border.new
           @cells = ActiveSupport::OrderedHash.new
-          @html_options = {}
-        end
-        
-        # Gets the html option for the row
-        def [](option)
-          @html_options[option.to_sym]
-        end
-        
-        # Sets the html option for the row
-        def []=(option, value)
-          @html_options[option.to_sym] = value
+          @html_options = html_options
         end
         
         # Builds a new data cell (i.e. &lt;td&gt;).  The class name will be
@@ -199,8 +205,8 @@ module PluginAWeek #:nodoc:
         # ...would generate the following tag:
         # 
         #   &lt;td class=&quot;author&quot;&gt;John Doe&lt;/td&gt;
-        def cell(class_name, content = '', html_options = {})
-          @cells[class_name] = Cell.new('td', class_name, content, html_options)
+        def cell(class_name, *args)
+          @cells[class_name] = Cell.new('td', class_name, *args)
         end
         
         # Builds a new header cell (i.e. &lt;th&gt;).  The class name will be
@@ -214,8 +220,8 @@ module PluginAWeek #:nodoc:
         # ...would generate the following tag:
         # 
         #   &lt;th class=&quot;title author&quot;&gt;Author Name&lt;/th&gt;
-        def header(class_name, content = '', html_options = {})
-          @cells[class_name] = Cell.new('th', class_name, content, html_options)
+        def header(class_name, *args)
+          @cells[class_name] = Cell.new('th', class_name, *args)
         end
         
         # Builds the row, including the border if it was specified to be used.
@@ -234,7 +240,8 @@ module PluginAWeek #:nodoc:
               if cell = @cells[class_name]
                 number_to_skip = (cell[:colspan] || 1) - 1
               else
-                cell = Cell.empty_data
+                cell = Cell.empty_data.dup
+                cell.html_options.set_or_prepend(:class, class_name)
               end
               
               html &lt;&lt; cell.build
@@ -246,7 +253,8 @@ module PluginAWeek #:nodoc:
           end
           
           options = @html_options.dup
-          options.set_or_append(:class, @options[:alternate] ? 'alt_row' : 'row')
+          options.set_or_prepend(:class, 'alternate') if @options[:alternate]
+          options.set_or_prepend(:class, 'row')
           html = content_tag('tr', html, options)
           
           if @options[:border] == :before
@@ -261,7 +269,7 @@ module PluginAWeek #:nodoc:
         private
         # Builds the border row and returns the html generated for it
         def build_border(columns = nil)
-          @border.cell[:colspan] = columns.size if columns &amp;&amp; @border.cell[:colspan].nil?
+          @border.cell[:colspan] = columns.size if columns &amp;&amp; columns.size &gt; 1 &amp;&amp; @border.cell[:colspan].nil?
           @border.build
         end
       end
@@ -273,20 +281,14 @@ module PluginAWeek #:nodoc:
       class Border
         include ActionView::Helpers::TagHelper
         
+        delegate  :[],
+                  :[]=,
+                    :to =&gt; :row_options
+        
         def initialize #:nodoc:
           @row_options, @cell_options = {}, {}
         end
         
-        # Gets the html option for the border
-        def [](option)
-          @row_options[option]
-        end
-        
-        # Sets the html option for the border
-        def []=(option, value)
-          @row_options[option] = value
-        end
-        
         # Gets the html options that will be used for the actual td cell within
         # the row
         def cell
@@ -294,6 +296,7 @@ module PluginAWeek #:nodoc:
         end
         
         # Builds the html representation of the border
+        # TODO: Type of border should be customizable
         def build
           html = '&lt;!-- --&gt;'
           html = content_tag('div', html, :class =&gt; 'horizontal_border')
@@ -318,8 +321,6 @@ module PluginAWeek #:nodoc:
         attr_reader :columns
         
         def initialize(collection, options = {}, html_options = {}) #:nodoc:
-          @collection = collection
-          
           options.assert_valid_keys(
             :alternate,
             :row_border,
@@ -328,24 +329,23 @@ module PluginAWeek #:nodoc:
             :no_content_on_empty
           )
           @options = {
-            :alternate =&gt; :odd,
-            :row_border =&gt; :after,
             :header =&gt; true,
             :footer =&gt; false,
             :no_content_on_empty =&gt; true
           }.update(options)
           
-          raise ArgumentError, ':alternate must be set to :odd or :even' if ![:odd, :even].include?(@options[:alternate])
-          raise ArgumentError, ':row_border must be set to :before or :after' if ![:before, :after].include?(@options[:row_border])
+          raise ArgumentError, ':alternate must be set to :odd or :even' if @options[:alternate] &amp;&amp; ![:odd, :even].include?(@options[:alternate])
+          raise ArgumentError, ':row_border must be set to :before or :after' if @options[:row_border] &amp;&amp; ![:before, :after].include?(@options[:row_border])
           
           @html_options = {
             :cellspacing =&gt; '0',
             :cellpadding =&gt; '0'
           }.update(html_options)
-          @html_options.set_or_append(:class, 'alternate') if options[:alternate]
+          @html_options.set_or_prepend(:class, 'alternate') if options[:alternate]
           
+          @collection = collection
           @columns = ActiveSupport::OrderedHash.new
-          @no_content_caption = &quot;Nothing to see here... move along&quot;
+          @no_content_caption = 'No matches found.'
           
           yield self if block_given?
         end
@@ -361,7 +361,7 @@ module PluginAWeek #:nodoc:
         #   end
         # 
         # ...will generate a table with 1 column
-        def column(class_name, caption)
+        def column(class_name, caption = class_name.to_s.titleize)
           @columns[class_name] = caption
         end
         
@@ -370,16 +370,19 @@ module PluginAWeek #:nodoc:
         # 
         # This method expects a block that defines the content within each cell
         # in a row.  See build_body for more information.
-        def build(&amp;block)
-          build_body(true, &amp;block)
+        def build(header_options = {}, header_html_options = {}, &amp;block)
+          build_body(header_options, header_html_options, true, &amp;block)
         end
         
         # Builds the header of the table.  The header row will be wrapped within
         # a thead tag.  If the size of the collection is 0 and :no_content_on_empty
         # was set to true, then the thead will be hidden.
-        def build_header
-          row = Row.new(:border =&gt; (@options[:dotted_header] ? :after : false))
-          row[:class] = 'header'
+        # 
+        # See Row#initialize for the possible configuration options.
+        def build_header(options = {}, html_options = {})
+          html_options.set_or_prepend(:class, 'header')
+          row = Row.new(options, html_options)
+          
           columns.each do |class_name, caption|
             row.header class_name, caption, :scope =&gt; 'col'
           end
@@ -411,13 +414,17 @@ module PluginAWeek #:nodoc:
         # to true, then the actual body will be replaced by a single row
         # containing the html that was stored in the CollectionTable's
         # no_content_caption.
-        def build_body(dump_table = true, &amp;block)
+        def build_body(header_options = {}, header_html_options = {}, dump_table = false, &amp;block)
           html = ''
           
           # Display nothing if there are no objects to display
-          if @collection.size == 0
+          if @collection.size == 0 &amp;&amp; @options[:no_content_on_empty]
             row = Row.new
-            row.cell :no_content, no_content_caption, :colspan =&gt; columns.size
+            
+            html_options = {}
+            html_options[:colspan] = columns.size if columns.size &gt; 1
+            row.cell :no_content, no_content_caption, html_options
+            
             html &lt;&lt; row.build
           else
             @collection.each_with_index do |object, i|
@@ -428,7 +435,7 @@ module PluginAWeek #:nodoc:
           @body = content_tag('tbody', html)
           
           if dump_table
-            content_tag('table', build_header + @body, @html_options)
+            content_tag('table', build_header(header_options, header_html_options) + @body, @html_options)
           end
         end
         
@@ -472,9 +479,11 @@ module PluginAWeek #:nodoc:
         # build_footer should be invoked AFTER build_body.  The result of
         # build_footer should then be used as output to your template.  It will
         # contain the header, body, and footer wrapped inside of a table tag.
-        def build_footer(&amp;block)
-          row = Row.new(:border =&gt; (@options[:dotted_header] ? :after : false))
-          row[:class] = 'footer'
+        # 
+        # See Row#initialize for the possible configuration options.
+        def build_footer(html_options = {}, &amp;block)
+          html_options.set_or_prepend(:class, 'footer')
+          row = Row.new({}, html_options)
           
           yield row
           </diff>
      <filename>lib/table_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,18 @@
-require 'test/unit'
+require File.dirname(__FILE__) + '/test_helper'
 
 class TableHelperTest &lt; Test::Unit::TestCase
-  # Replace this with your real tests.
-  def test_this_plugin
-    flunk
+  include PluginAWeek::Helpers::TableHelper
+  
+  def test_collection_table
+    table = collection_table([])
+    assert_not_nil table
+    assert_instance_of PluginAWeek::Helpers::TableHelper::CollectionTable, table
+  end
+  
+  def test_collection_table_yields
+    table = collection_table([]) do |yielded_table|
+      assert_not_nil yielded_table
+      assert_instance_of PluginAWeek::Helpers::TableHelper::CollectionTable, yielded_table
+    end
   end
 end</diff>
      <filename>test/table_helper_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>b66abeedd2fffc898e444875a69f489a545e01e9</id>
    </parent>
  </parents>
  <author>
    <name>Aaron Pfeifer</name>
    <email>aaron.pfeifer@gmail.com</email>
  </author>
  <url>http://github.com/pluginaweek/table_helper/commit/834015769d6aef162b4a0f4ef2b6978730823f64</url>
  <id>834015769d6aef162b4a0f4ef2b6978730823f64</id>
  <committed-date>2006-12-14T16:31:12-08:00</committed-date>
  <authored-date>2006-12-14T16:31:12-08:00</authored-date>
  <message>Added tests.
Fixed problems with using set_or_append; now uses set_or_prepend.
Refactored how setting and getting of html options is done.
Changed some of the documentation.
Fixed other miscellaneous issues.</message>
  <tree>f0b7e210415845cf8fa792cd85ff79358781e1c8</tree>
  <committer>
    <name>Aaron Pfeifer</name>
    <email>aaron.pfeifer@gmail.com</email>
  </committer>
</commit>
