tty-box 0.6.0 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66cd38e818e47ac372868586d8a76cdeb41a98aa9efc59430ec89acb84629410
4
- data.tar.gz: d96e96b93e46a9ed8b7432632618af9c54e4da5f9575df820c6f77987104baf2
3
+ metadata.gz: b9509817a402d4b790c91be69e5d9c11947ba1374a32ba0df8a06a18c333d371
4
+ data.tar.gz: ea1322ac1cedabf28d34c52fb666386b97013feffc6319cd9c24356039831597
5
5
  SHA512:
6
- metadata.gz: 6bc7bddb63b580b8430d3dcdf596801db37e2f5813c12b8512a89dc9aff4140ebf6478e91d828020ffb182c84ca81b990d3d346c27718491c420a5a0099deca7
7
- data.tar.gz: 43756dd006eec4de539d4a802c69afa04ca1b56fdd29505725d7001418a9e37f94005fc4606daa6be2564a8a12d2b876cbfb4d2ff1f0b43254c7580a99961b48
6
+ metadata.gz: 78d7164d16610ae4fc585977880845b3bd83c2be04e5c417ec08ce1aa00d0a56d6ff90efa5a3229742e97ea4836faadd140d59aca936ed545dd3606d85bf27d4
7
+ data.tar.gz: 8c7af2b0bf020d0208065a6afdd2ad8a28edfe3ed731582fbf20f4f8b301b574ebd54b5f0c7d9f05db853a7f9e35a507e5092f8a18ab62feefcc0a84851472bb
@@ -1,5 +1,18 @@
1
1
  # Change log
2
2
 
3
+ ## [v0.7.0] - 2020-12-20
4
+
5
+ ### Added
6
+ * Add :enable_color configuration to allow control over colouring
7
+
8
+ ### Changed
9
+ * Change to ensure non-negative space filler size
10
+ * Change to enforce private visibility for the private module methods
11
+
12
+ ### Fixed
13
+ * Fix box width calculation to ignore colored text by @LainLayer
14
+ * Fix drawing frame around multiline colored content
15
+
3
16
  ## [v0.6.0] - 2020-08-11
4
17
 
5
18
  ### Changed
@@ -62,6 +75,7 @@
62
75
 
63
76
  * Initial implementation and release
64
77
 
78
+ [v0.7.0]: https://github.com/piotrmurach/tty-box/compare/v0.6.0...v0.7.0
65
79
  [v0.6.0]: https://github.com/piotrmurach/tty-box/compare/v0.5.0...v0.6.0
66
80
  [v0.5.0]: https://github.com/piotrmurach/tty-box/compare/v0.4.1...v0.5.0
67
81
  [v0.4.1]: https://github.com/piotrmurach/tty-box/compare/v0.4.0...v0.4.1
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018 Piotr Murach
3
+ Copyright (c) 2018 Piotr Murach (https://piotrmurach.com)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  <div align="center">
2
- <a href="https://piotrmurach.github.io/tty" target="_blank"><img width="130" src="https://github.com/piotrmurach/tty/blob/master/images/tty.png" alt="tty logo" /></a>
2
+ <a href="https://ttytoolkit.org"><img width="130" src="https://github.com/piotrmurach/tty/blob/master/images/tty.png" alt="TTY Toolkit logo" /></a>
3
3
  </div>
4
4
 
5
5
  # TTY::Box [![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter]
6
6
 
7
7
  [![Gem Version](https://badge.fury.io/rb/tty-box.svg)][gem]
8
- [![Build Status](https://secure.travis-ci.org/piotrmurach/tty-box.svg?branch=master)][travis]
8
+ [![Actions CI](https://github.com/piotrmurach/tty-box/workflows/CI/badge.svg?branch=master)][gh_actions_ci]
9
9
  [![Build status](https://ci.appveyor.com/api/projects/status/h9b88fk5xpya3fh1?svg=true)][appveyor]
10
10
  [![Maintainability](https://api.codeclimate.com/v1/badges/dfac05073e1549e9dbb6/maintainability)][codeclimate]
11
11
  [![Coverage Status](https://coveralls.io/repos/github/piotrmurach/tty-box/badge.svg)][coverage]
@@ -13,7 +13,7 @@
13
13
 
14
14
  [gitter]: https://gitter.im/piotrmurach/tty
15
15
  [gem]: http://badge.fury.io/rb/tty-box
16
- [travis]: http://travis-ci.org/piotrmurach/tty-box
16
+ [gh_actions_ci]: https://github.com/piotrmurach/tty-box/actions?query=workflow%3ACI
17
17
  [appveyor]: https://ci.appveyor.com/project/piotrmurach/tty-box
18
18
  [codeclimate]: https://codeclimate.com/github/piotrmurach/tty-box/maintainability
19
19
  [coverage]: https://coveralls.io/github/piotrmurach/tty-box
@@ -310,7 +310,7 @@ print box
310
310
  If you want to remove a given border element as a value use `false`. For example to remove bottom border do:
311
311
 
312
312
  ```ruby
313
- TTY::Box.new(
313
+ TTY::Box.frame(
314
314
  width: 30, height: 10,
315
315
  border: {
316
316
  type: :thick,
@@ -335,6 +335,20 @@ style: {
335
335
 
336
336
  The above style configuration will produce the result similar to the top demo, a MS-DOS look & feel window.
337
337
 
338
+ You can disable or force output styling regardless of the terminal using the `enable_color` keyword. By default, the color support is automatically detected.
339
+
340
+ ```ruby
341
+ TTY::Box.frame({
342
+ enable_color: true, # force to always color output
343
+ style: {
344
+ border: {
345
+ fg: :bright_yellow,
346
+ bg: :blue
347
+ }
348
+ }
349
+ })
350
+ ```
351
+
338
352
  ### 2.7 formatting
339
353
 
340
354
  You can use `:align` keyword to format content either to be `:left`, `:center` or `:right` aligned:
@@ -1 +1 @@
1
- require_relative 'tty/box'
1
+ require_relative "tty/box"
@@ -1,17 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'strings'
4
- require 'pastel'
5
- require 'tty-cursor'
3
+ require "strings"
4
+ require "pastel"
5
+ require "tty-cursor"
6
6
 
7
- require_relative 'box/border'
8
- require_relative 'box/version'
7
+ require_relative "box/border"
8
+ require_relative "box/version"
9
9
 
10
10
  module TTY
11
11
  module Box
12
12
  module_function
13
13
 
14
14
  NEWLINE = "\n"
15
+ SPACE = " "
15
16
 
16
17
  LINE_BREAK = %r{\r\n|\r|\n}.freeze
17
18
 
@@ -69,8 +70,8 @@ module TTY
69
70
  TTY::Cursor
70
71
  end
71
72
 
72
- def color
73
- @color ||= Pastel.new
73
+ def color(enabled: nil)
74
+ @color ||= Pastel.new(enabled: enabled)
74
75
  end
75
76
 
76
77
  # A frame for info type message
@@ -135,7 +136,7 @@ module TTY
135
136
  bg: :bright_green,
136
137
  border: {
137
138
  fg: :black,
138
- bg: :bright_green,
139
+ bg: :bright_green
139
140
  }
140
141
  }
141
142
  }.merge(opts)
@@ -187,37 +188,36 @@ module TTY
187
188
  # the styling for the front and background
188
189
  #
189
190
  # @api public
190
- def frame(*content, top: nil, left: nil, width: nil, height: nil, align: :left,
191
- padding: 0, title: {}, border: :light, style: {})
191
+ def frame(*content, top: nil, left: nil, width: nil, height: nil,
192
+ align: :left, padding: 0, title: {}, border: :light, style: {},
193
+ enable_color: nil)
194
+ @color = nil
195
+ color(enabled: enable_color)
192
196
  output = []
193
197
  sep = NEWLINE
194
198
  position = top && left
195
199
 
196
200
  border = Border.parse(border)
197
- top_size = border.top? ? 1: 0
198
- bottom_size = border.bottom? ? 1: 0
201
+ top_size = border.top? ? 1 : 0
202
+ bottom_size = border.bottom? ? 1 : 0
199
203
  left_size = border.left? ? 1 : 0
200
204
  right_size = border.right ? 1 : 0
201
205
 
202
- if block_given?
203
- str = yield
204
- else
205
- str = case content.size
206
- when 0 then ""
207
- when 1 then content[0]
208
- else content.join(NEWLINE)
209
- end
210
- end
211
-
206
+ str = block_given? ? yield : content_to_str(content)
212
207
  sep = str[LINE_BREAK] || NEWLINE # infer line break
213
- lines = str.split(sep)
208
+ content_lines = str.split(sep)
209
+
214
210
  # infer dimensions
215
- dimensions = infer_dimensions(lines, padding)
211
+ dimensions = infer_dimensions(content_lines, padding)
216
212
  width ||= left_size + dimensions[0] + right_size
217
- width = [width, top_space_taken(title, border), bottom_space_taken(title, border)].max
218
-
213
+ width = [width,
214
+ top_space_taken(title, border),
215
+ bottom_space_taken(title, border)].max
219
216
  height ||= top_size + dimensions[1] + bottom_size
220
- content = format(str, width, padding, align) # adjust content
217
+
218
+ # apply formatting to content
219
+ formatted_lines = format(content_lines, width, padding, align, sep)
220
+
221
221
  # infer styling
222
222
  fg, bg = *extract_style(style)
223
223
  border_fg, border_bg = *extract_style(style[:border] || {})
@@ -234,19 +234,22 @@ module TTY
234
234
  output << border_bg.(border_fg.(pipe_char(border.type)))
235
235
  end
236
236
 
237
- content_size = width - left_size - right_size
238
- unless content[i].nil?
239
- output << bg.(fg.(content[i]))
240
- size = Strings::ANSI.sanitize(content[i]).scan(/[[:print:]]/).join.size
241
- content_size -= size
237
+ filler_size = width - left_size - right_size
238
+ if formatted_line = formatted_lines[i]
239
+ output << bg.(fg.(formatted_line))
240
+ line_content_size = Strings::ANSI.sanitize(formatted_line)
241
+ .scan(/[[:print:]]/).join.size
242
+ filler_size = [filler_size - line_content_size, 0].max
242
243
  end
244
+
243
245
  if style[:fg] || style[:bg] || !position # something to color
244
- output << bg.(fg.(" " * content_size))
246
+ output << bg.(fg.(SPACE * filler_size))
245
247
  end
246
248
 
247
249
  if border.right?
248
250
  if position
249
- output << cursor.move_to(left + width - right_size, top + i + top_size)
251
+ output << cursor.move_to(left + width - right_size,
252
+ top + i + top_size)
250
253
  end
251
254
  output << border_bg.(border_fg.(pipe_char(border.type)))
252
255
  end
@@ -262,6 +265,22 @@ module TTY
262
265
  output.join
263
266
  end
264
267
 
268
+ # Convert content array to string
269
+ #
270
+ # @param [Array<String>] content
271
+ #
272
+ # @return [String]
273
+ #
274
+ # @api private
275
+ def content_to_str(content)
276
+ case content.size
277
+ when 0 then ""
278
+ when 1 then content[0]
279
+ else content.join(NEWLINE)
280
+ end
281
+ end
282
+ private_class_method :content_to_str
283
+
265
284
  # Infer box dimensions based on content lines and padding
266
285
  #
267
286
  # @param [Array[String]] lines
@@ -272,30 +291,59 @@ module TTY
272
291
  # @api private
273
292
  def infer_dimensions(lines, padding)
274
293
  pad = Strings::Padder.parse(padding)
275
- content_height = lines.size
276
- content_width = lines.empty? ? 1 : lines.max_by(&:length).length
277
- width = pad.left + content_width + pad.right
278
- height = pad.top + content_height + pad.bottom
294
+ width = pad.left + content_width(lines) + pad.right
295
+ height = pad.top + lines.size + pad.bottom
279
296
  [width, height]
280
297
  end
298
+ private_class_method :infer_dimensions
281
299
 
282
- # Format content
300
+ # The maximum content width for all the lines
301
+ #
302
+ # @param [Array<String>] lines
303
+ #
304
+ # @return [Integer]
305
+ #
306
+ # @api private
307
+ def content_width(lines)
308
+ return 1 if lines.empty?
309
+
310
+ lines.map(&Strings::ANSI.method(:sanitize)).max_by(&:length).length
311
+ end
312
+ private_class_method :content_width
313
+
314
+ # Format content by wrapping, aligning and padding out
315
+ #
316
+ # @param [Array<String>] lines
317
+ # the lines to format
318
+ # @param [Integer] width
319
+ # the maximum width
320
+ # @param [Integer, Array<Integer>] padding
321
+ # the amount of padding
322
+ # @param [Symbol] align
323
+ # the type of alignment
324
+ # @param [String] separator
325
+ # the newline separator
283
326
  #
284
327
  # @return [Array[String]]
285
328
  #
286
329
  # @api private
287
- def format(content, width, padding, align)
288
- return "" if content.to_s.empty?
330
+ def format(lines, width, padding, align, separator)
331
+ return [] if lines.empty?
289
332
 
290
333
  pad = Strings::Padder.parse(padding)
291
334
  total_width = width - 2 - (pad.left + pad.right)
292
- sep = content[LINE_BREAK] || NEWLINE # infer line break
293
335
 
294
- wrapped = Strings.wrap(content, total_width)
295
- aligned = Strings.align(wrapped, total_width, direction: align)
296
- padded = Strings.pad(aligned, padding)
297
- padded.split(sep)
336
+ formatted = lines.each_with_object([]) do |line, acc|
337
+ wrapped = Strings::Wrap.wrap(line, total_width, separator: separator)
338
+ acc << Strings::Align.align(wrapped, total_width,
339
+ direction: align,
340
+ separator: separator)
341
+ end.join(separator)
342
+
343
+ Strings::Pad.pad(formatted, padding, separator: separator)
344
+ .split(separator)
298
345
  end
346
+ private_class_method :format
299
347
 
300
348
  # Convert style keywords into styling
301
349
  #
@@ -303,10 +351,11 @@ module TTY
303
351
  #
304
352
  # @api private
305
353
  def extract_style(style)
306
- fg = style[:fg] ? color.send(style[:fg]).detach : -> (c) { c }
307
- bg = style[:bg] ? color.send(:"on_#{style[:bg]}").detach : -> (c) { c }
354
+ fg = style[:fg] ? color.send(style[:fg]).detach : ->(c) { c }
355
+ bg = style[:bg] ? color.send(:"on_#{style[:bg]}").detach : ->(c) { c }
308
356
  [fg, bg]
309
357
  end
358
+ private_class_method :extract_style
310
359
 
311
360
  # Top space taken by titles and corners
312
361
  #
@@ -314,26 +363,39 @@ module TTY
314
363
  #
315
364
  # @api private
316
365
  def top_space_taken(title, border)
317
- top_titles_size(title) + top_left_corner(border).size + top_right_corner(border).size
366
+ top_titles_size(title) +
367
+ top_left_corner(border).size +
368
+ top_right_corner(border).size
318
369
  end
370
+ private_class_method :top_space_taken
319
371
 
320
372
  # Top left corner
321
373
  #
374
+ # @param [Border] border
375
+ #
322
376
  # @return [String]
323
377
  #
324
378
  # @api private
325
379
  def top_left_corner(border)
326
- border.top_left? && border.left? ? send(:"#{border.top_left}_char", border.type) : ""
380
+ return "" unless border.top_left? && border.left?
381
+
382
+ send(:"#{border.top_left}_char", border.type)
327
383
  end
384
+ private_class_method :top_left_corner
328
385
 
329
386
  # Top right corner
330
387
  #
388
+ # @param [Border] border
389
+ #
331
390
  # @return [String]
332
391
  #
333
392
  # @api private
334
393
  def top_right_corner(border)
335
- border.top_right? && border.right? ? send(:"#{border.top_right}_char", border.type) : ""
394
+ return "" unless border.top_right? && border.right?
395
+
396
+ send(:"#{border.top_right}_char", border.type)
336
397
  end
398
+ private_class_method :top_right_corner
337
399
 
338
400
  # Top titles size
339
401
  #
@@ -341,8 +403,11 @@ module TTY
341
403
  #
342
404
  # @api private
343
405
  def top_titles_size(title)
344
- title[:top_left].to_s.size + title[:top_center].to_s.size + title[:top_right].to_s.size
406
+ color.strip(title[:top_left].to_s).size +
407
+ color.strip(title[:top_center].to_s).size +
408
+ color.strip(title[:top_right].to_s).size
345
409
  end
410
+ private_class_method :top_titles_size
346
411
 
347
412
  # Top border
348
413
  #
@@ -364,34 +429,49 @@ module TTY
364
429
  bg.(fg.(line_char(border.type) * top_space_after)),
365
430
  bg.(fg.(title[:top_right].to_s)),
366
431
  bg.(fg.(top_right_corner(border)))
367
- ].join('')
432
+ ].join
368
433
  end
434
+ private_class_method :top_border
435
+
369
436
  # Bottom space taken by titles and corners
370
437
  #
371
438
  # @return [Integer]
372
439
  #
373
440
  # @api private
374
441
  def bottom_space_taken(title, border)
375
- bottom_titles_size(title) + bottom_left_corner(border).size + bottom_right_corner(border).size
442
+ bottom_titles_size(title) +
443
+ bottom_left_corner(border).size +
444
+ bottom_right_corner(border).size
376
445
  end
446
+ private_class_method :bottom_space_taken
377
447
 
378
448
  # Bottom left corner
379
449
  #
450
+ # @param [Border] border
451
+ #
380
452
  # @return [String]
381
453
  #
382
454
  # @api private
383
455
  def bottom_left_corner(border)
384
- border.bottom_left? && border.left? ? send(:"#{border.bottom_left}_char", border.type) : ""
456
+ return "" unless border.bottom_left? && border.left?
457
+
458
+ send(:"#{border.bottom_left}_char", border.type)
385
459
  end
460
+ private_class_method :bottom_left_corner
386
461
 
387
462
  # Bottom right corner
388
463
  #
464
+ # @param [Border] border
465
+ #
389
466
  # @return [String]
390
467
  #
391
468
  # @api private
392
469
  def bottom_right_corner(border)
393
- border.bottom_right? && border.right? ? send(:"#{border.bottom_right}_char", border.type) : ""
470
+ return "" unless border.bottom_right? && border.right?
471
+
472
+ send(:"#{border.bottom_right}_char", border.type)
394
473
  end
474
+ private_class_method :bottom_right_corner
395
475
 
396
476
  # Bottom titles size
397
477
  #
@@ -399,8 +479,11 @@ module TTY
399
479
  #
400
480
  # @api private
401
481
  def bottom_titles_size(title)
402
- title[:bottom_left].to_s.size + title[:bottom_center].to_s.size + title[:bottom_right].to_s.size
482
+ color.strip(title[:bottom_left].to_s).size +
483
+ color.strip(title[:bottom_center].to_s).size +
484
+ color.strip(title[:bottom_right].to_s).size
403
485
  end
486
+ private_class_method :bottom_titles_size
404
487
 
405
488
  # Bottom border
406
489
  #
@@ -422,7 +505,8 @@ module TTY
422
505
  bg.(fg.(line_char(border.type) * bottom_space_after)),
423
506
  bg.(fg.(title[:bottom_right].to_s)),
424
507
  bg.(fg.(bottom_right_corner(border)))
425
- ].join('')
508
+ ].join
426
509
  end
510
+ private_class_method :bottom_border
427
511
  end # TTY
428
512
  end # Box
@@ -6,18 +6,18 @@ module TTY
6
6
  #
7
7
  # @api private
8
8
  class Border
9
- BORDER_VALUES = [
10
- :corner_bottom_right,
11
- :corner_top_right,
12
- :corner_top_left,
13
- :corner_bottom_left,
14
- :divider_left,
15
- :divider_up,
16
- :divider_down,
17
- :divider_right,
18
- :line,
19
- :pipe,
20
- :cross
9
+ BORDER_VALUES = %i[
10
+ corner_bottom_right
11
+ corner_top_right
12
+ corner_top_left
13
+ corner_bottom_left
14
+ divider_left
15
+ divider_up
16
+ divider_down
17
+ divider_right
18
+ line
19
+ pipe
20
+ cross
21
21
  ].freeze
22
22
 
23
23
  def self.parse(border)
@@ -68,11 +68,17 @@ module TTY
68
68
  private
69
69
 
70
70
  # Check if border values name is allowed
71
+ #
72
+ # @raise [ArgumentError]
73
+ #
71
74
  # @api private
72
75
  def check_name(key, value)
73
- unless (BORDER_VALUES.include?(:"#{value}") || [true, false].include?(value))
74
- raise ArgumentError, "Invalid border value: '#{value}' for #{key.inspect}"
76
+ unless BORDER_VALUES.include?(:"#{value}") ||
77
+ [true, false].include?(value)
78
+ raise ArgumentError, "invalid #{key.inspect} border value: " \
79
+ "#{value.inspect}"
75
80
  end
81
+
76
82
  value
77
83
  end
78
84
  end # Border
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TTY
4
4
  module Box
5
- VERSION = "0.6.0"
5
+ VERSION = "0.7.0"
6
6
  end # Box
7
7
  end # TTY
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tty-box
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Murach
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-11 00:00:00.000000000 Z
11
+ date: 2020-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pastel