Skip to content

Commit

Permalink
all; release v2.3.0 (#22)
Browse files Browse the repository at this point in the history
Co-authored-by: Christian Muehlhaeuser <muesli@gmail.com>
Co-authored-by: JalonSolov <JalonSolov@gmail.com>
Co-authored-by: Felipe Dutra Tine e Silva <5888407+tkanos@users.noreply.github.com>
Co-authored-by: Felipe Dutra Tine e Silva <felipe.dutratineesilva@mindgeek.com>
  • Loading branch information
5 people committed Oct 30, 2022
1 parent 38cbd74 commit 56c819b
Show file tree
Hide file tree
Showing 46 changed files with 1,092 additions and 170 deletions.
182 changes: 110 additions & 72 deletions README.md

Large diffs are not rendered by default.

256 changes: 226 additions & 30 deletions box.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ package box

import (
"fmt"
"log"
"os"
"strings"

"github.com/gookit/color"
"github.com/huandu/xstrings"
"github.com/mattn/go-runewidth"
"github.com/muesli/reflow/wrap"
"golang.org/x/term"
)

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

// Box struct defines the Box to be made.
// Box defines the design
type Box struct {
TopRight string // Symbols used for TopRight Corner
TopLeft string // Symbols used for TopLeft Corner
Vertical string // Symbols used for Vertical Bars
BottomRight string // Symbols used for BottomRight Corner
BottomLeft string // Symbols used for BottomRight Corner
Horizontal string // Symbols used for Horizontal Bars
Config // Config for the Box struct
TopRight string // TopRight Corner Symbols
TopLeft string // TopLeft Corner Symbols
Vertical string // Vertical Bar Symbols
BottomRight string // BottomRight Corner Symbols
BottomLeft string // BottomLeft Corner Symbols
Horizontal string // Horizontal Bars Symbols
Config // Box Config
}

// Config is the configuration for the Box struct
// Config is the configuration needed for the Box to be designed
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 interface{} // Color of Box
Py int // Horizontal Padding
Px int // Vertical Padding
ContentAlign string // Content Alignment inside Box
Type string // Box Type
TitlePos string // Title Position
TitleColor interface{} // Title Color
ContentColor interface{} // Content Color
Color interface{} // Box Color
AllowWrapping bool // Flag to allow custom Content Wrapping
WrappingLimit int // Wrap the Content upto the Limit
}

// New takes struct Config and returns the specified Box struct.
// New takes Box Config and returns a Box from the given Config
func New(config Config) Box {
// Default Box Type is Single
if config.Type == "" {
Expand All @@ -55,10 +64,29 @@ func New(config Config) Box {
panic("Invalid Box Type provided")
}

// String returns the string representation of Box.
// String returns the string representation of Box
func (b Box) String(title, lines string) string {
var lines2 []string

// Allow Wrapping according to the user
if b.AllowWrapping {
// If limit not provided then use 2*TermWidth/3 as limit else
// use the one provided
if b.WrappingLimit != 0 {
lines = wrap.String(lines, b.WrappingLimit)
} else {
width, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil {
log.Fatal(err)
}
lines = wrap.String(lines, 2*width/3)
}
}

// Obtain Title and Content color
title = b.obtainTitleColor(title)
lines = b.obtainContentColor(lines)

// Default Position is Inside, no warning for invalid TitlePos as it is done
// in toString() method
if b.TitlePos == "" {
Expand All @@ -78,31 +106,40 @@ func (b Box) String(title, lines string) string {
return b.toString(title, lines2)
}

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

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

n := longestLine + (paddingCount * 2) + 2
n := _longestLine + (paddingCount * 2) + 2

if b.TitlePos != inside && runewidth.StringWidth(title) > n-2 {
if b.TitlePos != inside && runewidth.StringWidth(color.ClearCode(title)) > n-2 {
panic("Title must be shorter than the Top & 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
// Check b.TitlePos

var TitleBar string
// If title has tabs then expand them accordingly.
if strings.Contains(title, "\t") {
TitleBar = repeatWithString(b.Horizontal, n-2, xstrings.ExpandTabs(title, 4))
} else {
TitleBar = repeatWithString(b.Horizontal, n-2, title)
}

// Check b.TitlePos if it is not Inside
if b.TitlePos != inside {
TitleBar := repeatWithString(b.Horizontal, n-2, title)
switch b.TitlePos {
case "Top":
TopBar = b.TopLeft + TitleBar + b.TopRight
//fmt.Println(TopBar)
case "Bottom":
BottomBar = b.BottomLeft + TitleBar + b.BottomRight
default:
Expand All @@ -115,15 +152,20 @@ func (b Box) toString(title string, lines []string) string {
}
inside:
// Check type of b.Color then assign the Colors to TopBar and BottomBar accordingly
TopBar, BottomBar = b.checkColorType(TopBar, BottomBar)
// If title has tabs then expand them accordingly.
if strings.Contains(title, "\t") {
TopBar, BottomBar = b.checkColorType(TopBar, BottomBar, xstrings.ExpandTabs(title, 4))
} else {
TopBar, BottomBar = b.checkColorType(TopBar, BottomBar, title)
}

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
texts := b.addVertPadding(n)
texts = b.formatLine(lines2, longestLine, titleLen, sideMargin, title, texts)

texts = b.formatLine(lines2, _longestLine, titleLen, sideMargin, title, texts)
vertpadding := b.addVertPadding(n)
texts = append(texts, vertpadding...)

Expand All @@ -141,8 +183,124 @@ inside:
return sb.String()
}

// obtainColor obtains the Color from string, uint and [3]uint respectively
func (b Box) obtainColor() string {
// obtainTitleColor obtains TitleColor from types string, uint and [3]uint respectively
func (b Box) obtainTitleColor(title string) string {
if b.TitleColor == nil { // if nil then just return the string
return title
}
// Check if type of b.TitleColor is string
if str, ok := b.TitleColor.(string); ok {
// Hi Intensity Color
if strings.HasPrefix(str, "Hi") {
if _, ok := fgHiColors[str]; ok {
// If title has newlines in it then splitting would be needed
// as color won't be applied on all
if strings.Contains(title, "\n") {
return b.applyColorToAll(title, str, color.RGBColor{}, false)
}
return addStylePreservingOriginalFormat(title, fgHiColors[str].Sprint)
}
} else if _, ok := fgColors[str]; ok {
// If title has newlines in it then splitting would be needed
// as color won't be applied on all
if strings.Contains(title, "\n") {
return b.applyColorToAll(title, str, color.RGBColor{}, false)
}
return addStylePreservingOriginalFormat(title, fgColors[str].Sprint)
}
// Return a warning as TitleColor provided as a string is unknown and
// return without the color effect
errorMsg("[warning]: invalid value provided to Color, using default")
return title

// Check if type of b.TitleColor is uint
} else if hex, ok := b.TitleColor.(uint); ok {
// Break down the hex into R, G and B respectively
hexArray := [3]uint{hex >> 16, hex >> 8 & 0xff, hex & 0xff}
col := color.RGB(uint8(hexArray[0]), uint8(hexArray[1]), uint8(hexArray[2]))

// If title has newlines in it then splitting would be needed
// as color won't be applied on all
if strings.Contains(title, "\n") {
return b.applyColorToAll(title, "", col, true)
}
return addStylePreservingOriginalFormat(title, col.Sprint)

// Check if type of b.TitleColor is [3]uint
} else if rgb, ok := b.TitleColor.([3]uint); ok {
col := color.RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]))

// If title has newlines in it then splitting would be needed
// as color won't be applied on all
if strings.Contains(title, "\n") {
return b.applyColorToAll(title, "", col, true)
}
return b.roundOffTitleColor(col, addStylePreservingOriginalFormat(title, col.Sprint))
}
// Panic if b.TitleColor is an unexpected type
panic(fmt.Sprintf("expected string, [3]uint or uint not %T", b.TitleColor))
}

// obtainContentColor obtains ContentColor from types string, uint and [3]uint respectively
func (b Box) obtainContentColor(content string) string {
if b.ContentColor == nil { // if nil then just return the string
return content
}
// Check if type of b.ContentColor is string
if str, ok := b.ContentColor.(string); ok {
// Hi Intensity Color
if strings.HasPrefix(str, "Hi") {
if _, ok := fgHiColors[str]; ok {
// If Content has newlines in it then splitting would be needed
// as color won't be applied on all
if strings.Contains(content, "\n") {
return b.applyColorToAll(content, str, color.RGBColor{}, false)
}
return addStylePreservingOriginalFormat(content, fgHiColors[str].Sprint)
}
} else if _, ok := fgColors[str]; ok {
// If Content has newlines in it then splitting would be needed
// as color won't be applied on all
if strings.Contains(content, "\n") {
return b.applyColorToAll(content, str, color.RGBColor{}, false)
}
return addStylePreservingOriginalFormat(content, fgColors[str].Sprint)
}
// Return a warning as ContentColor provided as a string is unknown and
// return without the color effect
errorMsg("[warning]: invalid value provided to Color, using default")
return content

// Check if type of b.ContentColor is uint
} else if hex, ok := b.ContentColor.(uint); ok {
// Break down the hex into R, G and B respectively
hexArray := [3]uint{hex >> 16, hex >> 8 & 0xff, hex & 0xff}
col := color.RGB(uint8(hexArray[0]), uint8(hexArray[1]), uint8(hexArray[2]))

// If content has newlines in it then splitting would be needed
// as color won't be applied on all
if strings.Contains(content, "\n") {
return b.applyColorToAll(content, "", col, true)
}
return b.roundOffTitleColor(col, content)

// Check if type of b.ContentColor is [3]uint
} else if rgb, ok := b.ContentColor.([3]uint); ok {
col := color.RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]))

// If content has newlines in it then splitting would be needed
// as color won't be applied on all
if strings.Contains(content, "\n") {
return b.applyColorToAll(content, "", col, true)
}
return b.roundOffTitleColor(col, content)
}
// Panic if b.ContentColor is an unexpected type
panic(fmt.Sprintf("expected string, [3]uint or uint not %T", b.ContentColor))
}

// obtainColor obtains BoxColor from types string, uint and [3]uint respectively
func (b Box) obtainBoxColor() string {
if b.Color == nil { // if nil then just return the string
return b.Vertical
}
Expand Down Expand Up @@ -179,6 +337,25 @@ func (b Box) obtainColor() string {
func (b Box) Print(title, lines string) {
var lines2 []string

// Allow Wrapping according to the user
if b.AllowWrapping {
// If limit not provided then use 2*TermWidth/3 as limit else
// use the one provided
if b.WrappingLimit != 0 {
lines = wrap.String(lines, b.WrappingLimit)
} else {
width, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil {
log.Fatal(err)
}
lines = wrap.String(lines, 2*width/3)
}
}

// Obtain Title and Content color
title = b.obtainTitleColor(title)
lines = b.obtainContentColor(lines)

// Default Position is Inside, if invalid position is given then just raise a warning
// then use Default Position which is Inside
if b.TitlePos == "" {
Expand All @@ -201,10 +378,29 @@ func (b Box) Print(title, lines string) {
color.Print(b.toString(title, lines2))
}

// Println adds a newline before and after the Box
// Println adds a newline before and after printing the Box
func (b Box) Println(title, lines string) {
var lines2 []string

// Allow Wrapping according to the user
if b.AllowWrapping {
// If limit not provided then use 2*TermWidth/3 as limit else
// use the one provided
if b.WrappingLimit != 0 {
lines = wrap.String(lines, b.WrappingLimit)
} else {
width, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil {
log.Fatal(err)
}
lines = wrap.String(lines, 2*width/3)
}
}

// Obtain Title and Content color
title = b.obtainTitleColor(title)
lines = b.obtainContentColor(lines)

// Default Position is Inside, if invalid position is given then just raise a warning
// then use Default Position which is Inside
if b.TitlePos == "" {
Expand Down

0 comments on commit 56c819b

Please sign in to comment.