github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/resources/page/page_paths.go (about)

     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  }