postpub.go (4720B)
1 // Copyright 2020 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 postpub
15
16 import (
17 "fmt"
18 "reflect"
19 "strconv"
20 "strings"
21
22 "github.com/spf13/cast"
23
24 "github.com/gohugoio/hugo/common/hreflect"
25 "github.com/gohugoio/hugo/common/maps"
26 "github.com/gohugoio/hugo/media"
27 "github.com/gohugoio/hugo/resources/resource"
28 )
29
30 type PostPublishedResource interface {
31 resource.ResourceTypeProvider
32 resource.ResourceLinksProvider
33 resource.ResourceMetaProvider
34 resource.ResourceParamsProvider
35 resource.ResourceDataProvider
36 resource.OriginProvider
37
38 MediaType() map[string]any
39 }
40
41 const (
42 PostProcessPrefix = "__h_pp_l1"
43
44 // The suffix has an '=' in it to prevent the minifier to remove any enclosing
45 // quoutes around the attribute values.
46 // See issue #8884.
47 PostProcessSuffix = "__e="
48 )
49
50 func NewPostPublishResource(id int, r resource.Resource) PostPublishedResource {
51 return &PostPublishResource{
52 prefix: PostProcessPrefix + "_" + strconv.Itoa(id) + "_",
53 delegate: r,
54 }
55 }
56
57 // postPublishResource holds a Resource to be transformed post publishing.
58 type PostPublishResource struct {
59 prefix string
60 delegate resource.Resource
61 }
62
63 func (r *PostPublishResource) field(name string) string {
64 return r.prefix + name + PostProcessSuffix
65 }
66
67 func (r *PostPublishResource) Permalink() string {
68 return r.field("Permalink")
69 }
70
71 func (r *PostPublishResource) RelPermalink() string {
72 return r.field("RelPermalink")
73 }
74
75 func (r *PostPublishResource) Origin() resource.Resource {
76 return r.delegate
77 }
78
79 func (r *PostPublishResource) GetFieldString(pattern string) (string, bool) {
80 if r == nil {
81 panic("resource is nil")
82 }
83 prefixIdx := strings.Index(pattern, r.prefix)
84 if prefixIdx == -1 {
85 // Not a method on this resource.
86 return "", false
87 }
88
89 fieldAccessor := pattern[prefixIdx+len(r.prefix) : strings.Index(pattern, PostProcessSuffix)]
90
91 d := r.delegate
92 switch {
93 case fieldAccessor == "RelPermalink":
94 return d.RelPermalink(), true
95 case fieldAccessor == "Permalink":
96 return d.Permalink(), true
97 case fieldAccessor == "Name":
98 return d.Name(), true
99 case fieldAccessor == "Title":
100 return d.Title(), true
101 case fieldAccessor == "ResourceType":
102 return d.ResourceType(), true
103 case fieldAccessor == "Content":
104 content, err := d.(resource.ContentProvider).Content()
105 if err != nil {
106 return "", true
107 }
108 return cast.ToString(content), true
109 case strings.HasPrefix(fieldAccessor, "MediaType"):
110 return r.fieldToString(d.MediaType(), fieldAccessor), true
111 case fieldAccessor == "Data.Integrity":
112 return cast.ToString((d.Data().(map[string]any)["Integrity"])), true
113 default:
114 panic(fmt.Sprintf("unknown field accessor %q", fieldAccessor))
115 }
116 }
117
118 func (r *PostPublishResource) fieldToString(receiver any, path string) string {
119 fieldname := strings.Split(path, ".")[1]
120
121 receiverv := reflect.ValueOf(receiver)
122 switch receiverv.Kind() {
123 case reflect.Map:
124 v := receiverv.MapIndex(reflect.ValueOf(fieldname))
125 return cast.ToString(v.Interface())
126 default:
127 v := receiverv.FieldByName(fieldname)
128 if !v.IsValid() {
129 method := hreflect.GetMethodByName(receiverv, fieldname)
130 if method.IsValid() {
131 vals := method.Call(nil)
132 if len(vals) > 0 {
133 v = vals[0]
134 }
135
136 }
137 }
138
139 if v.IsValid() {
140 return cast.ToString(v.Interface())
141 }
142 return ""
143 }
144 }
145
146 func (r *PostPublishResource) Data() any {
147 m := map[string]any{
148 "Integrity": "",
149 }
150 insertFieldPlaceholders("Data", m, r.field)
151 return m
152 }
153
154 func (r *PostPublishResource) MediaType() map[string]any {
155 m := structToMapWithPlaceholders("MediaType", media.Type{}, r.field)
156 return m
157 }
158
159 func (r *PostPublishResource) ResourceType() string {
160 return r.field("ResourceType")
161 }
162
163 func (r *PostPublishResource) Name() string {
164 return r.field("Name")
165 }
166
167 func (r *PostPublishResource) Title() string {
168 return r.field("Title")
169 }
170
171 func (r *PostPublishResource) Params() maps.Params {
172 panic(r.fieldNotSupported("Params"))
173 }
174
175 func (r *PostPublishResource) Content() (any, error) {
176 return r.field("Content"), nil
177 }
178
179 func (r *PostPublishResource) fieldNotSupported(name string) string {
180 return fmt.Sprintf("method .%s is currently not supported in post-publish transformations.", name)
181 }