2017-03-09 13:19:29 -05:00
|
|
|
// 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"
|
|
|
|
|
2017-06-13 12:42:45 -04:00
|
|
|
"github.com/gohugoio/hugo/helpers"
|
|
|
|
"github.com/gohugoio/hugo/output"
|
2017-03-09 13:19:29 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2017-03-16 03:32:14 -04:00
|
|
|
Type output.Format
|
2017-03-09 13:19:29 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-03-16 03:32:14 -04:00
|
|
|
// createTargetPathDescriptor adapts a Page and the given output.Format into
|
2017-03-09 13:19:29 -05:00
|
|
|
// a targetPathDescriptor. This descriptor can then be used to create paths
|
|
|
|
// and URLs for this Page.
|
2017-03-16 03:32:14 -04:00
|
|
|
func (p *Page) createTargetPathDescriptor(t output.Format) (targetPathDescriptor, error) {
|
2017-03-17 11:35:09 -04:00
|
|
|
if p.targetPathDescriptorPrototype == nil {
|
2017-03-25 15:31:43 -04:00
|
|
|
panic(fmt.Sprintf("Must run initTargetPathDescriptor() for page %q, kind %q", p.Title, p.Kind))
|
2017-03-17 11:35:09 -04:00
|
|
|
}
|
|
|
|
d := *p.targetPathDescriptorPrototype
|
|
|
|
d.Type = t
|
|
|
|
return d, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Page) initTargetPathDescriptor() error {
|
|
|
|
|
|
|
|
d := &targetPathDescriptor{
|
2017-03-09 13:19:29 -05:00
|
|
|
PathSpec: p.s.PathSpec,
|
|
|
|
Kind: p.Kind,
|
|
|
|
Sections: p.sections,
|
|
|
|
UglyURLs: p.s.Info.uglyURLs,
|
2017-06-06 03:15:42 -04:00
|
|
|
Dir: filepath.ToSlash(p.Source.Dir()),
|
2017-03-09 13:19:29 -05:00
|
|
|
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 {
|
2017-03-17 11:35:09 -04:00
|
|
|
return err
|
2017-03-09 13:19:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
opath, _ = url.QueryUnescape(opath)
|
|
|
|
opath = filepath.FromSlash(opath)
|
|
|
|
d.ExpandedPermalink = opath
|
|
|
|
}
|
|
|
|
|
2017-03-17 11:35:09 -04:00
|
|
|
p.targetPathDescriptorPrototype = d
|
|
|
|
return nil
|
2017-03-09 13:19:29 -05:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// createTargetPath creates the target filename for this Page for the given
|
2017-03-16 03:32:14 -04:00
|
|
|
// output.Format. Some additional URL parts can also be provided, the typical
|
2017-03-09 13:19:29 -05:00
|
|
|
// use case being pagination.
|
2017-03-16 03:32:14 -04:00
|
|
|
func (p *Page) createTargetPath(t output.Format, addends ...string) (string, error) {
|
2017-03-09 13:19:29 -05:00
|
|
|
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
|
|
|
|
|
2017-04-27 03:33:40 -04:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2017-03-09 13:19:29 -05:00
|
|
|
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, ".") {
|
2017-06-20 02:45:52 -04:00
|
|
|
pagePath = filepath.Join(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix())
|
2017-03-09 13:19:29 -05:00
|
|
|
}
|
|
|
|
} 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 {
|
2017-06-20 02:45:52 -04:00
|
|
|
pagePath += d.Type.MediaType.Delimiter + d.Type.MediaType.Suffix
|
2017-03-09 13:19:29 -05:00
|
|
|
} else {
|
2017-06-20 02:45:52 -04:00
|
|
|
pagePath = filepath.Join(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix())
|
2017-03-09 13:19:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-06-20 02:45:52 -04:00
|
|
|
pagePath += base + d.Type.MediaType.FullSuffix()
|
2017-03-09 13:19:29 -05:00
|
|
|
|
|
|
|
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 {
|
|
|
|
|
2017-03-16 03:32:14 -04:00
|
|
|
if len(p.outputFormats) == 0 {
|
2017-03-09 13:19:29 -05:00
|
|
|
panic(fmt.Sprintf("Page %q missing output format(s)", p.Title))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Choose the main output format. In most cases, this will be HTML.
|
2017-03-21 19:25:55 -04:00
|
|
|
f := p.outputFormats[0]
|
|
|
|
|
|
|
|
return p.createRelativePermalinkForOutputFormat(f)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Page) createRelativePermalinkForOutputFormat(f output.Format) string {
|
|
|
|
tp, err := p.createTargetPath(f)
|
2017-03-09 13:19:29 -05:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
p.s.Log.ERROR.Printf("Failed to create permalink for page %q: %s", p.FullFilePath(), err)
|
|
|
|
return ""
|
|
|
|
}
|
2017-03-21 19:25:55 -04:00
|
|
|
// For /index.json etc. we must use the full path.
|
|
|
|
if strings.HasSuffix(f.BaseFilename(), "html") {
|
|
|
|
tp = strings.TrimSuffix(tp, f.BaseFilename())
|
|
|
|
}
|
2017-03-09 13:19:29 -05:00
|
|
|
|
|
|
|
return p.s.PathSpec.URLizeFilename(tp)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Page) TargetPath() (outfile string) {
|
|
|
|
// Delete in Hugo 0.22
|
2017-05-10 14:05:52 -04:00
|
|
|
helpers.Deprecated("Page", "TargetPath", "This method does not make sanse any more.", true)
|
2017-03-09 13:19:29 -05:00
|
|
|
return ""
|
|
|
|
}
|