diff options
author | bep <bjorn.erik.pedersen@gmail.com> | 2015-03-29 20:12:13 +0100 |
---|---|---|
committer | bep <bjorn.erik.pedersen@gmail.com> | 2015-03-29 21:12:13 +0200 |
commit | be6696c34b1ac262eccb90ab8785128cc4065444 (patch) | |
tree | 928628e2eb6f72f38313193802aeac1359d0c19f /helpers | |
parent | e8ca8602c003862fe3da87306c2b2c27918b29cc (diff) |
Handle views in combo with Ace base templates
As views looks like a regular template, but doesn't need a base template, we have to look inside it.
Altough really not needed by this commit, reading the full file content into memory just to do a substring search is a waste.
So this commit implements a `ReaderContains` func that in most cases should be much faster than doing an `ioutil.ReadAll` and `bytes.Contains`:
```
benchmark old ns/op new ns/op delta
BenchmarkReaderContains 78452 20260 -74.18%
benchmark old allocs new allocs delta
BenchmarkReaderContains 46 20 -56.52%
benchmark old bytes new bytes delta
BenchmarkReaderContains 46496 1258 -97.29%
```
Fixes #999
Diffstat (limited to 'helpers')
-rw-r--r-- | helpers/general.go | 36 | ||||
-rw-r--r-- | helpers/general_test.go | 97 | ||||
-rw-r--r-- | helpers/path.go | 18 |
3 files changed, 147 insertions, 4 deletions
diff --git a/helpers/general.go b/helpers/general.go index 3e0a85fbc..baf957d75 100644 --- a/helpers/general.go +++ b/helpers/general.go @@ -112,6 +112,42 @@ func BytesToReader(in []byte) io.Reader { return bytes.NewReader(in) } +// ReaderContains reports whether subslice is within r. +func ReaderContains(r io.Reader, subslice []byte) bool { + + if len(subslice) == 0 { + return false + } + + bufflen := len(subslice) * 4 + halflen := bufflen / 2 + buff := make([]byte, bufflen) + var err error + var n, i int + + for { + i++ + if i == 1 { + n, err = io.ReadAtLeast(r, buff[:halflen], halflen) + } else { + if i != 2 { + // shift left to catch overlapping matches + copy(buff[:], buff[halflen:]) + } + n, err = io.ReadAtLeast(r, buff[halflen:], halflen) + } + + if n > 0 && bytes.Contains(buff, subslice) { + return true + } + + if err != nil { + break + } + } + return false +} + // ThemeSet checks whether a theme is in use or not. func ThemeSet() bool { return viper.GetString("theme") != "" diff --git a/helpers/general_test.go b/helpers/general_test.go index b5706a445..496439db1 100644 --- a/helpers/general_test.go +++ b/helpers/general_test.go @@ -1,7 +1,9 @@ package helpers import ( + "bytes" "github.com/stretchr/testify/assert" + "io/ioutil" "reflect" "strings" "testing" @@ -44,6 +46,101 @@ func TestStringToReader(t *testing.T) { assert.Equal(t, asString, ReaderToString(asReader)) } +var containsTestText = (`На берегу пустынных волн +Стоял он, дум великих полн, +И вдаль глядел. Пред ним широко +Река неслася; бедный чёлн +По ней стремился одиноко. +По мшистым, топким берегам +Чернели избы здесь и там, +Приют убогого чухонца; +И лес, неведомый лучам +В тумане спрятанного солнца, +Кругом шумел. + +Τη γλώσσα μου έδωσαν ελληνική +το σπίτι φτωχικό στις αμμουδιές του Ομήρου. +Μονάχη έγνοια η γλώσσα μου στις αμμουδιές του Ομήρου. + +από το Άξιον Εστί +του Οδυσσέα Ελύτη + +Sîne klâwen durh die wolken sint geslagen, +er stîget ûf mit grôzer kraft, +ich sih in grâwen tägelîch als er wil tagen, +den tac, der im geselleschaft +erwenden wil, dem werden man, +den ich mit sorgen în verliez. +ich bringe in hinnen, ob ich kan. +sîn vil manegiu tugent michz leisten hiez. +`) + +var containsBenchTestData = []struct { + v1 string + v2 []byte + expect bool +}{ + {"abc", []byte("a"), true}, + {"abc", []byte("b"), true}, + {"abcdefg", []byte("efg"), true}, + {"abc", []byte("d"), false}, + {containsTestText, []byte("стремился"), true}, + {containsTestText, []byte(containsTestText[10:80]), true}, + {containsTestText, []byte(containsTestText[100:110]), true}, + {containsTestText, []byte(containsTestText[len(containsTestText)-100 : len(containsTestText)-10]), true}, + {containsTestText, []byte(containsTestText[len(containsTestText)-20:]), true}, + {containsTestText, []byte("notfound"), false}, +} + +// some corner cases +var containsAdditionalTestData = []struct { + v1 string + v2 []byte + expect bool +}{ + {"", []byte("a"), false}, + {"a", []byte(""), false}, + {"", []byte(""), false}, +} + +func TestReaderContains(t *testing.T) { + for i, this := range append(containsBenchTestData, containsAdditionalTestData...) { + result := ReaderContains(StringToReader(this.v1), this.v2) + if result != this.expect { + t.Errorf("[%d] Got %t but expected %t", i, result, this.expect) + } + } +} + +func BenchmarkReaderContains(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + for i, this := range containsBenchTestData { + result := ReaderContains(StringToReader(this.v1), this.v2) + if result != this.expect { + b.Errorf("[%d] Got %t but expected %t", i, result, this.expect) + } + } + } +} + +// kept to compare the above +func _BenchmarkReaderContains(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + for i, this := range containsBenchTestData { + bs, err := ioutil.ReadAll(StringToReader(this.v1)) + if err != nil { + b.Fatalf("Failed %s", err) + } + result := bytes.Contains(bs, this.v2) + if result != this.expect { + b.Errorf("[%d] Got %t but expected %t", i, result, this.expect) + } + } + } +} + func TestFindAvailablePort(t *testing.T) { addr, err := FindAvailablePort() assert.Nil(t, err) diff --git a/helpers/path.go b/helpers/path.go index 91419e0c9..546f23d36 100644 --- a/helpers/path.go +++ b/helpers/path.go @@ -16,16 +16,15 @@ package helpers import ( "errors" "fmt" + "github.com/spf13/afero" + jww "github.com/spf13/jwalterweatherman" + "github.com/spf13/viper" "io" "os" "path/filepath" "regexp" "strings" "unicode" - - "github.com/spf13/afero" - jww "github.com/spf13/jwalterweatherman" - "github.com/spf13/viper" ) // FilepathPathBridge is a bridge for common functionality in filepath vs path @@ -153,6 +152,17 @@ func IsEmpty(path string, fs afero.Fs) (bool, error) { return fi.Size() == 0, nil } +// Check if a file contains a specified string. +func FileContains(filename string, subslice []byte, fs afero.Fs) (bool, error) { + f, err := os.Open(filename) + if err != nil { + return false, err + } + defer f.Close() + + return ReaderContains(f, subslice), nil +} + // Check if a file or directory exists. func Exists(path string, fs afero.Fs) (bool, error) { _, err := fs.Stat(path) |