Commit f5c5444ee8cddb12613563d29645210531ce7f95

Parents: e033ad6679dcdeee561f55ce9d253e21d8c3f29e

From: Moritz Poldrack <git@moritz.sh>
Date: Mon Dec 6 16:07:24 2021 +0700

improved performance of strip by 1900%

		

Stats

demo/main.go +1/-3
strip.go +56/-3
strip_test.go +36/-0

Changeset

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
diff --git a/demo/main.go b/demo/main.go
index b38a1d3429ef26923136257f7a683aeefd0557d3..9e660c0f68ccdda952f5b0e40a20b50f46146129 100644
--- a/demo/main.go
+++ b/demo/main.go
@@ -8,7 +8,6 @@ 	"git.sr.ht/~poldi1405/go-ansi"
 )
 
 func main() {
-	goto link
 	fmt.Println("Styles:", "normal", ansi.Bold("bold"), ansi.Faint("faint"), ansi.Italic("italic"), ansi.Underscore("underscore"), ansi.DoubleUnderscore("double underscore"), ansi.Blink("blinking"), ansi.FastBlink("blinking fast"), ansi.ReverseVideo("FG and BG exchanged"), "["+ansi.Conceal("concealed")+"]", ansi.Strikethrough("strikethrough"))
 	fmt.Println("Colors:", "default", ansi.Black("black"), ansi.Red("red"), ansi.Green("green"), ansi.Yellow("yellow"), ansi.Blue("blue"), ansi.Magenta("magenta"), ansi.Cyan("cyan"), ansi.White("white"), ansi.Color256(25, "256color"), ansi.ColorTrue(172, 138, 39, "TrueColor"))
 	fmt.Println("Colors (BG):", "default", ansi.BlackBG("black"), ansi.RedBG("red"), ansi.GreenBG("green"), ansi.YellowBG("yellow"), ansi.BlueBG("blue"), ansi.MagentaBG("magenta"), ansi.CyanBG("cyan"), ansi.WhiteBG("white"), ansi.Color256BG(25, "256color"), ansi.ColorTrueBG(172, 138, 39, "TrueColor"))
@@ -57,7 +56,6 @@ 	time.Sleep(500 * time.Millisecond)
 	fmt.Print(ansi.MoveCursor(25, 60), ansi.ReverseVideo("so… where were we?"))
 	time.Sleep(500 * time.Millisecond)
 	fmt.Print(ansi.RestorePos(), ansi.ReverseVideo("We were here"))
-link:
 	fmt.Println(ansi.LinkString("http://example.com", "an example"))
-	fmt.Println(ansi.LinkFile("test.txt", "a file example"))
+	fmt.Println(ansi.LinkFile("test.png", "a file example"))
 }
diff --git a/strip.go b/strip.go
index fe325cb4882e04bb9dacfdd8020c11352492d770..b89c38c8536cf92c0ca9eebd53f3e57601946360 100644
--- a/strip.go
+++ b/strip.go
@@ -1,11 +1,64 @@
 package ansi
 
-import "regexp"
+import (
+	"bytes"
+	"fmt"
+)
 
 // StripString removes all ANSI-Escape sequences from the given string and
 // returns the cleaned version
 func StripString(str string) string {
-	return DetectionPattern.ReplaceAllString(str, "")
+	bts := []byte(str)
+	bts = stripStandard(bts)
+	bts = stripLink(bts)
+
+	return string(bts)
 }
 
-var DetectionPattern = regexp.MustCompile(`(?m)((\x1b\[[;\d]*[A-Za-z])*)`)
+func stripLink(bts []byte) []byte {
+	for { // stop if the last run did not match anything
+		index := bytes.Index(bts, []byte{0x1b, '\\'})
+		if index == -1 {
+			break
+		}
+		removeUntil := bytes.Index(bts[index+1:], []byte{0x1b})
+		if removeUntil == -1 {
+			break
+		}
+
+		bts = append(bts[:index], bts[index+removeUntil+1:]...)
+	}
+	bts = bytes.ReplaceAll(bts, []byte{0x1b, ']', '8', ';', ';', 0x1b, '\\'}, []byte{})
+	bts = bytes.ReplaceAll(bts, []byte{0x1b, ']', '8', ';', ';'}, []byte{})
+	return bts
+}
+
+func stripStandard(bts []byte) []byte {
+	matched := true
+	for matched { // stop if the last run did not match anything
+		index := bytes.Index(bts, []byte{0x1b, '['})
+		if index == -1 {
+			break
+		}
+		removeUntil := index + 2
+		for i := removeUntil; i < len(bts); i++ {
+			if (bts[i] >= 0x30 && bts[i] <= 0x39) || bts[i] == 0x3b {
+				removeUntil++
+				continue
+			}
+			if (bts[i] >= 0x41 && bts[i] <= 0x5a) || (bts[i] >= 0x61 && bts[i] <= 0x7a) {
+				removeUntil++
+				break
+			}
+			// we shouldn't be here in valid codes. anyway. stop
+			// what we're doing before be break something
+			fmt.Print("oppsie! ")
+			fmt.Println(bts[index:removeUntil])
+			matched = false
+			break
+		}
+
+		bts = append(bts[:index], bts[removeUntil:]...)
+	}
+	return bts
+}
diff --git a/strip_test.go b/strip_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..abd7e16a10ed927f0baf9df7bc1f2abae400b6de
--- /dev/null
+++ b/strip_test.go
@@ -0,0 +1,36 @@
+package ansi
+
+import (
+	"fmt"
+	"regexp"
+	"testing"
+)
+
+func BenchmarkStripping(b *testing.B) {
+	DetectionPattern := regexp.MustCompile(`(?m)(((\x1b\[[;\d]*[A-Za-z])|\x1b]8;;|\x1b\\.*?\x1b|]8;;\x1b\\)*)`)
+	str := fmt.Sprint(Black("black text"), "some kept text", ReverseVideo("inverted text"), Green("green text"), Bold("bold text"))
+
+	b.Run("regex", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			DetectionPattern.ReplaceAllString(str, "")
+		}
+	})
+
+	b.Run("split", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			StripString(str)
+		}
+	})
+}
+
+func TestStrip(t *testing.T) {
+	str := fmt.Sprint(Black("black text"), "some kept text", ReverseVideo("inverted text"), Green("green text"), Bold("bold text"))
+	if StripString(str) != "black textsome kept textinverted textgreen textbold text" {
+		t.Errorf("strip not successful!\ngot:      %s\n%v\nexpected: black textsome kept textinverted textgreen textbold text\n%v", StripString(str), []byte(StripString(str)), []byte("black textsome kept textinverted textgreen textbold text"))
+	}
+
+	str = fmt.Sprintf("Link to my homepage: %s", LinkString("https://moritz.sh", "Linktext"))
+	if StripString(str) != "Link to my homepage: https://moritz.sh" {
+		t.Errorf("strip not successful!\ngot:      %s\n%v\nexpected: Link to my homepage: https://moritz.sh\n%v", StripString(str), []byte(StripString(str)), []byte("Link to my homepage: https://moritz.sh"))
+	}
+}