tty-box 0.3.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 +56 -1
- data/LICENSE.txt +1 -1
- data/README.md +171 -34
- data/lib/tty-box.rb +1 -1
- data/lib/tty/box.rb +353 -59
- data/lib/tty/box/border.rb +21 -16
- data/lib/tty/box/version.rb +1 -1
- metadata +31 -55
- data/Rakefile +0 -8
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/examples/commander.rb +0 -54
- data/examples/connected.rb +0 -47
- data/spec/spec_helper.rb +0 -29
- data/spec/unit/align_spec.rb +0 -27
- data/spec/unit/border/parse_spec.rb +0 -61
- data/spec/unit/border_spec.rb +0 -149
- data/spec/unit/frame_spec.rb +0 -49
- data/spec/unit/padding_spec.rb +0 -46
- data/spec/unit/position_spec.rb +0 -23
- data/spec/unit/style_spec.rb +0 -121
- data/spec/unit/title_spec.rb +0 -35
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
- data/tty-box.gemspec +0 -29
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,55 @@
|
|
|
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
|
+
|
|
16
|
+
## [v0.6.0] - 2020-08-11
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
* Change to preserve newline characters when wrapping content
|
|
20
|
+
* Change gemspec to include metadata and remove test files
|
|
21
|
+
* Change to update pastel & strings dependencies
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
* Fix Ruby 2.7 warnings
|
|
25
|
+
|
|
26
|
+
## [v0.5.0] - 2019-10-08
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
* Add ability to create frames without specifying width or height
|
|
30
|
+
* Add #info, #warn, #success, #error ready frames for status messages inspired by conversation with Konstantin Gredeskoul(@kigster)
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
* Change #frame to accept content as an argument in addition to a block
|
|
34
|
+
* Change to match titles with border styling
|
|
35
|
+
|
|
36
|
+
## [v0.4.1] - 2019-08-28
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
* Add example to demonstrate different line endings
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
* Fix to handle different line endings
|
|
43
|
+
|
|
44
|
+
## [v0.4.0] - 2019-06-05
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
* Change gemspec to require Ruby >= 2.0.0
|
|
48
|
+
* Change to update tty-cursor dependency
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
* Fix issue with displaying box with colored content
|
|
52
|
+
|
|
3
53
|
## [v0.3.0] - 2018-10-08
|
|
4
54
|
|
|
5
55
|
### Added
|
|
@@ -7,7 +57,7 @@
|
|
|
7
57
|
* Add :ascii border type for drawing ASCII boxes
|
|
8
58
|
|
|
9
59
|
### Fixed
|
|
10
|
-
* Fix box color fill to
|
|
60
|
+
* Fix box color fill to correctly recognise missing borders and match the height and width
|
|
11
61
|
* Fix absolute content positioning when borders are missing
|
|
12
62
|
|
|
13
63
|
## [v0.2.1] - 2018-09-10
|
|
@@ -25,6 +75,11 @@
|
|
|
25
75
|
|
|
26
76
|
* Initial implementation and release
|
|
27
77
|
|
|
78
|
+
[v0.7.0]: https://github.com/piotrmurach/tty-box/compare/v0.6.0...v0.7.0
|
|
79
|
+
[v0.6.0]: https://github.com/piotrmurach/tty-box/compare/v0.5.0...v0.6.0
|
|
80
|
+
[v0.5.0]: https://github.com/piotrmurach/tty-box/compare/v0.4.1...v0.5.0
|
|
81
|
+
[v0.4.1]: https://github.com/piotrmurach/tty-box/compare/v0.4.0...v0.4.1
|
|
82
|
+
[v0.4.0]: https://github.com/piotrmurach/tty-box/compare/v0.3.0...v0.4.0
|
|
28
83
|
[v0.3.0]: https://github.com/piotrmurach/tty-box/compare/v0.2.1...v0.3.0
|
|
29
84
|
[v0.2.1]: https://github.com/piotrmurach/tty-box/compare/v0.2.0...v0.2.1
|
|
30
85
|
[v0.2.0]: https://github.com/piotrmurach/tty-box/compare/v0.1.0...v0.2.0
|
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,24 +13,24 @@
|
|
|
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
|
|
20
20
|
[inchpages]: http://inch-ci.org/github/piotrmurach/tty-box
|
|
21
21
|
|
|
22
|
-
> Draw various frames and boxes in
|
|
22
|
+
> Draw various frames and boxes in the terminal window.
|
|
23
23
|
|
|
24
24
|
**TTY::Box** provides box drawing component for [TTY](https://github.com/piotrmurach/tty) toolkit.
|
|
25
25
|
|
|
26
|
-

|
|
27
27
|
|
|
28
28
|
## Installation
|
|
29
29
|
|
|
30
30
|
Add this line to your application's Gemfile:
|
|
31
31
|
|
|
32
32
|
```ruby
|
|
33
|
-
gem
|
|
33
|
+
gem "tty-box"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
And then execute:
|
|
@@ -52,49 +52,90 @@ Or install it yourself as:
|
|
|
52
52
|
* [2.5 border](#25-border)
|
|
53
53
|
* [2.6 styling](#26-styling)
|
|
54
54
|
* [2.7 formatting](#27-formatting)
|
|
55
|
+
* [2.8 messages](#28-messages)
|
|
56
|
+
* [2.8.1 info](#281-info)
|
|
57
|
+
* [2.8.2 warn](#282-warn)
|
|
58
|
+
* [2.8.3 success](#283-success)
|
|
59
|
+
* [2.8.4 error](#284-error)
|
|
55
60
|
|
|
56
61
|
## 1. Usage
|
|
57
62
|
|
|
58
63
|
Using the `frame` method, you can draw a box in a terminal emulator:
|
|
59
64
|
|
|
60
65
|
```ruby
|
|
61
|
-
box = TTY::Box.frame
|
|
62
|
-
width: 30,
|
|
63
|
-
height: 10,
|
|
64
|
-
align: :center,
|
|
65
|
-
padding: 3
|
|
66
|
-
) do
|
|
67
|
-
"Drawing a box in terminal emulator"
|
|
68
|
-
end
|
|
69
|
-
|
|
66
|
+
box = TTY::Box.frame "Drawing a box in", "terminal emulator", padding: 3, align: :center
|
|
70
67
|
```
|
|
71
68
|
|
|
69
|
+
And then print:
|
|
70
|
+
|
|
72
71
|
```ruby
|
|
73
72
|
print box
|
|
74
73
|
# =>
|
|
75
|
-
#
|
|
76
|
-
# │
|
|
77
|
-
# │
|
|
78
|
-
# │
|
|
79
|
-
# │
|
|
80
|
-
# │
|
|
81
|
-
# │
|
|
82
|
-
# │
|
|
83
|
-
# │
|
|
84
|
-
#
|
|
74
|
+
# ┌───────────────────────┐
|
|
75
|
+
# │ │
|
|
76
|
+
# │ │
|
|
77
|
+
# │ │
|
|
78
|
+
# │ Drawing a box in │
|
|
79
|
+
# │ terminal emulator │
|
|
80
|
+
# │ │
|
|
81
|
+
# │ │
|
|
82
|
+
# │ │
|
|
83
|
+
# └───────────────────────┘
|
|
85
84
|
```
|
|
86
85
|
|
|
87
86
|
## 2. Interface
|
|
88
87
|
|
|
89
88
|
### 2.1 frame
|
|
90
89
|
|
|
91
|
-
You can draw a box in
|
|
90
|
+
You can draw a box in your terminal window by using the `frame` method and passing a content to display. By default the box will be drawn around the content.
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
print TTY::Box.frame "Hello world!"
|
|
94
|
+
# =>
|
|
95
|
+
# ┌────────────┐
|
|
96
|
+
# │Hello world!│
|
|
97
|
+
# └────────────┘
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
You can also provide multi line content as separate arguments.
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
print TTY::Box.frame "Hello", "world!"
|
|
104
|
+
# =>
|
|
105
|
+
# ┌──────┐
|
|
106
|
+
# │Hello │
|
|
107
|
+
# │world!│
|
|
108
|
+
# └──────┘
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Alternatively, provide a multi line content using newline chars in a single argument:
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
print TTY::Box.frame "Hello\nworld!"
|
|
115
|
+
# =>
|
|
116
|
+
# ┌──────┐
|
|
117
|
+
# │Hello │
|
|
118
|
+
# │world!│
|
|
119
|
+
# └──────┘
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Finally, you can use a block to specify content:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
print TTY::Box.frame { "Hello world!" }
|
|
126
|
+
# =>
|
|
127
|
+
# ┌────────────┐
|
|
128
|
+
# │Hello world!│
|
|
129
|
+
# └────────────┘
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
You can also enforce a given box size without any content and use [tty-cursor](https://github.com/piotrmurach/tty-cursor) to position content whatever you like.
|
|
92
133
|
|
|
93
134
|
```ruby
|
|
94
135
|
box = TTY::Box.frame(width: 30, height: 10)
|
|
95
136
|
```
|
|
96
137
|
|
|
97
|
-
|
|
138
|
+
When printed will produce the following output in your terminal:
|
|
98
139
|
|
|
99
140
|
```ruby
|
|
100
141
|
print box
|
|
@@ -111,8 +152,6 @@ print box
|
|
|
111
152
|
# └────────────────────────────┘
|
|
112
153
|
```
|
|
113
154
|
|
|
114
|
-
Then you can use [tty-cursor](https://github.com/piotrmurach/tty-cursor) to directly manipulate content to be displayed inside the box.
|
|
115
|
-
|
|
116
155
|
Alternatively, you can also pass a block to provide a content for the box:
|
|
117
156
|
|
|
118
157
|
```ruby
|
|
@@ -121,7 +160,7 @@ box = TTY::Box.frame(width: 30, height: 10) do
|
|
|
121
160
|
end
|
|
122
161
|
```
|
|
123
162
|
|
|
124
|
-
|
|
163
|
+
When printed will produce the following output in your terminal:
|
|
125
164
|
|
|
126
165
|
```ruby
|
|
127
166
|
print box
|
|
@@ -172,7 +211,7 @@ You can specify titles using the `:title` keyword and a hash value that contains
|
|
|
172
211
|
|
|
173
212
|
|
|
174
213
|
```ruby
|
|
175
|
-
box = TTY::Box.frame(width: 30, height: 10, title: {top_left:
|
|
214
|
+
box = TTY::Box.frame(width: 30, height: 10, title: {top_left: "TITLE", bottom_right: "v1.0"})
|
|
176
215
|
```
|
|
177
216
|
|
|
178
217
|
which when printed in console will render the following:
|
|
@@ -197,7 +236,7 @@ print box
|
|
|
197
236
|
There are three types of border `:ascii`, `:light`, `:thick`. By default the `:light` border is used. This can be changed using the `:border` keyword:
|
|
198
237
|
|
|
199
238
|
```ruby
|
|
200
|
-
box = TTY::Box.
|
|
239
|
+
box = TTY::Box.frame(width: 30, height: 10, border: :thick)
|
|
201
240
|
```
|
|
202
241
|
|
|
203
242
|
and printing the box out to console will produce:
|
|
@@ -268,10 +307,10 @@ print box
|
|
|
268
307
|
# ┼────────┼
|
|
269
308
|
```
|
|
270
309
|
|
|
271
|
-
If you want to
|
|
310
|
+
If you want to remove a given border element as a value use `false`. For example to remove bottom border do:
|
|
272
311
|
|
|
273
312
|
```ruby
|
|
274
|
-
TTY::Box.
|
|
313
|
+
TTY::Box.frame(
|
|
275
314
|
width: 30, height: 10,
|
|
276
315
|
border: {
|
|
277
316
|
type: :thick,
|
|
@@ -296,6 +335,20 @@ style: {
|
|
|
296
335
|
|
|
297
336
|
The above style configuration will produce the result similar to the top demo, a MS-DOS look & feel window.
|
|
298
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
|
+
|
|
299
352
|
### 2.7 formatting
|
|
300
353
|
|
|
301
354
|
You can use `:align` keyword to format content either to be `:left`, `:center` or `:right` aligned:
|
|
@@ -357,6 +410,90 @@ print box
|
|
|
357
410
|
#
|
|
358
411
|
```
|
|
359
412
|
|
|
413
|
+
### 2.8 messages
|
|
414
|
+
|
|
415
|
+

|
|
416
|
+
|
|
417
|
+
#### 2.8.1 info
|
|
418
|
+
|
|
419
|
+
To draw an information type box around your content use `info`:
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
box = TTY::Box.info("Deploying application")
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
And then print:
|
|
426
|
+
|
|
427
|
+
```ruby
|
|
428
|
+
print box
|
|
429
|
+
# =>
|
|
430
|
+
# ╔ ℹ INFO ═══════════════╗
|
|
431
|
+
# ║ ║
|
|
432
|
+
# ║ Deploying application ║
|
|
433
|
+
# ║ ║
|
|
434
|
+
# ╚═══════════════════════╝
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
#### 2.8.2 warn
|
|
438
|
+
|
|
439
|
+
To draw a warning type box around your content use `warn`:
|
|
440
|
+
|
|
441
|
+
```ruby
|
|
442
|
+
box = TTY::Box.warn("Deploying application")
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
And then print:
|
|
446
|
+
|
|
447
|
+
```ruby
|
|
448
|
+
print box
|
|
449
|
+
# =>
|
|
450
|
+
# ╔ ⚠ WARNING ════════════╗
|
|
451
|
+
# ║ ║
|
|
452
|
+
# ║ Deploying application ║
|
|
453
|
+
# ║ ║
|
|
454
|
+
# ╚═══════════════════════╝
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### 2.8.3 success
|
|
458
|
+
|
|
459
|
+
To draw a success type box around your content use `success`:
|
|
460
|
+
|
|
461
|
+
```ruby
|
|
462
|
+
box = TTY::Box.success("Deploying application")
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
And then print:
|
|
466
|
+
|
|
467
|
+
```ruby
|
|
468
|
+
print box
|
|
469
|
+
# =>
|
|
470
|
+
# ╔ ✔ OK ═════════════════╗
|
|
471
|
+
# ║ ║
|
|
472
|
+
# ║ Deploying application ║
|
|
473
|
+
# ║ ║
|
|
474
|
+
# ╚═══════════════════════╝
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
#### 2.8.4 error
|
|
478
|
+
|
|
479
|
+
To draw an error type box around your content use `error`:
|
|
480
|
+
|
|
481
|
+
```ruby
|
|
482
|
+
box = TTY::Box.error("Deploying application")
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
And then print:
|
|
486
|
+
|
|
487
|
+
```ruby
|
|
488
|
+
print box
|
|
489
|
+
# =>
|
|
490
|
+
# ╔ ⨯ ERROR ══════════════╗
|
|
491
|
+
# ║ ║
|
|
492
|
+
# ║ Deploying application ║
|
|
493
|
+
# ║ ║
|
|
494
|
+
# ╚═══════════════════════╝
|
|
495
|
+
```
|
|
496
|
+
|
|
360
497
|
## Development
|
|
361
498
|
|
|
362
499
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/tty-box.rb
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative "tty/box"
|
data/lib/tty/box.rb
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
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
|
+
NEWLINE = "\n"
|
|
15
|
+
SPACE = " "
|
|
16
|
+
|
|
17
|
+
LINE_BREAK = %r{\r\n|\r|\n}.freeze
|
|
18
|
+
|
|
14
19
|
BOX_CHARS = {
|
|
15
20
|
ascii: %w[+ + + + + + + + - | +],
|
|
16
21
|
light: %w[┘ ┐ ┌ └ ┤ ┴ ┬ ├ ─ │ ┼],
|
|
@@ -65,36 +70,162 @@ module TTY
|
|
|
65
70
|
TTY::Cursor
|
|
66
71
|
end
|
|
67
72
|
|
|
68
|
-
def color
|
|
69
|
-
@color ||= Pastel.new
|
|
73
|
+
def color(enabled: nil)
|
|
74
|
+
@color ||= Pastel.new(enabled: enabled)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# A frame for info type message
|
|
78
|
+
#
|
|
79
|
+
# @param [String] message
|
|
80
|
+
# the message to display
|
|
81
|
+
#
|
|
82
|
+
# @api public
|
|
83
|
+
def info(message, **opts)
|
|
84
|
+
new_opts = {
|
|
85
|
+
title: { top_left: " ℹ INFO " },
|
|
86
|
+
border: { type: :thick },
|
|
87
|
+
padding: 1,
|
|
88
|
+
style: {
|
|
89
|
+
fg: :black,
|
|
90
|
+
bg: :bright_blue,
|
|
91
|
+
border: {
|
|
92
|
+
fg: :black,
|
|
93
|
+
bg: :bright_blue
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}.merge(opts)
|
|
97
|
+
frame(**new_opts) { message }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# A frame for warning type message
|
|
101
|
+
#
|
|
102
|
+
# @param [String] message
|
|
103
|
+
# the message to display
|
|
104
|
+
#
|
|
105
|
+
# @api public
|
|
106
|
+
def warn(message, **opts)
|
|
107
|
+
new_opts = {
|
|
108
|
+
title: { top_left: " ⚠ WARNING " },
|
|
109
|
+
border: { type: :thick },
|
|
110
|
+
padding: 1,
|
|
111
|
+
style: {
|
|
112
|
+
fg: :black,
|
|
113
|
+
bg: :bright_yellow,
|
|
114
|
+
border: {
|
|
115
|
+
fg: :black,
|
|
116
|
+
bg: :bright_yellow
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}.merge(opts)
|
|
120
|
+
frame(**new_opts) { message }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# A frame for for success type message
|
|
124
|
+
#
|
|
125
|
+
# @param [String] message
|
|
126
|
+
# the message to display
|
|
127
|
+
#
|
|
128
|
+
# @api public
|
|
129
|
+
def success(message, **opts)
|
|
130
|
+
new_opts = {
|
|
131
|
+
title: { top_left: " ✔ OK " },
|
|
132
|
+
border: { type: :thick },
|
|
133
|
+
padding: 1,
|
|
134
|
+
style: {
|
|
135
|
+
fg: :black,
|
|
136
|
+
bg: :bright_green,
|
|
137
|
+
border: {
|
|
138
|
+
fg: :black,
|
|
139
|
+
bg: :bright_green
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}.merge(opts)
|
|
143
|
+
frame(**new_opts) { message }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# A frame for error type message
|
|
147
|
+
#
|
|
148
|
+
# @param [String] message
|
|
149
|
+
# the message to display
|
|
150
|
+
#
|
|
151
|
+
# @api public
|
|
152
|
+
def error(message, **opts)
|
|
153
|
+
new_opts = {
|
|
154
|
+
title: { top_left: " ⨯ ERROR " },
|
|
155
|
+
border: { type: :thick },
|
|
156
|
+
padding: 1,
|
|
157
|
+
style: {
|
|
158
|
+
fg: :bright_white,
|
|
159
|
+
bg: :red,
|
|
160
|
+
border: {
|
|
161
|
+
fg: :bright_white,
|
|
162
|
+
bg: :red
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}.merge(opts)
|
|
166
|
+
frame(**new_opts) { message }
|
|
70
167
|
end
|
|
71
168
|
|
|
72
169
|
# Create a frame
|
|
73
170
|
#
|
|
171
|
+
# @param [Integer] top
|
|
172
|
+
# the offset from the terminal top
|
|
173
|
+
# @param [Integer] left
|
|
174
|
+
# the offset from the terminal left
|
|
175
|
+
# @param [Integer] width
|
|
176
|
+
# the width of the box
|
|
177
|
+
# @param [Integer] height
|
|
178
|
+
# the height of the box
|
|
179
|
+
# @param [Symbol] align
|
|
180
|
+
# the content alignment
|
|
181
|
+
# @param [Integer,Array[Integer]] padding
|
|
182
|
+
# the padding around content
|
|
183
|
+
# @param [Hash] title
|
|
184
|
+
# the title for top or bottom border
|
|
185
|
+
# @param [Hash, Symbol] border
|
|
186
|
+
# the border type
|
|
187
|
+
# @param [Hash] style
|
|
188
|
+
# the styling for the front and background
|
|
189
|
+
#
|
|
74
190
|
# @api public
|
|
75
|
-
def frame(top: nil, left: nil, width:
|
|
76
|
-
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)
|
|
77
196
|
output = []
|
|
78
|
-
|
|
197
|
+
sep = NEWLINE
|
|
79
198
|
position = top && left
|
|
80
199
|
|
|
81
200
|
border = Border.parse(border)
|
|
82
|
-
top_size = border.top? ? 1: 0
|
|
83
|
-
bottom_size = border.bottom? ? 1: 0
|
|
201
|
+
top_size = border.top? ? 1 : 0
|
|
202
|
+
bottom_size = border.bottom? ? 1 : 0
|
|
84
203
|
left_size = border.left? ? 1 : 0
|
|
85
204
|
right_size = border.right ? 1 : 0
|
|
86
205
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
206
|
+
str = block_given? ? yield : content_to_str(content)
|
|
207
|
+
sep = str[LINE_BREAK] || NEWLINE # infer line break
|
|
208
|
+
content_lines = str.split(sep)
|
|
90
209
|
|
|
210
|
+
# infer dimensions
|
|
211
|
+
dimensions = infer_dimensions(content_lines, padding)
|
|
212
|
+
width ||= left_size + dimensions[0] + right_size
|
|
213
|
+
width = [width,
|
|
214
|
+
top_space_taken(title, border),
|
|
215
|
+
bottom_space_taken(title, border)].max
|
|
216
|
+
height ||= top_size + dimensions[1] + bottom_size
|
|
217
|
+
|
|
218
|
+
# apply formatting to content
|
|
219
|
+
formatted_lines = format(content_lines, width, padding, align, sep)
|
|
220
|
+
|
|
221
|
+
# infer styling
|
|
91
222
|
fg, bg = *extract_style(style)
|
|
92
223
|
border_fg, border_bg = *extract_style(style[:border] || {})
|
|
93
224
|
|
|
94
225
|
if border.top?
|
|
95
226
|
output << cursor.move_to(left, top) if position
|
|
96
227
|
output << top_border(title, width, border, style)
|
|
97
|
-
output <<
|
|
228
|
+
output << sep unless position
|
|
98
229
|
end
|
|
99
230
|
|
|
100
231
|
(height - top_size - bottom_size).times do |i|
|
|
@@ -103,47 +234,116 @@ module TTY
|
|
|
103
234
|
output << border_bg.(border_fg.(pipe_char(border.type)))
|
|
104
235
|
end
|
|
105
236
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
output << bg.(fg.(
|
|
109
|
-
|
|
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
|
|
110
243
|
end
|
|
244
|
+
|
|
111
245
|
if style[:fg] || style[:bg] || !position # something to color
|
|
112
|
-
output << bg.(fg.(
|
|
246
|
+
output << bg.(fg.(SPACE * filler_size))
|
|
113
247
|
end
|
|
114
248
|
|
|
115
249
|
if border.right?
|
|
116
250
|
if position
|
|
117
|
-
output << cursor.move_to(left + width - right_size,
|
|
251
|
+
output << cursor.move_to(left + width - right_size,
|
|
252
|
+
top + i + top_size)
|
|
118
253
|
end
|
|
119
254
|
output << border_bg.(border_fg.(pipe_char(border.type)))
|
|
120
255
|
end
|
|
121
|
-
output <<
|
|
256
|
+
output << sep unless position
|
|
122
257
|
end
|
|
123
258
|
|
|
124
259
|
if border.bottom?
|
|
125
260
|
output << cursor.move_to(left, top + height - bottom_size) if position
|
|
126
261
|
output << bottom_border(title, width, border, style)
|
|
127
|
-
output <<
|
|
262
|
+
output << sep unless position
|
|
128
263
|
end
|
|
129
264
|
|
|
130
265
|
output.join
|
|
131
266
|
end
|
|
132
267
|
|
|
133
|
-
#
|
|
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
|
+
|
|
284
|
+
# Infer box dimensions based on content lines and padding
|
|
285
|
+
#
|
|
286
|
+
# @param [Array[String]] lines
|
|
287
|
+
# @param [Array[Integer]|Integer] padding
|
|
288
|
+
#
|
|
289
|
+
# @return [Array[Integer]]
|
|
290
|
+
#
|
|
291
|
+
# @api private
|
|
292
|
+
def infer_dimensions(lines, padding)
|
|
293
|
+
pad = Strings::Padder.parse(padding)
|
|
294
|
+
width = pad.left + content_width(lines) + pad.right
|
|
295
|
+
height = pad.top + lines.size + pad.bottom
|
|
296
|
+
[width, height]
|
|
297
|
+
end
|
|
298
|
+
private_class_method :infer_dimensions
|
|
299
|
+
|
|
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
|
|
134
326
|
#
|
|
135
327
|
# @return [Array[String]]
|
|
136
328
|
#
|
|
137
329
|
# @api private
|
|
138
|
-
def format(
|
|
330
|
+
def format(lines, width, padding, align, separator)
|
|
331
|
+
return [] if lines.empty?
|
|
332
|
+
|
|
139
333
|
pad = Strings::Padder.parse(padding)
|
|
140
334
|
total_width = width - 2 - (pad.left + pad.right)
|
|
141
335
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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)
|
|
146
345
|
end
|
|
346
|
+
private_class_method :format
|
|
147
347
|
|
|
148
348
|
# Convert style keywords into styling
|
|
149
349
|
#
|
|
@@ -151,10 +351,63 @@ module TTY
|
|
|
151
351
|
#
|
|
152
352
|
# @api private
|
|
153
353
|
def extract_style(style)
|
|
154
|
-
fg = style[:fg] ? color.send(style[:fg]).detach : ->
|
|
155
|
-
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 }
|
|
156
356
|
[fg, bg]
|
|
157
357
|
end
|
|
358
|
+
private_class_method :extract_style
|
|
359
|
+
|
|
360
|
+
# Top space taken by titles and corners
|
|
361
|
+
#
|
|
362
|
+
# @return [Integer]
|
|
363
|
+
#
|
|
364
|
+
# @api private
|
|
365
|
+
def top_space_taken(title, border)
|
|
366
|
+
top_titles_size(title) +
|
|
367
|
+
top_left_corner(border).size +
|
|
368
|
+
top_right_corner(border).size
|
|
369
|
+
end
|
|
370
|
+
private_class_method :top_space_taken
|
|
371
|
+
|
|
372
|
+
# Top left corner
|
|
373
|
+
#
|
|
374
|
+
# @param [Border] border
|
|
375
|
+
#
|
|
376
|
+
# @return [String]
|
|
377
|
+
#
|
|
378
|
+
# @api private
|
|
379
|
+
def top_left_corner(border)
|
|
380
|
+
return "" unless border.top_left? && border.left?
|
|
381
|
+
|
|
382
|
+
send(:"#{border.top_left}_char", border.type)
|
|
383
|
+
end
|
|
384
|
+
private_class_method :top_left_corner
|
|
385
|
+
|
|
386
|
+
# Top right corner
|
|
387
|
+
#
|
|
388
|
+
# @param [Border] border
|
|
389
|
+
#
|
|
390
|
+
# @return [String]
|
|
391
|
+
#
|
|
392
|
+
# @api private
|
|
393
|
+
def top_right_corner(border)
|
|
394
|
+
return "" unless border.top_right? && border.right?
|
|
395
|
+
|
|
396
|
+
send(:"#{border.top_right}_char", border.type)
|
|
397
|
+
end
|
|
398
|
+
private_class_method :top_right_corner
|
|
399
|
+
|
|
400
|
+
# Top titles size
|
|
401
|
+
#
|
|
402
|
+
# @return [Integer]
|
|
403
|
+
#
|
|
404
|
+
# @api private
|
|
405
|
+
def top_titles_size(title)
|
|
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
|
|
409
|
+
end
|
|
410
|
+
private_class_method :top_titles_size
|
|
158
411
|
|
|
159
412
|
# Top border
|
|
160
413
|
#
|
|
@@ -162,28 +415,75 @@ module TTY
|
|
|
162
415
|
#
|
|
163
416
|
# @api private
|
|
164
417
|
def top_border(title, width, border, style)
|
|
165
|
-
top_titles_size = title[:top_left].to_s.size +
|
|
166
|
-
title[:top_center].to_s.size +
|
|
167
|
-
title[:top_right].to_s.size
|
|
168
418
|
fg, bg = *extract_style(style[:border] || {})
|
|
169
419
|
|
|
170
|
-
|
|
171
|
-
top_right = border.top_right? && border.right? ? send(:"#{border.top_right}_char", border.type) : ""
|
|
172
|
-
|
|
173
|
-
top_space_left = width - top_titles_size - top_left.size - top_right.size
|
|
420
|
+
top_space_left = width - top_space_taken(title, border)
|
|
174
421
|
top_space_before = top_space_left / 2
|
|
175
422
|
top_space_after = top_space_left / 2 + top_space_left % 2
|
|
176
423
|
|
|
177
424
|
[
|
|
178
|
-
bg.(fg.(
|
|
179
|
-
bg.(title[:top_left].to_s),
|
|
425
|
+
bg.(fg.(top_left_corner(border))),
|
|
426
|
+
bg.(fg.(title[:top_left].to_s)),
|
|
180
427
|
bg.(fg.(line_char(border.type) * top_space_before)),
|
|
181
|
-
bg.(title[:top_center].to_s),
|
|
428
|
+
bg.(fg.(title[:top_center].to_s)),
|
|
182
429
|
bg.(fg.(line_char(border.type) * top_space_after)),
|
|
183
|
-
bg.(title[:top_right].to_s),
|
|
184
|
-
bg.(fg.(
|
|
185
|
-
].join
|
|
430
|
+
bg.(fg.(title[:top_right].to_s)),
|
|
431
|
+
bg.(fg.(top_right_corner(border)))
|
|
432
|
+
].join
|
|
186
433
|
end
|
|
434
|
+
private_class_method :top_border
|
|
435
|
+
|
|
436
|
+
# Bottom space taken by titles and corners
|
|
437
|
+
#
|
|
438
|
+
# @return [Integer]
|
|
439
|
+
#
|
|
440
|
+
# @api private
|
|
441
|
+
def bottom_space_taken(title, border)
|
|
442
|
+
bottom_titles_size(title) +
|
|
443
|
+
bottom_left_corner(border).size +
|
|
444
|
+
bottom_right_corner(border).size
|
|
445
|
+
end
|
|
446
|
+
private_class_method :bottom_space_taken
|
|
447
|
+
|
|
448
|
+
# Bottom left corner
|
|
449
|
+
#
|
|
450
|
+
# @param [Border] border
|
|
451
|
+
#
|
|
452
|
+
# @return [String]
|
|
453
|
+
#
|
|
454
|
+
# @api private
|
|
455
|
+
def bottom_left_corner(border)
|
|
456
|
+
return "" unless border.bottom_left? && border.left?
|
|
457
|
+
|
|
458
|
+
send(:"#{border.bottom_left}_char", border.type)
|
|
459
|
+
end
|
|
460
|
+
private_class_method :bottom_left_corner
|
|
461
|
+
|
|
462
|
+
# Bottom right corner
|
|
463
|
+
#
|
|
464
|
+
# @param [Border] border
|
|
465
|
+
#
|
|
466
|
+
# @return [String]
|
|
467
|
+
#
|
|
468
|
+
# @api private
|
|
469
|
+
def bottom_right_corner(border)
|
|
470
|
+
return "" unless border.bottom_right? && border.right?
|
|
471
|
+
|
|
472
|
+
send(:"#{border.bottom_right}_char", border.type)
|
|
473
|
+
end
|
|
474
|
+
private_class_method :bottom_right_corner
|
|
475
|
+
|
|
476
|
+
# Bottom titles size
|
|
477
|
+
#
|
|
478
|
+
# @return [Integer]
|
|
479
|
+
#
|
|
480
|
+
# @api private
|
|
481
|
+
def bottom_titles_size(title)
|
|
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
|
|
485
|
+
end
|
|
486
|
+
private_class_method :bottom_titles_size
|
|
187
487
|
|
|
188
488
|
# Bottom border
|
|
189
489
|
#
|
|
@@ -191,28 +491,22 @@ module TTY
|
|
|
191
491
|
#
|
|
192
492
|
# @api private
|
|
193
493
|
def bottom_border(title, width, border, style)
|
|
194
|
-
bottom_titles_size = title[:bottom_left].to_s.size +
|
|
195
|
-
title[:bottom_center].to_s.size +
|
|
196
|
-
title[:bottom_right].to_s.size
|
|
197
494
|
fg, bg = *extract_style(style[:border] || {})
|
|
198
495
|
|
|
199
|
-
|
|
200
|
-
bottom_right = border.bottom_right? && border.right? ? send(:"#{border.bottom_right}_char", border.type) : ""
|
|
201
|
-
|
|
202
|
-
bottom_space_left = width - bottom_titles_size -
|
|
203
|
-
bottom_left.size - bottom_right.size
|
|
496
|
+
bottom_space_left = width - bottom_space_taken(title, border)
|
|
204
497
|
bottom_space_before = bottom_space_left / 2
|
|
205
498
|
bottom_space_after = bottom_space_left / 2 + bottom_space_left % 2
|
|
206
499
|
|
|
207
500
|
[
|
|
208
|
-
bg.(fg.(
|
|
209
|
-
bg.(title[:bottom_left].to_s),
|
|
501
|
+
bg.(fg.(bottom_left_corner(border))),
|
|
502
|
+
bg.(fg.(title[:bottom_left].to_s)),
|
|
210
503
|
bg.(fg.(line_char(border.type) * bottom_space_before)),
|
|
211
|
-
bg.(title[:bottom_center].to_s),
|
|
504
|
+
bg.(fg.(title[:bottom_center].to_s)),
|
|
212
505
|
bg.(fg.(line_char(border.type) * bottom_space_after)),
|
|
213
|
-
bg.(title[:bottom_right].to_s),
|
|
214
|
-
bg.(fg.(
|
|
215
|
-
].join
|
|
506
|
+
bg.(fg.(title[:bottom_right].to_s)),
|
|
507
|
+
bg.(fg.(bottom_right_corner(border)))
|
|
508
|
+
].join
|
|
216
509
|
end
|
|
510
|
+
private_class_method :bottom_border
|
|
217
511
|
end # TTY
|
|
218
512
|
end # Box
|