diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2017-03-09 19:19:29 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2017-03-27 15:43:56 +0200 |
commit | 6bf010fed432e5574e19fd2946ee6397d895950e (patch) | |
tree | 75282ccbd526adc8dba62f9392db282b3bcec49f /hugolib/page_paths.go | |
parent | c8fff9501d424882a42f750800d9982ec47df640 (diff) |
hugolib: Refactor/-work the permalink/target path logic
This is a pretty fundamental change in Hugo, but absolutely needed if we should have any hope of getting "multiple outputs" done.
This commit's goal is to say:
* Every file target path is created by `createTargetPath`, i.e. one function for all.
* That function takes every page and site parameter into account, to avoid fragile string parsing to uglify etc. later on.
* The path creation logic has full test coverage.
* All permalinks, paginator URLs etc. are then built on top of that same logic.
Fixes #1252
Fixes #2110
Closes #2374
Fixes #1885
Fixes #3102
Fixes #3179
Fixes #1641
Fixes #1989
Diffstat (limited to 'hugolib/page_paths.go')
-rw-r--r-- | hugolib/page_paths.go | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/hugolib/page_paths.go b/hugolib/page_paths.go new file mode 100644 index 000000000..1347102b8 --- /dev/null +++ b/hugolib/page_paths.go @@ -0,0 +1,230 @@ +// 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" + "path/filepath" + + "net/url" + "strings" + + "github.com/spf13/hugo/helpers" + "github.com/spf13/hugo/output" +) + +// targetPathDescriptor describes how a file path for a given resource +// should look like on the file system. The same descriptor is then later used to +// create both the permalinks and the relative links, paginator URLs etc. +// +// The big motivating behind this is to have only one source of truth for URLs, +// and by that also get rid of most of the fragile string parsing/encoding etc. +// +// Page.createTargetPathDescriptor is the Page adapter. +// +type targetPathDescriptor struct { + PathSpec *helpers.PathSpec + + Type output.Type + Kind string + + Sections []string + + // For regular content pages this is either + // 1) the Slug, if set, + // 2) the file base name (TranslationBaseName). + BaseName string + + // Source directory. + Dir string + + // Language prefix, set if multilingual and if page should be placed in its + // language subdir. + LangPrefix string + + // Page.URLPath.URL. Will override any Slug etc. for regular pages. + URL string + + // Used to create paginator links. + Addends string + + // The expanded permalink if defined for the section, ready to use. + ExpandedPermalink string + + // Some types cannot have uglyURLs, even if globally enabled, RSS being one example. + UglyURLs bool +} + +// createTargetPathDescriptor adapts a Page and the given output.Type into +// a targetPathDescriptor. This descriptor can then be used to create paths +// and URLs for this Page. +func (p *Page) createTargetPathDescriptor(t output.Type) (targetPathDescriptor, error) { + d := targetPathDescriptor{ + PathSpec: p.s.PathSpec, + Type: t, + Kind: p.Kind, + Sections: p.sections, + UglyURLs: p.s.Info.uglyURLs, + Dir: filepath.ToSlash(strings.ToLower(p.Source.Dir())), + URL: p.URLPath.URL, + } + + if p.Slug != "" { + d.BaseName = p.Slug + } else { + d.BaseName = p.TranslationBaseName() + } + + if p.shouldAddLanguagePrefix() { + d.LangPrefix = p.Lang() + } + + if override, ok := p.Site.Permalinks[p.Section()]; ok { + opath, err := override.Expand(p) + if err != nil { + return d, err + } + + opath, _ = url.QueryUnescape(opath) + opath = filepath.FromSlash(opath) + d.ExpandedPermalink = opath + + } + + return d, nil + +} + +// createTargetPath creates the target filename for this Page for the given +// output.Type. Some additional URL parts can also be provided, the typical +// use case being pagination. +func (p *Page) createTargetPath(t output.Type, addends ...string) (string, error) { + d, err := p.createTargetPathDescriptor(t) + if err != nil { + return "", nil + } + + if len(addends) > 0 { + d.Addends = filepath.Join(addends...) + } + + return createTargetPath(d), nil +} + +func createTargetPath(d targetPathDescriptor) string { + + pagePath := helpers.FilePathSeparator + + // The top level index files, i.e. the home page etc., needs + // the index base even when uglyURLs is enabled. + needsBase := true + + isUgly := d.UglyURLs && !d.Type.NoUgly + + if d.Kind != KindPage && len(d.Sections) > 0 { + pagePath = filepath.Join(d.Sections...) + needsBase = false + } + + if d.Type.Path != "" { + pagePath = filepath.Join(pagePath, d.Type.Path) + } + + if d.Kind == KindPage { + // Always use URL if it's specified + if d.URL != "" { + pagePath = filepath.Join(pagePath, d.URL) + if strings.HasSuffix(d.URL, "/") || !strings.Contains(d.URL, ".") { + pagePath = filepath.Join(pagePath, d.Type.BaseName+"."+d.Type.MediaType.Suffix) + } + } else { + if d.ExpandedPermalink != "" { + pagePath = filepath.Join(pagePath, d.ExpandedPermalink) + + } else { + if d.Dir != "" { + pagePath = filepath.Join(pagePath, d.Dir) + } + if d.BaseName != "" { + pagePath = filepath.Join(pagePath, d.BaseName) + } + } + + if d.Addends != "" { + pagePath = filepath.Join(pagePath, d.Addends) + } + + if isUgly { + pagePath += "." + d.Type.MediaType.Suffix + } else { + pagePath = filepath.Join(pagePath, d.Type.BaseName+"."+d.Type.MediaType.Suffix) + } + + if d.LangPrefix != "" { + pagePath = filepath.Join(d.LangPrefix, pagePath) + } + } + } else { + if d.Addends != "" { + pagePath = filepath.Join(pagePath, d.Addends) + } + + needsBase = needsBase && d.Addends == "" + + // No permalink expansion etc. for node type pages (for now) + base := "" + + if needsBase || !isUgly { + base = helpers.FilePathSeparator + d.Type.BaseName + } + + pagePath += base + "." + d.Type.MediaType.Suffix + + if d.LangPrefix != "" { + pagePath = filepath.Join(d.LangPrefix, pagePath) + } + } + + pagePath = filepath.Join(helpers.FilePathSeparator, pagePath) + + // Note: MakePathSanitized will lower case the path if + // disablePathToLower isn't set. + return d.PathSpec.MakePathSanitized(pagePath) +} + +func (p *Page) createRelativePermalink() string { + + if len(p.outputTypes) == 0 { + panic(fmt.Sprintf("Page %q missing output format(s)", p.Title)) + } + + // Choose the main output format. In most cases, this will be HTML. + outputType := p.outputTypes[0] + tp, err := p.createTargetPath(outputType) + + if err != nil { + p.s.Log.ERROR.Printf("Failed to create permalink for page %q: %s", p.FullFilePath(), err) + return "" + } + + tp = strings.TrimSuffix(tp, outputType.BaseFilename()) + + return p.s.PathSpec.URLizeFilename(tp) +} + +func (p *Page) TargetPath() (outfile string) { + // Delete in Hugo 0.22 + helpers.Deprecated("Page", "TargetPath", "This method does not make sanse any more.", false) + return "" +} |