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) diff --git a/tpl/template.go b/tpl/template.go index 171d61825..2db7c4f79 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -1349,6 +1349,8 @@ func isBackupFile(path string) bool { const baseAceFilename = "baseof.ace" +var aceTemplateInnerMarker = []byte("= content") + func isBaseTemplate(path string) bool { return strings.HasSuffix(path, baseAceFilename) } @@ -1391,14 +1393,22 @@ func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) { // ACE templates may have both a base and inner template. if filepath.Ext(path) == ".ace" && !strings.HasSuffix(filepath.Dir(path), "partials") { - // Look for the base first in the current path, then in _default. - p := filepath.Join(filepath.Dir(path), baseAceFilename) - if ok, err := helpers.Exists(p, hugofs.OsFs); err == nil && ok { - baseTemplatePath = p - } else { - p := filepath.Join(absPath, "_default", baseAceFilename) + // This may be a view that shouldn't have base template + // Have to look inside it to make sure + needsBase, err := helpers.FileContains(path, aceTemplateInnerMarker, hugofs.OsFs) + if err != nil { + return err + } + if needsBase { + // Look for the base first in the current path, then in _default. + p := filepath.Join(filepath.Dir(path), baseAceFilename) if ok, err := helpers.Exists(p, hugofs.OsFs); err == nil && ok { baseTemplatePath = p + } else { + p := filepath.Join(absPath, "_default", baseAceFilename) + if ok, err := helpers.Exists(p, hugofs.OsFs); err == nil && ok { + baseTemplatePath = p + } } } }