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