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 }