dining-table 0.1.0
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.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +59 -0
- data/LICENSE +21 -0
- data/README.md +298 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/dining-table.gemspec +60 -0
- data/lib/dining-table.rb +12 -0
- data/lib/dining-table/columns/actions_column.rb +36 -0
- data/lib/dining-table/columns/column.rb +55 -0
- data/lib/dining-table/config.rb +35 -0
- data/lib/dining-table/presenters/csv_presenter.rb +41 -0
- data/lib/dining-table/presenters/excel_presenter.rb +28 -0
- data/lib/dining-table/presenters/html_presenter.rb +142 -0
- data/lib/dining-table/presenters/presenter.rb +51 -0
- data/lib/dining-table/presenters/spreadsheet_presenter.rb +54 -0
- data/lib/dining-table/table.rb +69 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d79df74fda15fcb68b6215a45dbdee28c6ddf5d3
|
4
|
+
data.tar.gz: bb73bf1a0edc0fe3c0aeda71acafbc13ae337e65
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 637d781f1ec3417a01f6d1f3004ccc60e8a95b64bef354c96703760259643aab537dfcec0bacef622587fb72a7d9bba8cfa5411cb5c19685c6145ff4ea403b99
|
7
|
+
data.tar.gz: 9ef097bc93ee0492e6bda519020089349da0fb552473f9c74d81da15a4bc0153308707a7a66cd472e48887b45e76ebd8d5d099aee667da1f77275655ded343a2
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.4.0)
|
5
|
+
builder (3.2.2)
|
6
|
+
descendants_tracker (0.0.4)
|
7
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
8
|
+
faraday (0.9.2)
|
9
|
+
multipart-post (>= 1.2, < 3)
|
10
|
+
git (1.3.0)
|
11
|
+
github_api (0.13.1)
|
12
|
+
addressable (~> 2.4.0)
|
13
|
+
descendants_tracker (~> 0.0.4)
|
14
|
+
faraday (~> 0.8, < 0.10)
|
15
|
+
hashie (>= 3.4)
|
16
|
+
multi_json (>= 1.7.5, < 2.0)
|
17
|
+
oauth2
|
18
|
+
hashie (3.4.4)
|
19
|
+
highline (1.7.8)
|
20
|
+
json (1.8.3)
|
21
|
+
juwelier (2.1.1)
|
22
|
+
builder
|
23
|
+
bundler (>= 1.0)
|
24
|
+
git (>= 1.2.5)
|
25
|
+
github_api
|
26
|
+
highline (>= 1.6.15)
|
27
|
+
nokogiri (>= 1.5.10)
|
28
|
+
rake
|
29
|
+
rdoc
|
30
|
+
semver
|
31
|
+
jwt (1.5.1)
|
32
|
+
mini_portile2 (2.0.0)
|
33
|
+
multi_json (1.12.0)
|
34
|
+
multi_xml (0.5.5)
|
35
|
+
multipart-post (2.0.0)
|
36
|
+
nokogiri (1.6.7.2)
|
37
|
+
mini_portile2 (~> 2.0.0.rc2)
|
38
|
+
oauth2 (1.1.0)
|
39
|
+
faraday (>= 0.8, < 0.10)
|
40
|
+
jwt (~> 1.0, < 1.5.2)
|
41
|
+
multi_json (~> 1.3)
|
42
|
+
multi_xml (~> 0.5)
|
43
|
+
rack (>= 1.2, < 3)
|
44
|
+
rack (1.6.4)
|
45
|
+
rake (11.1.2)
|
46
|
+
rdoc (4.2.2)
|
47
|
+
json (~> 1.4)
|
48
|
+
semver (1.0.1)
|
49
|
+
thread_safe (0.3.5)
|
50
|
+
|
51
|
+
PLATFORMS
|
52
|
+
ruby
|
53
|
+
|
54
|
+
DEPENDENCIES
|
55
|
+
bundler (~> 1.0)
|
56
|
+
juwelier (~> 2.1.0)
|
57
|
+
|
58
|
+
BUNDLED WITH
|
59
|
+
1.12.3
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Michaël Van Damme
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
# dining-table
|
2
|
+
|
3
|
+
dining-table was inspired by the (now unfortunately unmaintained) [table_cloth](https://github.com/bobbytables/table_cloth) gem.
|
4
|
+
This gem is definitely not a drop-in replacement for [table-cloth](https://github.com/bobbytables/table_cloth), it aims to be less dependent on Rails
|
5
|
+
(no Rails required to use `dining-table`) and more flexible.
|
6
|
+
In addition, it not only supports HTML output but you can output tabular data in csv or xlsx formats as well.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add the following to your Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'dining-table'
|
14
|
+
```
|
15
|
+
|
16
|
+
## Basic example
|
17
|
+
|
18
|
+
A table is defined by creating a table class (usually placed in app/tables) that inherits from `DiningTable::Table` and implements the `define' method:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
class CarTable < DiningTable::Table
|
22
|
+
def define
|
23
|
+
column :brand
|
24
|
+
column :number_of_doors
|
25
|
+
column :stock
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
In your views, you can now provide a collection of `@cars` and render the table in HTML:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
<%= CarTable.new(@cars, self).render %>
|
34
|
+
```
|
35
|
+
|
36
|
+
By default, a table will have a header with column names, and no footer. There is one row per element in the collection (`@cars` in this example), and rows are rendered in order.
|
37
|
+
|
38
|
+
## Table class
|
39
|
+
|
40
|
+
### Defining columns
|
41
|
+
|
42
|
+
Columns are defined by using `column` in the `define` method of the table class. The content of the cell is determined by calling the
|
43
|
+
corresponding method of the object: for `column :brand`, the `brand` method is called for each car in the collection, and the result is placed in the appropriate cells.
|
44
|
+
If this is not what you want, you can provide a block:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class CarTable < DiningTable::Table
|
48
|
+
def define
|
49
|
+
column :brand do |object|
|
50
|
+
object.brand.upcase
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
### Headers and footers
|
57
|
+
|
58
|
+
When you don't explicitly specify a header, the header is set using `human_attribute_name` (if the objects in the collection respond to that method).
|
59
|
+
You can also manually specify a header:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class CarTable < DiningTable::Table
|
63
|
+
def define
|
64
|
+
column :brand, header: I18n.t('car.brand') do |object|
|
65
|
+
object.brand.upcase
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
The custom header can be a string, but also a lambda or a proc.
|
72
|
+
|
73
|
+
By default, `dining-table` doesn't add a footer to the table, except when at least one column explicitly specifies a footer:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class CarTable < DiningTable::Table
|
77
|
+
def define
|
78
|
+
column :brand
|
79
|
+
column :stock, footer: lambda { "Total: #{ collection.sum(&:stock) }" }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
Please note how the collection passed in when creating the table obect (`@cars` in `CarTable.new(@cars, self)`) is available as `collection`.
|
85
|
+
|
86
|
+
### Links and view helpers
|
87
|
+
|
88
|
+
When rendering the table in a view using `<%= CarTable.new(@cars, self).render %>`, the `self` parameter is the view context. It is made available through the `h`
|
89
|
+
method (or the `helpers` method if you prefer to be more explicit). You can use `h` to get access to methods like Rails' `link_to`, path helpers, and view helpers:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class CarTable < DiningTable::Table
|
93
|
+
def define
|
94
|
+
column :brand do |object|
|
95
|
+
h.link_to( object.brand, h.car_path(object) )
|
96
|
+
end
|
97
|
+
column :stock do |object|
|
98
|
+
h.number_with_delimiter( object.stock )
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
You can also use `h` in headers or footers:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class CarTable < DiningTable::Table
|
108
|
+
def define
|
109
|
+
column :brand, header: h.link_to('Brand', h.some_path)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
When you want to render a table outside of a view (or when rendering csv or xlsx tables, see further) you have the following options:
|
115
|
+
* Pass in `nil` if you don't use the `h` helper in any column: `CarTable.new(@cars, nil).render`
|
116
|
+
* If you do use `h`, pass in an object that responds to the methods you use. This might be a Rails view context, or any other object:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
# in app/tables/car_table.rb
|
120
|
+
class CarTable < DiningTable::Table
|
121
|
+
def define
|
122
|
+
column :brand do |object|
|
123
|
+
h.my_helper( object.brand )
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# somewhere else
|
129
|
+
class FakeViewContext
|
130
|
+
def my_helper(a)
|
131
|
+
a
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# when rendering
|
136
|
+
cars = Car.order(:brand)
|
137
|
+
CarTable.new(cars, FakeViewContext.new).render
|
138
|
+
```
|
139
|
+
|
140
|
+
### Actions
|
141
|
+
|
142
|
+
In the case of HTML tables, one often includes an actions column with links to show, edit, delete, etc. the object. While you can do that using a regular column
|
143
|
+
it is easier using a special actions column:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
class CarTable < DiningTable::Table
|
147
|
+
def define
|
148
|
+
column :brand
|
149
|
+
actions header: I18n.t('shared.action') do |object|
|
150
|
+
action { |object| h.link_to( I18n.t('shared.show'), h.car_path(object) ) }
|
151
|
+
action { |object| h.link_to( I18n.t('shared.edit'), h.edit_car_path(object) ) if object.editable? }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
### Options
|
158
|
+
|
159
|
+
When creating the table object to render, you can pass in an options hash:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
<%= CarTable.new(@cars, self, admin: current_user.admin? ).render %>
|
163
|
+
```
|
164
|
+
|
165
|
+
The passed in options are available as `options` when defining the table. One example of where this is useful is hiding certain columns or actions from non-admin users:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
class CarTable < DiningTable::Table
|
169
|
+
def define
|
170
|
+
admin = options[:admin]
|
171
|
+
|
172
|
+
column :brand
|
173
|
+
column :number_of_doors
|
174
|
+
column :stock if admin
|
175
|
+
actions header: I18n.t('shared.action') do |object|
|
176
|
+
action { |object| h.link_to( I18n.t('shared.show'), h.car_path(object) ) }
|
177
|
+
action { |object| h.link_to( I18n.t('shared.edit'), h.edit_car_path(object) ) if admin }
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
## Presenters
|
184
|
+
|
185
|
+
### HTML
|
186
|
+
|
187
|
+
The default presenter is HTML (i.e. `DiningTable::Presenters::HTMLPresenter`), so `CarTable.new(@cars, self).render` will generate a table in HTML.
|
188
|
+
When defining columns, you can specify options that apply only when using a certain presenter. For example, here we provide css classes for `td` and `th`
|
189
|
+
elements for some columns in the html table:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
class CarTable < DiningTable::Table
|
193
|
+
def define
|
194
|
+
column :brand
|
195
|
+
column :number_of_doors, html: { td_options: { class: 'center' }, th_options: { class: :center } }
|
196
|
+
column :stock, html: { td_options: { class: 'center' }, th_options: { class: :center } }
|
197
|
+
end
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
201
|
+
The same table class can also be used with other presenters (csv, xlsx or a custom presenter), but the options will only be in effect when using the HTML presenter.
|
202
|
+
|
203
|
+
By instantiating the presenter yourself it is possible to specify options. For example:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
<%= CarTable.new(@cars, self, presenter: DiningTable::Presenters::HTMLPresenter.new( class: 'table table-bordered' )).render %>
|
207
|
+
```
|
208
|
+
|
209
|
+
It is also possible to wrap the table in another tag (a div for instance):
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
<%= CarTable.new(@cars, self,
|
213
|
+
presenter: DiningTable::Presenters::HTMLPresenter.new( class: 'table table-bordered',
|
214
|
+
wrap: { tag: :div, class: 'table-responsive' } )).render %>
|
215
|
+
```
|
216
|
+
|
217
|
+
Both of these html options are usually best set as defaults, see [Configuration](#configuration)
|
218
|
+
|
219
|
+
### CSV
|
220
|
+
|
221
|
+
`dining-table` can also generate csv files instead of HTML tables. In order to do that, specify the presenter when instantiating the table object. You could do
|
222
|
+
the following in a Rails controller action, for instance:
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
def export_csv
|
226
|
+
collection = Car.order(:brand)
|
227
|
+
csv = CarTable.new( collection, nil, :presenter => DiningTable::Presenters::CSVPresenter.new ).render
|
228
|
+
send_data( csv, :filename => 'export.csv', :content_type => "text/csv" )
|
229
|
+
end
|
230
|
+
```
|
231
|
+
|
232
|
+
The CSV Presenter uses the CSV class from the Ruby standard library. Options passed in through the :csv key will be passed on to `CSV.new`:
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
csv = CarTable.new( collection, nil, :presenter => DiningTable::Presenters::CSVPresenter.new( csv: { col_sep: ';' } ) ).render
|
236
|
+
```
|
237
|
+
|
238
|
+
CSV options can also be set as defaults, see [Configuration](#configuration)
|
239
|
+
|
240
|
+
It can often be useful to use the same table class with both html and csv presenters. Usually, you don't want the action column in your csv file.
|
241
|
+
You can easilly omit it when the presenter is not HTML:
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
class CarTable < DiningTable::Table
|
245
|
+
def define
|
246
|
+
column :brand
|
247
|
+
actions header: I18n.t('shared.action') do |object|
|
248
|
+
action { |object| h.link_to( I18n.t('shared.show'), h.car_path(object) ) }
|
249
|
+
action { |object| h.link_to( I18n.t('shared.edit'), h.edit_car_path(object) ) }
|
250
|
+
end if presenter.type?(:html)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
```
|
254
|
+
|
255
|
+
Note that you also have access to the `presenter` inside column blocks, so if necessary you can adapt a column's content accordingly:
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
class CarTable < DiningTable::Table
|
259
|
+
def define
|
260
|
+
column :brand do |object|
|
261
|
+
if presenter.type?(:html)
|
262
|
+
h.link_to( object.brand, h.car_path(object) )
|
263
|
+
else
|
264
|
+
object.brand # no link for csv and xlsx
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
```
|
270
|
+
|
271
|
+
### Excel (xlsx)
|
272
|
+
|
273
|
+
The Excel presenter depends on [axlsx](https://github.com/randym/axlsx). Note that `dining-table` doesn't require `axlsx`, you have to add it to
|
274
|
+
your Gemfile yourself if you want to use the Excel presenter.
|
275
|
+
|
276
|
+
In order to use the Excel presenter, pass it in as a presenter and provide an axlsx worksheet:
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
collection = Car.order(:brand)
|
280
|
+
# sheet is the axlsx worksheet in which the table will be rendered
|
281
|
+
CarTable.new( collection, nil, :presenter => DiningTable::Presenters::ExcelPresenter.new( sheet ) ).render
|
282
|
+
```
|
283
|
+
|
284
|
+
## Configuration <a name="configuration"></a>
|
285
|
+
|
286
|
+
You can set default options for the different presenters in an initializer (e.g. `config/initializers/dining-table.rb`):
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
DiningTable.configure do |config|
|
290
|
+
config.html_presenter.default_options = { class: 'table table-bordered table-hover',
|
291
|
+
wrap: { tag: :div, class: 'table-responsive' } }
|
292
|
+
config.csv_presenter.default_options = { :csv => { col_sep: ';' } }
|
293
|
+
end
|
294
|
+
```
|
295
|
+
|
296
|
+
## Copyright
|
297
|
+
|
298
|
+
Copyright (c) 2016 Michaël Van Damme. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'juwelier'
|
15
|
+
Juwelier::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
17
|
+
gem.name = "dining-table"
|
18
|
+
gem.homepage = "http://github.com/mvdamme/dining-table"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Create tables easily. Supports html, csv and xlsx.}
|
21
|
+
gem.description = %Q{Easily output tabular data, be it in HTML, CSV or XLSX. Create clean table classes instead of messing with views to create nice tables.}
|
22
|
+
gem.email = "michael.vandamme@vub.ac.be"
|
23
|
+
gem.authors = ["Micha\u{eb}l Van Damme"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Juwelier::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Code coverage detail"
|
36
|
+
task :simplecov do
|
37
|
+
ENV['COVERAGE'] = "true"
|
38
|
+
Rake::Task['test'].execute
|
39
|
+
end
|
40
|
+
|
41
|
+
task :default => :test
|
42
|
+
|
43
|
+
require 'rdoc/task'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
46
|
+
|
47
|
+
rdoc.rdoc_dir = 'rdoc'
|
48
|
+
rdoc.title = "dining-table #{version}"
|
49
|
+
rdoc.rdoc_files.include('README*')
|
50
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Generated by juwelier
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: dining-table 0.1.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "dining-table"
|
9
|
+
s.version = "0.1.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Micha\u{eb}l Van Damme"]
|
14
|
+
s.date = "2016-07-31"
|
15
|
+
s.description = "Easily output tabular data, be it in HTML, CSV or XLSX. Create clean table classes instead of messing with views to create nice tables."
|
16
|
+
s.email = "michael.vandamme@vub.ac.be"
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"LICENSE",
|
25
|
+
"README.md",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"dining-table.gemspec",
|
29
|
+
"lib/dining-table.rb",
|
30
|
+
"lib/dining-table/columns/actions_column.rb",
|
31
|
+
"lib/dining-table/columns/column.rb",
|
32
|
+
"lib/dining-table/config.rb",
|
33
|
+
"lib/dining-table/presenters/csv_presenter.rb",
|
34
|
+
"lib/dining-table/presenters/excel_presenter.rb",
|
35
|
+
"lib/dining-table/presenters/html_presenter.rb",
|
36
|
+
"lib/dining-table/presenters/presenter.rb",
|
37
|
+
"lib/dining-table/presenters/spreadsheet_presenter.rb",
|
38
|
+
"lib/dining-table/table.rb"
|
39
|
+
]
|
40
|
+
s.homepage = "http://github.com/mvdamme/dining-table"
|
41
|
+
s.licenses = ["MIT"]
|
42
|
+
s.rubygems_version = "2.4.8"
|
43
|
+
s.summary = "Create tables easily. Supports html, csv and xlsx."
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
s.specification_version = 4
|
47
|
+
|
48
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
49
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
50
|
+
s.add_development_dependency(%q<juwelier>, ["~> 2.1.0"])
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
53
|
+
s.add_dependency(%q<juwelier>, ["~> 2.1.0"])
|
54
|
+
end
|
55
|
+
else
|
56
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
57
|
+
s.add_dependency(%q<juwelier>, ["~> 2.1.0"])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
data/lib/dining-table.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'dining-table/config'
|
2
|
+
|
3
|
+
require 'dining-table/table'
|
4
|
+
|
5
|
+
require 'dining-table/columns/column'
|
6
|
+
require 'dining-table/columns/actions_column'
|
7
|
+
|
8
|
+
require 'dining-table/presenters/presenter'
|
9
|
+
require 'dining-table/presenters/html_presenter'
|
10
|
+
require 'dining-table/presenters/spreadsheet_presenter'
|
11
|
+
require 'dining-table/presenters/csv_presenter'
|
12
|
+
require 'dining-table/presenters/excel_presenter'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module DiningTable
|
2
|
+
|
3
|
+
module Columns
|
4
|
+
|
5
|
+
class ActionsColumn < Column
|
6
|
+
|
7
|
+
def value(object)
|
8
|
+
if block
|
9
|
+
@incremental_value = ''
|
10
|
+
@current_object = object
|
11
|
+
self.instance_eval(&block)
|
12
|
+
@incremental_value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def action(&block)
|
19
|
+
action_value = yield(@current_object)
|
20
|
+
@incremental_value += action_value.try(:to_s) if action_value
|
21
|
+
end
|
22
|
+
|
23
|
+
# offer methods normally available on Table that could be used by the action blocks
|
24
|
+
[ :h, :helpers, :collection, :index, :presenter ].each do |method|
|
25
|
+
self.class_eval <<-eos, __FILE__, __LINE__+1
|
26
|
+
def #{method}(*args)
|
27
|
+
table.#{method}
|
28
|
+
end
|
29
|
+
eos
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module DiningTable
|
2
|
+
|
3
|
+
module Columns
|
4
|
+
|
5
|
+
class Column
|
6
|
+
|
7
|
+
attr_accessor :name, :table, :options, :block
|
8
|
+
|
9
|
+
def initialize( table, name, options = {}, &block)
|
10
|
+
self.table = table
|
11
|
+
self.name = name
|
12
|
+
self.options = options
|
13
|
+
self.block = block
|
14
|
+
end
|
15
|
+
|
16
|
+
def value(object)
|
17
|
+
if block
|
18
|
+
block.call(object, self)
|
19
|
+
else
|
20
|
+
object.send(name).try(:to_s) if object.respond_to?(name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def header
|
25
|
+
@header ||= begin
|
26
|
+
label = determine_label(:header)
|
27
|
+
return label if label
|
28
|
+
object_class = table.collection.first.try(:class)
|
29
|
+
object_class.human_attribute_name( name ) if object_class && object_class.respond_to?( :human_attribute_name )
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def footer
|
34
|
+
@footer ||= determine_label(:footer)
|
35
|
+
end
|
36
|
+
|
37
|
+
def options_for(identifier)
|
38
|
+
options[ identifier ] || { }
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def determine_label( name )
|
44
|
+
if options[ name ]
|
45
|
+
label_ = options[ name ]
|
46
|
+
label_ = label_.call if label_.respond_to?(:call)
|
47
|
+
return label_.try(:to_s)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# configuration, see http://robots.thoughtbot.com/mygem-configure-block.
|
2
|
+
module DiningTable
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_accessor :configuration
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.configure
|
9
|
+
self.configuration ||= Configuration.new
|
10
|
+
yield(configuration)
|
11
|
+
end
|
12
|
+
|
13
|
+
class PresenterConfiguration
|
14
|
+
attr_accessor :default_options
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@default_options = { }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Configuration
|
22
|
+
attr_accessor :html_presenter
|
23
|
+
attr_accessor :csv_presenter
|
24
|
+
attr_accessor :excel_presenter
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@html_presenter = PresenterConfiguration.new
|
28
|
+
@csv_presenter = PresenterConfiguration.new
|
29
|
+
@excel_presenter = PresenterConfiguration.new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
SuckerPunch::Backgroundable.configure {}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module DiningTable
|
2
|
+
|
3
|
+
module Presenters
|
4
|
+
|
5
|
+
class CSVPresenter < SpreadsheetPresenter
|
6
|
+
|
7
|
+
def initialize( *args )
|
8
|
+
super
|
9
|
+
self.output = ''
|
10
|
+
end
|
11
|
+
|
12
|
+
def identifier
|
13
|
+
:csv
|
14
|
+
end
|
15
|
+
|
16
|
+
def output
|
17
|
+
stringio.string
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_writer :output
|
23
|
+
attr_accessor :stringio
|
24
|
+
|
25
|
+
def csv
|
26
|
+
@csv ||= begin
|
27
|
+
self.stringio = StringIO.new
|
28
|
+
csv_options = options[:csv] || { }
|
29
|
+
CSV.new(stringio, csv_options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_row(array)
|
34
|
+
csv.add_row( array )
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DiningTable
|
2
|
+
|
3
|
+
module Presenters
|
4
|
+
|
5
|
+
class ExcelPresenter < SpreadsheetPresenter
|
6
|
+
|
7
|
+
def initialize( worksheet, *args )
|
8
|
+
super( *args )
|
9
|
+
self.worksheet = worksheet
|
10
|
+
end
|
11
|
+
|
12
|
+
def identifier
|
13
|
+
:xlsx
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_accessor :worksheet
|
19
|
+
|
20
|
+
def add_row(array)
|
21
|
+
worksheet.add_row( array )
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module DiningTable
|
2
|
+
|
3
|
+
module Presenters
|
4
|
+
|
5
|
+
class HTMLPresenter < Presenter
|
6
|
+
|
7
|
+
def initialize( *args )
|
8
|
+
super
|
9
|
+
self.output = ''
|
10
|
+
end
|
11
|
+
|
12
|
+
def identifier
|
13
|
+
:html
|
14
|
+
end
|
15
|
+
|
16
|
+
def start_table
|
17
|
+
if options[:wrap]
|
18
|
+
add_tag(:start, wrap_tag, wrap_options )
|
19
|
+
end
|
20
|
+
add_tag(:start, :table, options )
|
21
|
+
end
|
22
|
+
|
23
|
+
def end_table
|
24
|
+
add_tag(:end, :table)
|
25
|
+
if options[:wrap]
|
26
|
+
add_tag(:end, wrap_tag )
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def start_body
|
31
|
+
add_tag(:start, :tbody)
|
32
|
+
end
|
33
|
+
|
34
|
+
def end_body
|
35
|
+
add_tag(:end, :tbody)
|
36
|
+
end
|
37
|
+
|
38
|
+
def render_row( object )
|
39
|
+
add_tag(:start, :tr)
|
40
|
+
columns.each do |column|
|
41
|
+
value = column.value( object )
|
42
|
+
render_cell( value, column.options_for( identifier ) )
|
43
|
+
end
|
44
|
+
add_tag(:end, :tr)
|
45
|
+
end
|
46
|
+
|
47
|
+
def render_header
|
48
|
+
add_tag(:start, :thead)
|
49
|
+
add_tag(:start, :tr)
|
50
|
+
columns.each do |column|
|
51
|
+
value = column.header
|
52
|
+
render_header_cell( value, column.options_for( identifier ) )
|
53
|
+
end
|
54
|
+
add_tag(:end, :tr)
|
55
|
+
add_tag(:end, :thead)
|
56
|
+
end
|
57
|
+
|
58
|
+
def render_footer
|
59
|
+
footers = columns.each.map(&:footer)
|
60
|
+
if footers.map(&:blank?).uniq != [ true ]
|
61
|
+
add_tag(:start, :tfoot)
|
62
|
+
add_tag(:start, :tr)
|
63
|
+
columns.each_with_index do |column, index|
|
64
|
+
value = footers[index]
|
65
|
+
render_footer_cell( value, column.options_for( identifier ) )
|
66
|
+
end
|
67
|
+
add_tag(:end, :tr)
|
68
|
+
add_tag(:end, :tfoot)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def output
|
73
|
+
@output.html_safe
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
attr_writer :output
|
79
|
+
|
80
|
+
def output_
|
81
|
+
@output
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_tag(type, tag, options = {})
|
85
|
+
string = send("#{ type.to_s }_tag", tag, options)
|
86
|
+
output_ << string
|
87
|
+
end
|
88
|
+
|
89
|
+
def start_tag(tag, options = {})
|
90
|
+
"<#{ tag.to_s } #{ options_string(options) }>"
|
91
|
+
end
|
92
|
+
|
93
|
+
def end_tag(tag, options = {})
|
94
|
+
"</#{ tag.to_s }>"
|
95
|
+
end
|
96
|
+
|
97
|
+
def render_cell( string, options )
|
98
|
+
render_general_cell( string, options, :td, :td_options )
|
99
|
+
end
|
100
|
+
|
101
|
+
def render_header_cell( string, options )
|
102
|
+
render_general_cell( string, options, :th, :th_options )
|
103
|
+
end
|
104
|
+
|
105
|
+
def render_footer_cell( string, options )
|
106
|
+
render_general_cell( string, options, :td, :footer_options )
|
107
|
+
end
|
108
|
+
|
109
|
+
def render_general_cell( string, options, cell_tag, options_identifier )
|
110
|
+
add_tag(:start, cell_tag, options[ options_identifier ] )
|
111
|
+
output_ << string.to_s
|
112
|
+
add_tag(:end, cell_tag)
|
113
|
+
end
|
114
|
+
|
115
|
+
def options_string(options)
|
116
|
+
return '' if options.nil?
|
117
|
+
options.each_key.inject('') do |result, key|
|
118
|
+
result += " #{key.to_s}=\"#{ options[key] }\"" if !options_to_skip.include?( key )
|
119
|
+
result
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# don't output these keys as html options in options_string
|
124
|
+
def options_to_skip
|
125
|
+
[ :wrap ]
|
126
|
+
end
|
127
|
+
|
128
|
+
def wrap_tag
|
129
|
+
options[:wrap][:tag]
|
130
|
+
end
|
131
|
+
|
132
|
+
def wrap_options
|
133
|
+
options_ = options[:wrap].dup
|
134
|
+
options_.delete(:tag)
|
135
|
+
options_
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module DiningTable
|
2
|
+
|
3
|
+
module Presenters
|
4
|
+
|
5
|
+
class Presenter
|
6
|
+
|
7
|
+
attr_accessor :table, :options, :view_context
|
8
|
+
|
9
|
+
def initialize( options = {} )
|
10
|
+
self.options = default_options.merge( options )
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect_to( table )
|
14
|
+
self.table = table
|
15
|
+
end
|
16
|
+
|
17
|
+
def identifier
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def type?( identifier_ )
|
22
|
+
identifier == identifier_
|
23
|
+
end
|
24
|
+
|
25
|
+
[ :start_table, :end_table, :render_header, :start_body, :end_body, :row, :render_footer, :output ].each do |method|
|
26
|
+
self.class_eval <<-eos, __FILE__, __LINE__+1
|
27
|
+
def #{method}(*args)
|
28
|
+
end
|
29
|
+
eos
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def columns
|
35
|
+
table.columns
|
36
|
+
end
|
37
|
+
|
38
|
+
def default_options
|
39
|
+
presenter = "#{ identifier }_presenter"
|
40
|
+
if DiningTable.configuration.respond_to?( presenter )
|
41
|
+
DiningTable.configuration.send( presenter ).default_options
|
42
|
+
else
|
43
|
+
{ }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module DiningTable
|
2
|
+
|
3
|
+
module Presenters
|
4
|
+
|
5
|
+
class SpreadsheetPresenter < Presenter
|
6
|
+
|
7
|
+
def render_row( object )
|
8
|
+
values = columns.map do |column|
|
9
|
+
value = column.value( object )
|
10
|
+
value = clean( value ) if !!options[:clean]
|
11
|
+
value
|
12
|
+
end
|
13
|
+
add_row( values )
|
14
|
+
end
|
15
|
+
|
16
|
+
def render_header
|
17
|
+
add_row( header_strings )
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_footer
|
21
|
+
footers = footer_strings
|
22
|
+
if footers.map(&:blank?).uniq != [ true ]
|
23
|
+
add_row( footers )
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def header_strings
|
30
|
+
columns.map(&:header)
|
31
|
+
end
|
32
|
+
|
33
|
+
def footer_strings
|
34
|
+
columns.map(&:footer)
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_row( row )
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
def clean(string)
|
42
|
+
replacements = [['—', '--'], ['–', '-'], [' ', ' '] ]
|
43
|
+
base = view_context.strip_tags(string)
|
44
|
+
replacements.each do |pattern, replacement|
|
45
|
+
base.gsub!(pattern, replacement)
|
46
|
+
end
|
47
|
+
base
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module DiningTable
|
2
|
+
|
3
|
+
class Table
|
4
|
+
|
5
|
+
attr_accessor :collection, :presenter, :options, :index, :columns, :action_columns, :view_context
|
6
|
+
|
7
|
+
def initialize( collection, view_context, options = {} )
|
8
|
+
self.collection = collection
|
9
|
+
self.view_context = view_context
|
10
|
+
self.index = 0
|
11
|
+
self.columns = [ ]
|
12
|
+
self.options = options
|
13
|
+
initialize_presenter( options )
|
14
|
+
define
|
15
|
+
end
|
16
|
+
|
17
|
+
def define
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def render
|
22
|
+
presenter.start_table
|
23
|
+
presenter.render_header unless no_header
|
24
|
+
collection.each_with_index do |object, index_|
|
25
|
+
self.index = index_
|
26
|
+
presenter.render_row( object )
|
27
|
+
end
|
28
|
+
presenter.render_footer
|
29
|
+
presenter.end_table
|
30
|
+
presenter.output
|
31
|
+
end
|
32
|
+
|
33
|
+
def helpers
|
34
|
+
view_context
|
35
|
+
end
|
36
|
+
alias_method :h, :helpers
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_accessor :no_header
|
41
|
+
|
42
|
+
# auxiliary function
|
43
|
+
def column(name, options = {}, &block)
|
44
|
+
klass = options[:class]
|
45
|
+
klass ||= Columns::Column
|
46
|
+
self.columns << klass.new(self, name, options, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def actions(options = {}, &block)
|
50
|
+
column(:actions__, { :class => Columns::ActionsColumn }.merge( options ), &block )
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize_presenter( options )
|
54
|
+
self.presenter = options[ :presenter ]
|
55
|
+
self.presenter ||= default_presenter.new
|
56
|
+
self.presenter.connect_to( self )
|
57
|
+
end
|
58
|
+
|
59
|
+
def default_presenter
|
60
|
+
Presenters::HTMLPresenter
|
61
|
+
end
|
62
|
+
|
63
|
+
def skip_header
|
64
|
+
self.no_header = true
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dining-table
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michaël Van Damme
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: juwelier
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.1.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.1.0
|
41
|
+
description: Easily output tabular data, be it in HTML, CSV or XLSX. Create clean
|
42
|
+
table classes instead of messing with views to create nice tables.
|
43
|
+
email: michael.vandamme@vub.ac.be
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files:
|
47
|
+
- LICENSE
|
48
|
+
- README.md
|
49
|
+
files:
|
50
|
+
- Gemfile
|
51
|
+
- Gemfile.lock
|
52
|
+
- LICENSE
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- VERSION
|
56
|
+
- dining-table.gemspec
|
57
|
+
- lib/dining-table.rb
|
58
|
+
- lib/dining-table/columns/actions_column.rb
|
59
|
+
- lib/dining-table/columns/column.rb
|
60
|
+
- lib/dining-table/config.rb
|
61
|
+
- lib/dining-table/presenters/csv_presenter.rb
|
62
|
+
- lib/dining-table/presenters/excel_presenter.rb
|
63
|
+
- lib/dining-table/presenters/html_presenter.rb
|
64
|
+
- lib/dining-table/presenters/presenter.rb
|
65
|
+
- lib/dining-table/presenters/spreadsheet_presenter.rb
|
66
|
+
- lib/dining-table/table.rb
|
67
|
+
homepage: http://github.com/mvdamme/dining-table
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
metadata: {}
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 2.4.8
|
88
|
+
signing_key:
|
89
|
+
specification_version: 4
|
90
|
+
summary: Create tables easily. Supports html, csv and xlsx.
|
91
|
+
test_files: []
|