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