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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de83dc517a44cecc9c6936beafcc7a9e92bbef44b9f835ce8c5f537a34fcfb99
4
- data.tar.gz: 4e5a152bf222a4a1a267fa97699b8f20cbe33be48c9d7847b83172dc26708c2a
3
+ metadata.gz: b9509817a402d4b790c91be69e5d9c11947ba1374a32ba0df8a06a18c333d371
4
+ data.tar.gz: ea1322ac1cedabf28d34c52fb666386b97013feffc6319cd9c24356039831597
5
5
  SHA512:
6
- metadata.gz: 8979a9ad2c06c9ff1762005feabfbdf28456e75a496c3a39a4276b7fe73a31fc9bb69c8941d4470efb8c2a7277bdbdce62de7c788f95622a24c299cd015a2e72
7
- data.tar.gz: 99f56b7376c5d96b933e1deb3f7282d37e7b7ce10130240516dc3408387feb38edb567de17ee72a96e9c1e5de49b3cde2c3f2d35fd0ff6cfc22767e1a010fb9b
6
+ metadata.gz: 78d7164d16610ae4fc585977880845b3bd83c2be04e5c417ec08ce1aa00d0a56d6ff90efa5a3229742e97ea4836faadd140d59aca936ed545dd3606d85bf27d4
7
+ data.tar.gz: 8c7af2b0bf020d0208065a6afdd2ad8a28edfe3ed731582fbf20f4f8b301b574ebd54b5f0c7d9f05db853a7f9e35a507e5092f8a18ab62feefcc0a84851472bb
@@ -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 corretly recognise missing borders and match the height and width
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
@@ -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://cdn.rawgit.com/piotrmurach/tty/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,24 +13,24 @@
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
20
20
  [inchpages]: http://inch-ci.org/github/piotrmurach/tty-box
21
21
 
22
- > Draw various frames and boxes in your terminal interface.
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
- ![Box drawing](https://cdn.rawgit.com/piotrmurach/tty-box/master/assets/tty-box-drawing.png)
26
+ ![Box drawing](https://github.com/piotrmurach/tty-box/blob/master/assets/tty-box-drawing.png)
27
27
 
28
28
  ## Installation
29
29
 
30
30
  Add this line to your application's Gemfile:
31
31
 
32
32
  ```ruby
33
- gem 'tty-box'
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
- # │ Drawing a box in
80
- # │ terminal emulator
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 the top left corner of your terminal window by using the `frame` method and providing at the very minimum the height and the width:
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
- which when printed will prodcue the following output in your terminal:
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
- which when printed will produce the following output in your terminal:
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: 'TITLE', bottom_right: 'v1.0'})
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.new(width 30, height: 10, border: :thick)
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 remoe a given border element as a value use `false`. For example to remove bottom border do:
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.new(
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
+ ![Box messages](https://github.com/piotrmurach/tty-box/blob/master/assets/tty-box-messages.png)
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.
@@ -1 +1 @@
1
- require_relative 'tty/box'
1
+ require_relative "tty/box"
@@ -1,16 +1,21 @@
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
+ 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: 35, height: 3, align: :left, padding: 0,
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
- content = []
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
- if block_given?
88
- content = format(yield, width, padding, align)
89
- end
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 << "\n" unless position
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
- content_size = width - left_size - right_size
107
- unless content[i].nil?
108
- output << bg.(fg.(content[i]))
109
- content_size -= content[i].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
110
243
  end
244
+
111
245
  if style[:fg] || style[:bg] || !position # something to color
112
- output << bg.(fg.(' ' * content_size))
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, top + i + top_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 << "\n" unless position
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 << "\n" unless position
262
+ output << sep unless position
128
263
  end
129
264
 
130
265
  output.join
131
266
  end
132
267
 
133
- # Format content
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(content, width, padding, align)
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
- wrapped = Strings.wrap(content, total_width)
143
- aligned = Strings.align(wrapped, total_width, direction: align)
144
- padded = Strings.pad(aligned, padding)
145
- padded.split("\n")
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 : -> (c) { c }
155
- 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 }
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
- top_left = border.top_left? && border.left? ? send(:"#{border.top_left}_char", border.type) : ""
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.(top_left)),
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.(top_right))
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
- bottom_left = border.bottom_left? && border.left? ? send(:"#{border.bottom_left}_char", border.type) : ""
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.(bottom_left)),
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.(bottom_right))
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