marktable 0.0.5 → 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 +4 -4
- data/README.md +345 -6
- data/lib/marktable/formatters/base.rb +27 -0
- data/lib/marktable/formatters/csv.rb +24 -0
- data/lib/marktable/formatters/html.rb +50 -0
- data/lib/marktable/formatters/markdown.rb +66 -0
- data/lib/marktable/row.rb +74 -62
- data/lib/marktable/table.rb +53 -123
- data/lib/marktable/tables/array.rb +57 -0
- data/lib/marktable/tables/base.rb +33 -0
- data/lib/marktable/tables/csv.rb +71 -0
- data/lib/marktable/tables/html.rb +67 -0
- data/lib/marktable/tables/markdown.rb +82 -0
- data/lib/marktable/version.rb +5 -0
- data/lib/marktable.rb +10 -126
- data/spec/support/matchers/markdown_matchers.rb +34 -132
- metadata +29 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4dc7dda731d7cbac0c2b155c3c572c44954ca2f65281f3cf976b05ba9cd1532
|
4
|
+
data.tar.gz: 1261f5a0afd0bbf45a16d8e9d369ef61b8e9313e83788a1b5e10be8871fac962
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8127ab236b08a55f8d8d2fafcb116996d347527085af2dc1e71632b55930d326eae6e6b8bbac2e55c33965effa4d14863b4741e80c70297b2079bf715153dab6
|
7
|
+
data.tar.gz: 8400c3fbb70a9330d2db6c36f23599161ae35e4766c614597f773f38305ae9bf62708d053477d5e727d7cf51dee76490bb23743c0c77aca76a4a87997795e9be
|
data/README.md
CHANGED
@@ -1,8 +1,27 @@
|
|
1
1
|
# Marktable
|
2
2
|
|
3
|
-
Marktable is a Ruby gem for ...
|
4
3
|
|
5
|
-
|
4
|
+
A powerful Ruby library for parsing, manipulating, and generating Markdown tables with an elegant and intuitive API.
|
5
|
+
|
6
|
+
## 📚 Overview
|
7
|
+
|
8
|
+
Marktable allows you to seamlessly work with Markdown tables using familiar Ruby data structures. Whether you're parsing tables from Markdown documents, generating tables for documentation, or manipulating tabular data, Marktable provides simple yet powerful tools for all your Markdown table needs.
|
9
|
+
|
10
|
+
## ✨ Features
|
11
|
+
|
12
|
+
- **Custom RSpec matcher** for testing Markdown-like data structures (including html tables) against expected Markdown output
|
13
|
+
- **Parse** Markdown tables into Ruby data structures (arrays or hashes)
|
14
|
+
- **Generate** beautifully formatted Markdown tables from Ruby objects
|
15
|
+
- **Filter** and **transform** table data with Ruby's familiar block syntax
|
16
|
+
- **Auto-detect** headers from properly formatted Markdown tables
|
17
|
+
- **Handle** tables with or without headers
|
18
|
+
- **Support** for mismatched columns and keys in data
|
19
|
+
- **Convert** between array-based and hash-based representations
|
20
|
+
|
21
|
+
## Not supported
|
22
|
+
- Non-string values (E.g. complex objects) in the table rows.
|
23
|
+
|
24
|
+
## 📦 Installation
|
6
25
|
|
7
26
|
Add this line to your application's Gemfile:
|
8
27
|
|
@@ -22,13 +41,333 @@ Or install it yourself as:
|
|
22
41
|
gem install marktable
|
23
42
|
```
|
24
43
|
|
25
|
-
##
|
44
|
+
## 🚀 Quick Start
|
45
|
+
|
46
|
+
### Main use case (Rspec only at this time)
|
47
|
+
|
48
|
+
This gem started as a pet project aiming at:
|
49
|
+
- Simplifying the process of testing html table content
|
50
|
+
- Making these specs more developer-friendly, with a very readable format
|
51
|
+
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
visit my_path
|
55
|
+
actual_table = page.find('#my-table')
|
56
|
+
expected_table = <<~MARKDOWN
|
57
|
+
| Name | Age | City |
|
58
|
+
| ----- | --- | -------- |
|
59
|
+
| John | 30 | New York |
|
60
|
+
| Jane | 25 | Boston |
|
61
|
+
| Bob | 17 | Chicago |
|
62
|
+
MARKDOWN
|
63
|
+
|
64
|
+
expect(actual_table).to match_markdown(expected_table)
|
65
|
+
```
|
66
|
+
|
67
|
+
In case of semantical mismatch, the matcher will show an easy to verify markdown representation of the tables:
|
68
|
+
|
69
|
+
```markdown
|
70
|
+
Expected markdown table to match:
|
71
|
+
|
72
|
+
Expected:
|
73
|
+
| Name | Age | City |
|
74
|
+
| ----- | --- | -------- |
|
75
|
+
| John | 30 | New York |
|
76
|
+
| Jane | 25 | Boston |
|
77
|
+
| Bob | 17 | Chicago |
|
78
|
+
|
79
|
+
Actual:
|
80
|
+
| Name | Age |
|
81
|
+
| ---- | --- |
|
82
|
+
| John | 31 |
|
83
|
+
| Jane | 25 |
|
84
|
+
|
85
|
+
Parsed expected data: [{"Name" => "John", "Age" => "30"}, {"Name" => "Jane", "Age" => "25"}, {"Name" => "Bob", "Age" => "17"}]
|
86
|
+
Parsed actual data: [{"Name" => "John", "Age" => "31"}, {"Name" => "Jane", "Age" => "25"}]
|
87
|
+
```
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
### Parsing a Markdown Table
|
26
92
|
|
27
93
|
```ruby
|
28
94
|
require 'marktable'
|
29
|
-
|
95
|
+
|
96
|
+
markdown_table = <<~MARKDOWN
|
97
|
+
| Name | Age | City |
|
98
|
+
| ----- | --- | -------- |
|
99
|
+
| John | 30 | New York |
|
100
|
+
| Jane | 25 | Boston |
|
101
|
+
MARKDOWN
|
102
|
+
|
103
|
+
# Parse into an array of hashes (with auto-detected headers)
|
104
|
+
data = Marktable.parse(markdown_table)
|
105
|
+
# => [
|
106
|
+
# {"Name"=>"John", "Age"=>"30", "City"=>"New York"},
|
107
|
+
# {"Name"=>"Jane", "Age"=>"25", "City"=>"Boston"}
|
108
|
+
# ]
|
109
|
+
|
110
|
+
# Access data easily
|
111
|
+
puts data.first["Name"] # => "John"
|
112
|
+
```
|
113
|
+
|
114
|
+
### Generating a Markdown Table
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# Create a new table and add rows
|
118
|
+
table = Marktable.generate do |t|
|
119
|
+
t << {"Name" => "John", "Age" => "30", "City" => "New York"}
|
120
|
+
t << {"Name" => "Jane", "Age" => "25", "City" => "Boston"}
|
121
|
+
t << {"Name" => "Bob", "Age" => "17", "City" => "Chicago"}
|
122
|
+
end
|
123
|
+
|
124
|
+
puts table
|
125
|
+
# | Name | Age | City |
|
126
|
+
# | ---- | --- | -------- |
|
127
|
+
# | John | 30 | New York |
|
128
|
+
# | Jane | 25 | Boston |
|
129
|
+
# | Bob | 17 | Chicago |
|
130
|
+
```
|
131
|
+
|
132
|
+
## 📖 Usage Guide
|
133
|
+
|
134
|
+
### Working with Table Objects
|
135
|
+
|
136
|
+
Create a table object for more advanced operations:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
# Create from a markdown string
|
140
|
+
table = Marktable.new(markdown_table)
|
141
|
+
|
142
|
+
# Or create from array data
|
143
|
+
array_data = [
|
144
|
+
["Name", "Age", "City"],
|
145
|
+
["John", "30", "New York"],
|
146
|
+
["Jane", "25", "Boston"],
|
147
|
+
["Bob", "17", "Chicago"]
|
148
|
+
]
|
149
|
+
table = Marktable.new(array_data, headers: true)
|
150
|
+
|
151
|
+
# Or with auto-detected headers
|
152
|
+
array_data = [
|
153
|
+
{ "Name" => "John", "Age" => "30", "City" => "New York" },
|
154
|
+
{ "Name" => "Jane", "Age" => "25", "City" => "Boston" },
|
155
|
+
{ "Name" => "Bob", "Age" => "17", "City" => "Chicago" }
|
156
|
+
]
|
157
|
+
table = Marktable.new(array_data)
|
158
|
+
```
|
159
|
+
|
160
|
+
### Filtering Rows
|
161
|
+
|
162
|
+
Filter rows using pattern matching or blocks:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
# Filter with a regex pattern
|
166
|
+
nyc_residents = table.filter(/New York/)
|
167
|
+
|
168
|
+
# Filter with a block
|
169
|
+
adults = table.filter do |row|
|
170
|
+
row["Age"].to_i >= 18
|
171
|
+
end
|
172
|
+
|
173
|
+
puts adults
|
174
|
+
# | Name | Age | City |
|
175
|
+
# | ---- | --- | -------- |
|
176
|
+
# | John | 30 | New York |
|
177
|
+
# | Jane | 25 | Boston |
|
178
|
+
```
|
179
|
+
|
180
|
+
### Transforming Data
|
181
|
+
|
182
|
+
Transform table data with the map method:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
# Add 5 years to everyone's age
|
186
|
+
older = table.map do |row|
|
187
|
+
row.merge("Age" => (row["Age"].to_i + 5).to_s)
|
188
|
+
end
|
189
|
+
|
190
|
+
puts older
|
191
|
+
# | Name | Age | City |
|
192
|
+
# | ---- | --- | -------- |
|
193
|
+
# | John | 35 | New York |
|
194
|
+
# | Jane | 30 | Boston |
|
195
|
+
# | Bob | 22 | Chicago |
|
196
|
+
```
|
197
|
+
|
198
|
+
### Working with Arrays
|
199
|
+
|
200
|
+
Use arrays instead of hashes for row data:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
# Create a table with array rows
|
204
|
+
table = Marktable.new([], headers: false)
|
205
|
+
table << ["John", "30", "New York"]
|
206
|
+
table << ["Jane", "25", "Boston"]
|
207
|
+
|
208
|
+
puts table
|
209
|
+
# | John | 30 | New York |
|
210
|
+
# | Jane | 25 | Boston |
|
211
|
+
```
|
212
|
+
|
213
|
+
### Mixed Row Types
|
214
|
+
|
215
|
+
Marktable can handle mixed row types (arrays and hashes):
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
table = Marktable.generate do |t|
|
219
|
+
t << {"Name" => "John", "Age" => "30", "City" => "New York"}
|
220
|
+
t << ["Jane", "25", "Boston"] # Array with same column count
|
221
|
+
end
|
222
|
+
|
223
|
+
puts table
|
224
|
+
# | Name | Age | City |
|
225
|
+
# | ---- | --- | -------- |
|
226
|
+
# | John | 30 | New York |
|
227
|
+
# | Jane | 25 | Boston |
|
228
|
+
```
|
229
|
+
|
230
|
+
## 🧪 Testing with Custom Matchers
|
231
|
+
|
232
|
+
Marktable includes a custom RSpec matcher, `match_markdown`, to make testing Markdown-compatible data structures simple and reliable.
|
233
|
+
|
234
|
+
### Using the `match_markdown` Matcher
|
235
|
+
|
236
|
+
First, require the matcher in your spec_helper.rb or in the specific test file:
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
require 'marktable/rspec'
|
240
|
+
```
|
241
|
+
|
242
|
+
#### Testing Markdown Table Output
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
RSpec.describe ReportGenerator do
|
246
|
+
describe "#generate_customer_table" do
|
247
|
+
it "generates the expected customer table" do
|
248
|
+
actual = ReportGenerator.new(customers).generate_table
|
249
|
+
# Presume actual contains:
|
250
|
+
# [
|
251
|
+
# { id: 1, name: "John Smith", status: "Active" },
|
252
|
+
# { id: 2, name: "Jane Doe", status: "Pending" }
|
253
|
+
# ]
|
254
|
+
|
255
|
+
expected = <<~MARKDOWN
|
256
|
+
| ID | Name | Status |
|
257
|
+
| -- | ---------- | ------- |
|
258
|
+
| 1 | John Smith | Active |
|
259
|
+
| 2 | Jane Doe | Pending |
|
260
|
+
MARKDOWN
|
261
|
+
|
262
|
+
expect(result).to match_markdown(expected)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
30
266
|
```
|
31
267
|
|
32
|
-
|
268
|
+
#### Testing HTML Table Output
|
269
|
+
|
270
|
+
The `match_markdown` matcher can also compare HTML tables by extracting their semantic content:
|
271
|
+
|
272
|
+
```ruby
|
273
|
+
RSpec.describe HtmlReportGenerator do
|
274
|
+
describe "#generate_sales_report" do
|
275
|
+
it "generates the expected HTML table" do
|
276
|
+
sales_data = [
|
277
|
+
{ product: "Widget A", quantity: 150, revenue: "$3,000" },
|
278
|
+
{ product: "Widget B", quantity: 75, revenue: "$1,875" }
|
279
|
+
]
|
280
|
+
|
281
|
+
generator = HtmlReportGenerator.new(sales_data)
|
282
|
+
html_output = generator.generate_sales_report
|
283
|
+
|
284
|
+
# The matcher extracts the data structure from both the HTML and expected markdown
|
285
|
+
expected = <<~MARKDOWN
|
286
|
+
| Product | Quantity | Revenue |
|
287
|
+
| -------- | -------- | ------- |
|
288
|
+
| Widget A | 150 | $3,000 |
|
289
|
+
| Widget B | 75 | $1,875 |
|
290
|
+
MARKDOWN
|
291
|
+
|
292
|
+
# This will pass even though one is HTML and one is Markdown!
|
293
|
+
expect(html_output).to match_markdown(expected)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
#### Testing API JSON Responses
|
300
|
+
|
301
|
+
The matcher is also helpful when testing APIs that return tabular data:
|
302
|
+
|
303
|
+
```ruby
|
304
|
+
RSpec.describe "Products API" do
|
305
|
+
describe "GET /api/products" do
|
306
|
+
it "returns products in the expected format" do
|
307
|
+
# Setup test data and make request
|
308
|
+
get "/api/products"
|
309
|
+
|
310
|
+
# Parse JSON response
|
311
|
+
json_response = JSON.parse(response.body)
|
312
|
+
|
313
|
+
# Convert API response to a table
|
314
|
+
table = Marktable.new(json_response)
|
315
|
+
|
316
|
+
expected = <<~MARKDOWN
|
317
|
+
| id | name | category | price |
|
318
|
+
| -- | ----------- | -------- | ------ |
|
319
|
+
| 1 | Product One | Books | $19.99 |
|
320
|
+
| 2 | Product Two | Games | $59.99 |
|
321
|
+
MARKDOWN
|
322
|
+
|
323
|
+
expect(table).to match_markdown(expected)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
```
|
328
|
+
|
329
|
+
The `match_markdown` matcher compares tables semantically rather than character-by-character, which means:
|
330
|
+
|
331
|
+
- It ignores differences in whitespace padding
|
332
|
+
- It handles different column ordering in hash-based tables
|
333
|
+
- It works with both HTML and Markdown table formats
|
334
|
+
- It provides clear error messages showing the differences between tables
|
335
|
+
|
336
|
+
## 📋 API Reference
|
337
|
+
|
338
|
+
### Class Methods
|
339
|
+
|
340
|
+
- `Marktable.parse(table, headers: nil)` - Parse table string or array into an array of hashes/arrays
|
341
|
+
- `Marktable.new(table, headers: nil)` - Create a new Table object
|
342
|
+
- `Marktable.parse_line(row)` - Parse a single markdown row into an array
|
343
|
+
- `Marktable.generate { |t| ... }` - Generate a table with a block
|
344
|
+
|
345
|
+
### Instance Methods
|
346
|
+
|
347
|
+
- `table.to_a` - Convert table to array of hashes/arrays
|
348
|
+
- `table.to_s` / `table.generate` - Generate markdown representation
|
349
|
+
- `table.empty?` - Check if table is empty
|
350
|
+
- `table.column_count` - Get column count
|
351
|
+
- `table << row_data` - Add a row to the table
|
352
|
+
- `table.filter(pattern = nil) { |row| ... }` - Filter rows by pattern or block
|
353
|
+
- `table.map { |row| ... }` - Transform rows with a block
|
354
|
+
|
355
|
+
## 🧪 Development
|
356
|
+
|
357
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
358
|
+
|
359
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
360
|
+
|
361
|
+
## 🤝 Contributing
|
362
|
+
|
363
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/yourusername/marktable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/yourusername/marktable/blob/main/CODE_OF_CONDUCT.md).
|
364
|
+
|
365
|
+
1. Fork the repository
|
366
|
+
2. Create your feature branch: `git checkout -b my-new-feature`
|
367
|
+
3. Commit your changes: `git commit -am 'Add some feature'`
|
368
|
+
4. Push to the branch: `git push origin my-new-feature`
|
369
|
+
5. Submit a pull request
|
370
|
+
|
371
|
+
## 📄 License
|
33
372
|
|
34
|
-
|
373
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'markdown'
|
4
|
+
# require_relative "array"
|
5
|
+
require_relative 'csv'
|
6
|
+
require_relative 'html'
|
7
|
+
|
8
|
+
module Marktable
|
9
|
+
module Formatters
|
10
|
+
class Base
|
11
|
+
def self.for(type)
|
12
|
+
case type.to_sym
|
13
|
+
when :markdown
|
14
|
+
Markdown
|
15
|
+
when :array
|
16
|
+
Array
|
17
|
+
when :csv
|
18
|
+
CSV
|
19
|
+
when :html
|
20
|
+
HTML
|
21
|
+
else
|
22
|
+
raise ArgumentError, "Unknown table type: #{type}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module Marktable
|
6
|
+
module Formatters
|
7
|
+
class CSV
|
8
|
+
def self.format(rows, headers = nil)
|
9
|
+
return '' if rows.empty? && headers.nil?
|
10
|
+
|
11
|
+
::CSV.generate do |csv|
|
12
|
+
csv << headers if headers
|
13
|
+
rows.each do |row|
|
14
|
+
csv << format_row(row)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.format_row(row)
|
20
|
+
row.values.map { |val| val unless val.to_s.empty? }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module Marktable
|
6
|
+
module Formatters
|
7
|
+
class HTML
|
8
|
+
def self.format(rows, headers = nil)
|
9
|
+
return '' if rows.empty? && headers.nil?
|
10
|
+
|
11
|
+
builder = Nokogiri::HTML::Builder.new do |doc|
|
12
|
+
doc.table do
|
13
|
+
if headers
|
14
|
+
doc.thead do
|
15
|
+
doc.tr do
|
16
|
+
headers.each do |header|
|
17
|
+
doc.th { doc.text header }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
doc.tbody do
|
24
|
+
rows.each do |row|
|
25
|
+
doc.tr do
|
26
|
+
row.each_value do |cell|
|
27
|
+
doc.td do
|
28
|
+
cell_text = cell.to_s
|
29
|
+
if cell_text.include?('\n')
|
30
|
+
cell_text.split('\n').each_with_index do |line, index|
|
31
|
+
doc.br if index.positive?
|
32
|
+
doc.text line
|
33
|
+
end
|
34
|
+
else
|
35
|
+
doc.text cell_text
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Extract just the table element to avoid including DOCTYPE
|
46
|
+
builder.doc.at_css('table').to_html
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Marktable
|
4
|
+
module Formatters
|
5
|
+
class Markdown
|
6
|
+
def self.format(rows, headers = nil)
|
7
|
+
return '' if rows.empty? && headers.nil?
|
8
|
+
|
9
|
+
# Calculate column widths
|
10
|
+
widths = calculate_column_widths(rows, headers)
|
11
|
+
|
12
|
+
lines = []
|
13
|
+
|
14
|
+
# Add header row if we have headers
|
15
|
+
if headers
|
16
|
+
lines << Row.new(headers).to_markdown(widths)
|
17
|
+
lines << separator_row(widths)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add data rows
|
21
|
+
rows.each do |row|
|
22
|
+
lines << row.to_markdown(widths)
|
23
|
+
end
|
24
|
+
|
25
|
+
lines.join("\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.calculate_column_widths(rows, headers)
|
29
|
+
# Determine the maximum number of columns to consider
|
30
|
+
max_cols = headers ? headers.size : 0
|
31
|
+
|
32
|
+
if headers.nil?
|
33
|
+
# Without headers, find the maximum number of values across all rows
|
34
|
+
rows.each do |row|
|
35
|
+
max_cols = [max_cols, row.values.size].max
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initialize widths array with zeros
|
40
|
+
widths = Array.new(max_cols, 0)
|
41
|
+
|
42
|
+
# Process headers if available
|
43
|
+
headers&.each_with_index do |header, i|
|
44
|
+
width = header.to_s.length
|
45
|
+
widths[i] = width if width > widths[i]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Process row values, but only up to the max_cols
|
49
|
+
rows.each do |row|
|
50
|
+
values = row.values.take(max_cols)
|
51
|
+
values.each_with_index do |value, i|
|
52
|
+
width = value.to_s.length
|
53
|
+
widths[i] = width if width > widths[i]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
widths
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.separator_row(widths)
|
61
|
+
separator_parts = widths.map { |width| '-' * width }
|
62
|
+
"| #{separator_parts.join(' | ')} |"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|