dep ensure

This commit is contained in:
Michael Muré 2019-12-26 21:49:39 +01:00
parent 2852bb27e5
commit 6fffe24dea
6 changed files with 394 additions and 53 deletions

View File

@ -1,9 +1,9 @@
language: go language: go
go: go:
- 1.10.x
- 1.11.x - 1.11.x
- 1.12.x - 1.12.x
- 1.13.x
env: env:
- GO111MODULE=on - GO111MODULE=on

View File

@ -20,6 +20,7 @@ Included algorithms cover:
- trimming - trimming
- alignment - alignment
- escape sequence extraction and reapplication - escape sequence extraction and reapplication
- escape sequence snapshot and simplification
- truncation - truncation
## Example ## Example
@ -31,7 +32,7 @@ import (
"fmt" "fmt"
"strings" "strings"
text "github.com/MichaelMure/go-term-text" "github.com/MichaelMure/go-term-text"
) )
func main() { func main() {
@ -41,8 +42,10 @@ func main() {
"various graphic design. 一只 A Quick \x1b[31m敏捷的狐 Fox " + "various graphic design. 一只 A Quick \x1b[31m敏捷的狐 Fox " +
"狸跳过了\x1b[0mDog一只懒狗。" "狸跳过了\x1b[0mDog一只懒狗。"
output, n := text.WrapWithPadIndent(input, 60, output, n := text.Wrap(input, 60,
"\x1b[34m<-indent-> \x1b[0m", "\x1b[33m<-pad-> \x1b[0m") text.WrapIndent("\x1b[34m<-indent-> \x1b[0m"),
text.WrapPad("\x1b[33m<-pad-> \x1b[0m"),
)
fmt.Printf("output has %d lines\n\n", n) fmt.Printf("output has %d lines\n\n", n)

View File

@ -0,0 +1,265 @@
package text
import (
"fmt"
"strconv"
"strings"
)
const Escape = '\x1b'
type EscapeState struct {
Bold bool
Dim bool
Italic bool
Underlined bool
Blink bool
Reverse bool
Hidden bool
CrossedOut bool
FgColor Color
BgColor Color
}
type Color interface {
Codes() []string
}
func (es *EscapeState) Witness(s string) {
inEscape := false
var start int
runes := []rune(s)
for i, r := range runes {
if r == Escape {
inEscape = true
start = i
continue
}
if inEscape {
if r == 'm' {
inEscape = false
es.witnessCode(string(runes[start+1 : i]))
}
continue
}
}
}
func (es *EscapeState) witnessCode(s string) {
if s == "" {
return
}
if s == "[" {
es.reset()
return
}
if len(s) < 2 {
return
}
if s[0] != '[' {
return
}
s = s[1:]
split := strings.Split(s, ";")
dequeue := func() {
split = split[1:]
}
color := func(ground int) Color {
if len(split) < 1 {
// the whole sequence is broken, ignoring the rest
return nil
}
subCode := split[0]
dequeue()
switch subCode {
case "2":
if len(split) < 3 {
return nil
}
r, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
g, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
b, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
return &ColorRGB{ground: ground, R: r, G: g, B: b}
case "5":
if len(split) < 1 {
return nil
}
index, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
return &Color256{ground: ground, Index: index}
}
return nil
}
for len(split) > 0 {
code, err := strconv.Atoi(split[0])
if err != nil {
return
}
dequeue()
switch {
case code == 0:
es.reset()
case code == 1:
es.Bold = true
case code == 2:
es.Dim = true
case code == 3:
es.Italic = true
case code == 4:
es.Underlined = true
case code == 5:
es.Blink = true
// case code == 6:
case code == 7:
es.Reverse = true
case code == 8:
es.Hidden = true
case code == 9:
es.CrossedOut = true
case code == 21:
es.Bold = false
case code == 22:
es.Dim = false
case code == 23:
es.Italic = false
case code == 24:
es.Underlined = false
case code == 25:
es.Blink = false
// case code == 26:
case code == 27:
es.Reverse = false
case code == 28:
es.Hidden = false
case code == 29:
es.CrossedOut = false
case (code >= 30 && code <= 37) || code == 39 || (code >= 90 && code <= 97):
es.FgColor = ColorIndex(code)
case (code >= 40 && code <= 47) || code == 49 || (code >= 100 && code <= 107):
es.BgColor = ColorIndex(code)
case code == 38:
es.FgColor = color(code)
if es.FgColor == nil {
return
}
case code == 48:
es.BgColor = color(code)
if es.BgColor == nil {
return
}
}
}
}
func (es *EscapeState) reset() {
*es = EscapeState{}
}
func (es *EscapeState) String() string {
var codes []string
if es.Bold {
codes = append(codes, strconv.Itoa(1))
}
if es.Dim {
codes = append(codes, strconv.Itoa(2))
}
if es.Italic {
codes = append(codes, strconv.Itoa(3))
}
if es.Underlined {
codes = append(codes, strconv.Itoa(4))
}
if es.Blink {
codes = append(codes, strconv.Itoa(5))
}
if es.Reverse {
codes = append(codes, strconv.Itoa(7))
}
if es.Hidden {
codes = append(codes, strconv.Itoa(8))
}
if es.CrossedOut {
codes = append(codes, strconv.Itoa(9))
}
if es.FgColor != nil {
codes = append(codes, es.FgColor.Codes()...)
}
if es.BgColor != nil {
codes = append(codes, es.BgColor.Codes()...)
}
if len(codes) == 0 {
return "\x1b[0m"
}
return fmt.Sprintf("\x1b[%sm", strings.Join(codes, ";"))
}
type ColorIndex int
func (cInd ColorIndex) Codes() []string {
return []string{strconv.Itoa(int(cInd))}
}
type Color256 struct {
ground int
Index int
}
func (c256 Color256) Codes() []string {
return []string{
strconv.Itoa(c256.ground),
"5",
strconv.Itoa(c256.Index),
}
}
type ColorRGB struct {
ground int
R, G, B int
}
func (cRGB ColorRGB) Codes() []string {
return []string{
strconv.Itoa(cRGB.ground),
"2",
strconv.Itoa(cRGB.R),
strconv.Itoa(cRGB.G),
strconv.Itoa(cRGB.B),
}
}

View File

@ -1,8 +1,8 @@
module github.com/MichaelMure/go-term-text module github.com/MichaelMure/go-term-text
go 1.10 go 1.11
require ( require (
github.com/mattn/go-runewidth v0.0.4 github.com/mattn/go-runewidth v0.0.6
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0
) )

View File

@ -1,7 +1,7 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@ -13,52 +13,89 @@ func init() {
runewidth.DefaultCondition.EastAsianWidth = false runewidth.DefaultCondition.EastAsianWidth = false
} }
type wrapOpts struct {
indent string
pad string
align Alignment
}
// WrapOption is a functional option for the Wrap() function
type WrapOption func(opts *wrapOpts)
// WrapPad configure the padding with a string for Wrap()
func WrapPad(pad string) WrapOption {
return func(opts *wrapOpts) {
opts.pad = pad
}
}
// WrapPadded configure the padding with a number of space characters for Wrap()
func WrapPadded(padLen int) WrapOption {
return func(opts *wrapOpts) {
opts.pad = strings.Repeat(" ", padLen)
}
}
// WrapPad configure the indentation on the first line for Wrap()
func WrapIndent(indent string) WrapOption {
return func(opts *wrapOpts) {
opts.indent = indent
}
}
// WrapAlign configure the text alignment for Wrap()
func WrapAlign(align Alignment) WrapOption {
return func(opts *wrapOpts) {
opts.align = align
}
}
// allWrapOpts compile the set of WrapOption into a final wrapOpts
// from the default values.
func allWrapOpts(opts []WrapOption) *wrapOpts {
wrapOpts := &wrapOpts{
indent: "",
pad: "",
align: NoAlign,
}
for _, opt := range opts {
opt(wrapOpts)
}
if wrapOpts.indent == "" {
wrapOpts.indent = wrapOpts.pad
}
return wrapOpts
}
// Wrap a text for a given line size. // Wrap a text for a given line size.
// Handle properly terminal color escape code // Handle properly terminal color escape code
func Wrap(text string, lineWidth int) (string, int) { // Options are accepted to configure things like indent, padding or alignment.
return WrapLeftPadded(text, lineWidth, 0) // Return the wrapped text and the number of lines
} func Wrap(text string, lineWidth int, opts ...WrapOption) (string, int) {
wrapOpts := allWrapOpts(opts)
// WrapLeftPadded wrap a text for a given line size with a left padding. if lineWidth <= 0 {
// Handle properly terminal color escape code return "", 1
func WrapLeftPadded(text string, lineWidth int, leftPad int) (string, int) { }
pad := strings.Repeat(" ", leftPad)
return WrapWithPad(text, lineWidth, pad)
}
// WrapWithPad wrap a text for a given line size with a custom left padding
// Handle properly terminal color escape code
func WrapWithPad(text string, lineWidth int, pad string) (string, int) {
return WrapWithPadIndent(text, lineWidth, pad, pad)
}
// WrapWithPad wrap a text for a given line size with a custom left padding
// This function also align the result depending on the requested alignment.
// Handle properly terminal color escape code
func WrapWithPadAlign(text string, lineWidth int, pad string, align Alignment) (string, int) {
return WrapWithPadIndentAlign(text, lineWidth, pad, pad, align)
}
// WrapWithPadIndent wrap a text for a given line size with a custom left padding
// and a first line indent. The padding is not effective on the first line, indent
// is used instead, which allow to implement indents and outdents.
// Handle properly terminal color escape code
func WrapWithPadIndent(text string, lineWidth int, indent string, pad string) (string, int) {
return WrapWithPadIndentAlign(text, lineWidth, indent, pad, NoAlign)
}
// WrapWithPadIndentAlign wrap a text for a given line size with a custom left padding
// and a first line indent. The padding is not effective on the first line, indent
// is used instead, which allow to implement indents and outdents.
// This function also align the result depending on the requested alignment.
// Handle properly terminal color escape code
func WrapWithPadIndentAlign(text string, lineWidth int, indent string, pad string, align Alignment) (string, int) {
var lines []string var lines []string
nbLine := 0 nbLine := 0
if len(wrapOpts.indent) >= lineWidth {
// fallback rendering
lines = append(lines, strings.Repeat("⭬", lineWidth))
nbLine++
wrapOpts.indent = wrapOpts.pad
}
if len(wrapOpts.pad) >= lineWidth {
// fallback rendering
line := strings.Repeat("⭬", lineWidth)
return strings.Repeat(line+"\n", 5), 5
}
// Start with the indent // Start with the indent
padStr := indent padStr := wrapOpts.indent
padLen := Len(indent) padLen := Len(wrapOpts.indent)
// tabs are formatted as 4 spaces // tabs are formatted as 4 spaces
text = strings.Replace(text, "\t", " ", -1) text = strings.Replace(text, "\t", " ", -1)
@ -67,8 +104,8 @@ func WrapWithPadIndentAlign(text string, lineWidth int, indent string, pad strin
for i, line := range strings.Split(text, "\n") { for i, line := range strings.Split(text, "\n") {
// on the second line, use the padding instead // on the second line, use the padding instead
if i == 1 { if i == 1 {
padStr = pad padStr = wrapOpts.pad
padLen = Len(pad) padLen = Len(wrapOpts.pad)
} }
if line == "" || strings.TrimSpace(line) == "" { if line == "" || strings.TrimSpace(line) == "" {
@ -87,14 +124,14 @@ func WrapWithPadIndentAlign(text string, lineWidth int, indent string, pad strin
// use the first wrapped line, ignore everything else and // use the first wrapped line, ignore everything else and
// wrap the remaining of the line with the normal padding. // wrap the remaining of the line with the normal padding.
content := LineAlign(strings.TrimRight(split[0], " "), lineWidth-padLen, align) content := LineAlign(strings.TrimRight(split[0], " "), lineWidth-padLen, wrapOpts.align)
lines = append(lines, padStr+content) lines = append(lines, padStr+content)
nbLine++ nbLine++
line = strings.TrimPrefix(line, split[0]) line = strings.TrimPrefix(line, split[0])
line = strings.TrimLeft(line, " ") line = strings.TrimLeft(line, " ")
padStr = pad padStr = wrapOpts.pad
padLen = Len(pad) padLen = Len(wrapOpts.pad)
wrapped = softwrapLine(line, lineWidth-padLen) wrapped = softwrapLine(line, lineWidth-padLen)
split = strings.Split(wrapped, "\n") split = strings.Split(wrapped, "\n")
} }
@ -102,10 +139,10 @@ func WrapWithPadIndentAlign(text string, lineWidth int, indent string, pad strin
for j, seg := range split { for j, seg := range split {
if j == 0 { if j == 0 {
// keep the left padding of the wrapped line // keep the left padding of the wrapped line
content := LineAlign(strings.TrimRight(seg, " "), lineWidth-padLen, align) content := LineAlign(strings.TrimRight(seg, " "), lineWidth-padLen, wrapOpts.align)
lines = append(lines, padStr+content) lines = append(lines, padStr+content)
} else { } else {
content := LineAlign(strings.TrimSpace(seg), lineWidth-padLen, align) content := LineAlign(strings.TrimSpace(seg), lineWidth-padLen, wrapOpts.align)
lines = append(lines, padStr+content) lines = append(lines, padStr+content)
} }
nbLine++ nbLine++
@ -115,6 +152,42 @@ func WrapWithPadIndentAlign(text string, lineWidth int, indent string, pad strin
return strings.Join(lines, "\n"), nbLine return strings.Join(lines, "\n"), nbLine
} }
// WrapLeftPadded wrap a text for a given line size with a left padding.
// Handle properly terminal color escape code
func WrapLeftPadded(text string, lineWidth int, leftPad int) (string, int) {
return Wrap(text, lineWidth, WrapPadded(leftPad))
}
// WrapWithPad wrap a text for a given line size with a custom left padding
// Handle properly terminal color escape code
func WrapWithPad(text string, lineWidth int, pad string) (string, int) {
return Wrap(text, lineWidth, WrapPad(pad))
}
// WrapWithPad wrap a text for a given line size with a custom left padding
// This function also align the result depending on the requested alignment.
// Handle properly terminal color escape code
func WrapWithPadAlign(text string, lineWidth int, pad string, align Alignment) (string, int) {
return Wrap(text, lineWidth, WrapPad(pad), WrapAlign(align))
}
// WrapWithPadIndent wrap a text for a given line size with a custom left padding
// and a first line indent. The padding is not effective on the first line, indent
// is used instead, which allow to implement indents and outdents.
// Handle properly terminal color escape code
func WrapWithPadIndent(text string, lineWidth int, indent string, pad string) (string, int) {
return Wrap(text, lineWidth, WrapIndent(indent), WrapPad(pad))
}
// WrapWithPadIndentAlign wrap a text for a given line size with a custom left padding
// and a first line indent. The padding is not effective on the first line, indent
// is used instead, which allow to implement indents and outdents.
// This function also align the result depending on the requested alignment.
// Handle properly terminal color escape code
func WrapWithPadIndentAlign(text string, lineWidth int, indent string, pad string, align Alignment) (string, int) {
return Wrap(text, lineWidth, WrapIndent(indent), WrapPad(pad), WrapAlign(align))
}
// Break a line into several lines so that each line consumes at most // Break a line into several lines so that each line consumes at most
// 'textWidth' cells. Lines break at groups of white spaces and multibyte // 'textWidth' cells. Lines break at groups of white spaces and multibyte
// chars. Nothing is removed from the original text so that it behaves like a // chars. Nothing is removed from the original text so that it behaves like a