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