URI: 
       hugolib, target: Rework/move the target package - 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 e52e2a70e5e0a2d15fc9befbcd7290761c98589e
   DIR parent ea165bf9e71c7ca9ddb9f14ddbdbcd506ce554bb
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Fri,  3 Mar 2017 10:47:43 +0100
       
       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:
         A hugolib/alias.go                    |      69 ++++++++++++++++++++++++++++++
         M hugolib/handler_file.go             |       4 ++--
         M hugolib/hugo_sites_build.go         |       2 ++
         M hugolib/site.go                     |     164 +++++++++++--------------------
         M hugolib/site_render.go              |       6 +++---
         A hugolib/site_writer.go              |     193 +++++++++++++++++++++++++++++++
         A hugolib/site_writer_test.go         |     146 +++++++++++++++++++++++++++++++
         M hugolib/testhelpers_test.go         |       3 +++
         D target/alias_test.go                |      63 -------------------------------
         D target/file.go                      |      68 -------------------------------
         D target/htmlredirect.go              |     151 -------------------------------
         D target/memory.go                    |      37 -------------------------------
         D target/page.go                      |     103 -------------------------------
         D target/page_test.go                 |     113 -------------------------------
       
       14 files changed, 475 insertions(+), 647 deletions(-)
       ---
   DIR diff --git a/hugolib/alias.go 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
       +}
   DIR diff --git 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}
        }
   DIR diff --git 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
                                }
   DIR diff --git 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 {
   DIR diff --git 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
                                }
                        }
   DIR diff --git a/hugolib/site_writer.go 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)
       +}
   DIR diff --git a/hugolib/site_writer_test.go 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)
       +                }
       +        }
       +}
   DIR diff --git 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 {
   DIR diff --git a/target/alias_test.go b/target/alias_test.go
       @@ -1,63 +0,0 @@
       -// Copyright 2015 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 target
       -
       -import (
       -        "path/filepath"
       -        "runtime"
       -        "testing"
       -)
       -
       -func TestHTMLRedirectAlias(t *testing.T) {
       -        var o Translator
       -        o = new(HTMLRedirectAlias)
       -
       -        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 := o.Translate(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)
       -                }
       -        }
       -}
   DIR diff --git a/target/file.go b/target/file.go
       @@ -1,68 +0,0 @@
       -// Copyright 2016 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 target
       -
       -import (
       -        "io"
       -        "path/filepath"
       -
       -        "github.com/spf13/hugo/helpers"
       -        "github.com/spf13/hugo/hugofs"
       -)
       -
       -type Publisher interface {
       -        Publish(string, io.Reader) error
       -}
       -
       -type Translator interface {
       -        Translate(string) (string, error)
       -}
       -
       -// TODO(bep) consider other ways to solve this.
       -type OptionalTranslator interface {
       -        TranslateRelative(string) (string, error)
       -}
       -
       -type Output interface {
       -        Publisher
       -        Translator
       -}
       -
       -type Filesystem struct {
       -        PublishDir string
       -
       -        Fs *hugofs.Fs
       -}
       -
       -func (fs *Filesystem) Publish(path string, r io.Reader) (err error) {
       -        translated, err := fs.Translate(path)
       -        if err != nil {
       -                return
       -        }
       -
       -        return helpers.WriteToDisk(translated, r, fs.Fs.Destination)
       -}
       -
       -func (fs *Filesystem) Translate(src string) (dest string, err error) {
       -        return filepath.Join(fs.PublishDir, filepath.FromSlash(src)), nil
       -}
       -
       -func filename(f string) string {
       -        ext := filepath.Ext(f)
       -        if ext == "" {
       -                return f
       -        }
       -
       -        return f[:len(f)-len(ext)]
       -}
   DIR diff --git a/target/htmlredirect.go b/target/htmlredirect.go
       @@ -1,151 +0,0 @@
       -// Copyright 2016 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 target
       -
       -import (
       -        "bytes"
       -        "fmt"
       -        "html/template"
       -        "path/filepath"
       -        "runtime"
       -        "strings"
       -
       -        "github.com/spf13/hugo/helpers"
       -        "github.com/spf13/hugo/hugofs"
       -        jww "github.com/spf13/jwalterweatherman"
       -)
       -
       -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>"
       -const 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 AliasPublisher interface {
       -        Translator
       -        Publish(path string, permalink string, page interface{}) error
       -}
       -
       -type HTMLRedirectAlias struct {
       -        PublishDir string
       -        Templates  *template.Template
       -        AllowRoot  bool // for the language redirects
       -
       -        Fs *hugofs.Fs
       -}
       -
       -func (h *HTMLRedirectAlias) Translate(alias string) (aliasPath string, err error) {
       -        originalAlias := alias
       -        if len(alias) <= 0 {
       -                return "", fmt.Errorf("Alias \"\" is an empty string")
       -        }
       -
       -        alias = filepath.Clean(alias)
       -        components := strings.Split(alias, helpers.FilePathSeparator)
       -
       -        if !h.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 {
       -                                jww.ERROR.Println(m)
       -                        }
       -                        return "", fmt.Errorf("Cannot create \"%s\": Windows filename restriction", originalAlias)
       -                }
       -                for _, m := range msgs {
       -                        jww.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 {
       -                jww.INFO.Printf("Alias \"%s\" translated to \"%s\"\n", originalAlias, alias)
       -        }
       -
       -        return filepath.Join(h.PublishDir, alias), nil
       -}
       -
       -type AliasNode struct {
       -        Permalink string
       -        Page      interface{}
       -}
       -
       -func (h *HTMLRedirectAlias) Publish(path string, permalink string, page interface{}) (err error) {
       -        if path, err = h.Translate(path); err != nil {
       -                jww.ERROR.Printf("%s, skipping.", err)
       -                return nil
       -        }
       -
       -        t := "alias"
       -        if strings.HasSuffix(path, ".xhtml") {
       -                t = "alias-xhtml"
       -        }
       -
       -        template := defaultAliasTemplates
       -        if h.Templates != nil {
       -                template = h.Templates
       -                t = "alias.html"
       -        }
       -
       -        buffer := new(bytes.Buffer)
       -        err = template.ExecuteTemplate(buffer, t, &AliasNode{permalink, page})
       -        if err != nil {
       -                return
       -        }
       -
       -        return helpers.WriteToDisk(path, buffer, h.Fs.Destination)
       -}
   DIR diff --git a/target/memory.go b/target/memory.go
       @@ -1,37 +0,0 @@
       -// Copyright 2015 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 target
       -
       -import (
       -        "bytes"
       -        "io"
       -)
       -
       -type InMemoryTarget struct {
       -        Files map[string][]byte
       -}
       -
       -func (t *InMemoryTarget) Publish(label string, reader io.Reader) (err error) {
       -        if t.Files == nil {
       -                t.Files = make(map[string][]byte)
       -        }
       -        bytes := new(bytes.Buffer)
       -        bytes.ReadFrom(reader)
       -        t.Files[label] = bytes.Bytes()
       -        return
       -}
       -
       -func (t *InMemoryTarget) Translate(label string) (dest string, err error) {
       -        return label, nil
       -}
   DIR diff --git a/target/page.go b/target/page.go
       @@ -1,103 +0,0 @@
       -// Copyright 2016 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 target
       -
       -import (
       -        "html/template"
       -        "io"
       -        "path/filepath"
       -
       -        "github.com/spf13/hugo/helpers"
       -        "github.com/spf13/hugo/hugofs"
       -)
       -
       -type PagePublisher interface {
       -        Translator
       -        Publish(string, template.HTML) error
       -}
       -
       -type PagePub struct {
       -        UglyURLs         bool
       -        DefaultExtension string
       -        PublishDir       string
       -
       -        // LangDir will contain the subdir for the language, i.e. "en", "de" etc.
       -        // It will be empty if the site is rendered in root.
       -        LangDir string
       -
       -        Fs *hugofs.Fs
       -}
       -
       -func (pp *PagePub) Publish(path string, r io.Reader) (err error) {
       -
       -        translated, err := pp.Translate(path)
       -        if err != nil {
       -                return
       -        }
       -
       -        return helpers.WriteToDisk(translated, r, pp.Fs.Destination)
       -}
       -
       -func (pp *PagePub) Translate(src string) (dest string, err error) {
       -        dir, err := pp.TranslateRelative(src)
       -        if err != nil {
       -                return dir, err
       -        }
       -        if pp.PublishDir != "" {
       -                dir = filepath.Join(pp.PublishDir, dir)
       -        }
       -        return dir, nil
       -}
       -
       -func (pp *PagePub) TranslateRelative(src string) (dest string, err error) {
       -        if src == helpers.FilePathSeparator {
       -                return "index.html", nil
       -        }
       -
       -        dir, file := filepath.Split(src)
       -        isRoot := dir == ""
       -        ext := pp.extension(filepath.Ext(file))
       -        name := filename(file)
       -
       -        // TODO(bep) Having all of this path logic here seems wrong, but I guess
       -        // we'll clean this up when we redo the output files.
       -        // This catches the home page in a language sub path. They should never
       -        // have any ugly URLs.
       -        if pp.LangDir != "" && dir == helpers.FilePathSeparator && name == pp.LangDir {
       -                return filepath.Join(dir, name, "index"+ext), nil
       -        }
       -
       -        if pp.UglyURLs || file == "index.html" || (isRoot && file == "404.html") {
       -                return filepath.Join(dir, name+ext), nil
       -        }
       -
       -        return filepath.Join(dir, name, "index"+ext), nil
       -}
       -
       -func (pp *PagePub) extension(ext string) string {
       -        switch ext {
       -        case ".md", ".rst": // TODO make this list configurable.  page.go has the list of markup types.
       -                return ".html"
       -        }
       -
       -        if ext != "" {
       -                return ext
       -        }
       -
       -        if pp.DefaultExtension != "" {
       -                return pp.DefaultExtension
       -        }
       -
       -        return ".html"
       -}
   DIR diff --git a/target/page_test.go b/target/page_test.go
       @@ -1,113 +0,0 @@
       -// Copyright 2015 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 target
       -
       -import (
       -        "path/filepath"
       -        "testing"
       -
       -        "github.com/spf13/hugo/hugofs"
       -        "github.com/spf13/viper"
       -)
       -
       -func TestPageTranslator(t *testing.T) {
       -        fs := hugofs.NewMem(viper.New())
       -
       -        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 {
       -                f := &PagePub{Fs: fs}
       -                dest, err := f.Translate(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 TestPageTranslatorBase(t *testing.T) {
       -        tests := []struct {
       -                content  string
       -                expected string
       -        }{
       -                {"/", "a/base/index.html"},
       -        }
       -
       -        for _, test := range tests {
       -                f := &PagePub{PublishDir: "a/base"}
       -                fts := &PagePub{PublishDir: "a/base/"}
       -
       -                for _, fs := range []*PagePub{f, fts} {
       -                        dest, err := fs.Translate(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 TestTranslateUglyURLs(t *testing.T) {
       -        tests := []struct {
       -                content  string
       -                expected string
       -        }{
       -                {"foo.html", "foo.html"},
       -                {"/", "index.html"},
       -                {"section", "section.html"},
       -                {"index.html", "index.html"},
       -        }
       -
       -        for _, test := range tests {
       -                f := &PagePub{UglyURLs: true}
       -                dest, err := f.Translate(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)
       -                }
       -        }
       -}
       -
       -func TestTranslateDefaultExtension(t *testing.T) {
       -        f := &PagePub{DefaultExtension: ".foobar"}
       -        dest, _ := f.Translate("baz")
       -        if dest != filepath.FromSlash("baz/index.foobar") {
       -                t.Errorf("Translate expected return: %s, got %s", "baz/index.foobar", dest)
       -        }
       -}