mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
Make Page an interface
The main motivation of this commit is to add a `page.Page` interface to replace the very file-oriented `hugolib.Page` struct. This is all a preparation step for issue #5074, "pages from other data sources". But this also fixes a set of annoying limitations, especially related to custom output formats, and shortcodes. Most notable changes: * The inner content of shortcodes using the `{{%` as the outer-most delimiter will now be sent to the content renderer, e.g. Blackfriday. This means that any markdown will partake in the global ToC and footnote context etc. * The Custom Output formats are now "fully virtualized". This removes many of the current limitations. * The taxonomy list type now has a reference to the `Page` object. This improves the taxonomy template `.Title` situation and make common template constructs much simpler. See #5074 Fixes #5763 Fixes #5758 Fixes #5090 Fixes #5204 Fixes #4695 Fixes #5607 Fixes #5707 Fixes #5719 Fixes #3113 Fixes #5706 Fixes #5767 Fixes #5723 Fixes #5769 Fixes #5770 Fixes #5771 Fixes #5759 Fixes #5776 Fixes #5777 Fixes #5778
This commit is contained in:
81
resources/resource/dates.go
Normal file
81
resources/resource/dates.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2019 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 resource
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Dated = Dates{}
|
||||
|
||||
// Dated wraps a "dated resource". These are the 4 dates that makes
|
||||
// the date logic in Hugo.
|
||||
type Dated interface {
|
||||
Date() time.Time
|
||||
Lastmod() time.Time
|
||||
PublishDate() time.Time
|
||||
ExpiryDate() time.Time
|
||||
}
|
||||
|
||||
// Dates holds the 4 Hugo dates.
|
||||
type Dates struct {
|
||||
FDate time.Time
|
||||
FLastmod time.Time
|
||||
FPublishDate time.Time
|
||||
FExpiryDate time.Time
|
||||
}
|
||||
|
||||
func (d *Dates) UpdateDateAndLastmodIfAfter(in Dated) {
|
||||
if in.Date().After(d.Date()) {
|
||||
d.FDate = in.Date()
|
||||
}
|
||||
if in.Lastmod().After(d.Lastmod()) {
|
||||
d.FLastmod = in.Lastmod()
|
||||
}
|
||||
}
|
||||
|
||||
// IsFuture returns whether the argument represents the future.
|
||||
func IsFuture(d Dated) bool {
|
||||
if d.PublishDate().IsZero() {
|
||||
return false
|
||||
}
|
||||
return d.PublishDate().After(time.Now())
|
||||
}
|
||||
|
||||
// IsExpired returns whether the argument is expired.
|
||||
func IsExpired(d Dated) bool {
|
||||
if d.ExpiryDate().IsZero() {
|
||||
return false
|
||||
}
|
||||
return d.ExpiryDate().Before(time.Now())
|
||||
}
|
||||
|
||||
// IsZeroDates returns true if all of the dates are zero.
|
||||
func IsZeroDates(d Dated) bool {
|
||||
return d.Date().IsZero() && d.Lastmod().IsZero() && d.ExpiryDate().IsZero() && d.PublishDate().IsZero()
|
||||
}
|
||||
|
||||
func (p Dates) Date() time.Time {
|
||||
return p.FDate
|
||||
}
|
||||
|
||||
func (p Dates) Lastmod() time.Time {
|
||||
return p.FLastmod
|
||||
}
|
||||
|
||||
func (p Dates) PublishDate() time.Time {
|
||||
return p.FPublishDate
|
||||
}
|
||||
|
||||
func (p Dates) ExpiryDate() time.Time {
|
||||
return p.FExpiryDate
|
||||
}
|
89
resources/resource/params.go
Normal file
89
resources/resource/params.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2019 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 resource
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func Param(r ResourceParamsProvider, fallback map[string]interface{}, key interface{}) (interface{}, error) {
|
||||
keyStr, err := cast.ToStringE(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyStr = strings.ToLower(keyStr)
|
||||
result, _ := traverseDirectParams(r, fallback, keyStr)
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
keySegments := strings.Split(keyStr, ".")
|
||||
if len(keySegments) == 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return traverseNestedParams(r, fallback, keySegments)
|
||||
}
|
||||
|
||||
func traverseDirectParams(r ResourceParamsProvider, fallback map[string]interface{}, key string) (interface{}, error) {
|
||||
keyStr := strings.ToLower(key)
|
||||
if val, ok := r.Params()[keyStr]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if fallback == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return fallback[keyStr], nil
|
||||
}
|
||||
|
||||
func traverseNestedParams(r ResourceParamsProvider, fallback map[string]interface{}, keySegments []string) (interface{}, error) {
|
||||
result := traverseParams(keySegments, r.Params())
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if fallback != nil {
|
||||
result = traverseParams(keySegments, fallback)
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't find anything, but also no problems.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func traverseParams(keys []string, m map[string]interface{}) interface{} {
|
||||
// Shift first element off.
|
||||
firstKey, rest := keys[0], keys[1:]
|
||||
result := m[firstKey]
|
||||
|
||||
// No point in continuing here.
|
||||
if result == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
if len(rest) == 0 {
|
||||
// That was the last key.
|
||||
return result
|
||||
}
|
||||
|
||||
// That was not the last key.
|
||||
return traverseParams(rest, cast.ToStringMap(result))
|
||||
}
|
70
resources/resource/resource_helpers.go
Normal file
70
resources/resource/resource_helpers.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2019 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 resource
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// GetParam will return the param with the given key from the Resource,
|
||||
// nil if not found.
|
||||
func GetParam(r Resource, key string) interface{} {
|
||||
return getParam(r, key, false)
|
||||
}
|
||||
|
||||
// GetParamToLower is the same as GetParam but it will lower case any string
|
||||
// result, including string slices.
|
||||
func GetParamToLower(r Resource, key string) interface{} {
|
||||
return getParam(r, key, true)
|
||||
}
|
||||
|
||||
func getParam(r Resource, key string, stringToLower bool) interface{} {
|
||||
v := r.Params()[strings.ToLower(key)]
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch val := v.(type) {
|
||||
case bool:
|
||||
return val
|
||||
case string:
|
||||
if stringToLower {
|
||||
return strings.ToLower(val)
|
||||
}
|
||||
return val
|
||||
case int64, int32, int16, int8, int:
|
||||
return cast.ToInt(v)
|
||||
case float64, float32:
|
||||
return cast.ToFloat64(v)
|
||||
case time.Time:
|
||||
return val
|
||||
case []string:
|
||||
if stringToLower {
|
||||
return helpers.SliceToLower(val)
|
||||
}
|
||||
return v
|
||||
case map[string]interface{}: // JSON and TOML
|
||||
return v
|
||||
case map[interface{}]interface{}: // YAML
|
||||
return v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -14,6 +14,7 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
@@ -27,19 +28,32 @@ type Cloner interface {
|
||||
|
||||
// Resource represents a linkable resource, i.e. a content page, image etc.
|
||||
type Resource interface {
|
||||
resourceBase
|
||||
ResourceTypesProvider
|
||||
ResourceLinksProvider
|
||||
ResourceMetaProvider
|
||||
ResourceParamsProvider
|
||||
ResourceDataProvider
|
||||
}
|
||||
|
||||
// Permalink represents the absolute link to this resource.
|
||||
Permalink() string
|
||||
|
||||
// RelPermalink represents the host relative link to this resource.
|
||||
RelPermalink() string
|
||||
type ResourceTypesProvider interface {
|
||||
// MediaType is this resource's MIME type.
|
||||
MediaType() media.Type
|
||||
|
||||
// ResourceType is the resource type. For most file types, this is the main
|
||||
// part of the MIME type, e.g. "image", "application", "text" etc.
|
||||
// For content pages, this value is "page".
|
||||
ResourceType() string
|
||||
}
|
||||
|
||||
type ResourceLinksProvider interface {
|
||||
// Permalink represents the absolute link to this resource.
|
||||
Permalink() string
|
||||
|
||||
// RelPermalink represents the host relative link to this resource.
|
||||
RelPermalink() string
|
||||
}
|
||||
|
||||
type ResourceMetaProvider interface {
|
||||
// Name is the logical name of this resource. This can be set in the front matter
|
||||
// metadata for this resource. If not set, Hugo will assign a value.
|
||||
// This will in most cases be the base filename.
|
||||
@@ -50,20 +64,17 @@ type Resource interface {
|
||||
|
||||
// Title returns the title if set in front matter. For content pages, this will be the expected value.
|
||||
Title() string
|
||||
}
|
||||
|
||||
// Resource specific data set by Hugo.
|
||||
// One example would be.Data.Digest for fingerprinted resources.
|
||||
Data() interface{}
|
||||
|
||||
type ResourceParamsProvider interface {
|
||||
// Params set in front matter for this resource.
|
||||
Params() map[string]interface{}
|
||||
}
|
||||
|
||||
// resourceBase pulls out the minimal set of operations to define a Resource,
|
||||
// to simplify testing etc.
|
||||
type resourceBase interface {
|
||||
// MediaType is this resource's MIME type.
|
||||
MediaType() media.Type
|
||||
type ResourceDataProvider interface {
|
||||
// Resource specific data set by Hugo.
|
||||
// One example would be.Data.Digest for fingerprinted resources.
|
||||
Data() interface{}
|
||||
}
|
||||
|
||||
// ResourcesLanguageMerger describes an interface for merging resources from a
|
||||
@@ -81,11 +92,15 @@ type Identifier interface {
|
||||
|
||||
// ContentResource represents a Resource that provides a way to get to its content.
|
||||
// Most Resource types in Hugo implements this interface, including Page.
|
||||
type ContentResource interface {
|
||||
MediaType() media.Type
|
||||
ContentProvider
|
||||
}
|
||||
|
||||
// ContentProvider provides Content.
|
||||
// This should be used with care, as it will read the file content into memory, but it
|
||||
// should be cached as effectively as possible by the implementation.
|
||||
type ContentResource interface {
|
||||
resourceBase
|
||||
|
||||
type ContentProvider interface {
|
||||
// Content returns this resource's content. It will be equivalent to reading the content
|
||||
// that RelPermalink points to in the published folder.
|
||||
// The return type will be contextual, and should be what you would expect:
|
||||
@@ -101,6 +116,51 @@ type OpenReadSeekCloser func() (hugio.ReadSeekCloser, error)
|
||||
|
||||
// ReadSeekCloserResource is a Resource that supports loading its content.
|
||||
type ReadSeekCloserResource interface {
|
||||
resourceBase
|
||||
MediaType() media.Type
|
||||
ReadSeekCloser() (hugio.ReadSeekCloser, error)
|
||||
}
|
||||
|
||||
// LengthProvider is a Resource that provides a length
|
||||
// (typically the length of the content).
|
||||
type LengthProvider interface {
|
||||
Len() int
|
||||
}
|
||||
|
||||
// LanguageProvider is a Resource in a language.
|
||||
type LanguageProvider interface {
|
||||
Language() *langs.Language
|
||||
}
|
||||
|
||||
// TranslationKeyProvider connects translations of the same Resource.
|
||||
type TranslationKeyProvider interface {
|
||||
TranslationKey() string
|
||||
}
|
||||
|
||||
type resourceTypesHolder struct {
|
||||
mediaType media.Type
|
||||
resourceType string
|
||||
}
|
||||
|
||||
func (r resourceTypesHolder) MediaType() media.Type {
|
||||
return r.mediaType
|
||||
}
|
||||
|
||||
func (r resourceTypesHolder) ResourceType() string {
|
||||
return r.resourceType
|
||||
}
|
||||
|
||||
func NewResourceTypesProvider(mediaType media.Type, resourceType string) ResourceTypesProvider {
|
||||
return resourceTypesHolder{mediaType: mediaType, resourceType: resourceType}
|
||||
}
|
||||
|
||||
type languageHolder struct {
|
||||
lang *langs.Language
|
||||
}
|
||||
|
||||
func (l languageHolder) Language() *langs.Language {
|
||||
return l.lang
|
||||
}
|
||||
|
||||
func NewLanguageProvider(lang *langs.Language) LanguageProvider {
|
||||
return languageHolder{lang: lang}
|
||||
}
|
||||
|
Reference in New Issue
Block a user