tinytable 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.
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .rspec
6
+ .rvmrc
7
+ .yardoc
8
+ .DS_Store
9
+ Gemfile.lock
10
+ InstalledFiles
11
+ _yardoc
12
+ coverage
13
+ doc/
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ test/tmp
19
+ test/version_tmp
20
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Leo Cassarani
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,129 @@
1
+ # TinyTable
2
+
3
+ TinyTable is a Ruby gem that lets you create and output simple ASCII tables with minimal effort. It draws heavy inspiration from [Ruport](https://github.com/ruport/ruport) but, unlike Ruport, it has a very simple and focused API, and no external dependencies.
4
+
5
+ TinyTable is particularly well-suited to formatting the results of a Rake task or test suite. The output of `rake stats` is a perfect example of the kind of thing you might choose to use TinyTable for.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'tinytable'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install tinytable
20
+
21
+ ## Usage
22
+
23
+ Create a `TinyTable::Table` and start adding rows to it:
24
+
25
+ table = TinyTable::Table.new
26
+ table << ["Controllers", 169, 140]
27
+ table << ["Helpers", 48, 43]
28
+ table << ["Models", 149, 120]
29
+ puts table.to_text
30
+
31
+ This will output:
32
+
33
+ +-------------+-----+-----+
34
+ | Controllers | 169 | 140 |
35
+ | Helpers | 48 | 43 |
36
+ | Models | 149 | 120 |
37
+ +-------------+-----+-----+
38
+
39
+ By default, columns are left-aligned. However, you can specify the text alignment of each column by using its index (starting from 0):
40
+
41
+ table.align(0, TinyTable::CENTER)
42
+ table.align(1, TinyTable::RIGHT)
43
+ table.align(2, TinyTable::LEFT)
44
+
45
+ Output:
46
+
47
+ +-------------+-----+-----+
48
+ | Controllers | 169 | 140 |
49
+ | Helpers | 48 | 43 |
50
+ | Models | 149 | 120 |
51
+ +-------------+-----+-----+
52
+
53
+ ### Headers and Footers
54
+
55
+ Tables can have headers and footers:
56
+
57
+ table = TinyTable::Table.new
58
+
59
+ table.header = %w[Name Lines LOC]
60
+ table << ["Controllers", 169, 140]
61
+ table << ["Helpers", 48, 43]
62
+ table.footer = ["Total", 217, 183]
63
+
64
+ table.align(1, TinyTable::RIGHT)
65
+ table.align(2, TinyTable::RIGHT)
66
+
67
+ puts table.to_text
68
+
69
+ Output:
70
+
71
+ +-------------+-------+-----+
72
+ | Name | Lines | LOC |
73
+ +-------------+-------+-----+
74
+ | Controllers | 169 | 140 |
75
+ | Helpers | 48 | 43 |
76
+ +-------------+-------+-----+
77
+ | Total | 217 | 183 |
78
+ +-------------+-------+-----+
79
+
80
+ Header titles can also be passed into the constructor for the table.
81
+
82
+ When a table has a header, new rows can be added as hashes, and columns can be referred to by their header title when specifying text alignment:
83
+
84
+ table = TinyTable::Table.new("City", "County")
85
+ table.add "City" => "London", "County" => "Greater London"
86
+ table.add "County" => "Yorkshire", "City" => "Sheffield"
87
+ table.align("County", TinyTable::RIGHT)
88
+ puts table.to_text
89
+
90
+ Output:
91
+
92
+ +-----------+----------------+
93
+ | City | County |
94
+ +-----------+----------------+
95
+ | London | Greater London |
96
+ | Sheffield | Yorkshire |
97
+ +-----------+----------------+
98
+
99
+ ### Partial Rows
100
+
101
+ Rows can contain empty cells. When supplying an array, you can use `nil` to skip a column, or you can simply supply a smaller array if you want to skip the columns at the end. When using the hash syntax with header titles, just omit the value for the keys you wish to skip.
102
+
103
+ table = TinyTable::Table.new("City", "County")
104
+ table.add [nil, "Shropshire"]
105
+ table.add ["Sheffield"]
106
+ table.add "City" => "Bristol"
107
+ puts table.to_text
108
+
109
+ Output:
110
+
111
+ +-----------+------------+
112
+ | City | County |
113
+ +-----------+------------+
114
+ | | Shropshire |
115
+ | Sheffield | |
116
+ | Bristol | |
117
+ +-----------+------------+
118
+
119
+ ## Contributing
120
+
121
+ 1. Fork it
122
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
123
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
124
+ 4. Push to the branch (`git push origin my-new-feature`)
125
+ 5. Create new Pull Request
126
+
127
+ ## Copyright
128
+
129
+ Copyright (c) 2012 Leo Cassarani. See [LICENSE](https://github.com/leocassarani/tinytable/blob/master/LICENSE) for details.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new('spec')
6
+
7
+ task :default => :spec
@@ -0,0 +1,10 @@
1
+ require 'tinytable/version'
2
+ require 'tinytable/constants'
3
+ require 'tinytable/table'
4
+ require 'tinytable/text_formatter'
5
+ require 'tinytable/layout'
6
+ require 'tinytable/row'
7
+ require 'tinytable/cell'
8
+
9
+ module TinyTable
10
+ end
@@ -0,0 +1,24 @@
1
+ module TinyTable
2
+ class Cell
3
+ attr_reader :alignment
4
+
5
+ def initialize(text, alignment)
6
+ @text = text || ''
7
+ @alignment = alignment
8
+ end
9
+
10
+ def ==(obj)
11
+ return false unless obj.is_a? Cell
12
+ text == obj.text &&
13
+ alignment == obj.alignment
14
+ end
15
+
16
+ def text
17
+ @text.to_s
18
+ end
19
+
20
+ def width
21
+ text.length
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ module TinyTable
2
+ LEFT = :left
3
+ CENTER = :center
4
+ RIGHT = :right
5
+ end
@@ -0,0 +1,61 @@
1
+ module TinyTable
2
+ class Layout
3
+ attr_reader :column_count, :max_column_widths
4
+
5
+ def initialize(table)
6
+ @table = table
7
+ end
8
+
9
+ def analyze
10
+ @column_count = 0
11
+ @max_column_widths = []
12
+
13
+ analyze_header
14
+ analyze_rows
15
+ analyze_footer
16
+
17
+ self
18
+ end
19
+
20
+ private
21
+
22
+ def analyze_header
23
+ if @table.has_header?
24
+ analyze_row(@table.header)
25
+ end
26
+ end
27
+
28
+ def analyze_rows
29
+ if @table.has_rows?
30
+ @table.each_row do |row|
31
+ analyze_row(row)
32
+ end
33
+ end
34
+ end
35
+
36
+ def analyze_footer
37
+ if @table.has_footer?
38
+ analyze_row(@table.footer)
39
+ end
40
+ end
41
+
42
+ def analyze_row(row)
43
+ if row.cell_count > @column_count
44
+ @column_count = row.cell_count
45
+ end
46
+
47
+ row.each_cell_with_index do |cell, i|
48
+ analyze_cell(cell, i)
49
+ end
50
+ end
51
+
52
+ def analyze_cell(cell, i)
53
+ @max_column_widths[i] ||= 0
54
+ max_width = @max_column_widths[i]
55
+
56
+ if cell.width > max_width
57
+ @max_column_widths[i] = cell.width
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,37 @@
1
+ require 'forwardable'
2
+ require 'tinytable/constants'
3
+
4
+ module TinyTable
5
+ class Row
6
+ extend Forwardable
7
+ def_delegators :@cells, :empty?, :map
8
+
9
+ attr_reader :cells
10
+
11
+ def initialize(cells, alignments = [])
12
+ @cells = cells || []
13
+ @alignments = alignments
14
+ end
15
+
16
+ def ==(obj)
17
+ return false unless obj.is_a? Row
18
+ @cells == obj.cells
19
+ end
20
+
21
+ def cell_count
22
+ @cells.count
23
+ end
24
+
25
+ def each_cell_with_index(&block)
26
+ @cells.length.times do |idx|
27
+ block.call(cell_at(idx), idx)
28
+ end
29
+ end
30
+
31
+ def cell_at(idx)
32
+ text = @cells.fetch(idx, '')
33
+ alignment = @alignments[idx] || LEFT
34
+ Cell.new(text, alignment)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,79 @@
1
+ module TinyTable
2
+ class Table
3
+ attr_writer :header, :footer
4
+ attr_reader :rows
5
+
6
+ def initialize(*args)
7
+ header = args.first.is_a?(Array) ? args.first : args
8
+ @header = header
9
+ @rows = []
10
+ @alignments = []
11
+ end
12
+
13
+ def add(*args)
14
+ row = extract_row_args(args)
15
+ rows << row
16
+ end
17
+ alias_method :<<, :add
18
+
19
+ def align(column, alignment)
20
+ idx = case column
21
+ when String
22
+ @header.index(column)
23
+ when Fixnum
24
+ column
25
+ else
26
+ raise ArgumentError.new("Received a #{column.class} but expecting a Fixnum or String")
27
+ end
28
+ @alignments[idx] = alignment unless idx.nil?
29
+ end
30
+
31
+ def has_header?
32
+ !(header.nil? || header.empty?)
33
+ end
34
+
35
+ def has_rows?
36
+ !rows.empty?
37
+ end
38
+
39
+ def has_footer?
40
+ !(footer.nil? || footer.empty?)
41
+ end
42
+
43
+ def header
44
+ Row.new(@header, @alignments)
45
+ end
46
+
47
+ def each_row(&block)
48
+ rows.each do |cells|
49
+ row = Row.new(cells, @alignments)
50
+ block.call(row)
51
+ end
52
+ end
53
+
54
+ def footer
55
+ Row.new(@footer, @alignments)
56
+ end
57
+
58
+ def to_text
59
+ TinyTable::TextFormatter.new(self).render
60
+ end
61
+
62
+ private
63
+
64
+ def extract_row_args(args)
65
+ case args.first
66
+ when Array
67
+ args.first
68
+ when Hash
69
+ if has_header?
70
+ header.map { |key| args.first[key] }
71
+ else
72
+ raise ArgumentError.new("Received a Hash but no header row was given")
73
+ end
74
+ else
75
+ args
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,109 @@
1
+ require File.expand_path('../constants', __FILE__)
2
+
3
+ module TinyTable
4
+ class TextFormatter
5
+ CORNER = '+'
6
+ VERTICAL = '|'
7
+ HORIZONTAL = '-'
8
+ PADDING = ' '
9
+
10
+ STRING_ALIGN = {
11
+ LEFT => :ljust,
12
+ CENTER => :center,
13
+ RIGHT => :rjust
14
+ }
15
+
16
+ def initialize(table)
17
+ @table = table
18
+ @output = ''
19
+ end
20
+
21
+ def render
22
+ clear_output
23
+ precompute_table_layout
24
+
25
+ render_header
26
+ render_rows
27
+ render_footer
28
+
29
+ @output
30
+ end
31
+
32
+ private
33
+
34
+ def render_header
35
+ if @table.has_header?
36
+ hr
37
+ render_row(@table.header)
38
+ end
39
+ end
40
+
41
+ def render_rows
42
+ if @table.has_rows?
43
+ hr
44
+ @table.each_row { |row| render_row(row) }
45
+ hr
46
+ end
47
+ end
48
+
49
+ def render_footer
50
+ if @table.has_footer?
51
+ render_row(@table.footer)
52
+ hr
53
+ end
54
+ end
55
+
56
+ def hr
57
+ append CORNER
58
+ @column_widths.each do |w|
59
+ append HORIZONTAL * (w + 2)
60
+ append CORNER
61
+ end
62
+ new_line
63
+ end
64
+
65
+ def render_row(row)
66
+ append VERTICAL
67
+
68
+ @column_count.times do |i|
69
+ cell = row.cell_at(i)
70
+ render_cell(cell, i)
71
+ end
72
+
73
+ new_line
74
+ end
75
+
76
+ def render_cell(cell, i)
77
+ append PADDING
78
+
79
+ method = STRING_ALIGN[cell.alignment]
80
+ text = cell.text.__send__(method, @column_widths[i], PADDING)
81
+ append text
82
+
83
+ append PADDING
84
+ append VERTICAL
85
+ end
86
+
87
+ def append(text)
88
+ @output << text.to_s
89
+ end
90
+
91
+ def new_line
92
+ append "\n"
93
+ end
94
+
95
+ def clear_output
96
+ if RUBY_VERSION < "1.9"
97
+ @output = ''
98
+ else
99
+ @output.clear
100
+ end
101
+ end
102
+
103
+ def precompute_table_layout
104
+ layout = Layout.new(@table).analyze
105
+ @column_count = layout.column_count
106
+ @column_widths = layout.max_column_widths
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,3 @@
1
+ module TinyTable
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,141 @@
1
+ require File.expand_path('../../../lib/tinytable', __FILE__)
2
+
3
+ describe "a tiny table" do
4
+ it "supports tables with no header or footer" do
5
+ table = TinyTable::Table.new
6
+ table << ["London", "Greater London"]
7
+ table << ["Birmingham", "West Midlands"]
8
+ table << ["Manchester", "Greater Manchester"]
9
+ table.to_text.should == <<-EOF
10
+ +------------+--------------------+
11
+ | London | Greater London |
12
+ | Birmingham | West Midlands |
13
+ | Manchester | Greater Manchester |
14
+ +------------+--------------------+
15
+ EOF
16
+ end
17
+
18
+ it "supports a header" do
19
+ table = TinyTable::Table.new(%w[City County])
20
+ table << ["Liverpool", "Merseyside"]
21
+ table << ["Newcastle", "Tyne & Wear"]
22
+ table << ["Nottingham", "Nottinghamshire"]
23
+ table.to_text.should == <<-EOF
24
+ +------------+-----------------+
25
+ | City | County |
26
+ +------------+-----------------+
27
+ | Liverpool | Merseyside |
28
+ | Newcastle | Tyne & Wear |
29
+ | Nottingham | Nottinghamshire |
30
+ +------------+-----------------+
31
+ EOF
32
+ end
33
+
34
+ it "supports a footer" do
35
+ table = TinyTable::Table.new
36
+ table << ["Londoners", 8_294_058]
37
+ table << ["Brummies", 2_293_099]
38
+ table << ["Mancunians", 1_741_961]
39
+ table.footer = ["Total", 12_329_118]
40
+ table.to_text.should == <<-EOF
41
+ +------------+----------+
42
+ | Londoners | 8294058 |
43
+ | Brummies | 2293099 |
44
+ | Mancunians | 1741961 |
45
+ +------------+----------+
46
+ | Total | 12329118 |
47
+ +------------+----------+
48
+ EOF
49
+ end
50
+
51
+ it "supports both a header and a footer" do
52
+ table = TinyTable::Table.new
53
+ table.header = %w[City County Population]
54
+ table << ["London", "Greater London", 8_294_058]
55
+ table << ["Birmingham", "West Midlands", 2_293_099]
56
+ table << ["Manchester", "Greater Manchester", 1_741_961]
57
+ table.footer = ["Total", nil, 12_329_118]
58
+ table.to_text.should == <<-EOF
59
+ +------------+--------------------+------------+
60
+ | City | County | Population |
61
+ +------------+--------------------+------------+
62
+ | London | Greater London | 8294058 |
63
+ | Birmingham | West Midlands | 2293099 |
64
+ | Manchester | Greater Manchester | 1741961 |
65
+ +------------+--------------------+------------+
66
+ | Total | | 12329118 |
67
+ +------------+--------------------+------------+
68
+ EOF
69
+ end
70
+
71
+ it "returns an empty string if the table is completely empty" do
72
+ TinyTable::Table.new.to_text.should == ''
73
+ end
74
+
75
+ it "correctly handles rows with fewer cells than the rest" do
76
+ table = TinyTable::Table.new("City", "County", "Mayor")
77
+ table.add "London", "Greater London", "Boris Johnson"
78
+ table.add "Sheffield", "Yorkshire"
79
+ table.add "Bristol"
80
+ table.to_text.should == <<-EOF
81
+ +-----------+----------------+---------------+
82
+ | City | County | Mayor |
83
+ +-----------+----------------+---------------+
84
+ | London | Greater London | Boris Johnson |
85
+ | Sheffield | Yorkshire | |
86
+ | Bristol | | |
87
+ +-----------+----------------+---------------+
88
+ EOF
89
+ end
90
+
91
+ it "supports an alternative syntax based on argument lists" do
92
+ table = TinyTable::Table.new("City", "County", "Population")
93
+ table.add "London", "Greater London", 8_294_058
94
+ table.add "Birmingham", "West Midlands", 2_293_099
95
+ table.add "Manchester", "Greater Manchester", 1_741_961
96
+ table.footer = "Total", nil, 12_329_118
97
+ table.to_text.should == <<-EOF
98
+ +------------+--------------------+------------+
99
+ | City | County | Population |
100
+ +------------+--------------------+------------+
101
+ | London | Greater London | 8294058 |
102
+ | Birmingham | West Midlands | 2293099 |
103
+ | Manchester | Greater Manchester | 1741961 |
104
+ +------------+--------------------+------------+
105
+ | Total | | 12329118 |
106
+ +------------+--------------------+------------+
107
+ EOF
108
+ end
109
+
110
+ it "supports an alternative syntax based on hashes" do
111
+ table = TinyTable::Table.new("City", "County", "Mayor")
112
+ table.add "City" => "London", "Mayor" => "Boris Johnson", "County" => "Greater London"
113
+ table.add "City" => "Sheffield", "County" => "Yorkshire"
114
+ table.add "Mayor" => "Peter Soulsby", "City" => "Leicester"
115
+ table.to_text.should == <<-EOF
116
+ +-----------+----------------+---------------+
117
+ | City | County | Mayor |
118
+ +-----------+----------------+---------------+
119
+ | London | Greater London | Boris Johnson |
120
+ | Sheffield | Yorkshire | |
121
+ | Leicester | | Peter Soulsby |
122
+ +-----------+----------------+---------------+
123
+ EOF
124
+ end
125
+
126
+ it "allows the user to specify the alignment of column text" do
127
+ table = TinyTable::Table.new("City", "County", "Population")
128
+ table.align("City", TinyTable::CENTER)
129
+ table.align("Population", TinyTable::RIGHT)
130
+ table.add "London", "Greater London", 8_294_058
131
+ table.add "Sheffield", "Yorkshire", 2_293_099
132
+ table.to_text.should == <<-EOF
133
+ +-----------+----------------+------------+
134
+ | City | County | Population |
135
+ +-----------+----------------+------------+
136
+ | London | Greater London | 8294058 |
137
+ | Sheffield | Yorkshire | 2293099 |
138
+ +-----------+----------------+------------+
139
+ EOF
140
+ end
141
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../../../lib/tinytable/cell', __FILE__)
2
+ require File.expand_path('../../../lib/tinytable/constants', __FILE__)
3
+
4
+ describe TinyTable::Cell do
5
+ it "converts numbers to strings" do
6
+ TinyTable::Cell.new(123, TinyTable::LEFT).text.should == "123"
7
+ end
8
+
9
+ it "returns an empty string if its text was initialized as nil" do
10
+ TinyTable::Cell.new(nil, TinyTable::LEFT).text.should == ''
11
+ end
12
+
13
+ it "is equal to another cell with same (string) text and alignment" do
14
+ a1 = TinyTable::Cell.new(:a, TinyTable::LEFT)
15
+ a2 = TinyTable::Cell.new('a', TinyTable::LEFT)
16
+ a1.should == a2
17
+ end
18
+
19
+ it "is different from another cell with different text or alignment" do
20
+ a = TinyTable::Cell.new('a', TinyTable::LEFT)
21
+ b = TinyTable::Cell.new('b', TinyTable::LEFT)
22
+ a.should_not == b
23
+ end
24
+ end
@@ -0,0 +1,51 @@
1
+ require File.expand_path('../../../lib/tinytable/layout', __FILE__)
2
+ require File.expand_path('../../../lib/tinytable/row', __FILE__)
3
+ require File.expand_path('../../../lib/tinytable/cell', __FILE__)
4
+
5
+ describe TinyTable::Layout do
6
+ let(:table) { mock(:table, :has_header? => false, :has_rows? => true, :has_footer? => false) }
7
+ let(:layout) { TinyTable::Layout.new(table).analyze }
8
+
9
+ it "knows how many columns a table has" do
10
+ row1 = TinyTable::Row.new(["One"])
11
+ row2 = TinyTable::Row.new(["One", "Two"])
12
+ row3 = TinyTable::Row.new(["One", "Two", "Three"])
13
+ table.stub(:each_row).and_yield(row1).and_yield(row2).and_yield(row3)
14
+ layout.column_count.should == 3
15
+ end
16
+
17
+ it "knows the maximum width for each column in the table" do
18
+ row1 = TinyTable::Row.new(["123", "1", "12"])
19
+ row2 = TinyTable::Row.new(["12345", "123", "1"])
20
+ row3 = TinyTable::Row.new(["1234567", "12", "1"])
21
+ table.stub(:each_row).and_yield(row1).and_yield(row2).and_yield(row3)
22
+ layout.max_column_widths.should == [7, 3, 2]
23
+ end
24
+
25
+ it "includes the header row when calculating maximum column widths" do
26
+ table.stub(:has_header?) { true }
27
+ table.stub(:header) { TinyTable::Row.new(["12345", "1"]) }
28
+ table.stub(:each_row).and_yield(TinyTable::Row.new(["123", "12"]))
29
+ layout.max_column_widths.should == [5, 2]
30
+ end
31
+
32
+ it "includes the footer row when calculating maximum column widths" do
33
+ table.stub(:has_footer?) { true }
34
+ table.stub(:footer) { TinyTable::Row.new(["123", "12345"]) }
35
+ table.stub(:each_row).and_yield(TinyTable::Row.new(["1234", "12"]))
36
+ layout.max_column_widths.should == [4, 5]
37
+ end
38
+
39
+ it "correctly calculates the width of cells containing an integer" do
40
+ row1 = TinyTable::Row.new(["London", 8_294_058])
41
+ row2 = TinyTable::Row.new(["Liverpool", 830_112])
42
+ table.stub(:each_row).and_yield(row1).and_yield(row2)
43
+ layout.max_column_widths.should == [9, 7]
44
+ end
45
+
46
+ it "correctly deals with completely empty tables" do
47
+ table.stub(:has_rows?) { false }
48
+ layout.max_column_widths.should == []
49
+ layout.column_count.should be_zero
50
+ end
51
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../../../lib/tinytable/row', __FILE__)
2
+ require File.expand_path('../../../lib/tinytable/cell', __FILE__)
3
+
4
+ describe TinyTable::Row do
5
+ it "returns cells with the correct alignment" do
6
+ row = TinyTable::Row.new(['a', 'b'], [TinyTable::RIGHT, TinyTable::LEFT])
7
+ row.cell_at(0).should == TinyTable::Cell.new('a', TinyTable::RIGHT)
8
+ row.cell_at(1).should == TinyTable::Cell.new('b', TinyTable::LEFT)
9
+ end
10
+
11
+ it "defaults to left alignment unless otherwise specified" do
12
+ row = TinyTable::Row.new(['a'])
13
+ row.cell_at(0).should == TinyTable::Cell.new('a', TinyTable::LEFT)
14
+ end
15
+
16
+ it "knows if it's empty" do
17
+ TinyTable::Row.new([]).should be_empty
18
+ TinyTable::Row.new(['a']).should_not be_empty
19
+ end
20
+
21
+ it "is equal to another row with the same cells" do
22
+ TinyTable::Row.new(['a']).should == TinyTable::Row.new(['a'])
23
+ end
24
+ end
@@ -0,0 +1,129 @@
1
+ require File.expand_path('../../../lib/tinytable/table', __FILE__)
2
+ require File.expand_path('../../../lib/tinytable/row', __FILE__)
3
+ require File.expand_path('../../../lib/tinytable/cell', __FILE__)
4
+
5
+ module TinyTable
6
+ class TextFormatter ; end
7
+ end
8
+
9
+ describe TinyTable::Table do
10
+ let(:table) { TinyTable::Table.new }
11
+ let(:row) { %w[Liverpool Merseyside] }
12
+
13
+ it "stores and recalls rows" do
14
+ table << row
15
+ table.rows.should == [row]
16
+ end
17
+
18
+ it "exposes an iterator over the rows" do
19
+ row_obj = mock(:row_obj)
20
+ TinyTable::Row.stub(:new).with(row, anything) { row_obj }
21
+ table << row
22
+ table.each_row do |r|
23
+ r.should == row_obj
24
+ end
25
+ end
26
+
27
+ it "knows whether it contains any rows" do
28
+ table.should_not have_rows
29
+ table << row
30
+ table.should have_rows
31
+ end
32
+
33
+ it "uses a TextFormatter to render an ASCII representation of the table" do
34
+ formatter = mock(:formatter, :render => "ASCII table")
35
+ TinyTable::TextFormatter.stub(:new).with(table) { formatter }
36
+ table.to_text.should == "ASCII table"
37
+ end
38
+
39
+ it "stores and recalls a header row" do
40
+ table.header = ["City", "County"]
41
+ table.header.should == TinyTable::Row.new(["City", "County"])
42
+ end
43
+
44
+ it "doesn't return the header along with regular rows" do
45
+ table.header = ["City", "County"]
46
+ table << row
47
+ table.rows.should == [row]
48
+ end
49
+
50
+ it "stores and recalls a footer row" do
51
+ table.footer = ["Total", "123.45"]
52
+ table.footer.should == TinyTable::Row.new(["Total", "123.45"])
53
+ end
54
+
55
+ it "doesn't return the footer along with regular rows" do
56
+ table << row
57
+ table.footer = ["Total", "123.45"]
58
+ table.rows.should == [row]
59
+ end
60
+
61
+ it "supports a number of different ways to add new rows" do
62
+ table.add "London", "Greater London"
63
+ table.add ["Birmingham", "West Midlands"]
64
+ table << ["Manchester", "Greater Manchester"]
65
+ table.rows.should == [
66
+ ["London", "Greater London"],
67
+ ["Birmingham", "West Midlands"],
68
+ ["Manchester", "Greater Manchester"]
69
+ ]
70
+ end
71
+
72
+ it "supports a number of different ways to set the header" do
73
+ table.header = ["City", "County"]
74
+ table.header.should == TinyTable::Row.new(["City", "County"])
75
+
76
+ table.header = "City", "County"
77
+ table.header.should == TinyTable::Row.new(["City", "County"])
78
+
79
+ table = TinyTable::Table.new("City", "County")
80
+ table.header.should == TinyTable::Row.new(["City", "County"])
81
+
82
+ table = TinyTable::Table.new ["City", "County"]
83
+ table.header.should == TinyTable::Row.new(["City", "County"])
84
+ end
85
+
86
+ it "supports a number of different ways to set the footer" do
87
+ table.footer = ["Total", "300"]
88
+ table.footer.should == TinyTable::Row.new(["Total", "300"])
89
+
90
+ table.footer = "Total", "300"
91
+ table.footer.should == TinyTable::Row.new(["Total", "300"])
92
+ end
93
+
94
+ it "allows a row to be entered as a hash with header titles as its keys" do
95
+ table.header = "City", "County", "Population"
96
+ table.add 'City' => "Reading", 'Population' => 373_836, 'County' => "Berkshire"
97
+ table << { 'County' => "Hampshire", 'City' => "Winchester" }
98
+ table.rows.should == [
99
+ ["Reading", "Berkshire", 373_836],
100
+ ["Winchester", "Hampshire", nil]
101
+ ]
102
+ end
103
+
104
+ it "raises an ArgumentError if a hash is given without a header row" do
105
+ lambda {
106
+ table.add 'City' => "Reading", 'County' => "Berkshire"
107
+ }.should raise_error(ArgumentError)
108
+ end
109
+
110
+ it "allows the user to specify the alignment for a column" do
111
+ table.header = "City"
112
+ table.align("City", TinyTable::RIGHT)
113
+ table << ["London"]
114
+ table.each_row do |row|
115
+ row.cell_at(0).alignment.should == TinyTable::RIGHT
116
+ end
117
+ end
118
+
119
+ it "can use integers to specify the column to align" do
120
+ table.header = "City", "County"
121
+ table.align(0, TinyTable::CENTER)
122
+ table.align(1, TinyTable::RIGHT)
123
+ table << ["London", "Greater London"]
124
+ table.each_row do |row|
125
+ row.cell_at(0).alignment.should == TinyTable::CENTER
126
+ row.cell_at(1).alignment.should == TinyTable::RIGHT
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,103 @@
1
+ require File.expand_path('../../../lib/tinytable/text_formatter', __FILE__)
2
+ require File.expand_path('../../../lib/tinytable/row', __FILE__)
3
+ require File.expand_path('../../../lib/tinytable/cell', __FILE__)
4
+
5
+ module TinyTable
6
+ class Layout ; end
7
+ end
8
+
9
+ describe TinyTable::TextFormatter do
10
+ def make_row(row)
11
+ TinyTable::Row.new(row)
12
+ end
13
+
14
+ let(:table) { mock(:table, :has_header? => false,
15
+ :has_rows? => true,
16
+ :has_footer? => false) }
17
+ let(:layout) { mock(:layout, :column_count => 2) }
18
+ let(:formatter) { TinyTable::TextFormatter.new(table) }
19
+ before do
20
+ layout.stub(:analyze) { layout }
21
+ TinyTable::Layout.stub(:new).with(table) { layout }
22
+ end
23
+
24
+ it "formats a TinyTable as an ASCII table" do
25
+ table.stub(:each_row).and_yield(make_row %w[Liverpool Merseyside])
26
+ layout.stub(:max_column_widths) { ["Liverpool".length, "Merseyside".length] }
27
+ formatter.render.should == <<-EOF
28
+ +-----------+------------+
29
+ | Liverpool | Merseyside |
30
+ +-----------+------------+
31
+ EOF
32
+ end
33
+
34
+ it "adjusts the width of the table to fit the largest row" do
35
+ row1 = make_row %w[Liverpool Merseyside]
36
+ row2 = make_row %w[Nottingham Nottinghamshire]
37
+ table.stub(:each_row).and_yield(row1).and_yield(row2)
38
+ layout.stub(:max_column_widths) { ["Nottingham".length, "Nottinghamshire".length] }
39
+ formatter.render.should == <<-EOF
40
+ +------------+-----------------+
41
+ | Liverpool | Merseyside |
42
+ | Nottingham | Nottinghamshire |
43
+ +------------+-----------------+
44
+ EOF
45
+ end
46
+
47
+ it "renders the header row if the table has one" do
48
+ table.stub(:has_header?) { true }
49
+ table.stub(:header) { make_row %w[City County] }
50
+ table.stub(:each_row).and_yield(make_row %w[Southampton Hampshire])
51
+ layout.stub(:max_column_widths) { ["Southampton".length, "Hampshire".length] }
52
+
53
+ formatter.render.should == <<-EOF
54
+ +-------------+-----------+
55
+ | City | County |
56
+ +-------------+-----------+
57
+ | Southampton | Hampshire |
58
+ +-------------+-----------+
59
+ EOF
60
+ end
61
+
62
+ it "renders the footer row if the table has one" do
63
+ row1 = make_row %w[Londoners 8294058]
64
+ row2 = make_row %w[Brummies 2293099]
65
+
66
+ table.stub(:has_footer?) { true }
67
+ table.stub(:footer) { make_row %w[Total 10587157] }
68
+ table.stub(:each_row).and_yield(row1).and_yield(row2)
69
+
70
+ layout.stub(:max_column_widths) { ["Londoners".length, "10587157".length] }
71
+ formatter.render.should == <<-EOF
72
+ +-----------+----------+
73
+ | Londoners | 8294058 |
74
+ | Brummies | 2293099 |
75
+ +-----------+----------+
76
+ | Total | 10587157 |
77
+ +-----------+----------+
78
+ EOF
79
+ end
80
+
81
+ it "renders empty cells if a row has fewer columns than the rest" do
82
+ row1 = make_row ["London", "Greater London", "Boris Johnson"]
83
+ row2 = make_row ["Sheffield", "Yorkshire"]
84
+ table.stub(:each_row).and_yield(row1).and_yield(row2)
85
+
86
+ layout.stub(:max_column_widths) { ["Sheffield".length, "Greater London".length, "Boris Johnson".length] }
87
+ layout.stub(:column_count) { 3 }
88
+
89
+ formatter.render.should == <<-EOF
90
+ +-----------+----------------+---------------+
91
+ | London | Greater London | Boris Johnson |
92
+ | Sheffield | Yorkshire | |
93
+ +-----------+----------------+---------------+
94
+ EOF
95
+ end
96
+
97
+ it "returns an empty string if the table is completely empty" do
98
+ table.stub(:has_rows?) { false }
99
+ layout.stub(:max_column_widths) { [] }
100
+ layout.stub(:column_count) { 0 }
101
+ formatter.render.should == ''
102
+ end
103
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/tinytable/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Leo Cassarani"]
6
+ gem.email = ["leo.cassarani@me.com"]
7
+ gem.description = %q{Simple ASCII table generation in Ruby.}
8
+ gem.summary = %q{TinyTable lets you create and output simple ASCII tables with minimal effort.}
9
+ gem.homepage = "https://github.com/leocassarani/tinytable"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "tinytable"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = TinyTable::VERSION
17
+
18
+ gem.add_development_dependency "rake"
19
+ gem.add_development_dependency "rspec"
20
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tinytable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Leo Cassarani
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &70326742572440 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70326742572440
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &70326742571820 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70326742571820
36
+ description: Simple ASCII table generation in Ruby.
37
+ email:
38
+ - leo.cassarani@me.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - Gemfile
45
+ - LICENSE
46
+ - README.md
47
+ - Rakefile
48
+ - lib/tinytable.rb
49
+ - lib/tinytable/cell.rb
50
+ - lib/tinytable/constants.rb
51
+ - lib/tinytable/layout.rb
52
+ - lib/tinytable/row.rb
53
+ - lib/tinytable/table.rb
54
+ - lib/tinytable/text_formatter.rb
55
+ - lib/tinytable/version.rb
56
+ - spec/integration/integration_spec.rb
57
+ - spec/tinytable/cell_spec.rb
58
+ - spec/tinytable/layout_spec.rb
59
+ - spec/tinytable/row_spec.rb
60
+ - spec/tinytable/table_spec.rb
61
+ - spec/tinytable/text_formatter_spec.rb
62
+ - tinytable.gemspec
63
+ homepage: https://github.com/leocassarani/tinytable
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.17
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: TinyTable lets you create and output simple ASCII tables with minimal effort.
87
+ test_files:
88
+ - spec/integration/integration_spec.rb
89
+ - spec/tinytable/cell_spec.rb
90
+ - spec/tinytable/layout_spec.rb
91
+ - spec/tinytable/row_spec.rb
92
+ - spec/tinytable/table_spec.rb
93
+ - spec/tinytable/text_formatter_spec.rb