diff options
Diffstat (limited to 'hugolib/page_paths.go')
-rw-r--r-- | hugolib/page_paths.go | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/hugolib/page_paths.go b/hugolib/page_paths.go new file mode 100644 index 000000000..73fd62278 --- /dev/null +++ b/hugolib/page_paths.go @@ -0,0 +1,256 @@ +// 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/gohugoio/hugo/helpers" + "github.com/gohugoio/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.Format + 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.Format into +// a targetPathDescriptor. This descriptor can then be used to create paths +// and URLs for this Page. +func (p *Page) createTargetPathDescriptor(t output.Format) (targetPathDescriptor, error) { + if p.targetPathDescriptorPrototype == nil { + panic(fmt.Sprintf("Must run initTargetPathDescriptor() for page %q, kind %q", p.Title, p.Kind)) + } + d := *p.targetPathDescriptorPrototype + d.Type = t + return d, nil +} + +func (p *Page) initTargetPathDescriptor() error { + + d := &targetPathDescriptor{ + PathSpec: p.s.PathSpec, + Kind: p.Kind, + Sections: p.sections, + UglyURLs: p.s.Info.uglyURLs, + Dir: filepath.ToSlash(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 err + } + + opath, _ = url.QueryUnescape(opath) + opath = filepath.FromSlash(opath) + d.ExpandedPermalink = opath + } + + p.targetPathDescriptorPrototype = d + return nil + +} + +// createTargetPath creates the target filename for this Page for the given +// output.Format. Some additional URL parts can also be provided, the typical +// use case being pagination. +func (p *Page) createTargetPath(t output.Format, 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 the page output format's base name is the same as the page base name, + // we treat it as an ugly path, i.e. + // my-blog-post-1/index.md => my-blog-post-1/index.html + // (given the default values for that content file, i.e. no slug set etc.). + // This introduces the behaviour from < Hugo 0.20, see issue #3396. + if d.BaseName != "" && d.BaseName == d.Type.BaseName { + isUgly = true + } + + 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.FullSuffix()) + } + } 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.Delimiter + d.Type.MediaType.Suffix + } else { + pagePath = filepath.Join(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix()) + } + + 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.FullSuffix() + + 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.outputFormats) == 0 { + panic(fmt.Sprintf("Page %q missing output format(s)", p.Title)) + } + + // Choose the main output format. In most cases, this will be HTML. + f := p.outputFormats[0] + + return p.createRelativePermalinkForOutputFormat(f) + +} + +func (p *Page) createRelativePermalinkForOutputFormat(f output.Format) string { + tp, err := p.createTargetPath(f) + + if err != nil { + p.s.Log.ERROR.Printf("Failed to create permalink for page %q: %s", p.FullFilePath(), err) + return "" + } + // For /index.json etc. we must use the full path. + if strings.HasSuffix(f.BaseFilename(), "html") { + tp = strings.TrimSuffix(tp, f.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.", true) + return "" +} |