tty 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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