tty 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/.rspec +2 -1
  2. data/.travis.yml +3 -6
  3. data/README.md +232 -134
  4. data/lib/tty/plugins/plugin.rb +56 -0
  5. data/lib/tty/plugins.rb +75 -0
  6. data/lib/tty/shell/suggestion.rb +102 -0
  7. data/lib/tty/shell.rb +41 -14
  8. data/lib/tty/system/editor.rb +111 -0
  9. data/lib/tty/system/which.rb +13 -1
  10. data/lib/tty/system.rb +44 -28
  11. data/lib/tty/table/border/null.rb +0 -9
  12. data/lib/tty/table/border/row_line.rb +21 -0
  13. data/lib/tty/table/border.rb +63 -32
  14. data/lib/tty/table/border_dsl.rb +1 -1
  15. data/lib/tty/table/column_set.rb +16 -17
  16. data/lib/tty/table/field.rb +27 -7
  17. data/lib/tty/table/header.rb +18 -9
  18. data/lib/tty/table/operation/alignment_set.rb +20 -25
  19. data/lib/tty/table/operation/escape.rb +30 -0
  20. data/lib/tty/table/operation/filter.rb +36 -0
  21. data/lib/tty/table/operation/truncation.rb +22 -11
  22. data/lib/tty/table/operation/wrapped.rb +21 -10
  23. data/lib/tty/table/operations.rb +10 -8
  24. data/lib/tty/table/orientation/horizontal.rb +1 -1
  25. data/lib/tty/table/renderer/ascii.rb +3 -3
  26. data/lib/tty/table/renderer/basic.rb +135 -65
  27. data/lib/tty/table/renderer/color.rb +1 -4
  28. data/lib/tty/table/renderer/unicode.rb +3 -3
  29. data/lib/tty/table/renderer.rb +48 -61
  30. data/lib/tty/table/row.rb +30 -3
  31. data/lib/tty/table/transformation.rb +38 -0
  32. data/lib/tty/table/validatable.rb +7 -5
  33. data/lib/tty/table.rb +78 -99
  34. data/lib/tty/terminal/color.rb +2 -2
  35. data/lib/tty/terminal/echo.rb +1 -1
  36. data/lib/tty/terminal/pager/basic.rb +52 -0
  37. data/lib/tty/terminal/pager/system.rb +39 -0
  38. data/lib/tty/terminal/pager.rb +95 -0
  39. data/lib/tty/terminal.rb +30 -1
  40. data/lib/tty/version.rb +1 -1
  41. data/lib/tty.rb +41 -1
  42. data/spec/spec_helper.rb +20 -0
  43. data/spec/tty/plugins/find_spec.rb +28 -0
  44. data/spec/tty/plugins/load_spec.rb +20 -0
  45. data/spec/tty/plugins/plugin/load_spec.rb +30 -0
  46. data/spec/tty/plugins/plugin/new_spec.rb +18 -0
  47. data/spec/tty/shell/suggest_spec.rb +50 -0
  48. data/spec/tty/support/conversion_spec.rb +3 -3
  49. data/spec/tty/support/delegatable_spec.rb +1 -1
  50. data/spec/tty/support/equatable_spec.rb +6 -9
  51. data/spec/tty/system/editor/available_spec.rb +40 -0
  52. data/spec/tty/system/editor/build_spec.rb +40 -0
  53. data/spec/tty/system/editor/command_spec.rb +16 -0
  54. data/spec/tty/system/editor/executables_spec.rb +13 -0
  55. data/spec/tty/system/editor/invoke_spec.rb +38 -0
  56. data/spec/tty/system/editor/open_spec.rb +27 -0
  57. data/spec/tty/system/platform_spec.rb +4 -6
  58. data/spec/tty/system/which/which_spec.rb +48 -0
  59. data/spec/tty/system/which_spec.rb +8 -34
  60. data/spec/tty/table/border/ascii/rendering_spec.rb +19 -5
  61. data/spec/tty/table/border/new_spec.rb +1 -1
  62. data/spec/tty/table/border/null/rendering_spec.rb +24 -8
  63. data/spec/tty/table/border/unicode/rendering_spec.rb +19 -5
  64. data/spec/tty/table/column_set/extract_widths_spec.rb +4 -15
  65. data/spec/tty/table/column_set/total_width_spec.rb +15 -0
  66. data/spec/tty/table/data_spec.rb +14 -0
  67. data/spec/tty/table/each_spec.rb +17 -4
  68. data/spec/tty/table/each_with_index_spec.rb +34 -6
  69. data/spec/tty/table/field/length_spec.rb +21 -0
  70. data/spec/tty/table/field/lines_spec.rb +21 -0
  71. data/spec/tty/table/filter_spec.rb +23 -0
  72. data/spec/tty/table/header/call_spec.rb +1 -1
  73. data/spec/tty/table/header/height_spec.rb +27 -0
  74. data/spec/tty/table/initialize_spec.rb +6 -6
  75. data/spec/tty/table/operation/alignment_set/call_spec.rb +39 -0
  76. data/spec/tty/table/operation/escape/call_spec.rb +16 -0
  77. data/spec/tty/table/operation/filter/call_spec.rb +17 -0
  78. data/spec/tty/table/operation/truncation/call_spec.rb +15 -10
  79. data/spec/tty/table/operation/truncation/truncate_spec.rb +1 -1
  80. data/spec/tty/table/operation/wrapped/call_spec.rb +15 -10
  81. data/spec/tty/table/operation/wrapped/wrap_spec.rb +1 -1
  82. data/spec/tty/table/operations/new_spec.rb +4 -4
  83. data/spec/tty/table/options_spec.rb +0 -28
  84. data/spec/tty/table/orientation_spec.rb +5 -6
  85. data/spec/tty/table/properties_spec.rb +1 -4
  86. data/spec/tty/table/render_spec.rb +57 -0
  87. data/spec/tty/table/{renders_with_spec.rb → render_with_spec.rb} +29 -10
  88. data/spec/tty/table/renderer/ascii/render_spec.rb +68 -0
  89. data/spec/tty/table/renderer/ascii/separator_spec.rb +28 -0
  90. data/spec/tty/table/renderer/basic/alignment_spec.rb +18 -16
  91. data/spec/tty/table/renderer/basic/extract_column_widths_spec.rb +17 -12
  92. data/spec/tty/table/renderer/basic/filter_spec.rb +53 -0
  93. data/spec/tty/table/renderer/basic/multiline_content_spec.rb +135 -0
  94. data/spec/tty/table/renderer/basic/new_spec.rb +13 -2
  95. data/spec/tty/table/renderer/basic/options_spec.rb +48 -0
  96. data/spec/tty/table/renderer/basic/render_spec.rb +19 -121
  97. data/spec/tty/table/renderer/basic/separator_spec.rb +14 -48
  98. data/spec/tty/table/renderer/basic/truncation_spec.rb +35 -0
  99. data/spec/tty/table/renderer/basic/wrapping_spec.rb +40 -0
  100. data/spec/tty/table/{border_spec.rb → renderer/border_spec.rb} +17 -20
  101. data/spec/tty/table/renderer/select_spec.rb +22 -0
  102. data/spec/tty/table/{border → renderer}/style_spec.rb +13 -14
  103. data/spec/tty/table/renderer/unicode/render_spec.rb +68 -0
  104. data/spec/tty/table/renderer/unicode/separator_spec.rb +26 -0
  105. data/spec/tty/table/rotate_spec.rb +2 -3
  106. data/spec/tty/table/row/call_spec.rb +1 -1
  107. data/spec/tty/table/row/each_spec.rb +31 -0
  108. data/spec/tty/table/row/height_spec.rb +27 -0
  109. data/spec/tty/table/to_s_spec.rb +3 -3
  110. data/spec/tty/table/transformation/extract_tuples_spec.rb +35 -0
  111. data/spec/tty/table/validatable/validate_options_spec.rb +1 -2
  112. data/spec/tty/terminal/home_spec.rb +3 -3
  113. data/spec/tty/terminal/page_spec.rb +13 -0
  114. data/spec/tty/terminal/pager/available_spec.rb +40 -0
  115. data/spec/tty/terminal/pager/basic/page_spec.rb +54 -0
  116. data/spec/tty/terminal/pager/command_spec.rb +16 -0
  117. data/spec/tty/terminal/pager/executables_spec.rb +13 -0
  118. data/spec/tty/terminal/pager/page_spec.rb +47 -0
  119. data/spec/tty/terminal/pager/system/page_spec.rb +29 -0
  120. data/spec/tty/text/distance/distance_spec.rb +12 -0
  121. data/tty.gemspec +7 -3
  122. metadata +160 -27
  123. data/spec/tty/table/operation/alignment_set/align_rows_spec.rb +0 -53
  124. data/spec/tty/table/renderer/pick_renderer_spec.rb +0 -25
  125. data/spec/tty/table/renderer_spec.rb +0 -49
@@ -0,0 +1,75 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+
5
+ # A class responsible for managing plugins installation
6
+ class Plugins
7
+
8
+ PLUGIN_PREFIX = 'tty'
9
+
10
+ attr_accessor :plugins
11
+ private :plugins
12
+
13
+ # Initialize the Plugins
14
+ #
15
+ # @api public
16
+ def initialize
17
+ @plugins = []
18
+ end
19
+
20
+ # Load all plugins that are not enabled
21
+ #
22
+ # @api public
23
+ def load
24
+ plugins.each do |plugin|
25
+ plugin.load! unless plugin.enabled?
26
+ end
27
+ end
28
+
29
+ # Register plugin with name in internal array
30
+ #
31
+ # @param [String] name
32
+ #
33
+ # @param [TTY::Plugin] plugin
34
+ #
35
+ # @api public
36
+ def register(name, plugin=false)
37
+ if plugin && !loaded?(name)
38
+ plugins << plugin
39
+ end
40
+ end
41
+
42
+ # Find all installed TTY plugins and store them
43
+ #
44
+ # @api private
45
+ def find
46
+ Gem.refresh
47
+ Gem::Specification.each do |gem|
48
+ next unless gem.name =~ /^#{PLUGIN_PREFIX}/
49
+ plugin_name = gem.name[/^#{PLUGIN_PREFIX}-(.*)/, 1]
50
+ register(plugin_name, Plugin.new(plugin_name, gem))
51
+ end
52
+ plugins
53
+ end
54
+
55
+ # Return a list of all plugin names as strings
56
+ #
57
+ # @api public
58
+ def names
59
+ plugins.inject(Hash.new) do |hash, plugin|
60
+ hash[plugin.name] = plugin
61
+ hash
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ # Check if plugin is already loaded
68
+ #
69
+ # @api private
70
+ def loaded?(name)
71
+ plugins.any? { |plugin| plugin.gem_name == name }
72
+ end
73
+
74
+ end # PluginManager
75
+ end # TTY
@@ -0,0 +1,102 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ # A class responsible for shell prompt interactions.
5
+ class Shell
6
+
7
+ # A class representing a suggestion
8
+ class Suggestion
9
+
10
+ # @api private
11
+ attr_reader :shell
12
+ private :shell
13
+
14
+ # Number of spaces
15
+ #
16
+ # @api public
17
+ attr_reader :indent
18
+
19
+ # Text for a single suggestion
20
+ #
21
+ # @api public
22
+ attr_reader :single_text
23
+
24
+ # Text for multiple suggestions
25
+ #
26
+ # @api public
27
+ attr_reader :plural_text
28
+
29
+ DEFAULT_INDENT = 8
30
+
31
+ SINGLE_TEXT = 'Did you mean this?'
32
+
33
+ PLURAL_TEXT = 'Did you mean one of these?'
34
+
35
+ # Initialize a Suggestion
36
+ #
37
+ # @api public
38
+ def initialize(options={})
39
+ @indent = options.fetch(:indent) { DEFAULT_INDENT }
40
+ @single_text = options.fetch(:single_text) { SINGLE_TEXT }
41
+ @plural_text = options.fetch(:plural_text) { PLURAL_TEXT }
42
+ end
43
+
44
+ # Suggest matches out of possibile strings
45
+ #
46
+ # @param [String] message
47
+ #
48
+ # @param [Array[String]] possibilities
49
+ #
50
+ # @api public
51
+ def suggest(message, possibilities)
52
+ distances = measure_distances(message, possibilities)
53
+ minimum_distance = distances.keys.min
54
+ max_distance = distances.keys.max
55
+
56
+ if minimum_distance < max_distance
57
+ suggestions = distances[minimum_distance].sort
58
+ evaluate(suggestions)
59
+ else
60
+ nil
61
+ end
62
+ end
63
+
64
+ # Measure distances between messag and possibilities
65
+ #
66
+ # @param [String] message
67
+ #
68
+ # @param [Array[String]] possibilities
69
+ #
70
+ # @return [Hash]
71
+ #
72
+ # @api private
73
+ def measure_distances(message, possibilities)
74
+ distances = Hash.new { |hash, key| hash[key] = [] }
75
+
76
+ possibilities.each do |possibility|
77
+ distances[Text.distance(message, possibility)] << possibility
78
+ end
79
+ distances
80
+ end
81
+
82
+ # Build up a suggestion string
83
+ #
84
+ # @param [Array[String]] suggestions
85
+ #
86
+ # @return [String]
87
+ #
88
+ # @api private
89
+ def evaluate(suggestions)
90
+ suggestion = ""
91
+ if suggestions.one?
92
+ suggestion << single_text + "\n"
93
+ suggestion << (" " * indent + suggestions.first)
94
+ else
95
+ suggestion << plural_text + "\n"
96
+ suggestion << suggestions.map { |suggestion| " " * indent + suggestion }.join("\n")
97
+ end
98
+ end
99
+
100
+ end # Suggestion
101
+ end # Shell
102
+ end # TTY
data/lib/tty/shell.rb CHANGED
@@ -135,30 +135,57 @@ module TTY
135
135
  args.each { |message| say message, options.merge(:color => :red) }
136
136
  end
137
137
 
138
- def suggest(message, possibilities)
139
- distances = Hash.new { |hash, key| hash[key] = [] }
140
-
141
- possibilities.each do |possibility|
142
- distances[Text.distance(message, possibility)] << possibility
143
- end
144
-
145
- minimum_distance = distances.keys.min
146
- if minimum_distance < 4
147
- else
148
- end
138
+ # Takes the string provided by the user and compare it with other possible
139
+ # matches to suggest an unambigous string
140
+ #
141
+ # @example
142
+ # shell.suggest('sta', ['status', 'stage', 'commit', 'branch'])
143
+ # # => "status, stage"
144
+ #
145
+ # @param [String] message
146
+ #
147
+ # @param [Array] possibilities
148
+ #
149
+ # @param [Hash] options
150
+ # @option options [String] :indent
151
+ # The number of spaces for indentation
152
+ # @option options [String] :single_text
153
+ # The text for a single suggestion
154
+ # @option options [String] :plural_text
155
+ # The text for multiple suggestions
156
+ #
157
+ # @return [String]
158
+ #
159
+ # @api public
160
+ def suggest(message, possibilities, options={})
161
+ suggestion = Suggestion.new(options)
162
+ say(suggestion.suggest(message, possibilities))
149
163
  end
150
164
 
151
165
  # Print a table to shell.
152
166
  #
167
+ # @example of a table with rows rendered with ascii border
168
+ # rows = [[1], [2], [3]]
169
+ # TTY.shell.print_table rows, renderer: :ascii
170
+ #
153
171
  # @return [undefined]
154
172
  #
155
173
  # @api public
156
174
  def print_table(*args, &block)
157
- table = TTY::Table.new *args, &block
158
- say table.to_s
175
+ options = Utils.extract_options!(args)
176
+ renderer = options.fetch(:renderer) { :basic }
177
+ table = TTY::Table.new(*args, &block)
178
+ say table.render(renderer, options)
159
179
  end
160
180
 
161
- protected
181
+ # Check if outputing to shell
182
+ #
183
+ # @return [Boolean]
184
+ #
185
+ # @api public
186
+ def tty?
187
+ stdout.tty?
188
+ end
162
189
 
163
190
  def stdin
164
191
  $stdin
@@ -0,0 +1,111 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'shellwords'
4
+
5
+ module TTY
6
+ class System
7
+
8
+ # A class responsible for launching an editor
9
+ class Editor
10
+
11
+ attr_reader :file
12
+
13
+ # Initialize an Editor
14
+ #
15
+ # @param [String] file
16
+ #
17
+ # @api public
18
+ def initialize(file)
19
+ @file = file
20
+ end
21
+
22
+ # List possible executable for editor command
23
+ #
24
+ # @return [Array[String]]
25
+ #
26
+ # @api private
27
+ def self.executables
28
+ [ ENV['VISUAL'], ENV['EDITOR'], 'vi', 'emacs' ]
29
+ end
30
+
31
+ # Find available command
32
+ #
33
+ # @param [Array[String]] commands
34
+ #
35
+ # @return [String]
36
+ #
37
+ # @api public
38
+ def self.available(*commands)
39
+ commands = commands.empty? ? self.executables : commands
40
+ commands.compact.uniq.find { |cmd| System.exists?(cmd) }
41
+ end
42
+
43
+ # Finds command using a configured command(s) or detected shell commands.
44
+ #
45
+ # @param [Array[String]] commands
46
+ #
47
+ # @return [String]
48
+ #
49
+ # @api public
50
+ def self.command(*commands)
51
+ @command = if (@command && commands.empty?)
52
+ @command
53
+ else
54
+ available(*commands)
55
+ end
56
+ end
57
+
58
+ # Open file in system editor
59
+ #
60
+ # @param [String] file
61
+ # the name of the file
62
+ #
63
+ # @raise [TTY::CommandInvocationError]
64
+ #
65
+ # @return [Object]
66
+ #
67
+ # @api public
68
+ def self.open(file)
69
+ unless self.command
70
+ raise CommandInvocationError, "Please export $VISUAL or $EDITOR"
71
+ exit 1
72
+ end
73
+
74
+ new(file).invoke
75
+ end
76
+
77
+ # Build invocation command for editor
78
+ #
79
+ # @return [String]
80
+ #
81
+ # @api private
82
+ def build
83
+ escaped_file = if System.unix?
84
+ # Escape file string so it can be safely used in a Bourne shell
85
+ Shellwords.shellescape(file)
86
+ elsif System.windows?
87
+ file.gsub(/\//, '\\')
88
+ else
89
+ file
90
+ end
91
+ "#{Editor.command} #{escaped_file}"
92
+ end
93
+
94
+ # Inovke editor command in a shell
95
+ #
96
+ # @raise [TTY::CommandInvocationError]
97
+ #
98
+ # @api private
99
+ def invoke
100
+ command_invocation = build
101
+ status = system(*Shellwords.split(command_invocation))
102
+
103
+ unless status
104
+ raise CommandInvocationError, "`#{command_invocation}` failed with status: #{$? ? $?.exitstatus : nil}"
105
+ exit status
106
+ end
107
+ end
108
+
109
+ end # Editor
110
+ end # System
111
+ end # TTY
@@ -6,6 +6,18 @@ module TTY
6
6
  # A class responsible for finding an executable in the PATH
7
7
  class Which
8
8
 
9
+ attr_reader :command
10
+
11
+ # Initialize a Which
12
+ #
13
+ # @param [String] command
14
+ # the command to find
15
+ #
16
+ # @api public
17
+ def initialize(command)
18
+ @command = command
19
+ end
20
+
9
21
  # Find an executable in the PATH
10
22
  #
11
23
  # @param [String] command
@@ -18,7 +30,7 @@ module TTY
18
30
  # the full path to executable if found, `nil` otherwise
19
31
  #
20
32
  # @api public
21
- def which(command)
33
+ def which
22
34
  exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
23
35
  default_system_path.each do |path|
24
36
  exts.each do |ext|
data/lib/tty/system.rb CHANGED
@@ -5,34 +5,50 @@ require 'rbconfig'
5
5
  module TTY
6
6
  class System
7
7
 
8
- class << self
9
-
10
- # Check if windows platform.
11
- #
12
- # @return [Boolean]
13
- #
14
- # @api public
15
- def windows?
16
- RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/
17
- end
18
-
19
- # Check if unix platform
20
- #
21
- # @return [Boolean]
22
- #
23
- # @api public
24
- def unix?
25
- RbConfig::CONFIG['host_os'] =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
26
- end
27
-
28
- # Find an executable in the PATH
29
- #
30
- # @see TTY::System::Which
31
- #
32
- # @api public
33
- def which(command)
34
- Which.new(command)
35
- end
8
+ # Check if windows platform.
9
+ #
10
+ # @return [Boolean]
11
+ #
12
+ # @api public
13
+ def self.windows?
14
+ RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/
15
+ end
16
+
17
+ # Check if unix platform
18
+ #
19
+ # @return [Boolean]
20
+ #
21
+ # @api public
22
+ def self.unix?
23
+ RbConfig::CONFIG['host_os'] =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
24
+ end
25
+
26
+ # Find an executable in the PATH
27
+ #
28
+ # @see TTY::System::Which
29
+ #
30
+ # @api public
31
+ def self.which(command)
32
+ Which.new(command).which
33
+ end
34
+
35
+ # Check if command is available
36
+ #
37
+ # @param [String] name
38
+ # the command name
39
+ #
40
+ # @api public
41
+ def self.exists?(name)
42
+ !!self.which(name)
43
+ end
44
+
45
+ # Proxy to editor object
46
+ #
47
+ # @return [TTY::System::Editor]
48
+ #
49
+ # @api public
50
+ def self.editor
51
+ TTY::System::Editor
36
52
  end
37
53
 
38
54
  end # System
@@ -26,15 +26,6 @@ module TTY
26
26
  border ? super : nil
27
27
  end
28
28
 
29
- # A line spanning all columns delemited by space character.
30
- #
31
- # @return [String]
32
- #
33
- # @api private
34
- def row_line
35
- (border && !border.characters.empty?) ? super : row.join(SPACE_CHAR)
36
- end
37
-
38
29
  # A stub bottom line
39
30
  #
40
31
  # @api private
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ class Table
5
+ class Border
6
+
7
+ # A class for a table row line chars manipulation
8
+ class RowLine < Struct.new(:left, :center, :right)
9
+
10
+ # Colorize characters with a given style
11
+ #
12
+ # @api public
13
+ def colorize(style)
14
+ colorized_chars = Border.set_color(style, right, center, left)
15
+ self.right, self.center, self.left = colorized_chars
16
+ end
17
+
18
+ end # RowLine
19
+ end # Border
20
+ end # Table
21
+ end # TTY
@@ -15,18 +15,12 @@ module TTY
15
15
  # Represent a separtor on each row
16
16
  EACH_ROW = :each_row
17
17
 
18
- # The row cell widths
18
+ # The row field widths
19
19
  #
20
20
  # @api private
21
21
  attr_reader :widths
22
22
  private :widths
23
23
 
24
- # The table row
25
- #
26
- # @api private
27
- attr_reader :row
28
- private :row
29
-
30
24
  # The table custom border characters
31
25
  attr_reader :border
32
26
 
@@ -39,19 +33,19 @@ module TTY
39
33
 
40
34
  # Instantiate a new object
41
35
  #
42
- # @param [Array] row
36
+ # @param [Array] column_widths
37
+ # the table column widths
43
38
  #
44
39
  # @param [BorderOptions] options
45
40
  #
46
41
  # @return [Object]
47
42
  #
48
43
  # @api private
49
- def initialize(row, options=nil)
44
+ def initialize(column_widths, options = nil)
50
45
  if self.class == Border
51
46
  raise NotImplementedError, "#{self} is an abstract class"
52
47
  else
53
- @row = row
54
- @widths = row.map { |cell| cell.chars.to_a.size }
48
+ @widths = column_widths
55
49
  @border = TTY::Table::BorderOptions.from options
56
50
  end
57
51
  end
@@ -65,7 +59,7 @@ module TTY
65
59
  #
66
60
  # @api public
67
61
  def self.def_border(characters=(not_set=true), &block)
68
- return self.characters = characters if !not_set
62
+ return self.characters = characters unless not_set
69
63
 
70
64
  dsl = TTY::Table::BorderDSL.new(&block)
71
65
  self.characters = dsl.characters
@@ -116,6 +110,15 @@ module TTY
116
110
  (result = render(:top)).empty? ? nil : result
117
111
  end
118
112
 
113
+ # A line spannig all columns marking bottom of a table.
114
+ #
115
+ # @return [String]
116
+ #
117
+ # @api private
118
+ def bottom_line
119
+ (result = render(:bottom)).empty? ? nil : result
120
+ end
121
+
119
122
  # A line spanning all columns delemeting rows in a table.
120
123
  #
121
124
  # @return [String]
@@ -125,35 +128,61 @@ module TTY
125
128
  (result = render(:mid)).empty? ? nil : result
126
129
  end
127
130
 
128
- # A line spanning all columns delemeting cells in a row.
131
+ # A line spanning all columns delemeting fields in a row.
132
+ #
133
+ # @param [TTY::Table::Row] row
134
+ # the table row
129
135
  #
130
136
  # @return [String]
131
137
  #
132
- # @api private
133
- def row_line
134
- right_char = self['right']
135
- left_char = self['left']
136
- center_char = self['center']
138
+ # @api public
139
+ def row_line(row)
140
+ line = RowLine.new(self['left'], self['center'], self['right'])
141
+ line.colorize(border.style) if color?
137
142
 
138
- if color?
139
- right_char, center_char, left_char = Border.set_color(border.style, right_char, center_char, left_char)
140
- end
143
+ result = row_heights(row, line)
144
+ result.empty? ? EMPTY_CHAR : result
145
+ end
141
146
 
142
- result = left_char + row.join(center_char) + right_char
143
- result.empty? ? nil : result
147
+ protected
148
+
149
+ # Separate multiline string into individual rows with border.
150
+ #
151
+ # @param [TTY::Table::Row] row
152
+ # the table row
153
+ #
154
+ # @param [TTY::Table::Border::RowLine] line
155
+ #
156
+ # @api private
157
+ def row_heights(row, line)
158
+ if row.size > 0
159
+ row.height.times.map do |line_index|
160
+ row_height_line(row, line_index, line)
161
+ end.join("\n")
162
+ else
163
+ line.left + line.right
164
+ end
144
165
  end
145
166
 
146
- # A line spannig all columns marking bottom of a table.
167
+ # Generate border for a given multiline row
168
+ #
169
+ # @param [TTY::Table::Row] row
170
+ # the table row
171
+ #
172
+ # @param [Integer] line
173
+ # the index for current line inside multiline
174
+ #
175
+ # @param [TTY::Table::Border::RowLine] line
147
176
  #
148
177
  # @return [String]
149
178
  #
150
179
  # @api private
151
- def bottom_line
152
- (result = render(:bottom)).empty? ? nil : result
180
+ def row_height_line(row, line_index, line)
181
+ line.left + row.fields.each_with_index.map do |field, index|
182
+ (field.lines[line_index] || EMPTY_CHAR).ljust(widths[index])
183
+ end.join(line.center) + line.right
153
184
  end
154
185
 
155
- protected
156
-
157
186
  # Generate particular border type
158
187
  #
159
188
  # @param [String] type
@@ -164,11 +193,13 @@ module TTY
164
193
  type = type.to_s
165
194
  border_char = self[type]
166
195
  line = render_line(border_char,
167
- self["#{type}_left"] || border_char,
168
- self["#{type}_right"] || border_char,
169
- self["#{type}_mid"])
196
+ self["#{type}_left"] || border_char,
197
+ self["#{type}_right"] || border_char,
198
+ self["#{type}_mid"])
170
199
 
171
- line = Border.set_color(border.style, line) if color?
200
+ if color? && !line.empty?
201
+ line = Border.set_color(border.style, line)
202
+ end
172
203
  line
173
204
  end
174
205
 
@@ -22,7 +22,7 @@ module TTY
22
22
  def initialize(characters=nil, &block)
23
23
  @options = TTY::Table::BorderOptions.new
24
24
  @options.characters = characters if characters
25
- yield_or_eval &block if block_given?
25
+ yield_or_eval(&block) if block_given?
26
26
  end
27
27
 
28
28
  # Apply style color to the border