diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2019-01-02 12:33:26 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2019-03-23 18:51:22 +0100 |
commit | 597e418cb02883418f2cebb41400e8e61413f651 (patch) | |
tree | 177ad9c540b2583b6dab138c9f0490d28989c7f7 /resources/resource | |
parent | 44f5c1c14cb1f42cc5f01739c289e9cfc83602af (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.go | 81 | ||||
-rw-r--r-- | resources/resource/params.go | 89 | ||||
-rw-r--r-- | resources/resource/resource_helpers.go | 70 | ||||
-rw-r--r-- | resources/resource/resourcetypes.go | 98 |
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} +} |