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  }