URI: 
       page_paths.go - hugo - [fork] hugo port for 9front
  HTML git clone https://git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
       page_paths.go (10476B)
       ---
            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         "sync"
           21 
           22         "github.com/gohugoio/hugo/common/paths"
           23         "github.com/gohugoio/hugo/common/urls"
           24         "github.com/gohugoio/hugo/helpers"
           25         "github.com/gohugoio/hugo/output"
           26         "github.com/gohugoio/hugo/resources/kinds"
           27 )
           28 
           29 const slash = "/"
           30 
           31 // TargetPathDescriptor describes how a file path for a given resource
           32 // should look like on the file system. The same descriptor is then later used to
           33 // create both the permalinks and the relative links, paginator URLs etc.
           34 //
           35 // The big motivating behind this is to have only one source of truth for URLs,
           36 // and by that also get rid of most of the fragile string parsing/encoding etc.
           37 
           38 type TargetPathDescriptor struct {
           39         PathSpec *helpers.PathSpec
           40 
           41         Type output.Format
           42         Kind string
           43 
           44         Path    *paths.Path
           45         Section *paths.Path
           46 
           47         // For regular content pages this is either
           48         // 1) the Slug, if set,
           49         // 2) the file base name (TranslationBaseName).
           50         BaseName 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         // 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 urls.BaseURL
           96         var err error
           97         if f.Protocol != "" {
           98                 baseURL, err = s.Cfg.BaseURL().WithProtocol(f.Protocol)
           99                 if err != nil {
          100                         return ""
          101                 }
          102         } else {
          103                 baseURL = s.Cfg.BaseURL()
          104         }
          105         baseURLstr := baseURL.String()
          106         return s.PermalinkForBaseURL(p.Link, baseURLstr)
          107 }
          108 
          109 func CreateTargetPaths(d TargetPathDescriptor) (tp TargetPaths) {
          110         // Normalize all file Windows paths to simplify what's next.
          111         if helpers.FilePathSeparator != "/" {
          112                 d.PrefixFilePath = filepath.ToSlash(d.PrefixFilePath)
          113         }
          114 
          115         if !d.Type.Root && d.URL != "" && !strings.HasPrefix(d.URL, "/") {
          116                 // Treat this as a context relative URL
          117                 d.ForcePrefix = true
          118         }
          119 
          120         if d.URL != "" {
          121                 d.URL = filepath.ToSlash(d.URL)
          122                 if strings.Contains(d.URL, "..") {
          123                         d.URL = path.Join("/", d.URL)
          124                 }
          125         }
          126 
          127         if d.Type.Root && !d.ForcePrefix {
          128                 d.PrefixFilePath = ""
          129                 d.PrefixLink = ""
          130         }
          131 
          132         pb := getPagePathBuilder(d)
          133         defer putPagePathBuilder(pb)
          134 
          135         pb.fullSuffix = d.Type.MediaType.FirstSuffix.FullSuffix
          136 
          137         // The top level index files, i.e. the home page etc., needs
          138         // the index base even when uglyURLs is enabled.
          139         needsBase := true
          140 
          141         pb.isUgly = (d.UglyURLs || d.Type.Ugly) && !d.Type.NoUgly
          142         pb.baseNameSameAsType = !d.Path.IsBundle() && d.BaseName != "" && d.BaseName == d.Type.BaseName
          143         indexIsUglyKind := d.Kind == kinds.KindHome || d.Kind == kinds.KindSection || d.Kind == kinds.KindTaxonomy
          144         indexIsUglyKind = indexIsUglyKind && pb.isUgly
          145 
          146         if d.ExpandedPermalink == "" && pb.baseNameSameAsType {
          147                 pb.isUgly = true
          148         }
          149 
          150         if d.Type.Path != "" {
          151                 pb.Add(d.Type.Path)
          152         }
          153 
          154         if d.Type == output.HTTPStatus404HTMLFormat || d.Type == output.SitemapFormat || d.Type == output.RobotsTxtFormat {
          155                 pb.noSubResources = true
          156         } else if d.Kind != kinds.KindPage && d.URL == "" && d.Section.Base() != "/" {
          157                 if d.ExpandedPermalink != "" {
          158                         pb.Add(d.ExpandedPermalink)
          159                 } else {
          160                         pb.Add(d.Section.Base())
          161                 }
          162                 needsBase = false
          163         }
          164 
          165         if d.Kind != kinds.KindHome && d.URL != "" {
          166                 pb.Add(paths.FieldsSlash(d.URL)...)
          167 
          168                 if d.Addends != "" {
          169                         pb.Add(d.Addends)
          170                 }
          171 
          172                 hasDot := strings.Contains(d.URL, ".")
          173                 hasSlash := strings.HasSuffix(d.URL, "/")
          174 
          175                 if hasSlash || !hasDot {
          176                         pb.Add(d.Type.BaseName + pb.fullSuffix)
          177                 } else if hasDot {
          178                         pb.fullSuffix = paths.Ext(d.URL)
          179                 }
          180 
          181                 if pb.IsHtmlIndex() {
          182                         pb.linkUpperOffset = 1
          183                 }
          184 
          185                 if d.ForcePrefix {
          186 
          187                         // Prepend language prefix if not already set in URL
          188                         if d.PrefixFilePath != "" && !strings.HasPrefix(d.URL, "/"+d.PrefixFilePath) {
          189                                 pb.prefixPath = d.PrefixFilePath
          190                         }
          191 
          192                         if d.PrefixLink != "" && !strings.HasPrefix(d.URL, "/"+d.PrefixLink) {
          193                                 pb.prefixLink = d.PrefixLink
          194                         }
          195                 }
          196         } else if !kinds.IsBranch(d.Kind) {
          197                 if d.ExpandedPermalink != "" {
          198                         pb.Add(d.ExpandedPermalink)
          199                 } else {
          200                         if dir := d.Path.ContainerDir(); dir != "" {
          201                                 pb.Add(dir)
          202                         }
          203                         if d.BaseName != "" {
          204                                 pb.Add(d.BaseName)
          205                         } else {
          206                                 pb.Add(d.Path.BaseNameNoIdentifier())
          207                         }
          208                 }
          209 
          210                 if d.Addends != "" {
          211                         pb.Add(d.Addends)
          212                 }
          213 
          214                 if pb.isUgly {
          215                         pb.ConcatLast(pb.fullSuffix)
          216                 } else {
          217                         pb.Add(d.Type.BaseName + pb.fullSuffix)
          218                 }
          219 
          220                 if pb.IsHtmlIndex() {
          221                         pb.linkUpperOffset = 1
          222                 }
          223 
          224                 if d.PrefixFilePath != "" {
          225                         pb.prefixPath = d.PrefixFilePath
          226                 }
          227 
          228                 if d.PrefixLink != "" {
          229                         pb.prefixLink = d.PrefixLink
          230                 }
          231         } else {
          232                 if d.Addends != "" {
          233                         pb.Add(d.Addends)
          234                 }
          235 
          236                 needsBase = needsBase && d.Addends == ""
          237 
          238                 if needsBase || (!pb.isUgly || indexIsUglyKind) {
          239                         pb.Add(d.Type.BaseName + pb.fullSuffix)
          240                 } else {
          241                         pb.ConcatLast(pb.fullSuffix)
          242                 }
          243 
          244                 if !indexIsUglyKind && pb.IsHtmlIndex() {
          245                         pb.linkUpperOffset = 1
          246                 }
          247 
          248                 if d.PrefixFilePath != "" {
          249                         pb.prefixPath = d.PrefixFilePath
          250                 }
          251 
          252                 if d.PrefixLink != "" {
          253                         pb.prefixLink = d.PrefixLink
          254                 }
          255         }
          256 
          257         // if page URL is explicitly set in frontmatter,
          258         // preserve its value without sanitization
          259         if d.URL == "" {
          260                 // Note: MakePathSanitized will lower case the path if
          261                 // disablePathToLower isn't set.
          262                 pb.Sanitize()
          263         }
          264 
          265         link := pb.Link()
          266 
          267         pagePath := pb.PathFile()
          268 
          269         tp.TargetFilename = filepath.FromSlash(pagePath)
          270         if !pb.noSubResources {
          271                 tp.SubResourceBaseTarget = pb.PathDir()
          272                 tp.SubResourceBaseLink = pb.LinkDir()
          273         }
          274 
          275         // paths.{URL,Path}Escape rely on url.Parse which
          276         // will consider # a fragment identifier, so it and
          277         // and everything after it will be stripped from
          278         // `link`, so we need to escape it first.
          279         link = strings.ReplaceAll(link, "#", "%23")
          280 
          281         if d.URL != "" {
          282                 tp.Link = paths.URLEscape(link)
          283         } else {
          284                 // This is slightly faster for when we know we don't have any
          285                 // query or scheme etc.
          286                 tp.Link = paths.PathEscape(link)
          287         }
          288         if tp.Link == "" {
          289                 tp.Link = "/"
          290         }
          291 
          292         return
          293 }
          294 
          295 // When adding state here, remember to update putPagePathBuilder.
          296 type pagePathBuilder struct {
          297         els []string
          298 
          299         d TargetPathDescriptor
          300 
          301         // Builder state.
          302         isUgly             bool
          303         baseNameSameAsType bool
          304         noSubResources     bool
          305         fullSuffix         string // File suffix including any ".".
          306         prefixLink         string
          307         prefixPath         string
          308         linkUpperOffset    int
          309 }
          310 
          311 func (p *pagePathBuilder) Add(el ...string) {
          312         // Filter empty and slashes.
          313         n := 0
          314         for _, e := range el {
          315                 if e != "" && e != slash {
          316                         el[n] = e
          317                         n++
          318                 }
          319         }
          320         el = el[:n]
          321 
          322         p.els = append(p.els, el...)
          323 }
          324 
          325 func (p *pagePathBuilder) ConcatLast(s string) {
          326         if len(p.els) == 0 {
          327                 p.Add(s)
          328                 return
          329         }
          330         old := p.els[len(p.els)-1]
          331         if old == "" {
          332                 p.els[len(p.els)-1] = s
          333                 return
          334         }
          335         if old[len(old)-1] == '/' {
          336                 old = old[:len(old)-1]
          337         }
          338         p.els[len(p.els)-1] = old + s
          339 }
          340 
          341 func (p *pagePathBuilder) IsHtmlIndex() bool {
          342         return p.Last() == "index.html"
          343 }
          344 
          345 func (p *pagePathBuilder) Last() string {
          346         if p.els == nil {
          347                 return ""
          348         }
          349         return p.els[len(p.els)-1]
          350 }
          351 
          352 func (p *pagePathBuilder) Link() string {
          353         link := p.Path(p.linkUpperOffset)
          354 
          355         if p.baseNameSameAsType {
          356                 link = strings.TrimSuffix(link, p.d.BaseName)
          357         }
          358 
          359         if p.prefixLink != "" {
          360                 link = "/" + p.prefixLink + link
          361         }
          362 
          363         if p.linkUpperOffset > 0 && !strings.HasSuffix(link, "/") {
          364                 link += "/"
          365         }
          366 
          367         return link
          368 }
          369 
          370 func (p *pagePathBuilder) LinkDir() string {
          371         if p.noSubResources {
          372                 return ""
          373         }
          374 
          375         pathDir := p.PathDirBase()
          376 
          377         if p.prefixLink != "" {
          378                 pathDir = "/" + p.prefixLink + pathDir
          379         }
          380 
          381         return pathDir
          382 }
          383 
          384 func (p *pagePathBuilder) Path(upperOffset int) string {
          385         upper := len(p.els)
          386         if upperOffset > 0 {
          387                 upper -= upperOffset
          388         }
          389         pth := path.Join(p.els[:upper]...)
          390         return paths.AddLeadingSlash(pth)
          391 }
          392 
          393 func (p *pagePathBuilder) PathDir() string {
          394         dir := p.PathDirBase()
          395         if p.prefixPath != "" {
          396                 dir = "/" + p.prefixPath + dir
          397         }
          398         return dir
          399 }
          400 
          401 func (p *pagePathBuilder) PathDirBase() string {
          402         if p.noSubResources {
          403                 return ""
          404         }
          405 
          406         dir := p.Path(0)
          407         isIndex := strings.HasPrefix(p.Last(), p.d.Type.BaseName+".")
          408 
          409         if isIndex {
          410                 dir = paths.Dir(dir)
          411         } else {
          412                 dir = strings.TrimSuffix(dir, p.fullSuffix)
          413         }
          414 
          415         if dir == "/" {
          416                 dir = ""
          417         }
          418 
          419         return dir
          420 }
          421 
          422 func (p *pagePathBuilder) PathFile() string {
          423         dir := p.Path(0)
          424         if p.prefixPath != "" {
          425                 dir = "/" + p.prefixPath + dir
          426         }
          427         return dir
          428 }
          429 
          430 func (p *pagePathBuilder) Prepend(el ...string) {
          431         p.els = append(p.els[:0], append(el, p.els[0:]...)...)
          432 }
          433 
          434 func (p *pagePathBuilder) Sanitize() {
          435         for i, el := range p.els {
          436                 p.els[i] = p.d.PathSpec.MakePathSanitized(el)
          437         }
          438 }
          439 
          440 var pagePathBuilderPool = &sync.Pool{
          441         New: func() any {
          442                 return &pagePathBuilder{}
          443         },
          444 }
          445 
          446 func getPagePathBuilder(d TargetPathDescriptor) *pagePathBuilder {
          447         b := pagePathBuilderPool.Get().(*pagePathBuilder)
          448         b.d = d
          449         return b
          450 }
          451 
          452 func putPagePathBuilder(b *pagePathBuilder) {
          453         b.els = b.els[:0]
          454         b.fullSuffix = ""
          455         b.baseNameSameAsType = false
          456         b.isUgly = false
          457         b.noSubResources = false
          458         b.prefixLink = ""
          459         b.prefixPath = ""
          460         b.linkUpperOffset = 0
          461         pagePathBuilderPool.Put(b)
          462 }