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  }