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 +4 -4
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +1 -1
- data/README.md +18 -4
- data/lib/tty-box.rb +1 -1
- data/lib/tty/box.rb +142 -58
- data/lib/tty/box/border.rb +20 -14
- data/lib/tty/box/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9509817a402d4b790c91be69e5d9c11947ba1374a32ba0df8a06a18c333d371
|
4
|
+
data.tar.gz: ea1322ac1cedabf28d34c52fb666386b97013feffc6319cd9c24356039831597
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78d7164d16610ae4fc585977880845b3bd83c2be04e5c417ec08ce1aa00d0a56d6ff90efa5a3229742e97ea4836faadd140d59aca936ed545dd3606d85bf27d4
|
7
|
+
data.tar.gz: 8c7af2b0bf020d0208065a6afdd2ad8a28edfe3ed731582fbf20f4f8b301b574ebd54b5f0c7d9f05db853a7f9e35a507e5092f8a18ab62feefcc0a84851472bb
|
data/CHANGELOG.md
CHANGED
@@ -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
|
data/LICENSE.txt
CHANGED
@@ -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://
|
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]
|
6
6
|
|
7
7
|
[][gem]
|
8
|
-
[][gh_actions_ci]
|
9
9
|
[][appveyor]
|
10
10
|
[][codeclimate]
|
11
11
|
[][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
|
-
[
|
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.
|
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:
|
data/lib/tty-box.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative "tty/box"
|
data/lib/tty/box.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "strings"
|
4
|
+
require "pastel"
|
5
|
+
require "tty-cursor"
|
6
6
|
|
7
|
-
require_relative
|
8
|
-
require_relative
|
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,
|
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
|
-
|
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
|
-
|
208
|
+
content_lines = str.split(sep)
|
209
|
+
|
214
210
|
# infer dimensions
|
215
|
-
dimensions = infer_dimensions(
|
211
|
+
dimensions = infer_dimensions(content_lines, padding)
|
216
212
|
width ||= left_size + dimensions[0] + right_size
|
217
|
-
width = [width,
|
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
|
-
|
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
|
-
|
238
|
-
|
239
|
-
output << bg.(fg.(
|
240
|
-
|
241
|
-
|
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.(
|
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,
|
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
|
-
|
276
|
-
|
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
|
-
#
|
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(
|
288
|
-
return
|
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
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
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 : ->
|
307
|
-
bg = style[:bg] ? color.send(:"on_#{style[:bg]}").detach : ->
|
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) +
|
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?
|
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?
|
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 +
|
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) +
|
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?
|
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?
|
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 +
|
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
|
data/lib/tty/box/border.rb
CHANGED
@@ -6,18 +6,18 @@ module TTY
|
|
6
6
|
#
|
7
7
|
# @api private
|
8
8
|
class Border
|
9
|
-
BORDER_VALUES = [
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
74
|
-
|
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
|
data/lib/tty/box/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2020-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pastel
|