hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

page_paths.go (8460B)

    1 // Copyright 2019 The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 package page
   15 
   16 import (
   17 	"path"
   18 	"path/filepath"
   19 	"strings"
   20 
   21 	"github.com/gohugoio/hugo/helpers"
   22 	"github.com/gohugoio/hugo/output"
   23 )
   24 
   25 const slash = "/"
   26 
   27 // TargetPathDescriptor describes how a file path for a given resource
   28 // should look like on the file system. The same descriptor is then later used to
   29 // create both the permalinks and the relative links, paginator URLs etc.
   30 //
   31 // The big motivating behind this is to have only one source of truth for URLs,
   32 // and by that also get rid of most of the fragile string parsing/encoding etc.
   33 //
   34 //
   35 type TargetPathDescriptor struct {
   36 	PathSpec *helpers.PathSpec
   37 
   38 	Type output.Format
   39 	Kind string
   40 
   41 	Sections []string
   42 
   43 	// For regular content pages this is either
   44 	// 1) the Slug, if set,
   45 	// 2) the file base name (TranslationBaseName).
   46 	BaseName string
   47 
   48 	// Source directory.
   49 	Dir string
   50 
   51 	// Typically a language prefix added to file paths.
   52 	PrefixFilePath string
   53 
   54 	// Typically a language prefix added to links.
   55 	PrefixLink string
   56 
   57 	// If in multihost mode etc., every link/path needs to be prefixed, even
   58 	// if set in URL.
   59 	ForcePrefix bool
   60 
   61 	// URL from front matter if set. Will override any Slug etc.
   62 	URL string
   63 
   64 	// Used to create paginator links.
   65 	Addends string
   66 
   67 	// The expanded permalink if defined for the section, ready to use.
   68 	ExpandedPermalink string
   69 
   70 	// Some types cannot have uglyURLs, even if globally enabled, RSS being one example.
   71 	UglyURLs bool
   72 }
   73 
   74 // TODO(bep) move this type.
   75 type TargetPaths struct {
   76 
   77 	// Where to store the file on disk relative to the publish dir. OS slashes.
   78 	TargetFilename string
   79 
   80 	// The directory to write sub-resources of the above.
   81 	SubResourceBaseTarget string
   82 
   83 	// The base for creating links to sub-resources of the above.
   84 	SubResourceBaseLink string
   85 
   86 	// The relative permalink to this resources. Unix slashes.
   87 	Link string
   88 }
   89 
   90 func (p TargetPaths) RelPermalink(s *helpers.PathSpec) string {
   91 	return s.PrependBasePath(p.Link, false)
   92 }
   93 
   94 func (p TargetPaths) PermalinkForOutputFormat(s *helpers.PathSpec, f output.Format) string {
   95 	var baseURL string
   96 	var err error
   97 	if f.Protocol != "" {
   98 		baseURL, err = s.BaseURL.WithProtocol(f.Protocol)
   99 		if err != nil {
  100 			return ""
  101 		}
  102 	} else {
  103 		baseURL = s.BaseURL.String()
  104 	}
  105 
  106 	return s.PermalinkForBaseURL(p.Link, baseURL)
  107 }
  108 
  109 func isHtmlIndex(s string) bool {
  110 	return strings.HasSuffix(s, "/index.html")
  111 }
  112 
  113 func CreateTargetPaths(d TargetPathDescriptor) (tp TargetPaths) {
  114 	if d.Type.Name == "" {
  115 		panic("CreateTargetPath: missing type")
  116 	}
  117 
  118 	// Normalize all file Windows paths to simplify what's next.
  119 	if helpers.FilePathSeparator != slash {
  120 		d.Dir = filepath.ToSlash(d.Dir)
  121 		d.PrefixFilePath = filepath.ToSlash(d.PrefixFilePath)
  122 
  123 	}
  124 
  125 	if d.URL != "" && !strings.HasPrefix(d.URL, "/") {
  126 		// Treat this as a context relative URL
  127 		d.ForcePrefix = true
  128 	}
  129 
  130 	pagePath := slash
  131 	fullSuffix := d.Type.MediaType.FirstSuffix.FullSuffix
  132 
  133 	var (
  134 		pagePathDir string
  135 		link        string
  136 		linkDir     string
  137 	)
  138 
  139 	// The top level index files, i.e. the home page etc., needs
  140 	// the index base even when uglyURLs is enabled.
  141 	needsBase := true
  142 
  143 	isUgly := d.UglyURLs && !d.Type.NoUgly
  144 	baseNameSameAsType := d.BaseName != "" && d.BaseName == d.Type.BaseName
  145 
  146 	if d.ExpandedPermalink == "" && baseNameSameAsType {
  147 		isUgly = true
  148 	}
  149 
  150 	if d.Kind != KindPage && d.URL == "" && len(d.Sections) > 0 {
  151 		if d.ExpandedPermalink != "" {
  152 			pagePath = pjoin(pagePath, d.ExpandedPermalink)
  153 		} else {
  154 			pagePath = pjoin(d.Sections...)
  155 		}
  156 		needsBase = false
  157 	}
  158 
  159 	if d.Type.Path != "" {
  160 		pagePath = pjoin(pagePath, d.Type.Path)
  161 	}
  162 
  163 	if d.Kind != KindHome && d.URL != "" {
  164 		pagePath = pjoin(pagePath, d.URL)
  165 
  166 		if d.Addends != "" {
  167 			pagePath = pjoin(pagePath, d.Addends)
  168 		}
  169 
  170 		pagePathDir = pagePath
  171 		link = pagePath
  172 		hasDot := strings.Contains(d.URL, ".")
  173 		hasSlash := strings.HasSuffix(d.URL, slash)
  174 
  175 		if hasSlash || !hasDot {
  176 			pagePath = pjoin(pagePath, d.Type.BaseName+fullSuffix)
  177 		} else if hasDot {
  178 			pagePathDir = path.Dir(pagePathDir)
  179 		}
  180 
  181 		if !isHtmlIndex(pagePath) {
  182 			link = pagePath
  183 		} else if !hasSlash {
  184 			link += slash
  185 		}
  186 
  187 		linkDir = pagePathDir
  188 
  189 		if d.ForcePrefix {
  190 
  191 			// Prepend language prefix if not already set in URL
  192 			if d.PrefixFilePath != "" && !strings.HasPrefix(d.URL, slash+d.PrefixFilePath) {
  193 				pagePath = pjoin(d.PrefixFilePath, pagePath)
  194 				pagePathDir = pjoin(d.PrefixFilePath, pagePathDir)
  195 			}
  196 
  197 			if d.PrefixLink != "" && !strings.HasPrefix(d.URL, slash+d.PrefixLink) {
  198 				link = pjoin(d.PrefixLink, link)
  199 				linkDir = pjoin(d.PrefixLink, linkDir)
  200 			}
  201 		}
  202 
  203 	} else if d.Kind == KindPage {
  204 
  205 		if d.ExpandedPermalink != "" {
  206 			pagePath = pjoin(pagePath, d.ExpandedPermalink)
  207 		} else {
  208 			if d.Dir != "" {
  209 				pagePath = pjoin(pagePath, d.Dir)
  210 			}
  211 			if d.BaseName != "" {
  212 				pagePath = pjoin(pagePath, d.BaseName)
  213 			}
  214 		}
  215 
  216 		if d.Addends != "" {
  217 			pagePath = pjoin(pagePath, d.Addends)
  218 		}
  219 
  220 		link = pagePath
  221 
  222 		// TODO(bep) this should not happen after the fix in https://github.com/gohugoio/hugo/issues/4870
  223 		// but we may need some more testing before we can remove it.
  224 		if baseNameSameAsType {
  225 			link = strings.TrimSuffix(link, d.BaseName)
  226 		}
  227 
  228 		pagePathDir = link
  229 		link = link + slash
  230 		linkDir = pagePathDir
  231 
  232 		if isUgly {
  233 			pagePath = addSuffix(pagePath, fullSuffix)
  234 		} else {
  235 			pagePath = pjoin(pagePath, d.Type.BaseName+fullSuffix)
  236 		}
  237 
  238 		if !isHtmlIndex(pagePath) {
  239 			link = pagePath
  240 		}
  241 
  242 		if d.PrefixFilePath != "" {
  243 			pagePath = pjoin(d.PrefixFilePath, pagePath)
  244 			pagePathDir = pjoin(d.PrefixFilePath, pagePathDir)
  245 		}
  246 
  247 		if d.PrefixLink != "" {
  248 			link = pjoin(d.PrefixLink, link)
  249 			linkDir = pjoin(d.PrefixLink, linkDir)
  250 		}
  251 
  252 	} else {
  253 		if d.Addends != "" {
  254 			pagePath = pjoin(pagePath, d.Addends)
  255 		}
  256 
  257 		needsBase = needsBase && d.Addends == ""
  258 
  259 		// No permalink expansion etc. for node type pages (for now)
  260 		base := ""
  261 
  262 		if needsBase || !isUgly {
  263 			base = d.Type.BaseName
  264 		}
  265 
  266 		pagePathDir = pagePath
  267 		link = pagePath
  268 		linkDir = pagePathDir
  269 
  270 		if base != "" {
  271 			pagePath = path.Join(pagePath, addSuffix(base, fullSuffix))
  272 		} else {
  273 			pagePath = addSuffix(pagePath, fullSuffix)
  274 		}
  275 
  276 		if !isHtmlIndex(pagePath) {
  277 			link = pagePath
  278 		} else {
  279 			link += slash
  280 		}
  281 
  282 		if d.PrefixFilePath != "" {
  283 			pagePath = pjoin(d.PrefixFilePath, pagePath)
  284 			pagePathDir = pjoin(d.PrefixFilePath, pagePathDir)
  285 		}
  286 
  287 		if d.PrefixLink != "" {
  288 			link = pjoin(d.PrefixLink, link)
  289 			linkDir = pjoin(d.PrefixLink, linkDir)
  290 		}
  291 	}
  292 
  293 	pagePath = pjoin(slash, pagePath)
  294 	pagePathDir = strings.TrimSuffix(path.Join(slash, pagePathDir), slash)
  295 
  296 	hadSlash := strings.HasSuffix(link, slash)
  297 	link = strings.Trim(link, slash)
  298 	if hadSlash {
  299 		link += slash
  300 	}
  301 
  302 	if !strings.HasPrefix(link, slash) {
  303 		link = slash + link
  304 	}
  305 
  306 	linkDir = strings.TrimSuffix(path.Join(slash, linkDir), slash)
  307 
  308 	// if page URL is explicitly set in frontmatter,
  309 	// preserve its value without sanitization
  310 	if d.Kind != KindPage || d.URL == "" {
  311 		// Note: MakePathSanitized will lower case the path if
  312 		// disablePathToLower isn't set.
  313 		pagePath = d.PathSpec.MakePathSanitized(pagePath)
  314 		pagePathDir = d.PathSpec.MakePathSanitized(pagePathDir)
  315 		link = d.PathSpec.MakePathSanitized(link)
  316 		linkDir = d.PathSpec.MakePathSanitized(linkDir)
  317 	}
  318 
  319 	tp.TargetFilename = filepath.FromSlash(pagePath)
  320 	tp.SubResourceBaseTarget = filepath.FromSlash(pagePathDir)
  321 	tp.SubResourceBaseLink = linkDir
  322 	tp.Link = d.PathSpec.URLizeFilename(link)
  323 	if tp.Link == "" {
  324 		tp.Link = slash
  325 	}
  326 
  327 	return
  328 }
  329 
  330 func addSuffix(s, suffix string) string {
  331 	return strings.Trim(s, slash) + suffix
  332 }
  333 
  334 // Like path.Join, but preserves one trailing slash if present.
  335 func pjoin(elem ...string) string {
  336 	hadSlash := strings.HasSuffix(elem[len(elem)-1], slash)
  337 	joined := path.Join(elem...)
  338 	if hadSlash && !strings.HasSuffix(joined, slash) {
  339 		return joined + slash
  340 	}
  341 	return joined
  342 }