tablesmith 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/.gitignore +3 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +67 -0
- data/LICENSE +19 -0
- data/README.md +35 -0
- data/Rakefile +11 -0
- data/lib/tablesmith.rb +4 -0
- data/lib/tablesmith/active_record_source.rb +97 -0
- data/lib/tablesmith/batch.rb +145 -0
- data/lib/tablesmith/hash_rows_source.rb +63 -0
- data/lib/tablesmith/version.rb +3 -0
- data/spec/active_record_batch_spec.rb +330 -0
- data/spec/array_spec.rb +1 -0
- data/spec/batch_spec.rb +32 -0
- data/spec/fixtures.rb +70 -0
- data/spec/hash_rows_batch_spec.rb +55 -0
- data/spec/hash_spec.rb +1 -0
- data/spec/spec_helper.rb +3 -0
- data/tablesmith.gemspec +28 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0b2cd809d21c4144fbb28e472f12e4f77510817d
|
4
|
+
data.tar.gz: 664d3bb41153050b8476ac761e1b4063594c7327
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9ef5411a7defaefa9dd1246d985a6a063479832836d41a48a1f715aa9922833f049163ccb2e3cf44b5679a1acf212bdfa7bac9c6776eefe6f752de0ecb0367f0
|
7
|
+
data.tar.gz: 972584036fd2a3a4208a4aa82c06e39686c5cab70c9e47b53594d95d52f9120f5da4ff2d6b98f14bd776ee681534062d77c0fc558d76695b1e7184545afdfd0c
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.4
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
tablesmith (0.1.0)
|
5
|
+
text-table
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
remote: http://geminabox.lsqa.net/
|
10
|
+
specs:
|
11
|
+
activemodel (3.2.22.5)
|
12
|
+
activesupport (= 3.2.22.5)
|
13
|
+
builder (~> 3.0.0)
|
14
|
+
activerecord (3.2.22.5)
|
15
|
+
activemodel (= 3.2.22.5)
|
16
|
+
activesupport (= 3.2.22.5)
|
17
|
+
arel (~> 3.0.2)
|
18
|
+
tzinfo (~> 0.3.29)
|
19
|
+
activesupport (3.2.22.5)
|
20
|
+
i18n (~> 0.6, >= 0.6.4)
|
21
|
+
multi_json (~> 1.0)
|
22
|
+
arel (3.0.3)
|
23
|
+
builder (3.0.4)
|
24
|
+
coderay (1.1.1)
|
25
|
+
diff-lcs (1.3)
|
26
|
+
docile (1.1.5)
|
27
|
+
i18n (0.8.6)
|
28
|
+
json (2.1.0)
|
29
|
+
method_source (0.8.2)
|
30
|
+
multi_json (1.12.1)
|
31
|
+
pry (0.10.4)
|
32
|
+
coderay (~> 1.1.0)
|
33
|
+
method_source (~> 0.8.1)
|
34
|
+
slop (~> 3.4)
|
35
|
+
rake (10.5.0)
|
36
|
+
rspec (2.99.0)
|
37
|
+
rspec-core (~> 2.99.0)
|
38
|
+
rspec-expectations (~> 2.99.0)
|
39
|
+
rspec-mocks (~> 2.99.0)
|
40
|
+
rspec-core (2.99.2)
|
41
|
+
rspec-expectations (2.99.2)
|
42
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
43
|
+
rspec-mocks (2.99.4)
|
44
|
+
simplecov (0.14.1)
|
45
|
+
docile (~> 1.1.0)
|
46
|
+
json (>= 1.8, < 3)
|
47
|
+
simplecov-html (~> 0.10.0)
|
48
|
+
simplecov-html (0.10.1)
|
49
|
+
slop (3.6.0)
|
50
|
+
sqlite3 (1.3.13)
|
51
|
+
text-table (1.2.4)
|
52
|
+
tzinfo (0.3.53)
|
53
|
+
|
54
|
+
PLATFORMS
|
55
|
+
ruby
|
56
|
+
|
57
|
+
DEPENDENCIES
|
58
|
+
activerecord (~> 3.0)
|
59
|
+
pry
|
60
|
+
rake (~> 10.0)
|
61
|
+
rspec (~> 2.0)
|
62
|
+
simplecov
|
63
|
+
sqlite3
|
64
|
+
tablesmith!
|
65
|
+
|
66
|
+
BUNDLED WITH
|
67
|
+
1.15.1
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2012-2015 LivingSocial, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Tablesmith
|
2
|
+
|
3
|
+
Drop-in gem for console tables for Hash, Array and ActiveRecord.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'tablesmith'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install tablesmith
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Why Not #{other_gem}?
|
24
|
+
|
25
|
+
Happy to learn about something else already out there, but have struggled to find something
|
26
|
+
that doesn't require some sort of setup. I want drop-in ready-to-go table output for Hashes,
|
27
|
+
Arrays and ActiveRecord objects.
|
28
|
+
|
29
|
+
Other gems that I've tried out that are awesome and do much more than what Tablesmith does,
|
30
|
+
but don't seem to specialize in what I want.
|
31
|
+
|
32
|
+
- Hirb
|
33
|
+
- text-table
|
34
|
+
- table_print
|
35
|
+
- awesome_print
|
data/Rakefile
ADDED
data/lib/tablesmith.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
module Tablesmith::ActiveRecordSource
|
2
|
+
def convert_item_to_hash_row(item)
|
3
|
+
# TODO: reload ActiveRecords automagically
|
4
|
+
if item.respond_to? :serializable_hash
|
5
|
+
hash = item.serializable_hash(process_all_columns(serializable_options))
|
6
|
+
hash = fold_un_sourced_attributes_into_source_hash(first.class.name.underscore.to_sym, hash)
|
7
|
+
flatten_inner_hashes(hash)
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def column_order
|
14
|
+
@columns.map(&:full_unaliased_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
# allows overriding
|
18
|
+
def serializable_options
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
|
22
|
+
# TODO: memoize
|
23
|
+
def process_all_columns(serializable_options)
|
24
|
+
@columns = []
|
25
|
+
|
26
|
+
process_columns(serializable_options, first.class)
|
27
|
+
|
28
|
+
include = serializable_options[:include]
|
29
|
+
|
30
|
+
# swiped from activemodel-3.2.17/lib/active_model/serialization.rb
|
31
|
+
unless include.is_a?(Hash)
|
32
|
+
include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
|
33
|
+
end
|
34
|
+
|
35
|
+
include.each do |association, opts|
|
36
|
+
ar_class = first.class.reflections[association].klass
|
37
|
+
process_columns(opts, ar_class)
|
38
|
+
end
|
39
|
+
|
40
|
+
serializable_options
|
41
|
+
end
|
42
|
+
|
43
|
+
def process_columns(serializable_options, ar_class)
|
44
|
+
only = serializable_options[:only]
|
45
|
+
ar_columns = ar_class.columns.map { |c| Tablesmith::Column.new(name: c.name.to_s, source: ar_class.name.underscore) }
|
46
|
+
@columns += ar_columns
|
47
|
+
de_alias_columns(only, ar_columns) if only
|
48
|
+
@columns += (serializable_options[:methods] || []).map do |meth_sym|
|
49
|
+
Tablesmith::Column.new(name: meth_sym.to_s, source: ar_class.name.underscore)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def de_alias_columns(only, ar_columns)
|
54
|
+
only.map! do |attr|
|
55
|
+
hits = ar_columns.select { |c| c.name =~ /#{attr.to_s.gsub(/_/, '.*')}/ }
|
56
|
+
if hits.present?
|
57
|
+
hit = hits[0] # TODO: support multiple hits
|
58
|
+
if attr != hit
|
59
|
+
hit.alias = attr
|
60
|
+
hit.name
|
61
|
+
else
|
62
|
+
attr
|
63
|
+
end
|
64
|
+
else
|
65
|
+
attr
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def flatten_inner_hashes(hash)
|
71
|
+
new_hash = {}
|
72
|
+
stack = hash.each_pair.to_a
|
73
|
+
while ary = stack.shift
|
74
|
+
key, value = ary
|
75
|
+
if value.is_a?(Hash)
|
76
|
+
value.each_pair do |assoc_key, assoc_value|
|
77
|
+
new_hash["#{key}.#{assoc_key}"] = assoc_value.to_s
|
78
|
+
end
|
79
|
+
else
|
80
|
+
new_hash[key] = value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
new_hash
|
84
|
+
end
|
85
|
+
|
86
|
+
# Top-level attributes aren't nested in their own hash by default, this
|
87
|
+
# normalizes the overall hash by grouping those together:
|
88
|
+
#
|
89
|
+
# converts {"name"=>"supplier", "account"=>{"name"=>'account'}}
|
90
|
+
# into {"supplier"=>{"name"=>'supplier'}, "account"=>{"name"=>'account'}}
|
91
|
+
def fold_un_sourced_attributes_into_source_hash(source_sym, hash)
|
92
|
+
new_hash = {}
|
93
|
+
new_hash[source_sym] = hash
|
94
|
+
hash.delete_if { |k, v| new_hash[k] = v if v.is_a?(Hash) }
|
95
|
+
new_hash
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'text-table'
|
2
|
+
|
3
|
+
module Tablesmith
|
4
|
+
class Batch < Array
|
5
|
+
def method_missing(meth_id, *args)
|
6
|
+
count = 1
|
7
|
+
self.map do |t|
|
8
|
+
$stderr.print '.' if count.divmod(100)[1] == 0
|
9
|
+
count += 1
|
10
|
+
t.send(meth_id, *args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# irb
|
15
|
+
def inspect
|
16
|
+
pretty_inspect
|
17
|
+
end
|
18
|
+
|
19
|
+
# Pry 0.9 calls this
|
20
|
+
def pretty_inspect
|
21
|
+
text_table.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
# Pry 0.10 eventually uses PP, and this is the PP way to provide custom output
|
25
|
+
def pretty_print(pp)
|
26
|
+
pp.text pretty_inspect
|
27
|
+
end
|
28
|
+
|
29
|
+
def text_table
|
30
|
+
return ['(empty)'].to_text_table if self.empty?
|
31
|
+
|
32
|
+
rows = self.map { |item| convert_item_to_hash_row(item) }.compact
|
33
|
+
|
34
|
+
normalize_keys(rows)
|
35
|
+
|
36
|
+
rows.map! do |row|
|
37
|
+
# this sort gives preference to column_order then falls back to alphabetic for leftovers.
|
38
|
+
# this is handy when columns auto-generate based on hash data.
|
39
|
+
row.sort do |a, b|
|
40
|
+
a_col_name, b_col_name = [a.first, b.first]
|
41
|
+
a_col_index, b_col_index = [column_order.index(a_col_name), column_order.index(b_col_name)]
|
42
|
+
|
43
|
+
if a_col_index.nil? && b_col_index.nil?
|
44
|
+
a_col_name <=> b_col_name
|
45
|
+
else
|
46
|
+
(a_col_index || 999) <=> (b_col_index || 999)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
rows = create_headers(rows) + (rows.map { |r| r.map(&:last) })
|
52
|
+
rows.to_text_table
|
53
|
+
end
|
54
|
+
|
55
|
+
# override in subclass or mixin
|
56
|
+
def convert_item_to_hash_row(item)
|
57
|
+
item
|
58
|
+
end
|
59
|
+
|
60
|
+
# override in subclass or mixin
|
61
|
+
def column_order
|
62
|
+
[]
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# TODO: resolve with column_order
|
67
|
+
def columns
|
68
|
+
@columns
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_headers(rows)
|
72
|
+
column_names = rows.first.map(&:first)
|
73
|
+
grouped_headers(column_names) + [apply_column_aliases(column_names), :separator]
|
74
|
+
end
|
75
|
+
|
76
|
+
def grouped_headers(column_names)
|
77
|
+
groups = Hash.new { |h, k| h[k] = 0 }
|
78
|
+
column_names.map! do |name|
|
79
|
+
group, col = name.to_s.split(/\./)
|
80
|
+
col, group = [group, ''] if col.nil?
|
81
|
+
groups[group] += 1
|
82
|
+
col
|
83
|
+
end
|
84
|
+
if groups.keys.length == 1 # TODO: add option to show group header row when only one exists
|
85
|
+
[]
|
86
|
+
else
|
87
|
+
row = []
|
88
|
+
# this relies on Ruby versions where hash retains add order
|
89
|
+
groups.each_pair do |name, span|
|
90
|
+
row << {value: name, align: :center, colspan: span}
|
91
|
+
end
|
92
|
+
[row, :separator]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def apply_column_aliases(column_names)
|
97
|
+
column_names.map do |name|
|
98
|
+
instance = columns.detect { |ca| ca.name.to_s == name.to_s }
|
99
|
+
value = instance ? instance.display_name : name
|
100
|
+
{:value => value, :align => :center}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# not all resulting rows will have data in all columns, so make sure all rows pad out missing columns
|
105
|
+
def normalize_keys(rows)
|
106
|
+
all_keys = rows.map { |hash_row| hash_row.keys }.flatten.uniq
|
107
|
+
rows.map { |hash_row| all_keys.each { |key| hash_row[key] ||= '' } }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class Column
|
112
|
+
attr_accessor :source, :name, :alias
|
113
|
+
|
114
|
+
def initialize(attributes={})
|
115
|
+
@source = attributes.delete(:source)
|
116
|
+
@name = attributes.delete(:name)
|
117
|
+
@alias = attributes.delete(:alias)
|
118
|
+
end
|
119
|
+
|
120
|
+
def display_name
|
121
|
+
"#{@alias || @name}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def full_unaliased_name
|
125
|
+
"#{@source ? "#{@source}." : ''}#{@name}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class Array
|
131
|
+
def to_batch
|
132
|
+
b = Tablesmith::Batch.new(self)
|
133
|
+
|
134
|
+
if b.first && b.first.is_a?(ActiveRecord::Base)
|
135
|
+
b.extend Tablesmith::ActiveRecordSource
|
136
|
+
end
|
137
|
+
|
138
|
+
if b.first && b.first.is_a?(Hash)
|
139
|
+
b.extend Tablesmith::HashRowsSource
|
140
|
+
end
|
141
|
+
|
142
|
+
b
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Tablesmith::HashRowsSource
|
2
|
+
def text_table
|
3
|
+
build_columns if columns.nil?
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def convert_item_to_hash_row(item)
|
8
|
+
flatten_hash_to_row(item, columns)
|
9
|
+
end
|
10
|
+
|
11
|
+
def flatten_hash_to_row(deep_hash, columns)
|
12
|
+
row = ActiveSupport::OrderedHash.new
|
13
|
+
columns.each do |col_or_hash|
|
14
|
+
value_from_hash(row, deep_hash, col_or_hash)
|
15
|
+
end
|
16
|
+
row
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO: no support for deep
|
20
|
+
def build_columns
|
21
|
+
@columns ||= []
|
22
|
+
self.map do |hash_row|
|
23
|
+
@columns << hash_row.keys.map { |k| Tablesmith::Column.new(name: k) }
|
24
|
+
end
|
25
|
+
@columns.flatten!
|
26
|
+
end
|
27
|
+
|
28
|
+
def value_from_hash(row, deep_hash, col_or_hash)
|
29
|
+
case col_or_hash
|
30
|
+
when Tablesmith::Column
|
31
|
+
row[col_or_hash.display_name] = deep_hash[col_or_hash.name]
|
32
|
+
when Hash
|
33
|
+
col_or_hash.each_pair do |sub_hash_key, cols_or_hash|
|
34
|
+
[cols_or_hash].flatten.each do |inner_col_or_hash|
|
35
|
+
value_from_hash(row, deep_hash[sub_hash_key], inner_col_or_hash)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
rescue => e
|
42
|
+
$stderr.puts "#{e.message}: #{col_or_hash}" if @debug
|
43
|
+
end
|
44
|
+
|
45
|
+
def hash_rows_to_text_table(hash_rows)
|
46
|
+
require 'text-table'
|
47
|
+
|
48
|
+
header_row = hash_rows.first.keys
|
49
|
+
table = []
|
50
|
+
table << header_row
|
51
|
+
|
52
|
+
hash_rows.each do |hash_row|
|
53
|
+
row = []
|
54
|
+
header_row.each do |header|
|
55
|
+
row << hash_row[header]
|
56
|
+
end
|
57
|
+
table << row
|
58
|
+
end
|
59
|
+
|
60
|
+
# Array addition from text-table
|
61
|
+
table.to_table(:first_row_is_head => true)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,330 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'ActiveRecordSource' do
|
4
|
+
it 'outputs text table of multiple ActiveRecords' do
|
5
|
+
a = Person.new.tap { |c| c.first_name = 'A' }
|
6
|
+
b = Person.new.tap { |c| c.first_name = 'B' }
|
7
|
+
expected = <<-TABLE
|
8
|
+
+----+------------+-----------+-----+-------------------+
|
9
|
+
| id | first_name | last_name | age | custom_attributes |
|
10
|
+
+----+------------+-----------+-----+-------------------+
|
11
|
+
| | A | | | |
|
12
|
+
| | B | | | |
|
13
|
+
+----+------------+-----------+-----+-------------------+
|
14
|
+
TABLE
|
15
|
+
[a, b].to_batch.text_table.to_s.should == expected
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'outputs ActiveRecord in column order' do
|
19
|
+
p = Person.create(:first_name => 'chris', :last_name => 'mo', :age => 43)
|
20
|
+
expected = <<-TABLE
|
21
|
+
+----+------------+-----------+-----+-------------------+
|
22
|
+
| id | first_name | last_name | age | custom_attributes |
|
23
|
+
+----+------------+-----------+-----+-------------------+
|
24
|
+
| 1 | chris | mo | 43 | |
|
25
|
+
+----+------------+-----------+-----+-------------------+
|
26
|
+
TABLE
|
27
|
+
[p].to_batch.text_table.to_s.should == expected
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'handles custom serialization options in batch' do
|
31
|
+
p = Person.create(:first_name => 'chrismo', :age => 43)
|
32
|
+
|
33
|
+
expected = <<-TABLE
|
34
|
+
+------------+-----+-----------+
|
35
|
+
| first_name | age | year_born |
|
36
|
+
+------------+-----+-----------+
|
37
|
+
| chrismo | 43 | 1971 |
|
38
|
+
+------------+-----+-----------+
|
39
|
+
TABLE
|
40
|
+
b = [p].to_batch
|
41
|
+
|
42
|
+
def b.serializable_options
|
43
|
+
{:only => [:first_name, :age], :methods => [:year_born]}
|
44
|
+
end
|
45
|
+
|
46
|
+
b.text_table.to_s.should == expected
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'handles column name partials' do
|
50
|
+
p = Person.create(:first_name => 'chris', :last_name => 'mo', :age => 43)
|
51
|
+
expected = <<-TABLE
|
52
|
+
+-------+------+-----+
|
53
|
+
| first | last | age |
|
54
|
+
+-------+------+-----+
|
55
|
+
| chris | mo | 43 |
|
56
|
+
+-------+------+-----+
|
57
|
+
TABLE
|
58
|
+
b = [p].to_batch
|
59
|
+
|
60
|
+
def b.serializable_options
|
61
|
+
{:only => [:first, :last, :age]}
|
62
|
+
end
|
63
|
+
|
64
|
+
b.text_table.to_s.should == expected
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'handles column name partials across words' do
|
68
|
+
p = Person.create(:first_name => 'chris', :last_name => 'mo', :age => 43)
|
69
|
+
expected = <<-TABLE
|
70
|
+
+--------+--------+-----+
|
71
|
+
| f_name | l_name | age |
|
72
|
+
+--------+--------+-----+
|
73
|
+
| chris | mo | 43 |
|
74
|
+
+--------+--------+-----+
|
75
|
+
TABLE
|
76
|
+
b = [p].to_batch
|
77
|
+
|
78
|
+
def b.serializable_options
|
79
|
+
{:only => [:f_name, :l_name, :age]}
|
80
|
+
end
|
81
|
+
|
82
|
+
b.text_table.to_s.should == expected
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'handles explicit column aliases' do
|
86
|
+
p = Person.create(:first_name => 'chris', :last_name => 'mo', :age => 43)
|
87
|
+
expected = <<-TABLE
|
88
|
+
+---------------+----------+-----+
|
89
|
+
| primer_nombre | apellido | age |
|
90
|
+
+---------------+----------+-----+
|
91
|
+
| chris | mo | 43 |
|
92
|
+
+---------------+----------+-----+
|
93
|
+
TABLE
|
94
|
+
b = [p].to_batch
|
95
|
+
|
96
|
+
def b.columns
|
97
|
+
[Tablesmith::Column.new(name: :first_name, alias: :primer_nombre),
|
98
|
+
Tablesmith::Column.new(name: :last_name, alias: :apellido)]
|
99
|
+
end
|
100
|
+
|
101
|
+
def b.serializable_options
|
102
|
+
{:only => [:first_name, :last_name, :age]}
|
103
|
+
end
|
104
|
+
|
105
|
+
b.text_table.to_s.should == expected
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'handles associations without aliases' do
|
109
|
+
s = Supplier.create(name: 'supplier')
|
110
|
+
s.account = Account.create(name: 'account', tax_identification_number: '123456')
|
111
|
+
b = [s].to_batch
|
112
|
+
|
113
|
+
def b.serializable_options
|
114
|
+
{:only => [:name], :include => {:account => {:only => [:name, :tax_identification_number]}}}
|
115
|
+
end
|
116
|
+
|
117
|
+
expected = <<-TABLE
|
118
|
+
+----------+---------+---------------------------+
|
119
|
+
| supplier | account |
|
120
|
+
+----------+---------+---------------------------+
|
121
|
+
| name | name | tax_identification_number |
|
122
|
+
+----------+---------+---------------------------+
|
123
|
+
| supplier | account | 123456 |
|
124
|
+
+----------+---------+---------------------------+
|
125
|
+
TABLE
|
126
|
+
|
127
|
+
b.text_table.to_s.should == expected
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'handles associations with aliases' do
|
131
|
+
s = Supplier.create(name: 'supplier')
|
132
|
+
s.account = Account.create(name: 'account', tax_identification_number: '123456')
|
133
|
+
b = [s].to_batch
|
134
|
+
|
135
|
+
def b.serializable_options
|
136
|
+
{:only => [:name], :include => {:account => {:only => [:name, :tax_id]}}}
|
137
|
+
end
|
138
|
+
|
139
|
+
expected = <<-TABLE
|
140
|
+
+----------+---------+--------+
|
141
|
+
| supplier | account |
|
142
|
+
+----------+---------+--------+
|
143
|
+
| name | name | tax_id |
|
144
|
+
+----------+---------+--------+
|
145
|
+
| supplier | account | 123456 |
|
146
|
+
+----------+---------+--------+
|
147
|
+
TABLE
|
148
|
+
|
149
|
+
b.text_table.to_s.should == expected
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'retains serializable_options ordering'
|
153
|
+
|
154
|
+
it 'supports multiple associations'
|
155
|
+
|
156
|
+
it 'supports nested associations'
|
157
|
+
|
158
|
+
# may need/want to handle the hash resulting from an association differently from the hash resulting from a method/attr
|
159
|
+
it 'supports field with hash contents' do
|
160
|
+
p = Person.create(first_name: 'chrismo', custom_attributes: {skills: {instrument: 'piano', style: 'jazz'}})
|
161
|
+
b = [p].to_batch
|
162
|
+
|
163
|
+
a = format_ids([p.id])[0]
|
164
|
+
expected = <<-TABLE
|
165
|
+
+----+------------+-----------+-----+----------------------------------------+
|
166
|
+
| person | custom_attributes |
|
167
|
+
+----+------------+-----------+-----+----------------------------------------+
|
168
|
+
| id | first_name | last_name | age | skills |
|
169
|
+
+----+------------+-----------+-----+----------------------------------------+
|
170
|
+
|#{a}| chrismo | | | {:instrument=>"piano", :style=>"jazz"} |
|
171
|
+
+----+------------+-----------+-----+----------------------------------------+
|
172
|
+
TABLE
|
173
|
+
|
174
|
+
b.text_table.to_s.should == expected
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'supports multiple rows with different column counts' do
|
178
|
+
p2 = Person.create(first_name: 'romer', custom_attributes: {instrument: 'kazoo'})
|
179
|
+
p1 = Person.create(first_name: 'chrismo', custom_attributes: {instrument: 'piano', style: 'jazz'})
|
180
|
+
p3 = Person.create(first_name: 'glv', custom_attributes: {})
|
181
|
+
batch = [p2, p1, p3].to_batch
|
182
|
+
|
183
|
+
a, b, c = format_ids([p2.id, p1.id, p3.id])
|
184
|
+
|
185
|
+
expected = <<-TABLE
|
186
|
+
+----+------------+-----------+-----+------------+-----------+
|
187
|
+
| person | custom_attributes |
|
188
|
+
+----+------------+-----------+-----+------------+-----------+
|
189
|
+
| id | first_name | last_name | age | instrument | style |
|
190
|
+
+----+------------+-----------+-----+------------+-----------+
|
191
|
+
|#{a}| romer | | | kazoo | |
|
192
|
+
|#{b}| chrismo | | | piano | jazz |
|
193
|
+
|#{c}| glv | | | | |
|
194
|
+
+----+------------+-----------+-----+------------+-----------+
|
195
|
+
TABLE
|
196
|
+
|
197
|
+
batch.text_table.to_s.should == expected
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'supports consistent ordering of dynamic columns' do
|
201
|
+
p1 = Person.create(first_name: 'chrismo', custom_attributes: {instrument: 'piano', style: 'jazz'})
|
202
|
+
p2 = Person.create(first_name: 'romer', custom_attributes: {hobby: 'games'})
|
203
|
+
batch = [p1, p2].to_batch
|
204
|
+
|
205
|
+
a, b = format_ids([p1.id, p2.id])
|
206
|
+
|
207
|
+
expected = <<-TABLE
|
208
|
+
+----+------------+-----------+-----+--------+------------+--------+
|
209
|
+
| person | custom_attributes |
|
210
|
+
+----+------------+-----------+-----+--------+------------+--------+
|
211
|
+
| id | first_name | last_name | age | hobby | instrument | style |
|
212
|
+
+----+------------+-----------+-----+--------+------------+--------+
|
213
|
+
|#{a}| chrismo | | | | piano | jazz |
|
214
|
+
|#{b}| romer | | | games | | |
|
215
|
+
+----+------------+-----------+-----+--------+------------+--------+
|
216
|
+
TABLE
|
217
|
+
|
218
|
+
batch.text_table.to_s.should == expected
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'handles AR instance without an association present' do
|
222
|
+
s = Supplier.create(name: 'supplier')
|
223
|
+
b = [s].to_batch
|
224
|
+
|
225
|
+
def b.serializable_options
|
226
|
+
{:only => [:name], :include => {:account => {:only => [:name, :tax_id]}}}
|
227
|
+
end
|
228
|
+
|
229
|
+
expected = <<-TABLE
|
230
|
+
+----------+
|
231
|
+
| name |
|
232
|
+
+----------+
|
233
|
+
| supplier |
|
234
|
+
+----------+
|
235
|
+
TABLE
|
236
|
+
|
237
|
+
b.text_table.to_s.should == expected
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'properly groups when original columns not sequential' do
|
241
|
+
s2 = Supplier.create(name: 'sup. two', custom_attributes: {a: 1})
|
242
|
+
|
243
|
+
def s2.foo
|
244
|
+
''
|
245
|
+
end
|
246
|
+
|
247
|
+
b = [s2].to_batch
|
248
|
+
|
249
|
+
# methods need Columns as well
|
250
|
+
def b.serializable_options
|
251
|
+
{:only => [:name, :custom_attributes], :methods => [:foo]}
|
252
|
+
end
|
253
|
+
|
254
|
+
expected = <<-TABLE
|
255
|
+
+----------+------+-------------------+
|
256
|
+
| supplier | custom_attributes |
|
257
|
+
+----------+------+-------------------+
|
258
|
+
| name | foo | a |
|
259
|
+
+----------+------+-------------------+
|
260
|
+
| sup. two | | 1 |
|
261
|
+
+----------+------+-------------------+
|
262
|
+
TABLE
|
263
|
+
|
264
|
+
b.text_table.to_s.should == expected
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'supports one to many association' do
|
268
|
+
p = Parent.create(name: 'parent')
|
269
|
+
c = Child.create(name: 'child', parent: p)
|
270
|
+
|
271
|
+
b = [p].to_batch
|
272
|
+
|
273
|
+
# little weird looking at this point, but at least not broken
|
274
|
+
expected = <<-TABLE
|
275
|
+
+----+--------+-------------------+---------------------+
|
276
|
+
| id | name | custom_attributes | children |
|
277
|
+
+----+--------+-------------------+---------------------+
|
278
|
+
| 1 | parent | | [{"name"=>"child"}] |
|
279
|
+
+----+--------+-------------------+---------------------+
|
280
|
+
TABLE
|
281
|
+
|
282
|
+
def b.serializable_options
|
283
|
+
{:include => {:children => {:only => [:name]}}}
|
284
|
+
end
|
285
|
+
|
286
|
+
b.text_table.to_s.should == expected
|
287
|
+
end
|
288
|
+
|
289
|
+
def format_ids(ary)
|
290
|
+
ary.map {|value| " #{value.to_s.ljust(3)}" }
|
291
|
+
end
|
292
|
+
|
293
|
+
describe 'fold un-sourced attributes into source hash' do
|
294
|
+
let(:obj) { Object.new.extend Tablesmith::ActiveRecordSource }
|
295
|
+
|
296
|
+
it 'should handle simple hash' do
|
297
|
+
obj.fold_un_sourced_attributes_into_source_hash(:foo, {a: 1, b: 2}).should == {foo: {a: 1, b: 2}}
|
298
|
+
end
|
299
|
+
|
300
|
+
it 'should handle nested hashes' do
|
301
|
+
before = {'name' => 'chris', account: {'name' => 'account_name'}}
|
302
|
+
expected = {foo: {'name' => 'chris'}, account: {'name' => 'account_name'}}
|
303
|
+
obj.fold_un_sourced_attributes_into_source_hash(:foo, before).should == expected
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'should handle deep nested hashes' do
|
307
|
+
before = {'name' => 'chris', account: {'id' => {'name' => 'account_name', 'number' => 123456}}}
|
308
|
+
expected = {foo: {'name' => 'chris'}, account: {'id' => {'name' => 'account_name', 'number' => 123456}}}
|
309
|
+
obj.fold_un_sourced_attributes_into_source_hash(:foo, before).should == expected
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
describe 'flatten_inner_hashes' do
|
314
|
+
let(:obj) { Object.new.extend Tablesmith::ActiveRecordSource }
|
315
|
+
|
316
|
+
it 'should flatten inner hash' do
|
317
|
+
before = {foo: {'name' => 'chris'}, account: {'name' => 'account_name'}}
|
318
|
+
expected = {'foo.name' => 'chris', 'account.name' => 'account_name'}
|
319
|
+
|
320
|
+
obj.flatten_inner_hashes(before).should == expected
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'should to_s deep nested hashes' do
|
324
|
+
before = {foo: {'name' => 'chris'}, account: {'id' => {'name' => 'account_name', 'number' => 123456}}}
|
325
|
+
expected = {'foo.name' => 'chris', "account.id" => "{\"name\"=>\"account_name\", \"number\"=>123456}"}
|
326
|
+
|
327
|
+
obj.flatten_inner_hashes(before).should == expected
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
data/spec/array_spec.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Want stuff to work with plain Arrays
|
data/spec/batch_spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Tablesmith
|
4
|
+
|
5
|
+
describe Batch do
|
6
|
+
it 'should subclass array' do
|
7
|
+
b = Batch.new
|
8
|
+
b.length.should == 0
|
9
|
+
b << 1
|
10
|
+
b << 'a'
|
11
|
+
b[0].should == 1
|
12
|
+
b[1].should == 'a'
|
13
|
+
b.class.should == Batch
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should pass unmatched Array messages to all items' do
|
17
|
+
b = Batch.new
|
18
|
+
b.length.should == 0
|
19
|
+
b << 1
|
20
|
+
b << '2'
|
21
|
+
b.to_i.should == [1, 2]
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should handle empty Array' do
|
25
|
+
expected = <<-TEXT
|
26
|
+
+---------+
|
27
|
+
| (empty) |
|
28
|
+
+---------+
|
29
|
+
TEXT
|
30
|
+
[].to_batch.text_table.to_s.should == expected
|
31
|
+
end
|
32
|
+
end
|
data/spec/fixtures.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
|
4
|
+
|
5
|
+
class Person < ActiveRecord::Base
|
6
|
+
connection.create_table table_name, :force => true do |t|
|
7
|
+
t.string :first_name
|
8
|
+
t.string :last_name
|
9
|
+
t.integer :age
|
10
|
+
t.text :custom_attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
def year_born
|
14
|
+
Time.local(2014, 1, 1).year - self.age
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Parent < ActiveRecord::Base
|
19
|
+
has_many :children
|
20
|
+
|
21
|
+
connection.create_table table_name, :force => true do |t|
|
22
|
+
t.string :name
|
23
|
+
t.text :custom_attributes
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Child < ActiveRecord::Base
|
28
|
+
belongs_to :parent
|
29
|
+
|
30
|
+
connection.create_table table_name, :force => true do |t|
|
31
|
+
t.integer :parent_id
|
32
|
+
t.string :name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Supplier < ActiveRecord::Base
|
37
|
+
has_one :account
|
38
|
+
has_one :account_history, :through => :account
|
39
|
+
|
40
|
+
accepts_nested_attributes_for :account, :account_history
|
41
|
+
|
42
|
+
connection.create_table table_name, :force => true do |t|
|
43
|
+
t.integer :account_id
|
44
|
+
t.integer :account_history_id
|
45
|
+
t.string :name
|
46
|
+
t.text :custom_attributes
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Account < ActiveRecord::Base
|
51
|
+
belongs_to :supplier
|
52
|
+
has_one :account_history
|
53
|
+
|
54
|
+
accepts_nested_attributes_for :account_history
|
55
|
+
|
56
|
+
connection.create_table table_name, :force => true do |t|
|
57
|
+
t.integer :supplier_id
|
58
|
+
t.string :name
|
59
|
+
t.integer :tax_identification_number
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class AccountHistory < ActiveRecord::Base
|
64
|
+
belongs_to :account
|
65
|
+
|
66
|
+
connection.create_table table_name, :force => true do |t|
|
67
|
+
t.integer :account_id
|
68
|
+
t.integer :credit_rating
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'HashRowsSource' do
|
4
|
+
it 'outputs text table of simple hash row with default columns' do
|
5
|
+
expected = <<-TABLE
|
6
|
+
+---+---+
|
7
|
+
| a | b |
|
8
|
+
+---+---+
|
9
|
+
| 1 | 2 |
|
10
|
+
+---+---+
|
11
|
+
TABLE
|
12
|
+
[{a: 1, b: 2}].to_batch.text_table.to_s.should == expected
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'outputs text table of mixed columns hash rows with default columns' do
|
16
|
+
expected = <<-TABLE
|
17
|
+
+---+---+---+
|
18
|
+
| a | b | c |
|
19
|
+
+---+---+---+
|
20
|
+
| 1 | 2 | |
|
21
|
+
| 2 | | ! |
|
22
|
+
+---+---+---+
|
23
|
+
TABLE
|
24
|
+
[
|
25
|
+
{a: 1, b: 2},
|
26
|
+
{a: 2, c: '!'}
|
27
|
+
].to_batch.text_table.to_s.should == expected
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'outputs text table of deep hash rows with defined columns' do
|
31
|
+
expected = <<-TABLE
|
32
|
+
+---+---+---+
|
33
|
+
| | b |
|
34
|
+
+---+---+---+
|
35
|
+
| a | c | d |
|
36
|
+
+---+---+---+
|
37
|
+
| 1 | 2 | 2 |
|
38
|
+
+---+---+---+
|
39
|
+
TABLE
|
40
|
+
b = [{a: 1, b: {c: 2, d: 2}}].to_batch
|
41
|
+
def b.columns
|
42
|
+
[
|
43
|
+
Column.new(name: :a),
|
44
|
+
{b: [
|
45
|
+
Column.new(name: :c)
|
46
|
+
]}
|
47
|
+
]
|
48
|
+
end
|
49
|
+
# this would be nice. Payments has some code along these lines for BraintreeBatch? or some ActiveRecordSource re-use?
|
50
|
+
# b.text_table.to_s.should == expected
|
51
|
+
pending
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'outputs text table of deep hash rows with default columns'
|
55
|
+
end
|
data/spec/hash_spec.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Want stuff to work with a plain Hash
|
data/spec/spec_helper.rb
ADDED
data/tablesmith.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tablesmith/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'tablesmith'
|
8
|
+
gem.version = Tablesmith::VERSION
|
9
|
+
gem.authors = ['chrismo']
|
10
|
+
gem.email = ['chrismo@clabs.org']
|
11
|
+
gem.description = %q{Minimal console table}
|
12
|
+
gem.summary = %q{Minimal console table}
|
13
|
+
gem.homepage = 'http://github.com/livingsocial/tablesmith'
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
|
20
|
+
gem.add_dependency 'text-table'
|
21
|
+
|
22
|
+
gem.add_development_dependency 'activerecord', '~> 3.0'
|
23
|
+
gem.add_development_dependency 'pry'
|
24
|
+
gem.add_development_dependency 'rake', '~> 10.0'
|
25
|
+
gem.add_development_dependency 'rspec', '~> 2.0'
|
26
|
+
gem.add_development_dependency 'simplecov'
|
27
|
+
gem.add_development_dependency 'sqlite3'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tablesmith
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- chrismo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: text-table
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sqlite3
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Minimal console table
|
112
|
+
email:
|
113
|
+
- chrismo@clabs.org
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rspec"
|
120
|
+
- ".ruby-version"
|
121
|
+
- Gemfile
|
122
|
+
- Gemfile.lock
|
123
|
+
- LICENSE
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- lib/tablesmith.rb
|
127
|
+
- lib/tablesmith/active_record_source.rb
|
128
|
+
- lib/tablesmith/batch.rb
|
129
|
+
- lib/tablesmith/hash_rows_source.rb
|
130
|
+
- lib/tablesmith/version.rb
|
131
|
+
- spec/active_record_batch_spec.rb
|
132
|
+
- spec/array_spec.rb
|
133
|
+
- spec/batch_spec.rb
|
134
|
+
- spec/fixtures.rb
|
135
|
+
- spec/hash_rows_batch_spec.rb
|
136
|
+
- spec/hash_spec.rb
|
137
|
+
- spec/spec_helper.rb
|
138
|
+
- tablesmith.gemspec
|
139
|
+
homepage: http://github.com/livingsocial/tablesmith
|
140
|
+
licenses: []
|
141
|
+
metadata: {}
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 2.6.12
|
159
|
+
signing_key:
|
160
|
+
specification_version: 4
|
161
|
+
summary: Minimal console table
|
162
|
+
test_files:
|
163
|
+
- spec/active_record_batch_spec.rb
|
164
|
+
- spec/array_spec.rb
|
165
|
+
- spec/batch_spec.rb
|
166
|
+
- spec/fixtures.rb
|
167
|
+
- spec/hash_rows_batch_spec.rb
|
168
|
+
- spec/hash_spec.rb
|
169
|
+
- spec/spec_helper.rb
|