tty-box 0.6.0 → 0.7.0

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