Skip to content

Commit

Permalink
Add ParseStream function with new parse code
Browse files Browse the repository at this point in the history
The `ParseString` function code will be starting to be hard to be
maintained and possibly also very inefficient when trying to add more
features like supporting `\r` in the input content.

Introduce newly written parse function, which reads from an input reader
and interprets sequences while reading the content.
  • Loading branch information
HeavyWombat committed Dec 10, 2020
1 parent 32c3695 commit d3cadb2
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 12 deletions.
12 changes: 0 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/gonvenience/term v1.0.0 h1:joCB/j0Ngmdakd3muuLgAGPMf7DNKdoe708c1I6RiBs=
github.com/gonvenience/term v1.0.0/go.mod h1:wohD4Iqso9Eol7qc2VnNhSFFhZxok5PvO7pZhdrAn4E=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
Expand All @@ -29,25 +27,19 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
Expand All @@ -60,12 +52,10 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
Expand All @@ -77,9 +67,7 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
Expand Down
185 changes: 185 additions & 0 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
package bunt

import (
"bufio"
"bytes"
"fmt"
"io"
"regexp"
"sort"
"strconv"
Expand All @@ -48,6 +50,189 @@ func ProcessTextAnnotations() ParseOption {
}
}

// ParseStream reads from the input reader and parses all supported ANSI
// sequences that are relevant for colored strings.
//
// Please notes: This function is still under development and should replace
// the `ParseString` function code eventually.
func ParseStream(in io.Reader, opts ...ParseOption) (*String, error) {
var input *bufio.Reader
switch typed := in.(type) {
case *bufio.Reader:
input = typed

default:
input = bufio.NewReader(in)
}

type seq struct {
values string
suffix rune
}

var readSGR = func() seq {
var buf bytes.Buffer
for {
r, _, err := input.ReadRune()
if err == io.EOF {
break
}

switch r {
case 'h', 'l', 'm', 'r', 'A', 'B', 'C', 'D', 'H', 'f', 'g', 'K', 'J', 'y', 'q':
return seq{
values: buf.String(),
suffix: r,
}

default:
buf.WriteRune(r)
}
}

panic("failed to parse ANSI sequence")
}

var skipUntil = func(end rune) {
for {
r, _, err := input.ReadRune()
if err == io.EOF {
panic("reached end of file before reaching end identifier")
}

if r == end {
return
}
}
}

var result String
var line String
var lineIdx uint
var lineCap uint
var settings uint64

var readANSISeq = func() {
r, _, err := input.ReadRune()
if err == io.EOF {
return
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences
switch r {
case '[':
var seq = readSGR()
switch seq.suffix {
case 'm': // colors
settings, err = parseSelectGraphicRenditionEscapeSequence(seq.values)
if err != nil {
panic(err)
}

case 'K': // clear line
var start, end uint
switch seq.values {
case "", "0":
start, end = lineIdx, lineCap

case "1":
start, end = 0, lineIdx

case "2":
start, end = 0, lineCap
}

for i := start; i < end; i++ {
line[i].Symbol = ' '
line[i].Settings = 0
}

default:
// ignoring all other sequences
}

case ']':
skipUntil('\a')
}
}

var add = func(r rune) {
var cr = ColoredRune{Symbol: r, Settings: settings}

if lineIdx < lineCap {
line[lineIdx] = cr
lineIdx++

} else {
line = append(line, cr)
lineIdx++
lineCap++
}
}

var del = func() {
line = line[:len(line)-1]
lineIdx--
lineCap--
}

var flush = func() {
// Prepare to remove trailing spaces by finding the last non-space rune
var endIdx = len(line) - 1
for ; endIdx >= 0; endIdx-- {
if line[endIdx].Symbol != ' ' {
break
}
}

result = append(result, line[:endIdx+1]...)

line = String{}
lineIdx = 0
lineCap = 0
}

var newline = func() {
flush()
result = append(result, ColoredRune{Symbol: '\n'})
}

for {
r, _, err := input.ReadRune()
if err != nil && err == io.EOF {
break
}

switch r {
case '\x1b':
readANSISeq()

case '\r':
lineIdx = 0

case '\n':
newline()

case '\b':
del()

default:
add(r)
}
}

flush()

// Process optional parser options
for _, opt := range opts {
if err := opt(&result); err != nil {
return nil, err
}
}

return &result, nil
}

// ParseString parses a string that can contain both ANSI escape code Select
// Graphic Rendition (SGR) codes and Markdown style text annotations, for
// example *bold* or _italic_.
Expand Down
12 changes: 12 additions & 0 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
package bunt_test

import (
"strings"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

Expand All @@ -38,6 +40,16 @@ var _ = Describe("parse input string", func() {
TrueColorSetting = AUTO
})

Context("parse supported ANSI sequences from an input reader", func() {
It("should parse an example command line input without errors", func() {
example := "Hallo\rHello, \x1b[1mWorld\x1b[0m!"

result, err := ParseStream(strings.NewReader(example))
Expect(err).ToNot(HaveOccurred())
Expect(result.String()).To(Equal("Hello, \x1b[1mWorld\x1b[0m!"))
})
})

Context("parse Select Graphic Rendition (SGR) based input", func() {
It("should parse an input string with SGR parameters", func() {
input := "Example: \x1b[1mbold\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[4munderline\x1b[0m, \x1b[38;2;133;247;7mforeground\x1b[0m, and \x1b[48;2;133;247;7mbackground\x1b[0m."
Expand Down

0 comments on commit d3cadb2

Please sign in to comment.