Skip to content

Commit

Permalink
📝 bytestrings: add iterator over non-empty lines
Browse files Browse the repository at this point in the history
  • Loading branch information
database64128 committed Aug 23, 2024
1 parent 635191a commit d3da757
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 25 deletions.
30 changes: 30 additions & 0 deletions bytestrings/bytestrings.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package bytestrings

import (
"iter"
"strings"
"unsafe"
)

// NextNonEmptyLine returns the next non-empty line and the remaining text.
// The line has its line feed ('\n') and carriage return ('\r') characters removed.
func NextNonEmptyLine[T ~[]byte | ~string](text T) (T, T) {
for {
lfIndex := strings.IndexByte(*(*string)(unsafe.Pointer(&text)), '\n')
Expand All @@ -26,3 +28,31 @@ func NextNonEmptyLine[T ~[]byte | ~string](text T) (T, T) {
return line, text
}
}

// NonEmptyLines returns an iterator over the non-empty lines in the text,
// with line feed ('\n') and carriage return ('\r') characters removed.
func NonEmptyLines[T ~[]byte | ~string](text T) iter.Seq[T] {
return func(yield func(T) bool) {
for lfIndex := 0; len(text) > 0; text = text[lfIndex+1:] {
lfIndex = strings.IndexByte(*(*string)(unsafe.Pointer(&text)), '\n')
switch lfIndex {
case -1:
_ = yield(text)
return
case 0:
continue
}

line := text[:lfIndex]
if line[len(line)-1] == '\r' {
line = line[:len(line)-1]
}
if len(line) == 0 {
continue
}
if !yield(line) {
return
}
}
}
}
13 changes: 12 additions & 1 deletion bytestrings/bytestrings_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package bytestrings

import "testing"
import (
"slices"
"testing"
)

const multiline = "\n1\r\n2\n\n3\r\n\r\n4"

Expand Down Expand Up @@ -37,3 +40,11 @@ func TestNextNonEmptyLine(t *testing.T) {
t.Fatalf("Expected text '%s', got '%s'", multiline[13:], text)
}
}

func TestNonEmptyLines(t *testing.T) {
expectedLines := []string{"1", "2", "3", "4"}
lines := slices.AppendSeq(make([]string, 0, len(expectedLines)), NonEmptyLines(multiline))
if !slices.Equal(lines, expectedLines) {
t.Errorf("Expected lines %v, got %v", expectedLines, lines)
}
}
9 changes: 1 addition & 8 deletions cmd/shadowsocks-go-domain-set-converter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,7 @@ func DomainSetBuilderFromDlc(text string) (domainset.Builder, error) {
domainset.NewRegexpMatcherBuilder(0),
}

var line string

for {
line, text = bytestrings.NextNonEmptyLine(text)
if len(line) == 0 {
break
}

for line := range bytestrings.NonEmptyLines(text) {
if line[0] == '#' {
continue
}
Expand Down
16 changes: 10 additions & 6 deletions domainset/domainset.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"iter"
"strconv"
"strings"

Expand Down Expand Up @@ -206,8 +207,11 @@ func BuilderFromTextFunc(
newKeywordMatcherBuilderFunc,
newRegexpMatcherBuilderFunc func(int) MatcherBuilder,
) (Builder, error) {
line, text := bytestrings.NextNonEmptyLine(text)
if len(line) == 0 {
next, stop := iter.Pull(bytestrings.NonEmptyLines(text))
defer stop()

line, ok := next()
if !ok {
return Builder{}, errEmptySet
}

Expand All @@ -216,8 +220,8 @@ func BuilderFromTextFunc(
return Builder{}, err
}
if found {
line, text = bytestrings.NextNonEmptyLine(text)
if len(line) == 0 {
line, ok = next()
if !ok {
return Builder{}, errEmptySet
}
}
Expand Down Expand Up @@ -255,8 +259,8 @@ func BuilderFromTextFunc(
}

next:
line, text = bytestrings.NextNonEmptyLine(text)
if len(line) == 0 {
line, ok = next()
if !ok {
break
}
}
Expand Down
12 changes: 2 additions & 10 deletions prefixset/prefixset.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,9 @@ func (psc Config) IPSet() (*netipx.IPSet, error) {

// IPSetFromText parses prefixes from the text and builds a prefix set.
func IPSetFromText(text string) (*netipx.IPSet, error) {
var (
line string
sb netipx.IPSetBuilder
)

for {
line, text = bytestrings.NextNonEmptyLine(text)
if len(line) == 0 {
break
}
var sb netipx.IPSetBuilder

for line := range bytestrings.NonEmptyLines(text) {
if line[0] == '#' {
continue
}
Expand Down

0 comments on commit d3da757

Please sign in to comment.