github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/output/config.go (about)

     1  // Copyright 2023 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 output
    15  
    16  import (
    17  	"fmt"
    18  	"reflect"
    19  	"sort"
    20  	"strings"
    21  
    22  	"github.com/gohugoio/hugo/common/maps"
    23  	"github.com/gohugoio/hugo/config"
    24  	"github.com/gohugoio/hugo/media"
    25  	"github.com/mitchellh/mapstructure"
    26  )
    27  
    28  // OutputFormatConfig configures a single output format.
    29  type OutputFormatConfig struct {
    30  	// The MediaType string. This must be a configured media type.
    31  	MediaType string
    32  	Format
    33  }
    34  
    35  var defaultOutputFormat = Format{
    36  	BaseName: "index",
    37  	Rel:      "alternate",
    38  }
    39  
    40  func DecodeConfig(mediaTypes media.Types, in any) (*config.ConfigNamespace[map[string]OutputFormatConfig, Formats], error) {
    41  	buildConfig := func(in any) (Formats, any, error) {
    42  		f := make(Formats, len(DefaultFormats))
    43  		copy(f, DefaultFormats)
    44  		if in != nil {
    45  			m, err := maps.ToStringMapE(in)
    46  			if err != nil {
    47  				return nil, nil, fmt.Errorf("failed convert config to map: %s", err)
    48  			}
    49  			m = maps.CleanConfigStringMap(m)
    50  
    51  			for k, v := range m {
    52  				found := false
    53  				for i, vv := range f {
    54  					// Both are lower case.
    55  					if k == vv.Name {
    56  						// Merge it with the existing
    57  						if err := decode(mediaTypes, v, &f[i]); err != nil {
    58  							return f, nil, err
    59  						}
    60  						found = true
    61  					}
    62  				}
    63  				if found {
    64  					continue
    65  				}
    66  
    67  				newOutFormat := defaultOutputFormat
    68  				newOutFormat.Name = k
    69  				if err := decode(mediaTypes, v, &newOutFormat); err != nil {
    70  					return f, nil, err
    71  				}
    72  
    73  				f = append(f, newOutFormat)
    74  
    75  			}
    76  		}
    77  
    78  		// Also format is a map for documentation purposes.
    79  		docm := make(map[string]OutputFormatConfig, len(f))
    80  		for _, ff := range f {
    81  			docm[ff.Name] = OutputFormatConfig{
    82  				MediaType: ff.MediaType.Type,
    83  				Format:    ff,
    84  			}
    85  		}
    86  
    87  		sort.Sort(f)
    88  		return f, docm, nil
    89  	}
    90  
    91  	return config.DecodeNamespace[map[string]OutputFormatConfig](in, buildConfig)
    92  }
    93  
    94  func decode(mediaTypes media.Types, input any, output *Format) error {
    95  	config := &mapstructure.DecoderConfig{
    96  		Metadata:         nil,
    97  		Result:           output,
    98  		WeaklyTypedInput: true,
    99  		DecodeHook: func(a reflect.Type, b reflect.Type, c any) (any, error) {
   100  			if a.Kind() == reflect.Map {
   101  				dataVal := reflect.Indirect(reflect.ValueOf(c))
   102  				for _, key := range dataVal.MapKeys() {
   103  					keyStr, ok := key.Interface().(string)
   104  					if !ok {
   105  						// Not a string key
   106  						continue
   107  					}
   108  					if strings.EqualFold(keyStr, "mediaType") {
   109  						// If mediaType is a string, look it up and replace it
   110  						// in the map.
   111  						vv := dataVal.MapIndex(key)
   112  						vvi := vv.Interface()
   113  
   114  						switch vviv := vvi.(type) {
   115  						case media.Type:
   116  						// OK
   117  						case string:
   118  							mediaType, found := mediaTypes.GetByType(vviv)
   119  							if !found {
   120  								return c, fmt.Errorf("media type %q not found", vviv)
   121  							}
   122  							dataVal.SetMapIndex(key, reflect.ValueOf(mediaType))
   123  						default:
   124  							return nil, fmt.Errorf("invalid output format configuration; wrong type for media type, expected string (e.g. text/html), got %T", vvi)
   125  						}
   126  					}
   127  				}
   128  			}
   129  			return c, nil
   130  		},
   131  	}
   132  
   133  	decoder, err := mapstructure.NewDecoder(config)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	if err = decoder.Decode(input); err != nil {
   139  		return fmt.Errorf("failed to decode output format configuration: %w", err)
   140  	}
   141  
   142  	return nil
   143  
   144  }