mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
Make Page an interface
The main motivation of this commit is to add a `page.Page` interface to replace the very file-oriented `hugolib.Page` struct. This is all a preparation step for issue #5074, "pages from other data sources". But this also fixes a set of annoying limitations, especially related to custom output formats, and shortcodes. Most notable changes: * The inner content of shortcodes using the `{{%` as the outer-most delimiter will now be sent to the content renderer, e.g. Blackfriday. This means that any markdown will partake in the global ToC and footnote context etc. * The Custom Output formats are now "fully virtualized". This removes many of the current limitations. * The taxonomy list type now has a reference to the `Page` object. This improves the taxonomy template `.Title` situation and make common template constructs much simpler. See #5074 Fixes #5763 Fixes #5758 Fixes #5090 Fixes #5204 Fixes #4695 Fixes #5607 Fixes #5707 Fixes #5719 Fixes #3113 Fixes #5706 Fixes #5767 Fixes #5723 Fixes #5769 Fixes #5770 Fixes #5771 Fixes #5759 Fixes #5776 Fixes #5777 Fixes #5778
This commit is contained in:
334
resources/page/page_paths.go
Normal file
334
resources/page/page_paths.go
Normal file
@@ -0,0 +1,334 @@
|
||||
// Copyright 2019 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 page
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
)
|
||||
|
||||
const slash = "/"
|
||||
|
||||
// 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.
|
||||
//
|
||||
//
|
||||
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
|
||||
|
||||
// Typically a language prefix added to file paths.
|
||||
PrefixFilePath string
|
||||
|
||||
// Typically a language prefix added to links.
|
||||
PrefixLink string
|
||||
|
||||
// If in multihost mode etc., every link/path needs to be prefixed, even
|
||||
// if set in URL.
|
||||
ForcePrefix bool
|
||||
|
||||
// URL from front matter if set. Will override any Slug etc.
|
||||
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
|
||||
}
|
||||
|
||||
// TODO(bep) move this type.
|
||||
type TargetPaths struct {
|
||||
|
||||
// Where to store the file on disk relative to the publish dir. OS slashes.
|
||||
TargetFilename string
|
||||
|
||||
// The directory to write sub-resources of the above.
|
||||
SubResourceBaseTarget string
|
||||
|
||||
// The base for creating links to sub-resources of the above.
|
||||
SubResourceBaseLink string
|
||||
|
||||
// The relative permalink to this resources. Unix slashes.
|
||||
Link string
|
||||
}
|
||||
|
||||
func (p TargetPaths) RelPermalink(s *helpers.PathSpec) string {
|
||||
return s.PrependBasePath(p.Link, false)
|
||||
}
|
||||
|
||||
func (p TargetPaths) PermalinkForOutputFormat(s *helpers.PathSpec, f output.Format) string {
|
||||
var baseURL string
|
||||
var err error
|
||||
if f.Protocol != "" {
|
||||
baseURL, err = s.BaseURL.WithProtocol(f.Protocol)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
} else {
|
||||
baseURL = s.BaseURL.String()
|
||||
}
|
||||
|
||||
return s.PermalinkForBaseURL(p.Link, baseURL)
|
||||
}
|
||||
|
||||
func isHtmlIndex(s string) bool {
|
||||
return strings.HasSuffix(s, "/index.html")
|
||||
}
|
||||
|
||||
func CreateTargetPaths(d TargetPathDescriptor) (tp TargetPaths) {
|
||||
|
||||
if d.Type.Name == "" {
|
||||
panic("CreateTargetPath: missing type")
|
||||
}
|
||||
|
||||
// Normalize all file Windows paths to simplify what's next.
|
||||
if helpers.FilePathSeparator != slash {
|
||||
d.Dir = filepath.ToSlash(d.Dir)
|
||||
d.PrefixFilePath = filepath.ToSlash(d.PrefixFilePath)
|
||||
|
||||
}
|
||||
|
||||
pagePath := slash
|
||||
|
||||
var (
|
||||
pagePathDir string
|
||||
link string
|
||||
linkDir string
|
||||
)
|
||||
|
||||
// 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
|
||||
baseNameSameAsType := d.BaseName != "" && d.BaseName == d.Type.BaseName
|
||||
|
||||
if d.ExpandedPermalink == "" && baseNameSameAsType {
|
||||
isUgly = true
|
||||
}
|
||||
|
||||
if d.Kind != KindPage && d.URL == "" && len(d.Sections) > 0 {
|
||||
if d.ExpandedPermalink != "" {
|
||||
pagePath = pjoin(pagePath, d.ExpandedPermalink)
|
||||
} else {
|
||||
pagePath = pjoin(d.Sections...)
|
||||
}
|
||||
needsBase = false
|
||||
}
|
||||
|
||||
if d.Type.Path != "" {
|
||||
pagePath = pjoin(pagePath, d.Type.Path)
|
||||
}
|
||||
|
||||
if d.Kind != KindHome && d.URL != "" {
|
||||
pagePath = pjoin(pagePath, d.URL)
|
||||
|
||||
if d.Addends != "" {
|
||||
pagePath = pjoin(pagePath, d.Addends)
|
||||
}
|
||||
|
||||
pagePathDir = pagePath
|
||||
link = pagePath
|
||||
hasDot := strings.Contains(d.URL, ".")
|
||||
hasSlash := strings.HasSuffix(d.URL, slash)
|
||||
|
||||
if hasSlash || !hasDot {
|
||||
pagePath = pjoin(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix())
|
||||
} else if hasDot {
|
||||
pagePathDir = path.Dir(pagePathDir)
|
||||
}
|
||||
|
||||
if !isHtmlIndex(pagePath) {
|
||||
link = pagePath
|
||||
} else if !hasSlash {
|
||||
link += slash
|
||||
}
|
||||
|
||||
linkDir = pagePathDir
|
||||
|
||||
if d.ForcePrefix {
|
||||
|
||||
// Prepend language prefix if not already set in URL
|
||||
if d.PrefixFilePath != "" && !strings.HasPrefix(d.URL, slash+d.PrefixFilePath) {
|
||||
pagePath = pjoin(d.PrefixFilePath, pagePath)
|
||||
pagePathDir = pjoin(d.PrefixFilePath, pagePathDir)
|
||||
}
|
||||
|
||||
if d.PrefixLink != "" && !strings.HasPrefix(d.URL, slash+d.PrefixLink) {
|
||||
link = pjoin(d.PrefixLink, link)
|
||||
linkDir = pjoin(d.PrefixLink, linkDir)
|
||||
}
|
||||
}
|
||||
|
||||
} else if d.Kind == KindPage {
|
||||
|
||||
if d.ExpandedPermalink != "" {
|
||||
pagePath = pjoin(pagePath, d.ExpandedPermalink)
|
||||
|
||||
} else {
|
||||
if d.Dir != "" {
|
||||
pagePath = pjoin(pagePath, d.Dir)
|
||||
}
|
||||
if d.BaseName != "" {
|
||||
pagePath = pjoin(pagePath, d.BaseName)
|
||||
}
|
||||
}
|
||||
|
||||
if d.Addends != "" {
|
||||
pagePath = pjoin(pagePath, d.Addends)
|
||||
}
|
||||
|
||||
link = pagePath
|
||||
|
||||
if baseNameSameAsType {
|
||||
link = strings.TrimSuffix(link, d.BaseName)
|
||||
}
|
||||
|
||||
pagePathDir = link
|
||||
link = link + slash
|
||||
linkDir = pagePathDir
|
||||
|
||||
if isUgly {
|
||||
pagePath = addSuffix(pagePath, d.Type.MediaType.FullSuffix())
|
||||
} else {
|
||||
pagePath = pjoin(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix())
|
||||
}
|
||||
|
||||
if isUgly && !isHtmlIndex(pagePath) {
|
||||
link = pagePath
|
||||
}
|
||||
|
||||
if d.PrefixFilePath != "" {
|
||||
pagePath = pjoin(d.PrefixFilePath, pagePath)
|
||||
pagePathDir = pjoin(d.PrefixFilePath, pagePathDir)
|
||||
}
|
||||
|
||||
if d.PrefixLink != "" {
|
||||
link = pjoin(d.PrefixLink, link)
|
||||
linkDir = pjoin(d.PrefixLink, linkDir)
|
||||
}
|
||||
|
||||
} else {
|
||||
if d.Addends != "" {
|
||||
pagePath = pjoin(pagePath, d.Addends)
|
||||
}
|
||||
|
||||
needsBase = needsBase && d.Addends == ""
|
||||
|
||||
// No permalink expansion etc. for node type pages (for now)
|
||||
base := ""
|
||||
|
||||
if needsBase || !isUgly {
|
||||
base = d.Type.BaseName
|
||||
}
|
||||
|
||||
pagePathDir = pagePath
|
||||
link = pagePath
|
||||
linkDir = pagePathDir
|
||||
|
||||
if base != "" {
|
||||
pagePath = path.Join(pagePath, addSuffix(base, d.Type.MediaType.FullSuffix()))
|
||||
} else {
|
||||
pagePath = addSuffix(pagePath, d.Type.MediaType.FullSuffix())
|
||||
|
||||
}
|
||||
|
||||
if !isHtmlIndex(pagePath) {
|
||||
link = pagePath
|
||||
} else {
|
||||
link += slash
|
||||
}
|
||||
|
||||
if d.PrefixFilePath != "" {
|
||||
pagePath = pjoin(d.PrefixFilePath, pagePath)
|
||||
pagePathDir = pjoin(d.PrefixFilePath, pagePathDir)
|
||||
}
|
||||
|
||||
if d.PrefixLink != "" {
|
||||
link = pjoin(d.PrefixLink, link)
|
||||
linkDir = pjoin(d.PrefixLink, linkDir)
|
||||
}
|
||||
}
|
||||
|
||||
pagePath = pjoin(slash, pagePath)
|
||||
pagePathDir = strings.TrimSuffix(path.Join(slash, pagePathDir), slash)
|
||||
|
||||
hadSlash := strings.HasSuffix(link, slash)
|
||||
link = strings.Trim(link, slash)
|
||||
if hadSlash {
|
||||
link += slash
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(link, slash) {
|
||||
link = slash + link
|
||||
}
|
||||
|
||||
linkDir = strings.TrimSuffix(path.Join(slash, linkDir), slash)
|
||||
|
||||
// Note: MakePathSanitized will lower case the path if
|
||||
// disablePathToLower isn't set.
|
||||
pagePath = d.PathSpec.MakePathSanitized(pagePath)
|
||||
pagePathDir = d.PathSpec.MakePathSanitized(pagePathDir)
|
||||
link = d.PathSpec.MakePathSanitized(link)
|
||||
linkDir = d.PathSpec.MakePathSanitized(linkDir)
|
||||
|
||||
tp.TargetFilename = filepath.FromSlash(pagePath)
|
||||
tp.SubResourceBaseTarget = filepath.FromSlash(pagePathDir)
|
||||
tp.SubResourceBaseLink = linkDir
|
||||
tp.Link = d.PathSpec.URLizeFilename(link)
|
||||
if tp.Link == "" {
|
||||
tp.Link = slash
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func addSuffix(s, suffix string) string {
|
||||
return strings.Trim(s, slash) + suffix
|
||||
}
|
||||
|
||||
// Like path.Join, but preserves one trailing slash if present.
|
||||
func pjoin(elem ...string) string {
|
||||
hadSlash := strings.HasSuffix(elem[len(elem)-1], slash)
|
||||
joined := path.Join(elem...)
|
||||
if hadSlash && !strings.HasSuffix(joined, slash) {
|
||||
return joined + slash
|
||||
}
|
||||
return joined
|
||||
}
|
Reference in New Issue
Block a user