Skip to content

Commit

Permalink
Merge pull request #47 from rhysd/string-builder
Browse files Browse the repository at this point in the history
Optimize memory allocations on building patterns with `strings.Builder`
  • Loading branch information
mattn authored Sep 1, 2024
2 parents 92ed4e5 + 9005bdd commit df0b60a
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 67 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# go-zglob

[![Build Status](https://travis-ci.org/mattn/go-zglob.svg)](https://travis-ci.org/mattn/go-zglob)
[![Build Status](https://github.com/mattn/go-zglob/actions/workflows/go.yml/badge.svg)](https://github.com/mattn/go-zglob/actions/workflows/go.yml)

zglob

Expand Down
85 changes: 46 additions & 39 deletions zglob.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ var (
)

type zenv struct {
dirmask string
fre *regexp.Regexp
braceDir bool
pattern string
root string
dirmask string
fre *regexp.Regexp
pattern string
root string
}

func toSlash(path string) string {
Expand All @@ -51,7 +50,7 @@ func New(pattern string) (*zenv, error) {
globmask := ""
root := ""
for n, i := range strings.Split(toSlash(pattern), "/") {
if root == "" && (strings.Index(i, "*") != -1 || strings.Index(i, "{") != -1) {
if root == "" && strings.ContainsAny(i, "*{") {
if globmask == "" {
root = "."
} else {
Expand Down Expand Up @@ -92,111 +91,119 @@ func New(pattern string) (*zenv, error) {
globmask = toSlash(path.Clean(globmask))

cc := []rune(globmask)
dirmask := ""
filemask := ""
var dirmask strings.Builder
var filemask strings.Builder
staticDir := true
for i := 0; i < len(cc); i++ {
if i < len(cc)-2 && cc[i] == '\\' {
i++
filemask += fmt.Sprintf("[\\x%02X]", cc[i])
fmt.Fprintf(&filemask, "[\\x%02X]", cc[i])
if staticDir {
dirmask += string(cc[i])
dirmask.WriteRune(cc[i])
}
} else if cc[i] == '*' {
staticDir = false
if i < len(cc)-2 && cc[i+1] == '*' && cc[i+2] == '/' {
filemask += "(.*/)?"
filemask.WriteString("(.*/)?")
i += 2
} else {
filemask += "[^/]*"
filemask.WriteString("[^/]*")
}
} else if cc[i] == '[' { // range
staticDir = false
pattern := ""
var b strings.Builder
for j := i + 1; j < len(cc); j++ {
if cc[j] == ']' {
i = j
break
} else {
pattern += string(cc[j])
b.WriteRune(cc[j])
}
}
if pattern != "" {
filemask += "[" + pattern + "]"
if pattern := b.String(); pattern != "" {
filemask.WriteByte('[')
filemask.WriteString(pattern)
filemask.WriteByte(']')
continue
}
} else {
if cc[i] == '{' {
staticDir = false
pattern := ""
var b strings.Builder
for j := i + 1; j < len(cc); j++ {
if cc[j] == ',' {
pattern += "|"
b.WriteByte('|')
} else if cc[j] == '}' {
i = j
break
} else {
c := cc[j]
if c == '/' {
pattern += string(c)
b.WriteRune(c)
} else if ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 255 < c {
pattern += string(c)
b.WriteRune(c)
} else {
pattern += fmt.Sprintf("[\\x%02X]", c)
fmt.Fprintf(&b, "[\\x%02X]", c)
}
}
}
if pattern != "" {
filemask += "(" + pattern + ")"
if pattern := b.String(); pattern != "" {
filemask.WriteByte('(')
filemask.WriteString(pattern)
filemask.WriteByte(')')
continue
}
} else if i < len(cc)-1 && cc[i] == '!' && cc[i+1] == '(' {
i++
pattern := ""
var b strings.Builder
for j := i + 1; j < len(cc); j++ {
if cc[j] == ')' {
i = j
break
} else {
c := cc[j]
pattern += fmt.Sprintf("[^\\x%02X/]*", c)
fmt.Fprintf(&b, "[^\\x%02X/]*", c)
}
}
if pattern != "" {
if dirmask == "" {
dirmask = filemask
root = filemask
if pattern := b.String(); pattern != "" {
if dirmask.Len() == 0 {
m := filemask.String()
dirmask.WriteString(m)
root = m
}
filemask += pattern
filemask.WriteString(pattern)
continue
}
}
c := cc[i]
if c == '/' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 255 < c {
filemask += string(c)
filemask.WriteRune(c)
} else {
filemask += fmt.Sprintf("[\\x%02X]", c)
fmt.Fprintf(&filemask, "[\\x%02X]", c)
}
if staticDir {
dirmask += string(c)
dirmask.WriteRune(c)
}
}
}
if len(filemask) > 0 && filemask[len(filemask)-1] == '/' {
if m := filemask.String(); len(m) > 0 && m[len(m)-1] == '/' {
if root == "" {
root = filemask
root = m
}
filemask += "[^/]*"
filemask.WriteString("[^/]*")
}
var pat string
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
filemask = "(?i:" + filemask + ")"
pat = "^(?i:" + filemask.String() + ")$"
} else {
pat = "^" + filemask.String() + "$"
}
fre, err := regexp.Compile("^" + filemask + "$")
fre, err := regexp.Compile(pat)
if err != nil {
return nil, err
}
return &zenv{
dirmask: path.Dir(dirmask) + "/",
dirmask: path.Dir(dirmask.String()) + "/",
fre: fre,
pattern: pattern,
root: filepath.Clean(root),
Expand Down
59 changes: 32 additions & 27 deletions zglob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,9 @@ func fatalIf(err error) {
}
}

func setup(t *testing.T) string {
func setup() (string, string) {
tmpdir, err := ioutil.TempDir("", "zglob")
if err != nil {
t.Fatal(err)
}
fatalIf(err)

fatalIf(os.MkdirAll(filepath.Join(tmpdir, "foo/baz"), 0755))
fatalIf(os.MkdirAll(filepath.Join(tmpdir, "foo/bar"), 0755))
Expand All @@ -78,22 +76,17 @@ func setup(t *testing.T) string {
fatalIf(os.MkdirAll(filepath.Join(tmpdir, "zzz/nar/{noo,x}"), 0755))
fatalIf(ioutil.WriteFile(filepath.Join(tmpdir, "zzz/nar/{noo,x}/joo.png"), []byte{}, 0644))

return tmpdir
curdir, err := os.Getwd()
fatalIf(err)
fatalIf(os.Chdir(tmpdir))

return tmpdir, curdir
}

func TestGlob(t *testing.T) {
tmpdir := setup(t)
tmpdir, savedCwd := setup()
defer os.RemoveAll(tmpdir)

curdir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Chdir(tmpdir)
if err != nil {
t.Fatal(err)
}
defer os.Chdir(curdir)
defer os.Chdir(savedCwd)

tmpdir = "."
for _, test := range testGlobs {
Expand All @@ -115,18 +108,9 @@ func TestGlob(t *testing.T) {
}

func TestGlobAbs(t *testing.T) {
tmpdir := setup(t)
tmpdir, savedCwd := setup()
defer os.RemoveAll(tmpdir)

curdir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Chdir(tmpdir)
if err != nil {
t.Fatal(err)
}
defer os.Chdir(curdir)
defer os.Chdir(savedCwd)

for _, test := range testGlobs {
pattern := toSlash(path.Join(tmpdir, test.pattern))
Expand Down Expand Up @@ -228,3 +212,24 @@ func TestGlobError(t *testing.T) {
t.Errorf(`zglob failed: expected %v but got %v`, nil, got)
}
}

func BenchmarkGlob(b *testing.B) {
tmpdir, savedCwd := setup()
defer os.RemoveAll(tmpdir)
defer os.Chdir(savedCwd)

for i := 0; i < b.N; i++ {
for _, test := range testGlobs {
if test.err != "" {
continue
}
got, err := Glob(test.pattern)
if err != nil {
b.Fatal(err)
}
if len(got) != len(test.expected) {
b.Fatalf(`zglob failed: pattern %q: expected %v but got %v`, test.pattern, test.expected, got)
}
}
}
}

0 comments on commit df0b60a

Please sign in to comment.