URI: 
       Handle views in combo with Ace base templates - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit be6696c34b1ac262eccb90ab8785128cc4065444
   DIR parent e8ca8602c003862fe3da87306c2b2c27918b29cc
  HTML Author: bep <bjorn.erik.pedersen@gmail.com>
       Date:   Sun, 29 Mar 2015 20:12:13 +0100
       
       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:
         M helpers/general.go                  |      36 +++++++++++++++++++++++++++++++
         M helpers/general_test.go             |      97 ++++++++++++++++++++++++++++++
         M helpers/path.go                     |      18 ++++++++++++++----
         M tpl/template.go                     |      22 ++++++++++++++++------
       
       4 files changed, 163 insertions(+), 10 deletions(-)
       ---
   DIR diff --git 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") != ""
   DIR diff --git 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)
   DIR diff --git 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)
   DIR diff --git 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
       +                                                }
                                                }
                                        }
                                }