Skip to content

Commit 56c819b

Browse files
Delta456muesliJalonSolovtkanosFelipe Dutra Tine e Silva
authored
all; release v2.3.0 (#22)
Co-authored-by: Christian Muehlhaeuser <[email protected]> Co-authored-by: JalonSolov <[email protected]> Co-authored-by: Felipe Dutra Tine e Silva <[email protected]> Co-authored-by: Felipe Dutra Tine e Silva <[email protected]>
1 parent 38cbd74 commit 56c819b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1092
-170
lines changed

README.md

Lines changed: 110 additions & 72 deletions
Large diffs are not rendered by default.

box.go

Lines changed: 226 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ package box
22

33
import (
44
"fmt"
5+
"log"
6+
"os"
57
"strings"
68

79
"github.com/gookit/color"
10+
"github.com/huandu/xstrings"
811
"github.com/mattn/go-runewidth"
12+
"github.com/muesli/reflow/wrap"
13+
"golang.org/x/term"
914
)
1015

1116
const (
@@ -18,28 +23,32 @@ const (
1823
rightAlign = "%[1]s%[2]s%[4]s%[5]s%[3]s%[6]s%[1]s"
1924
)
2025

21-
// Box struct defines the Box to be made.
26+
// Box defines the design
2227
type Box struct {
23-
TopRight string // Symbols used for TopRight Corner
24-
TopLeft string // Symbols used for TopLeft Corner
25-
Vertical string // Symbols used for Vertical Bars
26-
BottomRight string // Symbols used for BottomRight Corner
27-
BottomLeft string // Symbols used for BottomRight Corner
28-
Horizontal string // Symbols used for Horizontal Bars
29-
Config // Config for the Box struct
28+
TopRight string // TopRight Corner Symbols
29+
TopLeft string // TopLeft Corner Symbols
30+
Vertical string // Vertical Bar Symbols
31+
BottomRight string // BottomRight Corner Symbols
32+
BottomLeft string // BottomLeft Corner Symbols
33+
Horizontal string // Horizontal Bars Symbols
34+
Config // Box Config
3035
}
3136

32-
// Config is the configuration for the Box struct
37+
// Config is the configuration needed for the Box to be designed
3338
type Config struct {
34-
Py int // Horizontal Padding
35-
Px int // Vertical Padding
36-
ContentAlign string // Content Alignment inside Box
37-
Type string // Type of Box
38-
TitlePos string // Title Position
39-
Color interface{} // Color of Box
39+
Py int // Horizontal Padding
40+
Px int // Vertical Padding
41+
ContentAlign string // Content Alignment inside Box
42+
Type string // Box Type
43+
TitlePos string // Title Position
44+
TitleColor interface{} // Title Color
45+
ContentColor interface{} // Content Color
46+
Color interface{} // Box Color
47+
AllowWrapping bool // Flag to allow custom Content Wrapping
48+
WrappingLimit int // Wrap the Content upto the Limit
4049
}
4150

42-
// New takes struct Config and returns the specified Box struct.
51+
// New takes Box Config and returns a Box from the given Config
4352
func New(config Config) Box {
4453
// Default Box Type is Single
4554
if config.Type == "" {
@@ -55,10 +64,29 @@ func New(config Config) Box {
5564
panic("Invalid Box Type provided")
5665
}
5766

58-
// String returns the string representation of Box.
67+
// String returns the string representation of Box
5968
func (b Box) String(title, lines string) string {
6069
var lines2 []string
6170

71+
// Allow Wrapping according to the user
72+
if b.AllowWrapping {
73+
// If limit not provided then use 2*TermWidth/3 as limit else
74+
// use the one provided
75+
if b.WrappingLimit != 0 {
76+
lines = wrap.String(lines, b.WrappingLimit)
77+
} else {
78+
width, _, err := term.GetSize(int(os.Stdout.Fd()))
79+
if err != nil {
80+
log.Fatal(err)
81+
}
82+
lines = wrap.String(lines, 2*width/3)
83+
}
84+
}
85+
86+
// Obtain Title and Content color
87+
title = b.obtainTitleColor(title)
88+
lines = b.obtainContentColor(lines)
89+
6290
// Default Position is Inside, no warning for invalid TitlePos as it is done
6391
// in toString() method
6492
if b.TitlePos == "" {
@@ -78,31 +106,40 @@ func (b Box) String(title, lines string) string {
78106
return b.toString(title, lines2)
79107
}
80108

81-
// toString is same as String except that it is used for printing Boxes
109+
// toString is an internal method and same as String method except that the main Box generation is done here
82110
func (b Box) toString(title string, lines []string) string {
83-
titleLen := len(strings.Split(title, n1))
111+
titleLen := len(strings.Split(color.ClearCode(title), n1))
84112
sideMargin := strings.Repeat(" ", b.Px)
85-
longestLine, lines2 := longestLine(lines)
113+
_longestLine, lines2 := longestLine(lines)
86114

87115
// Get padding on one side
88116
paddingCount := b.Px
89117

90-
n := longestLine + (paddingCount * 2) + 2
118+
n := _longestLine + (paddingCount * 2) + 2
91119

92-
if b.TitlePos != inside && runewidth.StringWidth(title) > n-2 {
120+
if b.TitlePos != inside && runewidth.StringWidth(color.ClearCode(title)) > n-2 {
93121
panic("Title must be shorter than the Top & Bottom Bars")
94122
}
95123

96124
// Create Top and Bottom Bars
97125
Bar := strings.Repeat(b.Horizontal, n-2)
98126
TopBar := b.TopLeft + Bar + b.TopRight
99127
BottomBar := b.BottomLeft + Bar + b.BottomRight
100-
// Check b.TitlePos
128+
129+
var TitleBar string
130+
// If title has tabs then expand them accordingly.
131+
if strings.Contains(title, "\t") {
132+
TitleBar = repeatWithString(b.Horizontal, n-2, xstrings.ExpandTabs(title, 4))
133+
} else {
134+
TitleBar = repeatWithString(b.Horizontal, n-2, title)
135+
}
136+
137+
// Check b.TitlePos if it is not Inside
101138
if b.TitlePos != inside {
102-
TitleBar := repeatWithString(b.Horizontal, n-2, title)
103139
switch b.TitlePos {
104140
case "Top":
105141
TopBar = b.TopLeft + TitleBar + b.TopRight
142+
//fmt.Println(TopBar)
106143
case "Bottom":
107144
BottomBar = b.BottomLeft + TitleBar + b.BottomRight
108145
default:
@@ -115,15 +152,20 @@ func (b Box) toString(title string, lines []string) string {
115152
}
116153
inside:
117154
// Check type of b.Color then assign the Colors to TopBar and BottomBar accordingly
118-
TopBar, BottomBar = b.checkColorType(TopBar, BottomBar)
155+
// If title has tabs then expand them accordingly.
156+
if strings.Contains(title, "\t") {
157+
TopBar, BottomBar = b.checkColorType(TopBar, BottomBar, xstrings.ExpandTabs(title, 4))
158+
} else {
159+
TopBar, BottomBar = b.checkColorType(TopBar, BottomBar, title)
160+
}
161+
119162
if b.TitlePos == inside && runewidth.StringWidth(TopBar) != runewidth.StringWidth(BottomBar) {
120163
panic("cannot create a Box with different sizes of Top and Bottom Bars")
121164
}
122165

123166
// Create lines to print
124167
texts := b.addVertPadding(n)
125-
texts = b.formatLine(lines2, longestLine, titleLen, sideMargin, title, texts)
126-
168+
texts = b.formatLine(lines2, _longestLine, titleLen, sideMargin, title, texts)
127169
vertpadding := b.addVertPadding(n)
128170
texts = append(texts, vertpadding...)
129171

@@ -141,8 +183,124 @@ inside:
141183
return sb.String()
142184
}
143185

144-
// obtainColor obtains the Color from string, uint and [3]uint respectively
145-
func (b Box) obtainColor() string {
186+
// obtainTitleColor obtains TitleColor from types string, uint and [3]uint respectively
187+
func (b Box) obtainTitleColor(title string) string {
188+
if b.TitleColor == nil { // if nil then just return the string
189+
return title
190+
}
191+
// Check if type of b.TitleColor is string
192+
if str, ok := b.TitleColor.(string); ok {
193+
// Hi Intensity Color
194+
if strings.HasPrefix(str, "Hi") {
195+
if _, ok := fgHiColors[str]; ok {
196+
// If title has newlines in it then splitting would be needed
197+
// as color won't be applied on all
198+
if strings.Contains(title, "\n") {
199+
return b.applyColorToAll(title, str, color.RGBColor{}, false)
200+
}
201+
return addStylePreservingOriginalFormat(title, fgHiColors[str].Sprint)
202+
}
203+
} else if _, ok := fgColors[str]; ok {
204+
// If title has newlines in it then splitting would be needed
205+
// as color won't be applied on all
206+
if strings.Contains(title, "\n") {
207+
return b.applyColorToAll(title, str, color.RGBColor{}, false)
208+
}
209+
return addStylePreservingOriginalFormat(title, fgColors[str].Sprint)
210+
}
211+
// Return a warning as TitleColor provided as a string is unknown and
212+
// return without the color effect
213+
errorMsg("[warning]: invalid value provided to Color, using default")
214+
return title
215+
216+
// Check if type of b.TitleColor is uint
217+
} else if hex, ok := b.TitleColor.(uint); ok {
218+
// Break down the hex into R, G and B respectively
219+
hexArray := [3]uint{hex >> 16, hex >> 8 & 0xff, hex & 0xff}
220+
col := color.RGB(uint8(hexArray[0]), uint8(hexArray[1]), uint8(hexArray[2]))
221+
222+
// If title has newlines in it then splitting would be needed
223+
// as color won't be applied on all
224+
if strings.Contains(title, "\n") {
225+
return b.applyColorToAll(title, "", col, true)
226+
}
227+
return addStylePreservingOriginalFormat(title, col.Sprint)
228+
229+
// Check if type of b.TitleColor is [3]uint
230+
} else if rgb, ok := b.TitleColor.([3]uint); ok {
231+
col := color.RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]))
232+
233+
// If title has newlines in it then splitting would be needed
234+
// as color won't be applied on all
235+
if strings.Contains(title, "\n") {
236+
return b.applyColorToAll(title, "", col, true)
237+
}
238+
return b.roundOffTitleColor(col, addStylePreservingOriginalFormat(title, col.Sprint))
239+
}
240+
// Panic if b.TitleColor is an unexpected type
241+
panic(fmt.Sprintf("expected string, [3]uint or uint not %T", b.TitleColor))
242+
}
243+
244+
// obtainContentColor obtains ContentColor from types string, uint and [3]uint respectively
245+
func (b Box) obtainContentColor(content string) string {
246+
if b.ContentColor == nil { // if nil then just return the string
247+
return content
248+
}
249+
// Check if type of b.ContentColor is string
250+
if str, ok := b.ContentColor.(string); ok {
251+
// Hi Intensity Color
252+
if strings.HasPrefix(str, "Hi") {
253+
if _, ok := fgHiColors[str]; ok {
254+
// If Content has newlines in it then splitting would be needed
255+
// as color won't be applied on all
256+
if strings.Contains(content, "\n") {
257+
return b.applyColorToAll(content, str, color.RGBColor{}, false)
258+
}
259+
return addStylePreservingOriginalFormat(content, fgHiColors[str].Sprint)
260+
}
261+
} else if _, ok := fgColors[str]; ok {
262+
// If Content has newlines in it then splitting would be needed
263+
// as color won't be applied on all
264+
if strings.Contains(content, "\n") {
265+
return b.applyColorToAll(content, str, color.RGBColor{}, false)
266+
}
267+
return addStylePreservingOriginalFormat(content, fgColors[str].Sprint)
268+
}
269+
// Return a warning as ContentColor provided as a string is unknown and
270+
// return without the color effect
271+
errorMsg("[warning]: invalid value provided to Color, using default")
272+
return content
273+
274+
// Check if type of b.ContentColor is uint
275+
} else if hex, ok := b.ContentColor.(uint); ok {
276+
// Break down the hex into R, G and B respectively
277+
hexArray := [3]uint{hex >> 16, hex >> 8 & 0xff, hex & 0xff}
278+
col := color.RGB(uint8(hexArray[0]), uint8(hexArray[1]), uint8(hexArray[2]))
279+
280+
// If content has newlines in it then splitting would be needed
281+
// as color won't be applied on all
282+
if strings.Contains(content, "\n") {
283+
return b.applyColorToAll(content, "", col, true)
284+
}
285+
return b.roundOffTitleColor(col, content)
286+
287+
// Check if type of b.ContentColor is [3]uint
288+
} else if rgb, ok := b.ContentColor.([3]uint); ok {
289+
col := color.RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]))
290+
291+
// If content has newlines in it then splitting would be needed
292+
// as color won't be applied on all
293+
if strings.Contains(content, "\n") {
294+
return b.applyColorToAll(content, "", col, true)
295+
}
296+
return b.roundOffTitleColor(col, content)
297+
}
298+
// Panic if b.ContentColor is an unexpected type
299+
panic(fmt.Sprintf("expected string, [3]uint or uint not %T", b.ContentColor))
300+
}
301+
302+
// obtainColor obtains BoxColor from types string, uint and [3]uint respectively
303+
func (b Box) obtainBoxColor() string {
146304
if b.Color == nil { // if nil then just return the string
147305
return b.Vertical
148306
}
@@ -179,6 +337,25 @@ func (b Box) obtainColor() string {
179337
func (b Box) Print(title, lines string) {
180338
var lines2 []string
181339

340+
// Allow Wrapping according to the user
341+
if b.AllowWrapping {
342+
// If limit not provided then use 2*TermWidth/3 as limit else
343+
// use the one provided
344+
if b.WrappingLimit != 0 {
345+
lines = wrap.String(lines, b.WrappingLimit)
346+
} else {
347+
width, _, err := term.GetSize(int(os.Stdout.Fd()))
348+
if err != nil {
349+
log.Fatal(err)
350+
}
351+
lines = wrap.String(lines, 2*width/3)
352+
}
353+
}
354+
355+
// Obtain Title and Content color
356+
title = b.obtainTitleColor(title)
357+
lines = b.obtainContentColor(lines)
358+
182359
// Default Position is Inside, if invalid position is given then just raise a warning
183360
// then use Default Position which is Inside
184361
if b.TitlePos == "" {
@@ -201,10 +378,29 @@ func (b Box) Print(title, lines string) {
201378
color.Print(b.toString(title, lines2))
202379
}
203380

204-
// Println adds a newline before and after the Box
381+
// Println adds a newline before and after printing the Box
205382
func (b Box) Println(title, lines string) {
206383
var lines2 []string
207384

385+
// Allow Wrapping according to the user
386+
if b.AllowWrapping {
387+
// If limit not provided then use 2*TermWidth/3 as limit else
388+
// use the one provided
389+
if b.WrappingLimit != 0 {
390+
lines = wrap.String(lines, b.WrappingLimit)
391+
} else {
392+
width, _, err := term.GetSize(int(os.Stdout.Fd()))
393+
if err != nil {
394+
log.Fatal(err)
395+
}
396+
lines = wrap.String(lines, 2*width/3)
397+
}
398+
}
399+
400+
// Obtain Title and Content color
401+
title = b.obtainTitleColor(title)
402+
lines = b.obtainContentColor(lines)
403+
208404
// Default Position is Inside, if invalid position is given then just raise a warning
209405
// then use Default Position which is Inside
210406
if b.TitlePos == "" {

0 commit comments

Comments
 (0)