github.com/fighterlyt/hugo@v0.47.1/hugolib/page_bundler_handlers.go (about) 1 // Copyright 2017-present 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 hugolib 15 16 import ( 17 "errors" 18 "fmt" 19 "path/filepath" 20 "sort" 21 22 "strings" 23 24 "github.com/gohugoio/hugo/helpers" 25 "github.com/gohugoio/hugo/resource" 26 ) 27 28 var ( 29 // This should be the only list of valid extensions for content files. 30 contentFileExtensions = []string{ 31 "html", "htm", 32 "mdown", "markdown", "md", 33 "asciidoc", "adoc", "ad", 34 "rest", "rst", 35 "mmark", 36 "org", 37 "pandoc", "pdc"} 38 39 contentFileExtensionsSet map[string]bool 40 ) 41 42 func init() { 43 contentFileExtensionsSet = make(map[string]bool) 44 for _, ext := range contentFileExtensions { 45 contentFileExtensionsSet[ext] = true 46 } 47 } 48 49 func newHandlerChain(s *Site) contentHandler { 50 c := &contentHandlers{s: s} 51 52 contentFlow := c.parsePage(c.processFirstMatch( 53 // Handles all files with a content file extension. See above. 54 c.handlePageContent(), 55 56 // Every HTML file without front matter will be passed on to this handler. 57 c.handleHTMLContent(), 58 )) 59 60 c.rootHandler = c.processFirstMatch( 61 contentFlow, 62 63 // Creates a file resource (image, CSS etc.) if there is a parent 64 // page set on the current context. 65 c.createResource(), 66 67 // Everything that isn't handled above, will just be copied 68 // to destination. 69 c.copyFile(), 70 ) 71 72 return c.rootHandler 73 74 } 75 76 type contentHandlers struct { 77 s *Site 78 rootHandler contentHandler 79 } 80 81 func (c *contentHandlers) processFirstMatch(handlers ...contentHandler) func(ctx *handlerContext) handlerResult { 82 return func(ctx *handlerContext) handlerResult { 83 for _, h := range handlers { 84 res := h(ctx) 85 if res.handled || res.err != nil { 86 return res 87 } 88 } 89 return handlerResult{err: errors.New("no matching handler found")} 90 } 91 } 92 93 type handlerContext struct { 94 // These are the pages stored in Site. 95 pages chan<- *Page 96 97 doNotAddToSiteCollections bool 98 99 currentPage *Page 100 parentPage *Page 101 102 bundle *bundleDir 103 104 source *fileInfo 105 106 // Relative path to the target. 107 target string 108 } 109 110 func (c *handlerContext) ext() string { 111 if c.currentPage != nil { 112 if c.currentPage.Markup != "" { 113 return c.currentPage.Markup 114 } 115 return c.currentPage.Ext() 116 } 117 118 if c.bundle != nil { 119 return c.bundle.fi.Ext() 120 } else { 121 return c.source.Ext() 122 } 123 } 124 125 func (c *handlerContext) targetPath() string { 126 if c.target != "" { 127 return c.target 128 } 129 130 return c.source.Filename() 131 } 132 133 func (c *handlerContext) file() *fileInfo { 134 if c.bundle != nil { 135 return c.bundle.fi 136 } 137 138 return c.source 139 } 140 141 // Create a copy with the current context as its parent. 142 func (c handlerContext) childCtx(fi *fileInfo) *handlerContext { 143 if c.currentPage == nil { 144 panic("Need a Page to create a child context") 145 } 146 147 c.target = strings.TrimPrefix(fi.Path(), c.bundle.fi.Dir()) 148 c.source = fi 149 150 c.doNotAddToSiteCollections = c.bundle != nil && c.bundle.tp != bundleBranch 151 152 c.bundle = nil 153 154 c.parentPage = c.currentPage 155 c.currentPage = nil 156 157 return &c 158 } 159 160 func (c *handlerContext) supports(exts ...string) bool { 161 ext := c.ext() 162 for _, s := range exts { 163 if s == ext { 164 return true 165 } 166 } 167 168 return false 169 } 170 171 func (c *handlerContext) isContentFile() bool { 172 return contentFileExtensionsSet[c.ext()] 173 } 174 175 type ( 176 handlerResult struct { 177 err error 178 handled bool 179 resource resource.Resource 180 } 181 182 contentHandler func(ctx *handlerContext) handlerResult 183 ) 184 185 var ( 186 notHandled handlerResult 187 ) 188 189 func (c *contentHandlers) parsePage(h contentHandler) contentHandler { 190 return func(ctx *handlerContext) handlerResult { 191 if !ctx.isContentFile() { 192 return notHandled 193 } 194 195 result := handlerResult{handled: true} 196 fi := ctx.file() 197 198 f, err := fi.Open() 199 if err != nil { 200 return handlerResult{err: fmt.Errorf("(%s) failed to open content file: %s", fi.Filename(), err)} 201 } 202 defer f.Close() 203 204 p := c.s.newPageFromFile(fi) 205 206 _, err = p.ReadFrom(f) 207 if err != nil { 208 return handlerResult{err: err} 209 } 210 211 if !p.shouldBuild() { 212 if !ctx.doNotAddToSiteCollections { 213 ctx.pages <- p 214 } 215 return result 216 } 217 218 ctx.currentPage = p 219 220 if ctx.bundle != nil { 221 // Add the bundled files 222 for _, fi := range ctx.bundle.resources { 223 childCtx := ctx.childCtx(fi) 224 res := c.rootHandler(childCtx) 225 if res.err != nil { 226 return res 227 } 228 if res.resource != nil { 229 if pageResource, ok := res.resource.(*Page); ok { 230 pageResource.resourcePath = filepath.ToSlash(childCtx.target) 231 pageResource.parent = p 232 } 233 p.Resources = append(p.Resources, res.resource) 234 } 235 } 236 237 sort.SliceStable(p.Resources, func(i, j int) bool { 238 if p.Resources[i].ResourceType() < p.Resources[j].ResourceType() { 239 return true 240 } 241 242 p1, ok1 := p.Resources[i].(*Page) 243 p2, ok2 := p.Resources[j].(*Page) 244 245 if ok1 != ok2 { 246 return ok2 247 } 248 249 if ok1 { 250 return defaultPageSort(p1, p2) 251 } 252 253 return p.Resources[i].RelPermalink() < p.Resources[j].RelPermalink() 254 }) 255 256 // Assign metadata from front matter if set 257 if len(p.resourcesMetadata) > 0 { 258 resource.AssignMetadata(p.resourcesMetadata, p.Resources...) 259 } 260 261 } 262 263 return h(ctx) 264 } 265 } 266 267 func (c *contentHandlers) handlePageContent() contentHandler { 268 return func(ctx *handlerContext) handlerResult { 269 if ctx.supports("html", "htm") { 270 return notHandled 271 } 272 273 p := ctx.currentPage 274 275 // Work on a copy of the raw content from now on. 276 p.createWorkContentCopy() 277 278 if err := p.processShortcodes(); err != nil { 279 p.s.Log.ERROR.Println(err) 280 } 281 282 if c.s.Cfg.GetBool("enableEmoji") { 283 p.workContent = helpers.Emojify(p.workContent) 284 } 285 286 p.workContent = p.replaceDivider(p.workContent) 287 p.workContent = p.renderContent(p.workContent) 288 289 tmpContent, tmpTableOfContents := helpers.ExtractTOC(p.workContent) 290 p.TableOfContents = helpers.BytesToHTML(tmpTableOfContents) 291 p.workContent = tmpContent 292 293 if !ctx.doNotAddToSiteCollections { 294 ctx.pages <- p 295 } 296 297 return handlerResult{handled: true, resource: p} 298 } 299 } 300 301 func (c *contentHandlers) handleHTMLContent() contentHandler { 302 return func(ctx *handlerContext) handlerResult { 303 if !ctx.supports("html", "htm") { 304 return notHandled 305 } 306 307 p := ctx.currentPage 308 309 p.createWorkContentCopy() 310 311 if err := p.processShortcodes(); err != nil { 312 p.s.Log.ERROR.Println(err) 313 } 314 315 if !ctx.doNotAddToSiteCollections { 316 ctx.pages <- p 317 } 318 319 return handlerResult{handled: true, resource: p} 320 } 321 } 322 323 func (c *contentHandlers) createResource() contentHandler { 324 return func(ctx *handlerContext) handlerResult { 325 if ctx.parentPage == nil { 326 return notHandled 327 } 328 329 resource, err := c.s.ResourceSpec.New( 330 resource.ResourceSourceDescriptor{ 331 TargetPathBuilder: ctx.parentPage.subResourceTargetPathFactory, 332 SourceFile: ctx.source, 333 RelTargetFilename: ctx.target, 334 URLBase: c.s.GetURLLanguageBasePath(), 335 TargetBasePaths: []string{c.s.GetTargetLanguageBasePath()}, 336 }) 337 338 return handlerResult{err: err, handled: true, resource: resource} 339 } 340 } 341 342 func (c *contentHandlers) copyFile() contentHandler { 343 return func(ctx *handlerContext) handlerResult { 344 f, err := c.s.BaseFs.Content.Fs.Open(ctx.source.Filename()) 345 if err != nil { 346 err := fmt.Errorf("failed to open file in copyFile: %s", err) 347 return handlerResult{err: err} 348 } 349 350 target := ctx.targetPath() 351 352 defer f.Close() 353 if err := c.s.publish(&c.s.PathSpec.ProcessingStats.Files, target, f); err != nil { 354 return handlerResult{err: err} 355 } 356 357 return handlerResult{handled: true} 358 } 359 }