tty 0.0.3 → 0.0.4

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.
data/README.md CHANGED
@@ -38,15 +38,18 @@ Or install it yourself as:
38
38
 
39
39
  ### Table
40
40
 
41
- To instantiate table
41
+ To instantiate table pass 2-dimensional array:
42
42
 
43
43
  ```ruby
44
44
  table = TTY::Table[['a1', 'a2'], ['b1', 'b2']]
45
45
  table = TTY::Table.new [['a1', 'a2'], ['b1', 'b2']]
46
46
  table = TTY::Table.new rows: [['a1', 'a2'], ['b1', 'b2']]
47
+
48
+ table = TTY::Table.new ['h1', 'h2'], [['a1', 'a2'], ['b1', 'b2']]
49
+ table = TTY::Table.new header: ['h1', 'h2'], rows: [['a1', 'a2'], ['b1', 'b2']]
47
50
  ```
48
51
 
49
- Apart from `rows`, you can provide other customization options such as
52
+ Apart from `rows` and `header`, you can provide other customization options such as
50
53
 
51
54
  ```ruby
52
55
  column_widths # enforce maximum columns widths
@@ -59,6 +62,7 @@ Table behaves like an Array so `<<`, `each` and familiar methods can be used
59
62
  ```ruby
60
63
  table << ['a1', 'a2', 'a3']
61
64
  table << ['b1', 'b2', 'b3']
65
+ table << ['a1', 'a2'] << ['b1', 'b2'] # chain rows assignment
62
66
 
63
67
  table.each { |row| ... } # iterate over rows
64
68
  table[i, j] # return element at row(i) and column(j)
@@ -66,6 +70,7 @@ Table behaves like an Array so `<<`, `each` and familiar methods can be used
66
70
  table.column(j) { ... } # return array for column(j)
67
71
  table.row_size # return row size
68
72
  table.column_size # return column size
73
+ table.size # return an array of [row_size, column_size]
69
74
  ```
70
75
 
71
76
  or pass your rows in a block
@@ -77,7 +82,6 @@ or pass your rows in a block
77
82
  end
78
83
  ```
79
84
 
80
-
81
85
  And then to print do
82
86
 
83
87
  ```ruby
data/Rakefile CHANGED
@@ -7,4 +7,6 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
7
7
  spec.pattern = FileList['spec/**/*_spec.rb']
8
8
  end
9
9
 
10
+ FileList['tasks/**/*.rake'].each { |task| import task }
11
+
10
12
  task :default => [:spec]
@@ -0,0 +1,151 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+
5
+ # Make it easy to define equality and hash methods.
6
+ module Equatable
7
+
8
+ # Hook into module inclusion.
9
+ #
10
+ # @param [Module] base
11
+ #
12
+ # @return [self]
13
+ #
14
+ # @api private
15
+ def self.included(base)
16
+ super
17
+ base.extend(self)
18
+ base.class_eval do
19
+ define_comparison_attrs
20
+ include Methods
21
+ define_methods
22
+ end
23
+ self
24
+ end
25
+
26
+ # Holds all attributes used for comparison.
27
+ #
28
+ # @return [Array<Symbol>]
29
+ #
30
+ # @api private
31
+ attr_reader :comparison_attrs
32
+
33
+ # Objects that include this module are assumed to be value objects.
34
+ # It is also assumed that the only values that affect the results of
35
+ # equality comparison are the values of the object's attributes.
36
+ #
37
+ # @param [Array<Symbol>] *args
38
+ #
39
+ # @return [undefined]
40
+ #
41
+ # @api public
42
+ def attr_reader(*args)
43
+ super
44
+ @comparison_attrs.concat(args)
45
+ end
46
+
47
+ # Copy the comparison_attrs into the subclass.
48
+ #
49
+ # @param [Class] subclass
50
+ #
51
+ # @api private
52
+ def inherited(subclass)
53
+ super
54
+ subclass.instance_variable_set(:@comparison_attrs, comparison_attrs.dup)
55
+ end
56
+
57
+ private
58
+
59
+ # Define class instance #comparison_attrs as an empty array.
60
+ #
61
+ # @return [undefined]
62
+ #
63
+ # @api private
64
+ def define_comparison_attrs
65
+ instance_variable_set('@comparison_attrs', [])
66
+ end
67
+
68
+ # Define all methods needed for ensuring object's equality.
69
+ #
70
+ # @return [undefined]
71
+ #
72
+ # @api private
73
+ def define_methods
74
+ define_compare
75
+ define_hash
76
+ define_inspect
77
+ end
78
+
79
+ # Define a #compare? method to check if the receiver is the same
80
+ # as the other object.
81
+ #
82
+ # @return [undefined]
83
+ #
84
+ # @api private
85
+ def define_compare
86
+ define_method(:compare?) do |comparator, other|
87
+ attrs = comparison_attrs || []
88
+ !attrs.find { |attr| send(attr).send(comparator, other.send(attr)) }
89
+ end
90
+ end
91
+
92
+ # Define a hash method that ensures that the hash value is the same for
93
+ # the same instance attributes and their corresponding values.
94
+ #
95
+ # @api private
96
+ def define_hash
97
+ define_method(:hash) do
98
+ klass = self.class
99
+ attrs = klass.comparison_attrs || []
100
+ ([klass] + attrs.map { |attr| send(attr)}).hash
101
+ end
102
+ end
103
+
104
+ # Define an inspect method that shows the class name and the values for the
105
+ # instance's attributes.
106
+ #
107
+ # @return [undefined]
108
+ #
109
+ # @api private
110
+ def define_inspect
111
+ define_method(:inspect) do
112
+ klass = self.class
113
+ name = klass.name || klass.inspect
114
+ attrs = klass.comparison_attrs || []
115
+ "#<#{name}#{attrs.map { |attr| " #{attr}=#{send(attr).inspect}" }.join}>"
116
+ end
117
+ end
118
+
119
+ module Methods
120
+
121
+ # Compare two objects for equality based on their value
122
+ # and being an instance of the given class.
123
+ #
124
+ # @param [Object] other
125
+ # the other object in comparison
126
+ #
127
+ # @return [Boolean]
128
+ #
129
+ # @api public
130
+ def eql?(other)
131
+ instance_of?(other.class) and compare?(__method__, other)
132
+ end
133
+
134
+ # Compare two objects for equality based on their value
135
+ # and being a subclass of the given class.
136
+ #
137
+ # @param [Object] other
138
+ # the other object in comparison
139
+ #
140
+ # @return [Boolean]
141
+ #
142
+ # @api public
143
+ def ==(other)
144
+ return false unless self.class <=> other.class
145
+ compare?(__method__, other)
146
+ end
147
+
148
+ end # Methods
149
+
150
+ end # Equatable
151
+ end # TTY
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  module TTY
2
4
  class Table
3
5
  module Operation
@@ -10,6 +12,9 @@ module TTY
10
12
 
11
13
  CENTER = :center.freeze
12
14
 
15
+ # Hold the type of alignment
16
+ #
17
+ # @api private
13
18
  attr_reader :type
14
19
 
15
20
  # Initialize an Alignment
@@ -25,7 +30,6 @@ module TTY
25
30
  assert_valid_type
26
31
  end
27
32
 
28
-
29
33
  # Assert the type is valid
30
34
  #
31
35
  # @return [undefined]
@@ -50,26 +54,49 @@ module TTY
50
54
  [LEFT, RIGHT, CENTER]
51
55
  end
52
56
 
53
- # Format cell with a given alignment
57
+ # Format field with a given alignment
58
+ #
59
+ # @param [Object] field
60
+ #
61
+ # @param [Integer] column_width
62
+ #
63
+ # @param [String] space
54
64
  #
55
65
  # @return [String] aligned
56
66
  #
57
67
  # @api public
58
- def format(cell, column_width, space='')
68
+ def format(field, column_width, space='')
59
69
  case type
60
70
  when :left
61
- "%-#{column_width}s#{space}" % cell.to_s
71
+ "%-#{column_width}s#{space}" % field.to_s
62
72
  when :right
63
- "%#{column_width}s#{space}" % cell.to_s
73
+ "%#{column_width}s#{space}" % field.to_s
64
74
  when :center
65
- chars = cell.to_s.chars.to_a
66
- if column_width >= chars.size
67
- right = ((pad_length = column_width - chars.length).to_f / 2).ceil
68
- left = pad_length - right
69
- [' ' * left, cell, ' ' * right].join
70
- else
71
- cell
72
- end
75
+ center_align field, column_width, space
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # Center aligns field
82
+ #
83
+ # @param [Object] field
84
+ #
85
+ # @param [Integer] column_width
86
+ #
87
+ # @param [String] space
88
+ #
89
+ # @return [String] aligned
90
+ #
91
+ # @api private
92
+ def center_align(field, column_width, space)
93
+ chars = field.to_s.chars.to_a
94
+ if column_width >= chars.size
95
+ right = ((pad_length = column_width - chars.length).to_f / 2).ceil
96
+ left = pad_length - right
97
+ [' ' * left, field, ' ' * right, space].join
98
+ else
99
+ "#{field}#{space}"
73
100
  end
74
101
  end
75
102
 
@@ -3,6 +3,8 @@
3
3
  module TTY
4
4
  class Table
5
5
  module Operation
6
+
7
+ # A class which responsiblity is to align table rows and header.
6
8
  class AlignmentSet
7
9
  include Enumerable
8
10
 
@@ -61,7 +63,7 @@ module TTY
61
63
  @alignments
62
64
  end
63
65
 
64
- # @return [Array]
66
+ # @return [Array[Alignment]]
65
67
  #
66
68
  # @api public
67
69
  def to_a
@@ -75,11 +77,20 @@ module TTY
75
77
  to_ary.empty?
76
78
  end
77
79
 
78
- # Align the supplied rows with the correct alignment
80
+ # Aligns table header
81
+ #
82
+ # @return [Array[String]]
83
+ #
84
+ # @api public
85
+ def align_header(header, options={})
86
+ align_row(header, options)
87
+ end
88
+
89
+ # Align the supplied rows with the correct alignment.
79
90
  #
80
91
  # @param [Array] rows
81
92
  #
82
- # @return [Array]
93
+ # @return [Array[Array]]
83
94
  # the aligned rows
84
95
  #
85
96
  # @api private
@@ -95,17 +106,17 @@ module TTY
95
106
  #
96
107
  # @param [Hash] options
97
108
  #
98
- # @return [String]
109
+ # @return [Array[String]]
99
110
  #
100
111
  # @api private
101
112
  def align_row(row, options={})
102
- line = ""
113
+ line = []
103
114
  row.each_with_index do |cell, index|
104
115
  column_width = options[:column_widths][index]
105
116
  alignment = Alignment.new self[index]
106
117
 
107
118
  if index == row.size - 1
108
- line << alignment.format(cell, column_width)
119
+ line << alignment.format(cell, column_width)
109
120
  else
110
121
  line << alignment.format(cell, column_width, ' ')
111
122
  end
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ class Table
5
+ module Operation
6
+
7
+ # A class responsible for shortening text.
8
+ class Truncation
9
+
10
+ def truncate(string, width, options={})
11
+ trailing = options.fetch :trailing, '…'
12
+
13
+ as_unicode do
14
+ chars = string.chars.to_a
15
+ print 'CHARS '
16
+ p chars
17
+ if chars.length < width
18
+ chars.join
19
+ else
20
+ traling_size = trailing.chars.to_a.size
21
+ ( chars[0, width-traling_size].join ) + trailing
22
+ end
23
+ end
24
+ end
25
+
26
+ if "".respond_to?(:encode)
27
+ def as_unicode
28
+ yield
29
+ end
30
+ else
31
+ def as_unicode
32
+ old, $KCODE = $KCODE, "U"
33
+ yield
34
+ ensure
35
+ $KCODE = old
36
+ end
37
+ end
38
+
39
+ end # Truncation
40
+ end # Operation
41
+ end # Table
42
+ end # TTY
@@ -3,23 +3,25 @@
3
3
  module TTY
4
4
  class Table
5
5
  module Renderer
6
+
7
+ # Renders table without any border styles.
6
8
  class Basic
9
+ extend TTY::Delegatable
7
10
 
8
11
  attr_reader :padding
9
12
 
10
13
  attr_reader :indent
11
14
 
12
- attr_reader :column_widths
13
-
14
- attr_reader :column_aligns
15
-
16
- # Return an array of table rows
15
+ # Table to be rendered
17
16
  #
18
- # @return [Array[Array]]
17
+ # @return [TTY::Table]
19
18
  #
20
- # @api private
21
- attr_reader :rows
22
- private :rows
19
+ # @api public
20
+ attr_reader :table
21
+
22
+ TABLE_DELEGATED_METHODS = [:column_widths, :alignments]
23
+
24
+ delegatable_method :table, *TABLE_DELEGATED_METHODS
23
25
 
24
26
  # Return an AlignmentSet object for processing alignments
25
27
  #
@@ -34,7 +36,6 @@ module TTY
34
36
  # @param [Hash] options
35
37
  # :indent - Indent the first column by indent value
36
38
  # :padding - Pad out the row cell by padding value
37
- # :col_widths - Enforce particular column width values
38
39
  #
39
40
  # @return [Table::Renderer::Basic]
40
41
  def initialize(options={})
@@ -49,10 +50,6 @@ module TTY
49
50
  def setup(options = {})
50
51
  @padding = 0
51
52
  @indent = options.fetch :indent, 0
52
- # TODO: assert that row_size is the same as column widths & aligns
53
- @column_widths = options.fetch :column_widths, []
54
- @column_aligns = options.fetch :column_aligns, []
55
- @alignment_set = TTY::Table::Operation::AlignmentSet.new column_aligns
56
53
  self
57
54
  end
58
55
  private :setup
@@ -68,29 +65,33 @@ module TTY
68
65
  end
69
66
 
70
67
  # @api public
71
- def self.render(rows, options={})
72
- new(options).render(rows)
68
+ def self.render(table, options={})
69
+ new(options).render(table)
73
70
  end
74
71
 
75
72
  # Renders table
76
73
  #
77
- # @param [Enumerable] rows
78
- # the table rows
74
+ # @param [TTY::Table] table
75
+ # the table to be rendered
79
76
  #
80
77
  # @return [String] string representation of table
81
78
  #
82
79
  # @api public
83
- def render(rows, options={})
84
- return if rows.empty?
85
- setup(options)
86
- # TODO: Decide about table orientation
87
- @rows = rows
80
+ def render(table)
81
+ @table = table
82
+ return if table.to_a.empty?
83
+ # setup(options)
88
84
  body = []
89
- unless rows.length.zero?
90
- extract_column_widths(rows)
85
+ unless table.length.zero?
86
+ extract_column_widths
87
+ # TODO: throw an error if too many columns as compared to terminal width
88
+ # and then change table.orientation from vertical to horizontal
89
+ # TODO: Decide about table orientation
90
+
91
+ body += render_header
91
92
  body += render_rows
92
93
  end
93
- body.join("\n")
94
+ body.compact.join("\n")
94
95
  end
95
96
 
96
97
  # Calcualte total table width
@@ -102,25 +103,43 @@ module TTY
102
103
  column_widths.reduce(:+)
103
104
  end
104
105
 
106
+ private
107
+
105
108
  # Calcualte maximum column widths
106
109
  #
107
110
  # @return [Array] column widths
108
111
  #
109
112
  # @api private
110
- def extract_column_widths(rows)
113
+ def extract_column_widths
111
114
  return column_widths unless column_widths.empty?
112
- # TODO: throw an error if too many columns as compared to terminal width
113
- colcount = rows.max{ |a,b| a.size <=> b.size }.size
115
+ data = table.header ? table.to_a + [table.header] : table.to_a
116
+ colcount = data.max{ |a,b| a.size <=> b.size }.size
114
117
  maximas = []
115
118
  start = 0
116
119
 
117
120
  start.upto(colcount - 1) do |index|
118
- maximum = rows.map { |row|
121
+ maximum = data.map { |row|
119
122
  row[index] ? (row[index].to_s.size) : 0
120
123
  }.max
121
124
  maximas << maximum
122
125
  end
123
- @column_widths = maximas
126
+ table.column_widths = maximas
127
+ end
128
+
129
+ # Format the header
130
+ #
131
+ # @return [Array[String]]
132
+ #
133
+ # @api private
134
+ def render_header
135
+ header = table.header
136
+ if header
137
+ aligned = alignments.align_header header,
138
+ :column_widths => column_widths
139
+ [aligned.join]
140
+ else
141
+ []
142
+ end
124
143
  end
125
144
 
126
145
  # Format the rows
@@ -129,7 +148,9 @@ module TTY
129
148
  #
130
149
  # @api private
131
150
  def render_rows
132
- alignment_set.align_rows rows, :column_widths => column_widths
151
+ aligned = alignments.align_rows table.to_a,
152
+ :column_widths => column_widths
153
+ aligned.map { |row| row.join }
133
154
  end
134
155
 
135
156
  end # Basic
@@ -3,6 +3,10 @@
3
3
  module TTY
4
4
  class Table
5
5
 
6
+ # Determine renderer based on terminal capabilities
7
+ #
8
+ # @return [TTY::Table::Renderer]
9
+ #
6
10
  # @api public
7
11
  def self.renderer
8
12
  @renderer ||= if TTY.terminal.color?
@@ -45,6 +49,14 @@ module TTY
45
49
  self.renderer = RENDERER_MAPPER[:"#{options[:renderer]}"].new
46
50
  end
47
51
 
52
+ # Determine renderer class based on string name
53
+ #
54
+ # @param [TTY::Table::Renderer] renderer
55
+ # the renderer used for displaying table
56
+ #
57
+ # @return [TTY::Table::Renderer]
58
+ #
59
+ # @api private
48
60
  def pick_renderer(renderer)
49
61
  if renderer
50
62
  RENDERER_MAPPER[renderer].new
@@ -62,6 +74,11 @@ module TTY
62
74
  @renderer ||= TTY::Table.renderer.new
63
75
  end
64
76
 
77
+ # Set the renderer
78
+ #
79
+ # @return [TTY::Table::Renderer]
80
+ #
81
+ # @api private
65
82
  def renderer=(renderer)
66
83
  @renderer = renderer
67
84
  end
@@ -23,9 +23,6 @@ module TTY
23
23
  end
24
24
  end
25
25
 
26
- def assert_alignments
27
- end
28
-
29
26
  def assert_matching_widths(rows)
30
27
  end
31
28