summaryrefslogtreecommitdiffstats
path: root/resources/resource
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-01-02 12:33:26 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-03-23 18:51:22 +0100
commit597e418cb02883418f2cebb41400e8e61413f651 (patch)
tree177ad9c540b2583b6dab138c9f0490d28989c7f7 /resources/resource
parent44f5c1c14cb1f42cc5f01739c289e9cfc83602af (diff)
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
Diffstat (limited to 'resources/resource')
-rw-r--r--resources/resource/dates.go81
-rw-r--r--resources/resource/params.go89
-rw-r--r--resources/resource/resource_helpers.go70
-rw-r--r--resources/resource/resourcetypes.go98
4 files changed, 319 insertions, 19 deletions
diff --git a/resources/resource/dates.go b/resources/resource/dates.go
new file mode 100644
index 000000000..f26c44787
--- /dev/null
+++ b/resources/resource/dates.go
@@ -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
+}
diff --git a/resources/resource/params.go b/resources/resource/params.go
new file mode 100644
index 000000000..f6ecea35a
--- /dev/null
+++ b/resources/resource/params.go
@@ -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))
+}
diff --git a/resources/resource/resource_helpers.go b/resources/resource/resource_helpers.go
new file mode 100644
index 000000000..b0830a83c
--- /dev/null
+++ b/resources/resource/resource_helpers.go
@@ -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
+}
diff --git a/resources/resource/resourcetypes.go b/resources/resource/resourcetypes.go
index 120d753e4..5a5839735 100644
--- a/resources/resource/resourcetypes.go
+++ b/resources/resource/resourcetypes.go
@@ -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
-
- // Permalink represents the absolute link to this resource.
- Permalink() string
+ ResourceTypesProvider
+ ResourceLinksProvider
+ ResourceMetaProvider
+ ResourceParamsProvider
+ ResourceDataProvider
+}
- // 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.
-// 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
+ 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 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}
+}