github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/tpl/transform/unmarshal.go (about) 1 // Copyright 2019 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 transform 15 16 import ( 17 "io/ioutil" 18 "strings" 19 20 "github.com/gohugoio/hugo/resources/resource" 21 22 "github.com/gohugoio/hugo/common/types" 23 24 "github.com/mitchellh/mapstructure" 25 26 "github.com/gohugoio/hugo/helpers" 27 "github.com/gohugoio/hugo/parser/metadecoders" 28 "github.com/pkg/errors" 29 30 "github.com/spf13/cast" 31 ) 32 33 // Unmarshal unmarshals the data given, which can be either a string, json.RawMessage 34 // or a Resource. Supported formats are JSON, TOML, YAML, and CSV. 35 // You can optionally provide an options map as the first argument. 36 func (ns *Namespace) Unmarshal(args ...interface{}) (interface{}, error) { 37 if len(args) < 1 || len(args) > 2 { 38 return nil, errors.New("unmarshal takes 1 or 2 arguments") 39 } 40 41 var data interface{} 42 decoder := metadecoders.Default 43 44 if len(args) == 1 { 45 data = args[0] 46 } else { 47 m, ok := args[0].(map[string]interface{}) 48 if !ok { 49 return nil, errors.New("first argument must be a map") 50 } 51 52 var err error 53 54 data = args[1] 55 decoder, err = decodeDecoder(m) 56 if err != nil { 57 return nil, errors.WithMessage(err, "failed to decode options") 58 } 59 } 60 61 if r, ok := data.(resource.UnmarshableResource); ok { 62 key := r.Key() 63 64 if key == "" { 65 return nil, errors.New("no Key set in Resource") 66 } 67 68 if decoder != metadecoders.Default { 69 key += decoder.OptionsKey() 70 } 71 72 return ns.cache.GetOrCreate(key, func() (interface{}, error) { 73 f := metadecoders.FormatFromMediaType(r.MediaType()) 74 if f == "" { 75 return nil, errors.Errorf("MIME %q not supported", r.MediaType()) 76 } 77 78 reader, err := r.ReadSeekCloser() 79 if err != nil { 80 return nil, err 81 } 82 defer reader.Close() 83 84 b, err := ioutil.ReadAll(reader) 85 if err != nil { 86 return nil, err 87 } 88 89 return decoder.Unmarshal(b, f) 90 }) 91 } 92 93 dataStr, err := types.ToStringE(data) 94 if err != nil { 95 return nil, errors.Errorf("type %T not supported", data) 96 } 97 98 if dataStr == "" { 99 return nil, errors.New("no data to transform") 100 } 101 102 key := helpers.MD5String(dataStr) 103 104 return ns.cache.GetOrCreate(key, func() (interface{}, error) { 105 f := decoder.FormatFromContentString(dataStr) 106 if f == "" { 107 return nil, errors.New("unknown format") 108 } 109 110 return decoder.Unmarshal([]byte(dataStr), f) 111 }) 112 } 113 114 func decodeDecoder(m map[string]interface{}) (metadecoders.Decoder, error) { 115 opts := metadecoders.Default 116 117 if m == nil { 118 return opts, nil 119 } 120 121 // mapstructure does not support string to rune conversion, so do that manually. 122 // See https://github.com/mitchellh/mapstructure/issues/151 123 for k, v := range m { 124 if strings.EqualFold(k, "Delimiter") { 125 r, err := stringToRune(v) 126 if err != nil { 127 return opts, err 128 } 129 opts.Delimiter = r 130 delete(m, k) 131 132 } else if strings.EqualFold(k, "Comment") { 133 r, err := stringToRune(v) 134 if err != nil { 135 return opts, err 136 } 137 opts.Comment = r 138 delete(m, k) 139 } 140 } 141 142 err := mapstructure.WeakDecode(m, &opts) 143 144 return opts, err 145 } 146 147 func stringToRune(v interface{}) (rune, error) { 148 s, err := cast.ToStringE(v) 149 if err != nil { 150 return 0, err 151 } 152 153 if len(s) == 0 { 154 return 0, nil 155 } 156 157 var r rune 158 159 for i, rr := range s { 160 if i == 0 { 161 r = rr 162 } else { 163 return 0, errors.Errorf("invalid character: %q", v) 164 } 165 } 166 167 return r, nil 168 }