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 }