2017-03-06 13:16:31 -05:00
// Copyright 2017 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
import (
2017-03-22 04:54:56 -04:00
"fmt"
2017-03-19 10:25:32 -04:00
"html/template"
2017-03-21 19:25:55 -04:00
"strings"
2017-03-09 13:19:29 -05:00
"sync"
2017-03-22 06:03:42 -04:00
"github.com/spf13/hugo/media"
2017-03-26 13:34:30 -04:00
"github.com/spf13/hugo/helpers"
2017-03-06 13:16:31 -05:00
"github.com/spf13/hugo/output"
)
// PageOutput represents one of potentially many output formats of a given
// Page.
type PageOutput struct {
* Page
2017-03-09 13:19:29 -05:00
// Pagination
paginator * Pager
paginatorInit sync . Once
// Keep this to create URL/path variations, i.e. paginators.
targetPathDescriptor targetPathDescriptor
2017-03-16 03:32:14 -04:00
outputFormat output . Format
2017-03-06 13:16:31 -05:00
}
2017-03-09 13:19:29 -05:00
func ( p * PageOutput ) targetPath ( addends ... string ) ( string , error ) {
2017-03-16 03:32:14 -04:00
tp , err := p . createTargetPath ( p . outputFormat , addends ... )
2017-03-09 13:19:29 -05:00
if err != nil {
return "" , err
}
return tp , nil
}
2017-03-16 03:32:14 -04:00
func newPageOutput ( p * Page , createCopy bool , f output . Format ) ( * PageOutput , error ) {
2017-03-25 13:28:38 -04:00
// TODO(bep) This is only needed for tests and we should get rid of it.
2017-03-17 11:35:09 -04:00
if p . targetPathDescriptorPrototype == nil {
if err := p . initTargetPathDescriptor ( ) ; err != nil {
return nil , err
}
if err := p . initURLs ( ) ; err != nil {
return nil , err
}
}
2017-03-07 03:55:17 -05:00
if createCopy {
p = p . copy ( )
}
2017-03-09 13:19:29 -05:00
2017-03-16 03:32:14 -04:00
td , err := p . createTargetPathDescriptor ( f )
2017-03-09 13:19:29 -05:00
if err != nil {
return nil , err
}
return & PageOutput {
Page : p ,
2017-03-16 03:32:14 -04:00
outputFormat : f ,
2017-03-09 13:19:29 -05:00
targetPathDescriptor : td ,
} , nil
2017-03-06 13:16:31 -05:00
}
// copy creates a copy of this PageOutput with the lazy sync.Once vars reset
// so they will be evaluated again, for word count calculations etc.
func ( p * PageOutput ) copy ( ) * PageOutput {
2017-03-16 03:32:14 -04:00
c , err := newPageOutput ( p . Page , true , p . outputFormat )
2017-03-09 13:19:29 -05:00
if err != nil {
panic ( err )
}
return c
2017-03-06 13:16:31 -05:00
}
2017-03-19 10:25:32 -04:00
2017-03-26 13:34:30 -04:00
func ( p * PageOutput ) layouts ( layouts ... string ) ( [ ] string , error ) {
2017-03-25 13:28:38 -04:00
if len ( layouts ) == 0 && p . selfLayout != "" {
2017-03-26 13:34:30 -04:00
return [ ] string { p . selfLayout } , nil
2017-03-19 10:25:32 -04:00
}
layoutOverride := ""
if len ( layouts ) > 0 {
layoutOverride = layouts [ 0 ]
}
return p . s . layoutHandler . For (
p . layoutDescriptor ,
layoutOverride ,
p . outputFormat )
}
func ( p * PageOutput ) Render ( layout ... string ) template . HTML {
2017-03-26 12:51:12 -04:00
if ! p . checkRender ( ) {
2017-03-29 02:08:45 -04:00
return ""
2017-03-26 12:51:12 -04:00
}
2017-03-26 13:34:30 -04:00
l , err := p . layouts ( layout ... )
if err != nil {
helpers . DistinctErrorLog . Printf ( "in .Render: Failed to resolve layout %q for page %q" , layout , p . pathOrTitle ( ) )
2017-03-27 14:43:49 -04:00
return ""
2017-03-26 13:34:30 -04:00
}
2017-03-27 14:43:49 -04:00
for _ , layout := range l {
templ := p . s . Tmpl . Lookup ( layout )
if templ == nil {
// This is legacy from when we had only one output format and
// HTML templates only. Some have references to layouts without suffix.
// We default to good old HTML.
templ = p . s . Tmpl . Lookup ( layout + ".html" )
}
if templ != nil {
res , err := templ . ExecuteToString ( p )
if err != nil {
helpers . DistinctErrorLog . Printf ( "in .Render: Failed to execute template %q for page %q" , layout , p . pathOrTitle ( ) )
return template . HTML ( "" )
}
return template . HTML ( res )
}
}
return ""
2017-03-19 10:25:32 -04:00
}
func ( p * Page ) Render ( layout ... string ) template . HTML {
2017-03-26 12:51:12 -04:00
if ! p . checkRender ( ) {
2017-03-29 02:08:45 -04:00
return ""
2017-03-26 12:51:12 -04:00
}
2017-03-26 05:45:12 -04:00
p . pageOutputInit . Do ( func ( ) {
// If Render is called in a range loop, the page output isn't available.
// So, create one.
outFormat := p . outputFormats [ 0 ]
pageOutput , err := newPageOutput ( p , true , outFormat )
if err != nil {
2017-03-26 13:34:30 -04:00
p . s . Log . ERROR . Printf ( "Failed to create output page for type %q for page %q: %s" , outFormat . Name , p . pathOrTitle ( ) , err )
2017-03-26 05:45:12 -04:00
return
}
p . mainPageOutput = pageOutput
} )
2017-03-19 10:25:32 -04:00
return p . mainPageOutput . Render ( layout ... )
}
2017-03-21 19:25:55 -04:00
2017-03-26 12:51:12 -04:00
// We may fix this in the future, but the layout handling in Render isn't built
// for list pages.
func ( p * Page ) checkRender ( ) bool {
if p . Kind != KindPage {
2017-03-29 02:08:45 -04:00
helpers . DistinctWarnLog . Printf ( ".Render only available for regular pages, not for of kind %q. You probably meant .Site.RegularPages and not.Site.Pages." , p . Kind )
2017-03-26 12:51:12 -04:00
return false
}
return true
}
2017-03-21 19:25:55 -04:00
// OutputFormats holds a list of the relevant output formats for a given resource.
type OutputFormats [ ] * OutputFormat
// And OutputFormat links to a representation of a resource.
type OutputFormat struct {
2017-03-22 04:54:56 -04:00
// Rel constains a value that can be used to construct a rel link.
// This is value is fetched from the output format definition.
// Note that for pages with only one output format,
// this method will always return "canonical".
// TODO(bep) output -- the above may not be correct for CSS etc. Figure out a way around that.
// TODO(bep) output -- re the above, maybe add a "alternate" filter to AlternativeOutputFormats.
// As an example, the AMP output format will, by default, return "amphtml".
//
// See:
// https://www.ampproject.org/docs/guides/deploy/discovery
//
// Most other output formats will have "alternate" as value for this.
Rel string
// It may be tempting to export this, but let us hold on to that horse for a while.
2017-03-21 19:25:55 -04:00
f output . Format
2017-03-22 06:03:42 -04:00
2017-03-21 19:25:55 -04:00
p * Page
}
2017-03-22 04:54:56 -04:00
// Name returns this OutputFormat's name, i.e. HTML, AMP, JSON etc.
func ( o OutputFormat ) Name ( ) string {
return o . f . Name
}
2017-03-22 06:03:42 -04:00
// MediaType returns this OutputFormat's MediaType (MIME type).
func ( o OutputFormat ) MediaType ( ) media . Type {
return o . f . MediaType
}
2017-03-21 19:25:55 -04:00
// OutputFormats gives the output formats for this Page.
func ( p * Page ) OutputFormats ( ) OutputFormats {
var o OutputFormats
for _ , f := range p . outputFormats {
2017-03-24 06:25:25 -04:00
o = append ( o , newOutputFormat ( p , f ) )
2017-03-21 19:25:55 -04:00
}
return o
}
2017-03-24 06:25:25 -04:00
func newOutputFormat ( p * Page , f output . Format ) * OutputFormat {
rel := f . Rel
isCanonical := len ( p . outputFormats ) == 1
if isCanonical {
rel = "canonical"
}
return & OutputFormat { Rel : rel , f : f , p : p }
}
2017-03-22 04:54:56 -04:00
// OutputFormats gives the alternative output formats for this PageOutput.
func ( p * PageOutput ) AlternativeOutputFormats ( ) ( OutputFormats , error ) {
var o OutputFormats
for _ , of := range p . OutputFormats ( ) {
if of . f == p . outputFormat {
continue
}
o = append ( o , of )
}
return o , nil
}
// AlternativeOutputFormats is only available on the top level rendering
// entry point, and not inside range loops on the Page collections.
// This method is just here to inform users of that restriction.
func ( p * Page ) AlternativeOutputFormats ( ) ( OutputFormats , error ) {
return nil , fmt . Errorf ( "AlternativeOutputFormats only available from the top level template context for page %q" , p . Path ( ) )
}
2017-03-21 19:25:55 -04:00
// Get gets a OutputFormat given its name, i.e. json, html etc.
// It returns nil if not found.
func ( o OutputFormats ) Get ( name string ) * OutputFormat {
name = strings . ToLower ( name )
for _ , f := range o {
if strings . ToLower ( f . f . Name ) == name {
return f
}
}
return nil
}
// Permalink returns the absolute permalink to this output format.
func ( o * OutputFormat ) Permalink ( ) string {
rel := o . p . createRelativePermalinkForOutputFormat ( o . f )
2017-03-23 15:05:10 -04:00
perm , _ := o . p . s . permalinkForOutputFormat ( rel , o . f )
return perm
2017-03-21 19:25:55 -04:00
}
// Permalink returns the relative permalink to this output format.
func ( o * OutputFormat ) RelPermalink ( ) string {
rel := o . p . createRelativePermalinkForOutputFormat ( o . f )
return o . p . s . PathSpec . PrependBasePath ( rel )
}