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