mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
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
This commit is contained in:
parent
e8ca8602c0
commit
be6696c34b
4 changed files with 163 additions and 10 deletions
|
@ -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") != ""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue