summaryrefslogtreecommitdiffstats
path: root/hugolib
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-03-03 10:47:43 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-03-04 23:33:35 +0100
commite52e2a70e5e0a2d15fc9befbcd7290761c98589e (patch)
treece133a021fcd7535ec2c89ea7897ff543b710a4a /hugolib
parentea165bf9e71c7ca9ddb9f14ddbdbcd506ce554bb (diff)
hugolib, target: Rework/move the target package
This relates to #3123. The interfaces and types in `target` made sense at some point, but now this package is too restricted to a hardcoded set of media types. The overall current logic: * Create a file path based on some `Translator` with some hardcoded logic handling uglyURLs, hardcoded html suffix etc. * In in some cases (alias), a template is applied to create the alias file. * Then the content is written to destination. One could argue that it is the last bullet that is the actual core responsibility. This commit fixes that by moving the `hugolib`-related logic where it belong, and simplify the code, i.e. remove the abstractions. This code will most certainly evolve once we start on #3123, but now it is at least possible to understand where to start. Fixes #3123
Diffstat (limited to 'hugolib')
-rw-r--r--hugolib/alias.go69
-rw-r--r--hugolib/handler_file.go4
-rw-r--r--hugolib/hugo_sites_build.go2
-rw-r--r--hugolib/site.go164
-rw-r--r--hugolib/site_render.go6
-rw-r--r--hugolib/site_writer.go193
-rw-r--r--hugolib/site_writer_test.go146
-rw-r--r--hugolib/testhelpers_test.go3
8 files changed, 475 insertions, 112 deletions
diff --git a/hugolib/alias.go b/hugolib/alias.go
new file mode 100644
index 000000000..67c9dc785
--- /dev/null
+++ b/hugolib/alias.go
@@ -0,0 +1,69 @@
+// Copyright 2017 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugolib
+
+import (
+ "bytes"
+ "html/template"
+ "io"
+)
+
+const (
+ alias = "<!DOCTYPE html><html><head><title>{{ .Permalink }}</title><link rel=\"canonical\" href=\"{{ .Permalink }}\"/><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" /><meta http-equiv=\"refresh\" content=\"0; url={{ .Permalink }}\" /></head></html>"
+ aliasXHtml = "<!DOCTYPE html><html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>{{ .Permalink }}</title><link rel=\"canonical\" href=\"{{ .Permalink }}\"/><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" /><meta http-equiv=\"refresh\" content=\"0; url={{ .Permalink }}\" /></head></html>"
+)
+
+var defaultAliasTemplates *template.Template
+
+func init() {
+ defaultAliasTemplates = template.New("")
+ template.Must(defaultAliasTemplates.New("alias").Parse(alias))
+ template.Must(defaultAliasTemplates.New("alias-xhtml").Parse(aliasXHtml))
+}
+
+type aliasHandler struct {
+ Templates *template.Template
+}
+
+func newAliasHandler(t *template.Template) aliasHandler {
+ return aliasHandler{t}
+}
+
+func (a aliasHandler) renderAlias(isXHTML bool, permalink string, page *Page) (io.Reader, error) {
+ t := "alias"
+ if isXHTML {
+ t = "alias-xhtml"
+ }
+
+ template := defaultAliasTemplates
+ if a.Templates != nil {
+ template = a.Templates
+ t = "alias.html"
+ }
+
+ data := struct {
+ Permalink string
+ Page *Page
+ }{
+ permalink,
+ page,
+ }
+
+ buffer := new(bytes.Buffer)
+ err := template.ExecuteTemplate(buffer, t, data)
+ if err != nil {
+ return nil, err
+ }
+ return buffer, nil
+}
diff --git a/hugolib/handler_file.go b/hugolib/handler_file.go
index 71b895603..f5f65d7d4 100644
--- a/hugolib/handler_file.go
+++ b/hugolib/handler_file.go
@@ -39,7 +39,7 @@ type defaultHandler struct{ basicFileHandler }
func (h defaultHandler) Extensions() []string { return []string{"*"} }
func (h defaultHandler) FileConvert(f *source.File, s *Site) HandledResult {
- s.writeDestFile(f.Path(), f.Contents)
+ s.w.writeDestFile(f.Path(), f.Contents)
return HandledResult{file: f}
}
@@ -48,6 +48,6 @@ type cssHandler struct{ basicFileHandler }
func (h cssHandler) Extensions() []string { return []string{"css"} }
func (h cssHandler) FileConvert(f *source.File, s *Site) HandledResult {
x := cssmin.Minify(f.Bytes())
- s.writeDestFile(f.Path(), bytes.NewReader(x))
+ s.w.writeDestFile(f.Path(), bytes.NewReader(x))
return HandledResult{file: f}
}
diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
index 2e54cb7a8..471c1a65d 100644
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -194,6 +194,8 @@ func (h *HugoSites) assemble(config *BuildCfg) error {
func (h *HugoSites) render(config *BuildCfg) error {
if !config.SkipRender {
for _, s := range h.Sites {
+ s.initSiteWriter()
+
if err := s.render(); err != nil {
return err
}
diff --git a/hugolib/site.go b/hugolib/site.go
index e5b30a38d..1512819d5 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -39,7 +39,6 @@ import (
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/parser"
"github.com/spf13/hugo/source"
- "github.com/spf13/hugo/target"
"github.com/spf13/hugo/tpl"
"github.com/spf13/hugo/transform"
"github.com/spf13/nitro"
@@ -92,18 +91,20 @@ type Site struct {
// is set.
taxonomiesOrigKey map[string]string
- Source source.Input
- Sections Taxonomy
- Info SiteInfo
- Menus Menus
- timer *nitro.B
- targets targetList
- targetListInit sync.Once
- draftCount int
- futureCount int
- expiredCount int
- Data map[string]interface{}
- Language *helpers.Language
+ Source source.Input
+ Sections Taxonomy
+ Info SiteInfo
+ Menus Menus
+ timer *nitro.B
+
+ // This is not a pointer by design.
+ w siteWriter
+
+ draftCount int
+ futureCount int
+ expiredCount int
+ Data map[string]interface{}
+ Language *helpers.Language
disabledKinds map[string]bool
@@ -139,6 +140,7 @@ func newSite(cfg deps.DepsCfg) (*Site, error) {
s := &Site{PageCollections: c, Language: cfg.Language, disabledKinds: disabledKinds}
s.Info = newSiteInfo(siteBuilderCfg{s: s, pageCollections: c, language: s.Language})
+
return s, nil
}
@@ -210,14 +212,6 @@ func NewSiteForCfg(cfg deps.DepsCfg) (*Site, error) {
return s, nil
}
-type targetList struct {
- page target.Output
- pageUgly target.Output
- file target.Output
- alias target.AliasPublisher
- languageAlias target.AliasPublisher
-}
-
type SiteInfo struct {
// atomic requires 64-bit alignment for struct field access
// According to the docs, " The first word in a global variable or in an
@@ -1180,6 +1174,8 @@ func (s *Site) convertSource() chan error {
numWorkers := getGoMaxProcs() * 4
wg := &sync.WaitGroup{}
+ s.initSiteWriter()
+
for i := 0; i < numWorkers; i++ {
wg.Add(2)
go fileConverter(s, fileConvChan, results, wg)
@@ -1757,7 +1753,7 @@ func (s *Site) renderAndWriteXML(name string, dest string, d interface{}, layout
transformer := transform.NewChain(transform.AbsURLInXML)
transformer.Apply(outBuffer, renderBuffer, path)
- return s.writeDestFile(dest, outBuffer)
+ return s.w.writeDestFile(dest, outBuffer)
}
@@ -1774,14 +1770,13 @@ func (s *Site) renderAndWritePage(name string, dest string, d interface{}, layou
outBuffer := bp.GetBuffer()
defer bp.PutBuffer(outBuffer)
- var pageTarget target.Output
+ // Note: this is not a pointer, as we may mutate the state below.
+ w := s.w
if p, ok := d.(*Page); ok && p.IsPage() && path.Ext(p.URLPath.URL) != "" {
// user has explicitly set a URL with extension for this page
// make sure it sticks even if "ugly URLs" are turned off.
- pageTarget = s.pageUglyTarget()
- } else {
- pageTarget = s.pageTarget()
+ w.uglyURLs = true
}
transformLinks := transform.NewEmptyTransforms()
@@ -1804,7 +1799,7 @@ func (s *Site) renderAndWritePage(name string, dest string, d interface{}, layou
var path []byte
if s.Info.relativeURLs {
- translated, err := pageTarget.(target.OptionalTranslator).TranslateRelative(dest)
+ translated, err := w.baseTargetPathPage(dest)
if err != nil {
return err
}
@@ -1844,7 +1839,7 @@ Your rendered home page is blank: /index.html is zero-length
}
- if err = s.writeDestPage(dest, pageTarget, outBuffer); err != nil {
+ if err = w.writeDestPage(dest, outBuffer); err != nil {
return err
}
@@ -1893,95 +1888,37 @@ func (s *Site) renderThing(d interface{}, layout string, w io.Writer) error {
}
-func (s *Site) pageTarget() target.Output {
- s.initTargetList()
- return s.targets.page
-}
-
-func (s *Site) pageUglyTarget() target.Output {
- s.initTargetList()
- return s.targets.pageUgly
-}
-
-func (s *Site) fileTarget() target.Output {
- s.initTargetList()
- return s.targets.file
-}
-
-func (s *Site) aliasTarget() target.AliasPublisher {
- s.initTargetList()
- return s.targets.alias
-}
-
-func (s *Site) languageAliasTarget() target.AliasPublisher {
- s.initTargetList()
- return s.targets.languageAlias
+func (s *Site) langDir() string {
+ if s.Language.Lang != s.Info.multilingual.DefaultLang.Lang || s.Info.defaultContentLanguageInSubdir {
+ return s.Language.Lang
+ }
+ return ""
}
-func (s *Site) initTargetList() {
+func (s *Site) initSiteWriter() {
if s.Fs == nil {
panic("Must have Fs")
}
- s.targetListInit.Do(func() {
- langDir := ""
- if s.Language.Lang != s.Info.multilingual.DefaultLang.Lang || s.Info.defaultContentLanguageInSubdir {
- langDir = s.Language.Lang
- }
- if s.targets.page == nil {
- s.targets.page = &target.PagePub{
- Fs: s.Fs,
- PublishDir: s.absPublishDir(),
- UglyURLs: s.Cfg.GetBool("uglyURLs"),
- LangDir: langDir,
- }
- }
- if s.targets.pageUgly == nil {
- s.targets.pageUgly = &target.PagePub{
- Fs: s.Fs,
- PublishDir: s.absPublishDir(),
- UglyURLs: true,
- LangDir: langDir,
- }
- }
- if s.targets.file == nil {
- s.targets.file = &target.Filesystem{
- Fs: s.Fs,
- PublishDir: s.absPublishDir(),
- }
- }
- if s.targets.alias == nil {
- s.targets.alias = &target.HTMLRedirectAlias{
- Fs: s.Fs,
- PublishDir: s.absPublishDir(),
- Templates: s.Tmpl.Lookup("alias.html"),
- }
- }
- if s.targets.languageAlias == nil {
- s.targets.languageAlias = &target.HTMLRedirectAlias{
- Fs: s.Fs,
- PublishDir: s.absPublishDir(),
- AllowRoot: true,
- }
- }
- })
+ s.w = siteWriter{
+ langDir: s.langDir(),
+ publishDir: s.absPublishDir(),
+ uglyURLs: s.Cfg.GetBool("uglyURLs"),
+ relativeURLs: s.Info.relativeURLs,
+ fs: s.Fs,
+ log: s.Log,
+ }
}
-func (s *Site) writeDestFile(path string, reader io.Reader) (err error) {
- s.Log.DEBUG.Println("creating file:", path)
- return s.fileTarget().Publish(path, reader)
+func (s *Site) writeDestAlias(path, permalink string, p *Page) (err error) {
+ return s.publishDestAlias(false, path, permalink, p)
}
-func (s *Site) writeDestPage(path string, publisher target.Publisher, reader io.Reader) (err error) {
- s.Log.DEBUG.Println("creating page:", path)
- return publisher.Publish(path, reader)
-}
+func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, p *Page) (err error) {
+ w := s.w
+ w.allowRoot = allowRoot
-// AliasPublisher
-func (s *Site) writeDestAlias(path, permalink string, p *Page) (err error) {
- return s.publishDestAlias(s.aliasTarget(), path, permalink, p)
-}
+ isXHTML := strings.HasSuffix(path, ".xhtml")
-func (s *Site) publishDestAlias(aliasPublisher target.AliasPublisher, path, permalink string, p *Page) (err error) {
if s.Info.relativeURLs {
// convert `permalink` into URI relative to location of `path`
baseURL := helpers.SanitizeURLKeepTrailingSlash(s.Cfg.GetString("baseURL"))
@@ -1995,7 +1932,20 @@ func (s *Site) publishDestAlias(aliasPublisher target.AliasPublisher, path, perm
permalink = filepath.ToSlash(permalink)
}
s.Log.DEBUG.Println("creating alias:", path, "redirecting to", permalink)
- return aliasPublisher.Publish(path, permalink, p)
+
+ targetPath, err := w.targetPathAlias(path)
+ if err != nil {
+ return err
+ }
+
+ handler := newAliasHandler(s.Tmpl.Lookup("alias.html"))
+ aliasContent, err := handler.renderAlias(isXHTML, permalink, p)
+ if err != nil {
+ return err
+ }
+
+ return w.publish(targetPath, aliasContent)
+
}
func (s *Site) draftStats() string {
diff --git a/hugolib/site_render.go b/hugolib/site_render.go
index 5709aa83a..81cad525c 100644
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -257,7 +257,7 @@ func (s *Site) renderRobotsTXT() error {
err := s.renderForLayouts("robots", n, outBuffer, s.appendThemeTemplates(rLayouts)...)
if err == nil {
- err = s.writeDestFile("robots.txt", outBuffer)
+ err = s.w.writeDestFile("robots.txt", outBuffer)
}
return err
@@ -284,13 +284,13 @@ func (s *Site) renderAliases() error {
if s.Info.defaultContentLanguageInSubdir {
mainLangURL := s.PathSpec.AbsURL(mainLang.Lang, false)
s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
- if err := s.publishDestAlias(s.languageAliasTarget(), "/", mainLangURL, nil); err != nil {
+ if err := s.publishDestAlias(true, "/", mainLangURL, nil); err != nil {
return err
}
} else {
mainLangURL := s.PathSpec.AbsURL("", false)
s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
- if err := s.publishDestAlias(s.languageAliasTarget(), mainLang.Lang, mainLangURL, nil); err != nil {
+ if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, nil); err != nil {
return err
}
}
diff --git a/hugolib/site_writer.go b/hugolib/site_writer.go
new file mode 100644
index 000000000..ec0b888e6
--- /dev/null
+++ b/hugolib/site_writer.go
@@ -0,0 +1,193 @@
+// Copyright 2017 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugolib
+
+import (
+ "fmt"
+ "io"
+ "path/filepath"
+ "runtime"
+ "strings"
+
+ "github.com/spf13/hugo/helpers"
+ "github.com/spf13/hugo/hugofs"
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+// We may find some abstractions/interface(s) here once we star with
+// "Multiple Output Types".
+type siteWriter struct {
+ langDir string
+ publishDir string
+ relativeURLs bool
+ uglyURLs bool
+ allowRoot bool // For aliases
+
+ fs *hugofs.Fs
+
+ log *jww.Notepad
+}
+
+func (w siteWriter) targetPathPage(src string) (string, error) {
+ dir, err := w.baseTargetPathPage(src)
+ if err != nil {
+ return "", err
+ }
+ if w.publishDir != "" {
+ dir = filepath.Join(w.publishDir, dir)
+ }
+ return dir, nil
+}
+
+func (w siteWriter) baseTargetPathPage(src string) (string, error) {
+ if src == helpers.FilePathSeparator {
+ return "index.html", nil
+ }
+
+ dir, file := filepath.Split(src)
+ isRoot := dir == ""
+ ext := extension(filepath.Ext(file))
+ name := filename(file)
+
+ if w.langDir != "" && dir == helpers.FilePathSeparator && name == w.langDir {
+ return filepath.Join(dir, name, "index"+ext), nil
+ }
+
+ if w.uglyURLs || file == "index.html" || (isRoot && file == "404.html") {
+ return filepath.Join(dir, name+ext), nil
+ }
+
+ dir = filepath.Join(dir, name, "index"+ext)
+
+ return dir, nil
+
+}
+
+func (w siteWriter) targetPathFile(src string) (string, error) {
+ return filepath.Join(w.publishDir, filepath.FromSlash(src)), nil
+}
+
+func (w siteWriter) targetPathAlias(src string) (string, error) {
+ originalAlias := src
+ if len(src) <= 0 {
+ return "", fmt.Errorf("Alias \"\" is an empty string")
+ }
+
+ alias := filepath.Clean(src)
+ components := strings.Split(alias, helpers.FilePathSeparator)
+
+ if !w.allowRoot && alias == helpers.FilePathSeparator {
+ return "", fmt.Errorf("Alias \"%s\" resolves to website root directory", originalAlias)
+ }
+
+ // Validate against directory traversal
+ if components[0] == ".." {
+ return "", fmt.Errorf("Alias \"%s\" traverses outside the website root directory", originalAlias)
+ }
+
+ // Handle Windows file and directory naming restrictions
+ // See "Naming Files, Paths, and Namespaces" on MSDN
+ // https://msdn.microsoft.com/en-us/library/aa365247%28v=VS.85%29.aspx?f=255&MSPPError=-2147217396
+ msgs := []string{}
+ reservedNames := []string{"CON", "PRN", "AUX", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}
+
+ if strings.ContainsAny(alias, ":*?\"<>|") {
+ msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains invalid characters on Windows: : * ? \" < > |", originalAlias))
+ }
+ for _, ch := range alias {
+ if ch < ' ' {
+ msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains ASCII control code (0x00 to 0x1F), invalid on Windows: : * ? \" < > |", originalAlias))
+ continue
+ }
+ }
+ for _, comp := range components {
+ if strings.HasSuffix(comp, " ") || strings.HasSuffix(comp, ".") {
+ msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains component with a trailing space or period, problematic on Windows", originalAlias))
+ }
+ for _, r := range reservedNames {
+ if comp == r {
+ msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains component with reserved name \"%s\" on Windows", originalAlias, r))
+ }
+ }
+ }
+ if len(msgs) > 0 {
+ if runtime.GOOS == "windows" {
+ for _, m := range msgs {
+ w.log.ERROR.Println(m)
+ }
+ return "", fmt.Errorf("Cannot create \"%s\": Windows filename restriction", originalAlias)
+ }
+ for _, m := range msgs {
+ w.log.WARN.Println(m)
+ }
+ }
+
+ // Add the final touch
+ alias = strings.TrimPrefix(alias, helpers.FilePathSeparator)
+ if strings.HasSuffix(alias, helpers.FilePathSeparator) {
+ alias = alias + "index.html"
+ } else if !strings.HasSuffix(alias, ".html") {
+ alias = alias + helpers.FilePathSeparator + "index.html"
+ }
+ if originalAlias != alias {
+ w.log.INFO.Printf("Alias \"%s\" translated to \"%s\"\n", originalAlias, alias)
+ }
+
+ return filepath.Join(w.publishDir, alias), nil
+}
+
+func extension(ext string) string {
+ switch ext {
+ case ".md", ".rst":
+ return ".html"
+ }
+
+ if ext != "" {
+ return ext
+ }
+
+ return ".html"
+}
+
+func filename(f string) string {
+ ext := filepath.Ext(f)
+ if ext == "" {
+ return f
+ }
+
+ return f[:len(f)-len(ext)]
+}
+
+func (w siteWriter) writeDestPage(path string, reader io.Reader) (err error) {
+ w.log.DEBUG.Println("creating page:", path)
+ targetPath, err := w.targetPathPage(path)
+ if err != nil {
+ return err
+ }
+
+ return w.publish(targetPath, reader)
+}
+
+func (w siteWriter) writeDestFile(path string, r io.Reader) (err error) {
+ w.log.DEBUG.Println("creating file:", path)
+ targetPath, err := w.targetPathFile(path)
+ if err != nil {
+ return err
+ }
+ return w.publish(targetPath, r)
+}
+
+func (w siteWriter) publish(path string, r io.Reader) (err error) {
+ return helpers.WriteToDisk(path, r, w.fs.Destination)
+}
diff --git a/hugolib/site_writer_test.go b/hugolib/site_writer_test.go
new file mode 100644
index 000000000..e983e5265
--- /dev/null
+++ b/hugolib/site_writer_test.go
@@ -0,0 +1,146 @@
+// Copyright 2017 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugolib
+
+import (
+ "path/filepath"
+ "runtime"
+ "testing"
+)
+
+func TestTargetPathHTMLRedirectAlias(t *testing.T) {
+ w := siteWriter{log: newErrorLogger()}
+
+ errIsNilForThisOS := runtime.GOOS != "windows"
+
+ tests := []struct {
+ value string
+ expected string
+ errIsNil bool
+ }{
+ {"", "", false},
+ {"s", filepath.FromSlash("s/index.html"), true},
+ {"/", "", false},
+ {"alias 1", filepath.FromSlash("alias 1/index.html"), true},
+ {"alias 2/", filepath.FromSlash("alias 2/index.html"), true},
+ {"alias 3.html", "alias 3.html", true},
+ {"alias4.html", "alias4.html", true},
+ {"/alias 5.html", "alias 5.html", true},
+ {"/трям.html", "трям.html", true},
+ {"../../../../tmp/passwd", "", false},
+ {"/foo/../../../../tmp/passwd", filepath.FromSlash("tmp/passwd/index.html"), true},
+ {"foo/../../../../tmp/passwd", "", false},
+ {"C:\\Windows", filepath.FromSlash("C:\\Windows/index.html"), errIsNilForThisOS},
+ {"/trailing-space /", filepath.FromSlash("trailing-space /index.html"), errIsNilForThisOS},
+ {"/trailing-period./", filepath.FromSlash("trailing-period./index.html"), errIsNilForThisOS},
+ {"/tab\tseparated/", filepath.FromSlash("tab\tseparated/index.html"), errIsNilForThisOS},
+ {"/chrome/?p=help&ctx=keyboard#topic=3227046", filepath.FromSlash("chrome/?p=help&ctx=keyboard#topic=3227046/index.html"), errIsNilForThisOS},
+ {"/LPT1/Printer/", filepath.FromSlash("LPT1/Printer/index.html"), errIsNilForThisOS},
+ }
+
+ for _, test := range tests {
+ path, err := w.targetPathAlias(test.value)
+ if (err == nil) != test.errIsNil {
+ t.Errorf("Expected err == nil => %t, got: %t. err: %s", test.errIsNil, err == nil, err)
+ continue
+ }
+ if err == nil && path != test.expected {
+ t.Errorf("Expected: \"%s\", got: \"%s\"", test.expected, path)
+ }
+ }
+}
+
+func TestTargetPathPage(t *testing.T) {
+ w := siteWriter{log: newErrorLogger()}
+
+ tests := []struct {
+ content string
+ expected string
+ }{
+ {"/", "index.html"},
+ {"index.html", "index.html"},
+ {"bar/index.html", "bar/index.html"},
+ {"foo", "foo/index.html"},
+ {"foo.html", "foo/index.html"},
+ {"foo.xhtml", "foo/index.xhtml"},
+ {"section", "section/index.html"},
+ {"section/", "section/index.html"},
+ {"section/foo", "section/foo/index.html"},
+ {"section/foo.html", "section/foo/index.html"},
+ {"section/foo.rss", "section/foo/index.rss"},
+ }
+
+ for _, test := range tests {
+ dest, err := w.targetPathPage(filepath.FromSlash(test.content))
+ expected := filepath.FromSlash(test.expected)
+ if err != nil {
+ t.Fatalf("Translate returned and unexpected err: %s", err)
+ }
+
+ if dest != expected {
+ t.Errorf("Translate expected return: %s, got: %s", expected, dest)
+ }
+ }
+}
+
+func TestTargetPathPageBase(t *testing.T) {
+ w := siteWriter{log: newErrorLogger()}
+
+ tests := []struct {
+ content string
+ expected string
+ }{
+ {"/", "a/base/index.html"},
+ }
+
+ for _, test := range tests {
+
+ for _, pd := range []string{"a/base", "a/base/"} {
+ w.publishDir = pd
+ dest, err := w.targetPathPage(test.content)
+ if err != nil {
+ t.Fatalf("Translated returned and err: %s", err)
+ }
+
+ if dest != filepath.FromSlash(test.expected) {
+ t.Errorf("Translate expected: %s, got: %s", test.expected, dest)
+ }
+ }
+ }
+}
+
+func TestTargetPathUglyURLs(t *testing.T) {
+ w := siteWriter{log: newErrorLogger(), uglyURLs: true}
+
+ tests := []struct {
+ content string
+ expected string
+ }{
+ {"foo.html", "foo.html"},
+ {"/", "index.html"},
+ {"section", "section.html"},
+ {"index.html", "index.html"},
+ }
+
+ for _, test := range tests {
+ dest, err := w.targetPathPage(filepath.FromSlash(test.content))
+ if err != nil {
+ t.Fatalf("Translate returned an unexpected err: %s", err)
+ }
+
+ if dest != test.expected {
+ t.Errorf("Translate expected return: %s, got: %s", test.expected, dest)
+ }
+ }
+}
diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go
index 92af07a46..a78b73c8d 100644
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -151,6 +151,9 @@ func newDebugLogger() *jww.Notepad {
return jww.NewNotepad(jww.LevelDebug, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
}
+func newErrorLogger() *jww.Notepad {
+ return jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+}
func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.Template) error {
return func(templ tpl.Template) error {