github.com/neohugo/neohugo@v0.123.8/tpl/urls/urls.go (about) 1 // Copyright 2017 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 urls provides template functions to deal with URLs. 15 package urls 16 17 import ( 18 "errors" 19 "fmt" 20 "html/template" 21 "net/url" 22 23 "github.com/neohugo/neohugo/common/urls" 24 "github.com/neohugo/neohugo/deps" 25 "github.com/spf13/cast" 26 ) 27 28 // New returns a new instance of the urls-namespaced template functions. 29 func New(deps *deps.Deps) *Namespace { 30 return &Namespace{ 31 deps: deps, 32 multihost: deps.Conf.IsMultihost(), 33 } 34 } 35 36 // Namespace provides template functions for the "urls" namespace. 37 type Namespace struct { 38 deps *deps.Deps 39 multihost bool 40 } 41 42 // AbsURL takes the string s and converts it to an absolute URL. 43 func (ns *Namespace) AbsURL(s any) (string, error) { 44 ss, err := cast.ToStringE(s) 45 if err != nil { 46 return "", nil 47 } 48 49 return ns.deps.PathSpec.AbsURL(ss, false), nil 50 } 51 52 // Parse parses rawurl into a URL structure. The rawurl may be relative or 53 // absolute. 54 func (ns *Namespace) Parse(rawurl any) (*url.URL, error) { 55 s, err := cast.ToStringE(rawurl) 56 if err != nil { 57 return nil, fmt.Errorf("error in Parse: %w", err) 58 } 59 60 return url.Parse(s) 61 } 62 63 // RelURL takes the string s and prepends the relative path according to a 64 // page's position in the project directory structure. 65 func (ns *Namespace) RelURL(s any) (string, error) { 66 ss, err := cast.ToStringE(s) 67 if err != nil { 68 return "", nil 69 } 70 71 return ns.deps.PathSpec.RelURL(ss, false), nil 72 } 73 74 // URLize returns the the strings s formatted as an URL. 75 func (ns *Namespace) URLize(s any) (string, error) { 76 ss, err := cast.ToStringE(s) 77 if err != nil { 78 return "", nil 79 } 80 return ns.deps.PathSpec.URLize(ss), nil 81 } 82 83 // Anchorize creates sanitized anchor name version of the string s that is compatible 84 // with how your configured markdown renderer does it. 85 func (ns *Namespace) Anchorize(s any) (string, error) { 86 ss, err := cast.ToStringE(s) 87 if err != nil { 88 return "", nil 89 } 90 return ns.deps.ContentSpec.SanitizeAnchorName(ss), nil 91 } 92 93 // Ref returns the absolute URL path to a given content item from Page p. 94 func (ns *Namespace) Ref(p any, args any) (string, error) { 95 pp, ok := p.(urls.RefLinker) 96 if !ok { 97 return "", errors.New("invalid Page received in Ref") 98 } 99 argsm, err := ns.refArgsToMap(args) 100 if err != nil { 101 return "", err 102 } 103 s, err := pp.Ref(argsm) 104 return s, err 105 } 106 107 // RelRef returns the relative URL path to a given content item from Page p. 108 func (ns *Namespace) RelRef(p any, args any) (string, error) { 109 pp, ok := p.(urls.RefLinker) 110 if !ok { 111 return "", errors.New("invalid Page received in RelRef") 112 } 113 argsm, err := ns.refArgsToMap(args) 114 if err != nil { 115 return "", err 116 } 117 118 s, err := pp.RelRef(argsm) 119 return s, err 120 } 121 122 func (ns *Namespace) refArgsToMap(args any) (map[string]any, error) { 123 var ( 124 s string 125 of string 126 ) 127 128 v := args 129 if _, ok := v.([]any); ok { 130 v = cast.ToStringSlice(v) 131 } 132 133 switch v := v.(type) { 134 case map[string]any: 135 return v, nil 136 case map[string]string: 137 m := make(map[string]any) 138 for k, v := range v { 139 m[k] = v 140 } 141 return m, nil 142 case []string: 143 if len(v) == 0 || len(v) > 2 { 144 return nil, fmt.Errorf("invalid number of arguments to ref") 145 } 146 // These were the options before we introduced the map type: 147 s = v[0] 148 if len(v) == 2 { 149 of = v[1] 150 } 151 default: 152 var err error 153 s, err = cast.ToStringE(args) 154 if err != nil { 155 return nil, err 156 } 157 158 } 159 160 return map[string]any{ 161 "path": s, 162 "outputFormat": of, 163 }, nil 164 } 165 166 // RelLangURL takes the string s and prepends the relative path according to a 167 // page's position in the project directory structure and the current language. 168 func (ns *Namespace) RelLangURL(s any) (string, error) { 169 ss, err := cast.ToStringE(s) 170 if err != nil { 171 return "", err 172 } 173 174 return ns.deps.PathSpec.RelURL(ss, !ns.multihost), nil 175 } 176 177 // AbsLangURL the string s and converts it to an absolute URL according 178 // to a page's position in the project directory structure and the current 179 // language. 180 func (ns *Namespace) AbsLangURL(s any) (string, error) { 181 ss, err := cast.ToStringE(s) 182 if err != nil { 183 return "", err 184 } 185 186 return ns.deps.PathSpec.AbsURL(ss, !ns.multihost), nil 187 } 188 189 // JoinPath joins the provided elements into a URL string and cleans the result 190 // of any ./ or ../ elements. If the argument list is empty, JoinPath returns 191 // an empty string. 192 func (ns *Namespace) JoinPath(elements ...any) (string, error) { 193 if len(elements) == 0 { 194 return "", nil 195 } 196 197 var selements []string 198 for _, e := range elements { 199 switch v := e.(type) { 200 case []string: 201 selements = append(selements, v...) 202 case []any: 203 for _, e := range v { 204 se, err := cast.ToStringE(e) 205 if err != nil { 206 return "", err 207 } 208 selements = append(selements, se) 209 } 210 default: 211 se, err := cast.ToStringE(e) 212 if err != nil { 213 return "", err 214 } 215 selements = append(selements, se) 216 } 217 } 218 219 result, err := url.JoinPath(selements[0], selements[1:]...) 220 if err != nil { 221 return "", err 222 } 223 return result, nil 224 } 225 226 // URLEncode escapes the string so it can be safely placed 227 // inside a URL query. 228 func (ns *Namespace) URLEncode(rawurl interface{}) (template.HTML, error) { 229 s, err := cast.ToStringE(rawurl) 230 if err != nil { 231 return "", err 232 } 233 234 return template.HTML(url.QueryEscape(s)), nil 235 } 236 237 // URLDecode does the inverse transformation of QueryEscape, 238 // converting each 3-byte encoded substring of the form "%AB" into the 239 // hex-decoded byte 0xAB. 240 func (ns *Namespace) URLDecode(rawurl interface{}) (string, error) { 241 s, err := cast.ToStringE(rawurl) 242 if err != nil { 243 return "", err 244 } 245 246 urldecode, err := url.QueryUnescape(s) 247 if err != nil { 248 // this mean, we cannot urldecode this string 249 // then just return as it was 250 return s, nil 251 } 252 253 return urldecode, nil 254 }