tty 0.0.9 → 0.0.10

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.
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