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 }