diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 9fbcf90..756b90d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v2 with: - go-version: ^1.13 + go-version: ^1.15 id: go - name: Check out code into the Go module directory @@ -31,7 +31,7 @@ jobs: fi - name: Build - run: go build -v . + run: go build -o box - name: Test run: go test -v . diff --git a/README.md b/README.md index c973f0d..24aa8b3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ -# Box CLI Maker ๐Ÿ“ฆ +
+

Box CLI Maker ๐Ÿ“ฆ

+
+

+Box CLI Maker is a Highly Customized Terminal Box Creator. +

+ +
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/Delta456/box-cli-maker) [![CI](https://github.com/Delta456/box-cli-maker/workflows/Box%20CLI%20Maker/badge.svg)](https://github.com/Delta456/box-cli-maker/actions?query=workflow%3A"Box+CLI+Maker") @@ -7,17 +14,16 @@ [![GolangCI](https://golangci.com/badges/github.com/moul/golang-repo-template.svg)](https://golangci.com/r/github.com/Delta456/box-cli-maker) [![GitHub release](https://img.shields.io/github/release/Delta456/box-cli-maker.svg)](https://github.com/Delta456/box-cli-maker/releases) - -Box CLI Maker is a Highly Customized Terminal Box Creator. +
## Features -- Make Terminal Box in 8๏ธโƒฃ inbuilt different style -- Color Support ๐ŸŽจ +- Make Terminal Box in 8๏ธโƒฃ inbuilt different styles +- 16 Inbuilt Colors and Custom (24 bit) Colors Support ๐ŸŽจ - Custom Title Positions - Make your own Terminal Box style ๐Ÿ“ฆ - Align the text according to the need -- Unicode and Emoji Support ๐Ÿ˜‹ +- Unicode, Emoji and [Windows Console](https://en.wikipedia.org/wiki/Windows_Console) Support ๐Ÿ˜‹ - Written in ๐Ÿ‡ฌ ๐Ÿ‡ด ## Installation @@ -132,7 +138,7 @@ type Box struct { BottomRight string // BottomRight corner used for Symbols BottomLeft string // BotromLeft corner used for Symbols Horizontal string // Symbols used for Horizontal Bars - Con Config // Configuration for the Box to be made + Config // Configuration for the Box to be made } ``` @@ -182,6 +188,12 @@ var fgHiColors = map[string]color.Attribute{ If you want High Intensity Colors then the Color name should start with `Hi`. If Color option is empty or invalid then Box with default Color is formed. +It can even have custom color which can be provided in `[3]uint` and `uint` though the elements of the array must be in a range of `[0x0, 0xFF]` and `uint` must be in a range of `[0x000000, 0xFFFFFF]`. + +If you want to use the string representation of the `Box` and print them for [`Windows Console`](https://en.wikipedia.org/wiki/Windows_Console) then you would have to use `box.Output` as the passing stream to the respective functions. + +`Windows Console` is 4 bit (16 colors) so Custom Colors will not work for them but the `Box` will be printed correctly without the Color effect. + ### Note #### Vertical Alignment @@ -203,9 +215,9 @@ I thank the following people and their packages whom I have studied and was able - [thecodrr/boxx](https://github.com/thecodrr/boxx) - [Atrox/box](https://github.com/Atrox/box) -- [sindreorhus-cli-boxs](https://github.com/sindresorhus/cli-boxes) +- [sindreorhus-cli-boxes](https://github.com/sindresorhus/cli-boxes) -Special thanks to [@elimsteve](https://github.com/elimisteve) who helped me to optimize and tell me the best ways possible to fix my problems. +Special thanks to [@elimsteve](https://github.com/elimisteve) who helped me to optimize the code and told me the best possible ways to fix my problems and [@JalonSolov](https://github.com/JalonSolov) for tab lines support. Kudos to [moul/golang-repo-template](https://github.com/moul/golang-repo-template) for their Go template. diff --git a/box.go b/box.go index af67da4..8ac82ff 100644 --- a/box.go +++ b/box.go @@ -3,6 +3,7 @@ package box import ( "fmt" "os" + "runtime" "strings" "github.com/fatih/color" @@ -11,10 +12,11 @@ import ( const ( n1 = "\n" - // sep = separator, sp = spacing, ln = line; os = oddSpace; s = space - centerAlign = "{sep}{sp}{ln}{os}{sp}{sep}" - leftAlign = "{sep}{px}{ln}{os}{sp}{s}{sep}" - rightAlign = "{sep}{sp}{os}{s}{ln}{px}{sep}" + + // 1 = separator, 2 = spacing, 3 = line; 4 = oddSpace; 5 = space; 6 = sideMargin + centerAlign = "%[1]s%[2]s%[3]s%[4]s%[2]s%[1]s" + leftAlign = "%[1]s%[6]s%[3]s%[4]s%[2]s%[5]s%[1]s" + rightAlign = "%[1]s%[2]s%[4]s%[5]s%[3]s%[6]s%[1]s" ) // Box struct defines the Box to be made. @@ -25,44 +27,50 @@ type Box struct { BottomRight string // BottomRight corner used for Symbols BottomLeft string // BotromLeft corner used for Symbols Horizontal string // Symbols used for Horizontal Bars - Con Config // Config for the Box struct + Config // Config for the Box struct } // Config is the configuration for the Box struct type Config struct { - Py int // Horizontal Padding - Px int // Vertical Padding - ContentAlign string // Content Alignment inside Box - Type string // Type of Box - TitlePos string // Title Position - Color string // Color of Box + Py int // Horizontal Padding + Px int // Vertical Padding + ContentAlign string // Content Alignment inside Box + Type string // Type of Box + TitlePos string // Title Position + Color interface{} // Color of Box } // New takes struct Config and returns the specified Box struct. func New(config Config) Box { - if _, ok := boxs[config.Type]; ok { - BoxNew := boxs[config.Type] - BoxNew.Con = config - return BoxNew + // Default Box Type is Single + if config.Type == "" { + boxNew := boxes["Single"] + boxNew.Config = config + return boxNew + // Check if the Box Type provided is valid else panic + } else if _, ok := boxes[config.Type]; ok { + boxNew := boxes[config.Type] + boxNew.Config = config + return boxNew } panic("Invalid Box Type provided") - } // String returns the string representation of Box. func (b Box) String(title, lines string) string { var lines2 []string - // Default Position is Inside - if b.Con.TitlePos == "" { - b.Con.TitlePos = "Inside" + // Default Position is Inside, no warning for invalid TitlePos as it is done + // in toString() method + if b.TitlePos == "" { + b.TitlePos = "Inside" } // if Title is empty then TitlePos should be Inside if title != "" { - if b.Con.TitlePos != "Inside" && strings.Contains(title, "\n") { + if b.TitlePos != "Inside" && strings.Contains(title, "\n") { panic("Multilines are only supported inside only") } - if b.Con.TitlePos == "Inside" { + if b.TitlePos == "Inside" { lines2 = append(lines2, strings.Split(title, n1)...) lines2 = append(lines2, []string{""}...) // for empty line between title and content } @@ -74,76 +82,88 @@ func (b Box) String(title, lines string) string { // toString is same as String except that it is used for printing Boxes func (b Box) toString(title string, lines []string) string { titleLen := len(strings.Split(title, n1)) - sideMargin := strings.Repeat(" ", b.Con.Px) - longestLine := longestLine(lines) + sideMargin := strings.Repeat(" ", b.Px) + longestLine, lines2 := longestLine(lines) - // get padding on one side - paddingCount := b.Con.Px + // Get padding on one side + paddingCount := b.Px n := longestLine + (paddingCount * 2) + 2 - if b.Con.TitlePos != "Inside" && runewidth.StringWidth(title) > n-2 { - panic("Title must be lower in length than the Top & Bottom Bars") + if b.TitlePos != "Inside" && runewidth.StringWidth(title) > n-2 { + panic("Title must be shorter than the Top & Bottom Bars") } - // create Top and Bottom Bars + // Create Top and Bottom Bars Bar := strings.Repeat(b.Horizontal, n-2) TopBar := b.TopLeft + Bar + b.TopRight BottomBar := b.BottomLeft + Bar + b.BottomRight - if b.Con.TitlePos != "Inside" { + if b.TitlePos != "Inside" { TitleBar := repeatWithString(b.Horizontal, n-2, title) - if b.Con.TitlePos == "Top" { + if b.TitlePos == "Top" { TopBar = b.TopLeft + TitleBar + b.TopRight - } else if b.Con.TitlePos == "Bottom" { + } else if b.TitlePos == "Bottom" { BottomBar = b.BottomLeft + TitleBar + b.BottomRight } else { - fmt.Fprintln(os.Stderr, color.RedString("[error]: invalid value provided for TitlePos, using default")) + // Duplicate warning done here if the String() Method is used + // instead of using Print() and Println() methods + errorMsg("[warning]: invalid value provided for TitlePos, using default") + // Using goto here to inorder to exit the current if branch + goto inside } } - - if b.Con.Color != "" { - if strings.HasPrefix(b.Con.Color, "Hi") { - if _, ok := fgHiColors[b.Con.Color]; ok { - Style := color.New(fgHiColors[b.Con.Color]).SprintfFunc() +inside: + if b.Color != nil { + if str, ok := b.Color.(string); ok { + if strings.HasPrefix(str, "Hi") { + if _, ok := fgHiColors[str]; ok { + Style := color.New(fgHiColors[str]).SprintfFunc() + TopBar = Style(TopBar) + BottomBar = Style(BottomBar) + } + } else if _, ok := fgColors[str]; ok { + Style := color.New(fgColors[str]).SprintfFunc() TopBar = Style(TopBar) BottomBar = Style(BottomBar) - + } else { + errorMsg("[warning]: invalid value provided to Color, using default") } - } else if _, ok := fgColors[b.Con.Color]; ok { - Style := color.New(fgColors[b.Con.Color]).SprintfFunc() - TopBar = Style(TopBar) - BottomBar = Style(BottomBar) + } else if hex, ok := b.Color.(uint); ok { + TopBar = rgbHex(hex, TopBar) + BottomBar = rgbHex(hex, BottomBar) + } else if rgb, ok := b.Color.([3]uint); ok { + TopBar = rgbArray(rgb, TopBar) + BottomBar = rgbArray(rgb, BottomBar) } else { - fmt.Fprintln(os.Stderr, color.RedString("[error]: invalid value provided to Color, using default")) + fmt.Fprintln(os.Stderr, fmt.Sprintf("expected string, [3]uint or uint not %T using default", b.Color)) } } - - if b.Con.TitlePos == "Inside" && runewidth.StringWidth(TopBar) != runewidth.StringWidth(BottomBar) { + if b.TitlePos == "Inside" && runewidth.StringWidth(TopBar) != runewidth.StringWidth(BottomBar) { panic("cannot create a Box with different sizes of Top and Bottom Bars") } - // create lines to print + // Create lines to print var texts []string texts = b.addVertPadding(n) - for i, line := range lines { - length := runewidth.StringWidth(line) + for i, line := range lines2 { + length := line.len - // use later + // Use later var space, oddSpace string - // if current text is shorter than the longest one + // If current text is shorter than the longest one // center the text, so it looks better if length < longestLine { - // difference between longest and current one + // Difference between longest and current one diff := longestLine - length // the spaces to add on each side toAdd := diff / 2 space = strings.Repeat(" ", toAdd) - // if the difference between the longest and current one + // If the difference between the longest and current one // is odd, we have to add one additional space before the last vertical separator if diff%2 != 0 { oddSpace = " " @@ -151,78 +171,122 @@ func (b Box) toString(title string, lines []string) string { } spacing := space + sideMargin - format := b.findAlign() + var format string if i < titleLen && title != "" { format = centerAlign + } else { + format = b.findAlign() } - // obtain color + + // Obtain color sep := b.obtainColor() - // TODO: find a better way - formatted := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(format, "{sep}", sep), "{sp}", spacing), "{ln}", line), "{os}", oddSpace), "{s}", space), "{px}", sideMargin) + formatted := fmt.Sprintf(format, sep, spacing, line.line, oddSpace, space, sideMargin) texts = append(texts, formatted) } vertpadding := b.addVertPadding(n) texts = append(texts, vertpadding...) - return TopBar + n1 + strings.Join(texts, n1) + n1 + BottomBar + n1 + // Using strings.Builder is more efficient and faster + // than concatenating 6 times + var sb strings.Builder + + sb.WriteString(TopBar) + sb.WriteString(n1) + sb.WriteString(strings.Join(texts, n1)) + sb.WriteString(n1) + sb.WriteString(BottomBar) + sb.WriteString(n1) + + return sb.String() } +// obtainColor obtains the Color from string, uint and [3]uint respectively func (b Box) obtainColor() string { - if strings.HasPrefix(b.Con.Color, "Hi") { - if _, ok := fgHiColors[b.Con.Color]; ok { - Style := color.New(fgHiColors[b.Con.Color]).SprintfFunc() + if b.Color == nil { // if nil then just return the string + return b.Vertical + } + if str, ok := b.Color.(string); ok { + if strings.HasPrefix(str, "Hi") { + if _, ok := fgHiColors[str]; ok { + Style := color.New(fgHiColors[str]).SprintfFunc() + return Style(b.Vertical) + } + } else if _, ok := fgColors[str]; ok { + Style := color.New(fgColors[str]).SprintfFunc() return Style(b.Vertical) } - } else if _, ok := fgColors[b.Con.Color]; ok { - Style := color.New(fgColors[b.Con.Color]).SprintfFunc() - return Style(b.Vertical) + errorMsg("[warning]: invalid value provided to Color, using default") + return b.Vertical + } else if hex, ok := b.Color.(uint); ok { + return rgbHex(hex, b.Vertical) + } else if rgb, ok := b.Color.([3]uint); ok { + return rgbArray(rgb, b.Vertical) } - fmt.Fprintln(os.Stderr, color.RedString("[error]: invalid value provided to Color, using default")) - return b.Vertical + panic(fmt.Sprintf("expected string, [3]uint or uint not %T", b.Color)) } -// Print prints the box +// Print prints the Box func (b Box) Print(title, lines string) { var lines2 []string - // Default Position is Inside - if b.Con.TitlePos == "" { - b.Con.TitlePos = "Inside" + // Default Position is Inside, if invalid position is given then just raise a warning + // then use Default Position which is Inside + if b.TitlePos == "" { + b.TitlePos = "Inside" + } else if b.TitlePos != "Bottom" && b.TitlePos != "Top" { + errorMsg("[warning]: invalid value provided for TitlePos, using default") + b.TitlePos = "Inside" } // if Title is empty then TitlePos should be Inside if title != "" { - if b.Con.TitlePos != "Inside" && strings.Contains(title, "\n") { + if b.TitlePos != "Inside" && strings.Contains(title, "\n") { panic("Multilines are only supported inside only") } - if b.Con.TitlePos == "Inside" { + if b.TitlePos == "Inside" { lines2 = append(lines2, strings.Split(title, n1)...) lines2 = append(lines2, []string{""}...) // for empty line between title and content } } lines2 = append(lines2, strings.Split(lines, n1)...) - fmt.Print(b.toString(title, lines2)) + if runtime.GOOS == "windows" { + // Windows Console is 4 bit (16 colors only supported) so if the custom color + // is out of their range then just correctly print the Box without the color effect + fmt.Fprint(Output, b.toString(title, lines2)) + } else { + fmt.Print(b.toString(title, lines2)) + } } -// Println adds a newline before and after the box +// Println adds a newline before and after the Box func (b Box) Println(title, lines string) { var lines2 []string - // Default Position is Inside - if b.Con.TitlePos == "" { - b.Con.TitlePos = "Inside" + // Default Position is Inside, if invalid position is given then just raise a warning + // then use Default Position which is Inside + if b.TitlePos == "" { + b.TitlePos = "Inside" + } else if b.TitlePos != "Bottom" && b.TitlePos != "Top" { + errorMsg("[warning]: invalid value provided for TitlePos, using default") + b.TitlePos = "Inside" } // if Title is empty then TitlePos should be Inside if title != "" { - if b.Con.TitlePos != "Inside" && strings.Contains(title, "\n") { + if b.TitlePos != "Inside" && strings.Contains(title, "\n") { panic("Multilines are only supported inside only") } - if b.Con.TitlePos == "Inside" { + if b.TitlePos == "Inside" { lines2 = append(lines2, strings.Split(title, n1)...) lines2 = append(lines2, []string{""}...) // for empty line between title and content } } lines2 = append(lines2, strings.Split(lines, n1)...) - fmt.Printf("\n%s\n", b.toString(title, lines2)) + if runtime.GOOS == "windows" { + // Windows Console is 4 bit (16 colors only supported) so if the custom color + // is out of their range then just correctly print the Box without the color effect + fmt.Fprintf(Output, "\n%s\n", b.toString(title, lines2)) + } else { + fmt.Printf("\n%s\n", b.toString(title, lines2)) + } } diff --git a/box_test.go b/box_test.go new file mode 100644 index 0000000..0700a48 --- /dev/null +++ b/box_test.go @@ -0,0 +1,110 @@ +package box + +import ( + "fmt" + "testing" +) + +func TestInbuiltStyles(t *testing.T) { + cases := map[string]string{ + "Single": "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Box CLI Maker โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n", + "Single Double": "โ•“โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•–\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ Box CLI Maker โ•‘\nโ•‘ โ•‘\nโ•‘ Highly Customized Terminal Box Maker โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•™โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•œ\n", + "Double": "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ Box CLI Maker โ•‘\nโ•‘ โ•‘\nโ•‘ Highly Customized Terminal Box Maker โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n", + "Double Single": "โ•’โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ••\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Box CLI Maker โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ•˜โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•›\n", + "Bold": "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ Box CLI Maker โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ Highly Customized Terminal Box Maker โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”›\n", + "Round": "โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Box CLI Maker โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ\n", + "Hidden": "+ +\n \n \n \n \n \n Box CLI Maker \n \n Highly Customized Terminal Box Maker \n \n \n \n \n \n+ +\n", + "Classic": "+----------------------------------------+\n| |\n| |\n| |\n| |\n| |\n| Box CLI Maker |\n| |\n| Highly Customized Terminal Box Maker |\n| |\n| |\n| |\n| |\n| |\n+----------------------------------------+\n", + } + for key := range cases { + Box := New(Config{Px: 2, Py: 5, Type: key}) + box := Box.String("Box CLI Maker", "Highly Customized Terminal Box Maker") + if cases[key] != box { + t.Fatal(fmt.Sprintf(key, "Style", cases[key], "and", box, "are not same")) + } + } +} + +func TestPrintColorBox(t *testing.T) { + StyleCases := []string{"Single", "Double", "Single Double", "Double Single", "Bold", "Round", "Hidden", "Classic"} + ColorTypes := []string{"Black", "Blue", "Red", "Green", "Yellow", "Cyan", "Magenta", "HiBlack", "HiBlue", "HiRed", "HiGreen", "HiYellow", "HiCyan", "HiMagenta"} + + for i := 0; i < len(StyleCases); i++ { + for j := 0; j < len(ColorTypes); j++ { + Box := New(Config{Px: 2, Py: 5, Type: StyleCases[i], Color: ColorTypes[j]}) + fmt.Print(fmt.Sprint("Using ", StyleCases[i], " as Style and ", ColorTypes[j], " as Color")) + Box.Println("Box CLI Maker", "Highly Customized Terminal Box Maker") + } + } +} + +func TestTitlePos(t *testing.T) { + cases := map[string]map[string]string{ + "Inside": { + "Single": "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Box CLI Maker โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n", + "Single Double": "โ•“โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•–\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ Box CLI Maker โ•‘\nโ•‘ โ•‘\nโ•‘ Highly Customized Terminal Box Maker โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•™โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•œ\n", + "Double": "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ Box CLI Maker โ•‘\nโ•‘ โ•‘\nโ•‘ Highly Customized Terminal Box Maker โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n", + "Double Single": "โ•’โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ••\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Box CLI Maker โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ•˜โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•›\n", + "Bold": "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ Box CLI Maker โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ Highly Customized Terminal Box Maker โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”›\n", + "Round": "โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Box CLI Maker โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ\n", + "Hidden": "+ +\n \n \n \n \n \n Box CLI Maker \n \n Highly Customized Terminal Box Maker \n \n \n \n \n \n+ +\n", + "Classic": "+----------------------------------------+\n| |\n| |\n| |\n| |\n| |\n| Box CLI Maker |\n| |\n| Highly Customized Terminal Box Maker |\n| |\n| |\n| |\n| |\n| |\n+----------------------------------------+\n", + }, + "Bottom": { + "Single": "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”” Box CLI Maker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n", + "Single Double": "โ•“โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•–\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ Highly Customized Terminal Box Maker โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•™ Box CLI Maker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•œ\n", + "Double": "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ Highly Customized Terminal Box Maker โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•š Box CLI Maker โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n", + "Double Single": "โ•’โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ••\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ•˜ Box CLI Maker โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•›\n", + "Bold": "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ Highly Customized Terminal Box Maker โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”— Box CLI Maker โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”›\n", + "Round": "โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ•ฐ Box CLI Maker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ\n", + "Hidden": "+ +\n \n \n \n \n \n Highly Customized Terminal Box Maker \n \n \n \n \n \n+ Box CLI Maker +\n", + "Classic": "+----------------------------------------+\n| |\n| |\n| |\n| |\n| |\n| Highly Customized Terminal Box Maker |\n| |\n| |\n| |\n| |\n| |\n+ Box CLI Maker -------------------------+\n", + }, + "Top": { + "Single": "โ”Œ Box CLI Maker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n", + "Single Double": "โ•“ Box CLI Maker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•–\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ Highly Customized Terminal Box Maker โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•™โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•œ\n", + "Double": "โ•” Box CLI Maker โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ Highly Customized Terminal Box Maker โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•‘ โ•‘\nโ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n", + "Double Single": "โ•’ Box CLI Maker โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ••\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ•˜โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•›\n", + "Bold": "โ” Box CLI Maker โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ Highly Customized Terminal Box Maker โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”ƒ โ”ƒ\nโ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”›\n", + "Round": "โ•ญ Box CLI Maker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ Highly Customized Terminal Box Maker โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ”‚ โ”‚\nโ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ\n", + "Hidden": "+ Box CLI Maker +\n \n \n \n \n \n Highly Customized Terminal Box Maker \n \n \n \n \n \n+ +\n", + "Classic": "+ Box CLI Maker -------------------------+\n| |\n| |\n| |\n| |\n| |\n| Highly Customized Terminal Box Maker |\n| |\n| |\n| |\n| |\n| |\n+----------------------------------------+\n", + }, + } + for titlePos, val := range cases { + for style := range val { + Box := New(Config{Px: 2, Py: 5, Type: style, TitlePos: titlePos}) + box := Box.String("Box CLI Maker", "Highly Customized Terminal Box Maker") + if box != val[style] { + t.Error(fmt.Sprintf("Using %s as Style and %s as TitlePos but %s and %s are not same", style, titlePos, box, val[style])) + } + } + } +} + +func TestPrintMultiandTabLineString(t *testing.T) { + StyleCases := []string{"Single", "Double", "Single Double", "Double Single", "Bold", "Round", "Hidden", "Classic"} + ColorTypes := []string{"Black", "Blue", "Red", "Green", "Yellow", "Cyan", "Magenta", "HiBlack", "HiBlue", "HiRed", "HiGreen", "HiYellow", "HiCyan", "HiMagenta"} + + for i := 0; i < len(StyleCases); i++ { + for j := 0; j < len(ColorTypes); j++ { + Box := New(Config{Px: 2, Py: 5, Type: StyleCases[i], Color: ColorTypes[j]}) + fmt.Print(fmt.Sprint("Using ", StyleCases[i], " as Style and ", ColorTypes[j], " as Color")) + Box.Println("Box CLI Maker", `Make + Highly + Customized + Terminal + Boxes`) + } + } +} + +func TestUnicodeString(t *testing.T) { + // English, Japanese, Chinese(Traditional), Korean, French, Spanish, Latin, Greek + titles := []string{"Box CLI Maker", "ใƒœใƒƒใ‚ฏใ‚นใƒกใƒผใ‚ซใƒผ", "็›’ๅญ่ฃฝ้€ ๅ•†", "๋ฐ•์Šค ๋ฉ”์ด์ปค", "Crรฉateur de boรฎte CLI", "Fabricante de cajas", "Qui fecit me arca CLI", "ฮšฮฟฯ…ฯ„ฮฏ CLI Maker"} + lines := []string{"Make Highly Customized Terminal Boxes", "้ซ˜ๅบฆใซใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ•ใ‚ŒใŸ็ซฏๅญใƒœใƒƒใ‚ฏใ‚นใ‚’ไฝœๆˆใ™ใ‚‹", "่ฃฝไฝœ้ซ˜ๅบฆๅฎšๅˆถ็š„ๆŽฅ็ทš็›’", "๊ณ ๋„๋กœ ๋งž์ถคํ™” ๋œ ํ„ฐ๋ฏธ๋„ ๋ฐ•์Šค ๋งŒ๋“ค๊ธฐ", "Crรฉez des boรฎtes ร  bornes hautement personnalisรฉes", "Haga cajas de terminales altamente personalizadas", "Fac multum Customized Terminal Pyxidas", "ฮ”ฮทฮผฮนฮฟฯ…ฯฮณฮฎฯƒฯ„ฮต ฯ€ฮฟฮปฯ ฯ€ฯฮฟฯƒฮฑฯฮผฮฟฯƒฮผฮญฮฝฮฑ ฯ„ฮตฯฮผฮฑฯ„ฮนฮบฮฌ ฮบฮฟฯ…ฯ„ฮนฮฌ"} + for i := 0; i < len(titles); i++ { + Box := New(Config{Px: 2, Py: 5}) + Box.Println(titles[i], lines[i]) + } +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..6c96932 --- /dev/null +++ b/doc.go @@ -0,0 +1,89 @@ +/* +Package Box CLI Maker is a Highly Customized Terminal Box Creator written in Go. + +It provides many styles and options to make Boxes. There are 8 inbuilt styles and Color support via RGB uint, RGB Array of [3]uint and string (given). + +Inbuilt Box Styles: +Single +Double +Bold +Single Double +Double Single +Round +Hidden +Classic + +Inbuilt Colors: +Black +Blue +Red +Yellow +Green +Cyan +Magenta +HiBlack +HiBlue +HiRed +HiYellow +HiGreen +HiCyan +HiMagenta + +It also has Unicode and Emoji support which works across all terminals though there might be some terminals which do not support +Unicode and Emoji like Windows CMD and Powershell. Unlike other terminals makers Box CLI Mkaer supports tab and multi line string. + +Basic Example: + +Box := box.New(box.Config{Px: 2, Py: 5, Type: "Single", Color: "Cyan"}) +Box.Print("Box CLI Maker", "Highly Customized Terminal Box Maker") + +You can specify and change the options by changing the above Config struct. +If "Style" isn't provided in box.Config struct then by default it will be "Single". + +You can customize and change the TitlePos to Inside, Top, Bottom and ContentAlign to Left, Right and Center. +By default TitlePos is Inside and ContentAlign is Left. + +Box := box.New(box.Config{Px: 2, Py: 5, Type: "Single", TitlePos: "Top", ContentAlign: "Left"}) + +If you want the string representation of the Box then you can just use String() method of the Box: + +If you want the Box to be printed correctly irrespective of it will form the correct color or not on Windows then you will have to add Box.Output +as the passing stream to Fprintf(), Fprintln() and etc to the passing stream functions: + +if runtime.GOOS == "windows" { + fmt.Fprintf(box.Output, box_str) +} + +Box := box.New(box.Config{Px: 2, Py: 5, Type: "Single", Color: "Cyan"}) +b := Box.String("Box CLI Maker", "Highly Customized Terminal Box Maker") +... // use it afterwards + +If you do not want the title and lines to be there then you can just leave i.e. put an empty in the both places.const + +The following two will not be applicable for terminals which don't have 24 bit support i.e. True Color ANSI Code. +If used then the color effect will not be there. + +RBG Uint Example: + +Box := box.New(box.Config{Px: 2, Py: 5, Type: "Single", Color: uint(0x34562f)}) +Box.Print("Box CLI Maker", "Highly Customized Terminal Box Maker") + +Note: Uint must be in a range of [0x000000, 0xFFFFFF] else it will panic. + + +RBG [3]uint Example: + +Box := box.New(box.Config{Px: 2, Py: 5, Type: "Single", Color: [3]uint{23, 56, 78}}) +Box.Print("Box CLI Maker", "Highly Customized Terminal Box Maker") + +Note: [3]uint array elements must be in a range of [0x0, 0xFF] else it will panic. + + +You can even make your custom Box Style by using box.Box struct: + +config := box.Config{Px: 2, Py: 3, Type: "", TitlePos: "Inside"} +boxNew := box.Box{TopRight: "*", TopLeft: "*", BottomRight: "*", BottomLeft: "*", Horizontal: "-", Vertical: "|", Config: config} +... // use it afterwards +*/ + +package box diff --git a/go.mod b/go.mod index 94bb74c..2070067 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,9 @@ module github.com/Delta456/box-cli-maker -go 1.14 +go 1.15 require ( - github.com/fatih/color v1.9.0 - github.com/mattn/go-colorable v0.1.7 // indirect + github.com/fatih/color v1.10.0 github.com/mattn/go-runewidth v0.0.9 - golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c // indirect + golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf // indirect ) diff --git a/go.sum b/go.sum index 240b0bf..6cb3e30 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,14 @@ -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c= -golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM= +golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf h1:kt3wY1Lu5MJAnKTfoMR52Cu4gwvna4VTzNOiT8tY73s= +golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/types.go b/types.go index 0c5a908..5975523 100644 --- a/types.go +++ b/types.go @@ -2,89 +2,96 @@ package box import "github.com/fatih/color" -var boxs map[string]Box = map[string]Box{ - "Single": { - TopRight: "โ”", - TopLeft: "โ”Œ", - BottomRight: "โ”˜", - BottomLeft: "โ””", - Horizontal: "โ”€", - Vertical: "โ”‚", - }, - "Double": { - TopRight: "โ•—", - TopLeft: "โ•”", - BottomRight: "โ•", - BottomLeft: "โ•š", - Horizontal: "โ•", - Vertical: "โ•‘", - }, - "Round": { - TopRight: "โ•ฎ", - TopLeft: "โ•ญ", - BottomRight: "โ•ฏ", - BottomLeft: "โ•ฐ", - Horizontal: "โ”€", - Vertical: "โ”‚", - }, - "Bold": { - TopRight: "โ”“", - TopLeft: "โ”", - BottomRight: "โ”›", - BottomLeft: "โ”—", - Horizontal: "โ”", - Vertical: "โ”ƒ", - }, - "Single Double": { - TopRight: "โ•–", - TopLeft: "โ•“", - BottomRight: "โ•œ", - BottomLeft: "โ•™", - Horizontal: "โ”€", - Vertical: "โ•‘", - }, - "Double Single": { - TopRight: "โ••", - TopLeft: "โ•’", - BottomRight: "โ•›", - BottomLeft: "โ•˜", - Horizontal: "โ•", - Vertical: "โ”‚", - }, - "Classic": { - TopRight: "+", - TopLeft: "+", - BottomRight: "+", - BottomLeft: "+", - Horizontal: "-", - Vertical: "|", - }, - "Hidden": { - TopRight: "+", - TopLeft: "+", - BottomRight: "+", - BottomLeft: "+", - Horizontal: " ", - Vertical: " ", - }, -} - -var fgColors map[string]color.Attribute = map[string]color.Attribute{ - "Black": color.FgBlack, - "Blue": color.FgBlue, - "Red": color.FgRed, - "Green": color.FgGreen, - "Yellow": color.FgYellow, - "Cyan": color.FgCyan, - "Magenta": color.FgMagenta, -} - -var fgHiColors map[string]color.Attribute = map[string]color.Attribute{ - "HiBlack": color.FgHiBlack, - "HiBlue": color.FgHiBlue, - "HiRed": color.FgHiRed, - "HiGreen": color.FgHiGreen, - "HiYellow": color.FgHiYellow, - "HiCyan": color.FgHiCyan, - "HiMagenta": color.FgHiMagenta, -} +var ( + // boxes are inbuilt styles provided by the module + boxes map[string]Box = map[string]Box{ + "Single": { + TopRight: "โ”", + TopLeft: "โ”Œ", + BottomRight: "โ”˜", + BottomLeft: "โ””", + Horizontal: "โ”€", + Vertical: "โ”‚", + }, + "Double": { + TopRight: "โ•—", + TopLeft: "โ•”", + BottomRight: "โ•", + BottomLeft: "โ•š", + Horizontal: "โ•", + Vertical: "โ•‘", + }, + "Round": { + TopRight: "โ•ฎ", + TopLeft: "โ•ญ", + BottomRight: "โ•ฏ", + BottomLeft: "โ•ฐ", + Horizontal: "โ”€", + Vertical: "โ”‚", + }, + "Bold": { + TopRight: "โ”“", + TopLeft: "โ”", + BottomRight: "โ”›", + BottomLeft: "โ”—", + Horizontal: "โ”", + Vertical: "โ”ƒ", + }, + "Single Double": { + TopRight: "โ•–", + TopLeft: "โ•“", + BottomRight: "โ•œ", + BottomLeft: "โ•™", + Horizontal: "โ”€", + Vertical: "โ•‘", + }, + "Double Single": { + TopRight: "โ••", + TopLeft: "โ•’", + BottomRight: "โ•›", + BottomLeft: "โ•˜", + Horizontal: "โ•", + Vertical: "โ”‚", + }, + "Classic": { + TopRight: "+", + TopLeft: "+", + BottomRight: "+", + BottomLeft: "+", + Horizontal: "-", + Vertical: "|", + }, + "Hidden": { + TopRight: "+", + TopLeft: "+", + BottomRight: "+", + BottomLeft: "+", + Horizontal: " ", + Vertical: " ", + }, + } + // fgColors are the inbuilt Foreground Colors provided by the module + fgColors map[string]color.Attribute = map[string]color.Attribute{ + "Black": color.FgBlack, + "Blue": color.FgBlue, + "Red": color.FgRed, + "Green": color.FgGreen, + "Yellow": color.FgYellow, + "Cyan": color.FgCyan, + "Magenta": color.FgMagenta, + } + // fgHiColors are the inbuilt Hi-intensity Foreground Colors provided by the module + fgHiColors map[string]color.Attribute = map[string]color.Attribute{ + "HiBlack": color.FgHiBlack, + "HiBlue": color.FgHiBlue, + "HiRed": color.FgHiRed, + "HiGreen": color.FgHiGreen, + "HiYellow": color.FgHiYellow, + "HiCyan": color.FgHiCyan, + "HiMagenta": color.FgHiMagenta, + } + // Output is an io.Writer and instance of + // color.Output which is needed + // for outputting in windows console + Output = color.Output +) diff --git a/util.go b/util.go index f99acf0..cfed9a9 100644 --- a/util.go +++ b/util.go @@ -1,21 +1,29 @@ package box import ( + "bufio" "fmt" "os" + "runtime" "strings" "github.com/fatih/color" "github.com/mattn/go-runewidth" ) +// expandedLine stores a tab-expanded line, and its visible length. +type expandedLine struct { + line string // tab-expanded line + len int // line's visible length +} + // addVertPadding adds Vertical Padding func (b Box) addVertPadding(len int) []string { padding := strings.Repeat(" ", len-2) vertical := b.obtainColor() - var texts []string - for i := 0; i < b.Con.Py; i++ { + var texts = make([]string, 0, b.Py) + for i := 0; i < b.Py; i++ { texts = append(texts, (vertical + padding + vertical)) } return texts @@ -23,28 +31,51 @@ func (b Box) addVertPadding(len int) []string { // findAlign checks ContentAlign and returns Alignment func (b Box) findAlign() string { - if b.Con.ContentAlign == "Center" { + if b.ContentAlign == "Center" { return centerAlign - } else if b.Con.ContentAlign == "Right" { + } else if b.ContentAlign == "Right" { return rightAlign - } else if b.Con.ContentAlign == "Left" { + // If ContentAlign isn't provided then by default Alignment is Left + } else if b.ContentAlign == "Left" || b.ContentAlign == "" { return leftAlign } else { - fmt.Fprintln(os.Stderr, color.RedString("[error]: invalid value provided to Alignment, using default")) + // Raise a warning if the ContentAlign isn't invalid + errorMsg("[warning]: invalid value provided to Alignment, using default") return leftAlign } } -// longestLine returns longest line -func longestLine(lines []string) int { +// longestLine expands tabs in lines and determines longest visible +// return longest length and array of expanded lines +func longestLine(lines []string) (int, []expandedLine) { longest := 0 + var expandedLines []expandedLine + var tmpLine strings.Builder + var lineLen int + for _, line := range lines { - length := runewidth.StringWidth(line) - if length > longest { - longest = length + tmpLine.Reset() + + for _, c := range line { + lineLen = runewidth.StringWidth(tmpLine.String()) + + if c == '\t' { + tmpLine.WriteString(strings.Repeat(" ", 8-(lineLen&7))) + } else { + tmpLine.WriteRune(c) + } + } + + lineLen = runewidth.StringWidth(tmpLine.String()) + + expandedLines = append(expandedLines, expandedLine{tmpLine.String(), lineLen}) + + if lineLen > longest { + longest = lineLen } } - return longest + + return longest, expandedLines } func repeatWithString(c string, n int, str string) string { @@ -53,3 +84,43 @@ func repeatWithString(c string, n int, str string) string { strNew := fmt.Sprintf(" %s %s", str, bar) return strNew } + +// rgb returns the custom RGB formed string +// only works with 24 bit color supported terminals +// Taken from https://github.com/vlang/v/blob/master/vlib/term/colors.v#L10-L12 +func rgb(r, g, b uint, msg, open, close string) string { + return fmt.Sprintf("\x1b[%s;2;%s;%s;%sm%s\x1b[%sm", open, fmt.Sprint(r), fmt.Sprint(g), fmt.Sprint(b), msg, close) +} + +// rgbArray returns the custom RGB formed string from [3]uint values +// All the elements must be in a range of [0x0, 0xFF] +func rgbArray(r [3]uint, msg string) string { + for _, ele := range r { + if ele > 0xFF || ele < 0x0 { + panic("RGB Array Elements must be in a range of [0x0, 0xFF]") + } + } + return rgb(r[0], r[1], r[2], msg, "38", "39") +} + +// rgbHex returns the custom RGB formed string from hexadecimal +// Taken from https://github.com/vlang/v/blob/master/vlib/term/colors.v#L22-L24 +// All the elements must be in a range of [0x000000, 0xFFFFFF] +func rgbHex(hex uint, msg string) string { + if hex < 0x00_0000 || hex > 0xFF_FFFF { + panic(fmt.Sprint(hex, "must be in a range of [0x000000, 0xFFFFFF]")) + } + return rgb(hex>>16, hex>>8&0xFF, hex&0xFF, msg, "38", "39") +} + +// errorMsg prints the msg to os.Stderr in Red ANSI Color according to the system +func errorMsg(msg string) { + if runtime.GOOS == "windows" { + // Using Output instead of os.Stderr because Output will enable ANSI Color on Winodws Console + fmt.Fprintln(Output, color.RedString(msg)) + // Using bufio.NewWriter for flushing the message to os.Stderr stream + bufio.NewWriter(os.Stderr).Flush() + } else { + fmt.Fprintln(os.Stderr, color.RedString(msg)) + } +}