github.com/gohugoio/hugo@v0.88.1/helpers/general.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 helpers
    15  
    16  import (
    17  	"bytes"
    18  	"crypto/md5"
    19  	"encoding/hex"
    20  	"fmt"
    21  	"io"
    22  	"net"
    23  	"os"
    24  	"path/filepath"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  	"unicode"
    30  	"unicode/utf8"
    31  
    32  	"github.com/gohugoio/hugo/common/loggers"
    33  
    34  	"github.com/mitchellh/hashstructure"
    35  
    36  	"github.com/gohugoio/hugo/hugofs"
    37  
    38  	"github.com/gohugoio/hugo/common/hugo"
    39  
    40  	"github.com/spf13/afero"
    41  
    42  	"github.com/jdkato/prose/transform"
    43  
    44  	bp "github.com/gohugoio/hugo/bufferpool"
    45  	"github.com/spf13/pflag"
    46  )
    47  
    48  // FilePathSeparator as defined by os.Separator.
    49  const FilePathSeparator = string(filepath.Separator)
    50  
    51  // FindAvailablePort returns an available and valid TCP port.
    52  func FindAvailablePort() (*net.TCPAddr, error) {
    53  	l, err := net.Listen("tcp", ":0")
    54  	if err == nil {
    55  		defer l.Close()
    56  		addr := l.Addr()
    57  		if a, ok := addr.(*net.TCPAddr); ok {
    58  			return a, nil
    59  		}
    60  		return nil, fmt.Errorf("unable to obtain a valid tcp port: %v", addr)
    61  	}
    62  	return nil, err
    63  }
    64  
    65  // InStringArray checks if a string is an element of a slice of strings
    66  // and returns a boolean value.
    67  func InStringArray(arr []string, el string) bool {
    68  	for _, v := range arr {
    69  		if v == el {
    70  			return true
    71  		}
    72  	}
    73  	return false
    74  }
    75  
    76  // FirstUpper returns a string with the first character as upper case.
    77  func FirstUpper(s string) string {
    78  	if s == "" {
    79  		return ""
    80  	}
    81  	r, n := utf8.DecodeRuneInString(s)
    82  	return string(unicode.ToUpper(r)) + s[n:]
    83  }
    84  
    85  // UniqueStrings returns a new slice with any duplicates removed.
    86  func UniqueStrings(s []string) []string {
    87  	unique := make([]string, 0, len(s))
    88  	set := map[string]interface{}{}
    89  	for _, val := range s {
    90  		if _, ok := set[val]; !ok {
    91  			unique = append(unique, val)
    92  			set[val] = val
    93  		}
    94  	}
    95  	return unique
    96  }
    97  
    98  // UniqueStringsReuse returns a slice with any duplicates removed.
    99  // It will modify the input slice.
   100  func UniqueStringsReuse(s []string) []string {
   101  	set := map[string]interface{}{}
   102  	result := s[:0]
   103  	for _, val := range s {
   104  		if _, ok := set[val]; !ok {
   105  			result = append(result, val)
   106  			set[val] = val
   107  		}
   108  	}
   109  	return result
   110  }
   111  
   112  // UniqueStringsReuse returns a sorted slice with any duplicates removed.
   113  // It will modify the input slice.
   114  func UniqueStringsSorted(s []string) []string {
   115  	if len(s) == 0 {
   116  		return nil
   117  	}
   118  	ss := sort.StringSlice(s)
   119  	ss.Sort()
   120  	i := 0
   121  	for j := 1; j < len(s); j++ {
   122  		if !ss.Less(i, j) {
   123  			continue
   124  		}
   125  		i++
   126  		s[i] = s[j]
   127  	}
   128  
   129  	return s[:i+1]
   130  }
   131  
   132  // ReaderToBytes takes an io.Reader argument, reads from it
   133  // and returns bytes.
   134  func ReaderToBytes(lines io.Reader) []byte {
   135  	if lines == nil {
   136  		return []byte{}
   137  	}
   138  	b := bp.GetBuffer()
   139  	defer bp.PutBuffer(b)
   140  
   141  	b.ReadFrom(lines)
   142  
   143  	bc := make([]byte, b.Len())
   144  	copy(bc, b.Bytes())
   145  	return bc
   146  }
   147  
   148  // ReaderToString is the same as ReaderToBytes, but returns a string.
   149  func ReaderToString(lines io.Reader) string {
   150  	if lines == nil {
   151  		return ""
   152  	}
   153  	b := bp.GetBuffer()
   154  	defer bp.PutBuffer(b)
   155  	b.ReadFrom(lines)
   156  	return b.String()
   157  }
   158  
   159  // ReaderContains reports whether subslice is within r.
   160  func ReaderContains(r io.Reader, subslice []byte) bool {
   161  	if r == nil || len(subslice) == 0 {
   162  		return false
   163  	}
   164  
   165  	bufflen := len(subslice) * 4
   166  	halflen := bufflen / 2
   167  	buff := make([]byte, bufflen)
   168  	var err error
   169  	var n, i int
   170  
   171  	for {
   172  		i++
   173  		if i == 1 {
   174  			n, err = io.ReadAtLeast(r, buff[:halflen], halflen)
   175  		} else {
   176  			if i != 2 {
   177  				// shift left to catch overlapping matches
   178  				copy(buff[:], buff[halflen:])
   179  			}
   180  			n, err = io.ReadAtLeast(r, buff[halflen:], halflen)
   181  		}
   182  
   183  		if n > 0 && bytes.Contains(buff, subslice) {
   184  			return true
   185  		}
   186  
   187  		if err != nil {
   188  			break
   189  		}
   190  	}
   191  	return false
   192  }
   193  
   194  // GetTitleFunc returns a func that can be used to transform a string to
   195  // title case.
   196  //
   197  // The supported styles are
   198  //
   199  // - "Go" (strings.Title)
   200  // - "AP" (see https://www.apstylebook.com/)
   201  // - "Chicago" (see http://www.chicagomanualofstyle.org/home.html)
   202  //
   203  // If an unknown or empty style is provided, AP style is what you get.
   204  func GetTitleFunc(style string) func(s string) string {
   205  	switch strings.ToLower(style) {
   206  	case "go":
   207  		return strings.Title
   208  	case "chicago":
   209  		tc := transform.NewTitleConverter(transform.ChicagoStyle)
   210  		return tc.Title
   211  	default:
   212  		tc := transform.NewTitleConverter(transform.APStyle)
   213  		return tc.Title
   214  	}
   215  }
   216  
   217  // HasStringsPrefix tests whether the string slice s begins with prefix slice s.
   218  func HasStringsPrefix(s, prefix []string) bool {
   219  	return len(s) >= len(prefix) && compareStringSlices(s[0:len(prefix)], prefix)
   220  }
   221  
   222  // HasStringsSuffix tests whether the string slice s ends with suffix slice s.
   223  func HasStringsSuffix(s, suffix []string) bool {
   224  	return len(s) >= len(suffix) && compareStringSlices(s[len(s)-len(suffix):], suffix)
   225  }
   226  
   227  func compareStringSlices(a, b []string) bool {
   228  	if a == nil && b == nil {
   229  		return true
   230  	}
   231  
   232  	if a == nil || b == nil {
   233  		return false
   234  	}
   235  
   236  	if len(a) != len(b) {
   237  		return false
   238  	}
   239  
   240  	for i := range a {
   241  		if a[i] != b[i] {
   242  			return false
   243  		}
   244  	}
   245  
   246  	return true
   247  }
   248  
   249  // LogPrinter is the common interface of the JWWs loggers.
   250  type LogPrinter interface {
   251  	// Println is the only common method that works in all of JWWs loggers.
   252  	Println(a ...interface{})
   253  }
   254  
   255  // DistinctLogger ignores duplicate log statements.
   256  type DistinctLogger struct {
   257  	loggers.Logger
   258  	sync.RWMutex
   259  	m map[string]bool
   260  }
   261  
   262  func (l *DistinctLogger) Reset() {
   263  	l.Lock()
   264  	defer l.Unlock()
   265  
   266  	l.m = make(map[string]bool)
   267  }
   268  
   269  // Println will log the string returned from fmt.Sprintln given the arguments,
   270  // but not if it has been logged before.
   271  func (l *DistinctLogger) Println(v ...interface{}) {
   272  	// fmt.Sprint doesn't add space between string arguments
   273  	logStatement := strings.TrimSpace(fmt.Sprintln(v...))
   274  	l.printIfNotPrinted("println", logStatement, func() {
   275  		l.Logger.Println(logStatement)
   276  	})
   277  }
   278  
   279  // Printf will log the string returned from fmt.Sprintf given the arguments,
   280  // but not if it has been logged before.
   281  func (l *DistinctLogger) Printf(format string, v ...interface{}) {
   282  	logStatement := fmt.Sprintf(format, v...)
   283  	l.printIfNotPrinted("printf", logStatement, func() {
   284  		l.Logger.Printf(format, v...)
   285  	})
   286  }
   287  
   288  func (l *DistinctLogger) Debugf(format string, v ...interface{}) {
   289  	logStatement := fmt.Sprintf(format, v...)
   290  	l.printIfNotPrinted("debugf", logStatement, func() {
   291  		l.Logger.Debugf(format, v...)
   292  	})
   293  }
   294  
   295  func (l *DistinctLogger) Debugln(v ...interface{}) {
   296  	logStatement := fmt.Sprint(v...)
   297  	l.printIfNotPrinted("debugln", logStatement, func() {
   298  		l.Logger.Debugln(v...)
   299  	})
   300  }
   301  
   302  func (l *DistinctLogger) Infof(format string, v ...interface{}) {
   303  	logStatement := fmt.Sprintf(format, v...)
   304  	l.printIfNotPrinted("info", logStatement, func() {
   305  		l.Logger.Infof(format, v...)
   306  	})
   307  }
   308  
   309  func (l *DistinctLogger) Infoln(v ...interface{}) {
   310  	logStatement := fmt.Sprint(v...)
   311  	l.printIfNotPrinted("infoln", logStatement, func() {
   312  		l.Logger.Infoln(v...)
   313  	})
   314  }
   315  
   316  func (l *DistinctLogger) Warnf(format string, v ...interface{}) {
   317  	logStatement := fmt.Sprintf(format, v...)
   318  	l.printIfNotPrinted("warnf", logStatement, func() {
   319  		l.Logger.Warnf(format, v...)
   320  	})
   321  }
   322  func (l *DistinctLogger) Warnln(v ...interface{}) {
   323  	logStatement := fmt.Sprint(v...)
   324  	l.printIfNotPrinted("warnln", logStatement, func() {
   325  		l.Logger.Warnln(v...)
   326  	})
   327  }
   328  func (l *DistinctLogger) Errorf(format string, v ...interface{}) {
   329  	logStatement := fmt.Sprint(v...)
   330  	l.printIfNotPrinted("errorf", logStatement, func() {
   331  		l.Logger.Errorf(format, v...)
   332  	})
   333  }
   334  
   335  func (l *DistinctLogger) Errorln(v ...interface{}) {
   336  	logStatement := fmt.Sprint(v...)
   337  	l.printIfNotPrinted("errorln", logStatement, func() {
   338  		l.Logger.Errorln(v...)
   339  	})
   340  }
   341  
   342  func (l *DistinctLogger) hasPrinted(key string) bool {
   343  	l.RLock()
   344  	defer l.RUnlock()
   345  	_, found := l.m[key]
   346  	return found
   347  }
   348  
   349  func (l *DistinctLogger) printIfNotPrinted(level, logStatement string, print func()) {
   350  	key := level + logStatement
   351  	if l.hasPrinted(key) {
   352  		return
   353  	}
   354  	l.Lock()
   355  	print()
   356  	l.m[key] = true
   357  	l.Unlock()
   358  }
   359  
   360  // NewDistinctErrorLogger creates a new DistinctLogger that logs ERRORs
   361  func NewDistinctErrorLogger() loggers.Logger {
   362  	return &DistinctLogger{m: make(map[string]bool), Logger: loggers.NewErrorLogger()}
   363  }
   364  
   365  // NewDistinctLogger creates a new DistinctLogger that logs to the provided logger.
   366  func NewDistinctLogger(logger loggers.Logger) loggers.Logger {
   367  	return &DistinctLogger{m: make(map[string]bool), Logger: logger}
   368  }
   369  
   370  // NewDistinctWarnLogger creates a new DistinctLogger that logs WARNs
   371  func NewDistinctWarnLogger() loggers.Logger {
   372  	return &DistinctLogger{m: make(map[string]bool), Logger: loggers.NewWarningLogger()}
   373  }
   374  
   375  var (
   376  	// DistinctErrorLog can be used to avoid spamming the logs with errors.
   377  	DistinctErrorLog = NewDistinctErrorLogger()
   378  
   379  	// DistinctWarnLog can be used to avoid spamming the logs with warnings.
   380  	DistinctWarnLog = NewDistinctWarnLogger()
   381  )
   382  
   383  // InitLoggers resets the global distinct loggers.
   384  func InitLoggers() {
   385  	DistinctErrorLog.Reset()
   386  	DistinctWarnLog.Reset()
   387  
   388  }
   389  
   390  // Deprecated informs about a deprecation, but only once for a given set of arguments' values.
   391  // If the err flag is enabled, it logs as an ERROR (will exit with -1) and the text will
   392  // point at the next Hugo release.
   393  // The idea is two remove an item in two Hugo releases to give users and theme authors
   394  // plenty of time to fix their templates.
   395  func Deprecated(item, alternative string, err bool) {
   396  	if err {
   397  		DistinctErrorLog.Errorf("%s is deprecated and will be removed in Hugo %s. %s", item, hugo.CurrentVersion.Next().ReleaseVersion(), alternative)
   398  	} else {
   399  		DistinctWarnLog.Warnf("%s is deprecated and will be removed in a future release. %s", item, alternative)
   400  	}
   401  }
   402  
   403  // SliceToLower goes through the source slice and lowers all values.
   404  func SliceToLower(s []string) []string {
   405  	if s == nil {
   406  		return nil
   407  	}
   408  
   409  	l := make([]string, len(s))
   410  	for i, v := range s {
   411  		l[i] = strings.ToLower(v)
   412  	}
   413  
   414  	return l
   415  }
   416  
   417  // MD5String takes a string and returns its MD5 hash.
   418  func MD5String(f string) string {
   419  	h := md5.New()
   420  	h.Write([]byte(f))
   421  	return hex.EncodeToString(h.Sum([]byte{}))
   422  }
   423  
   424  // MD5FromFileFast creates a MD5 hash from the given file. It only reads parts of
   425  // the file for speed, so don't use it if the files are very subtly different.
   426  // It will not close the file.
   427  func MD5FromFileFast(r io.ReadSeeker) (string, error) {
   428  	const (
   429  		// Do not change once set in stone!
   430  		maxChunks = 8
   431  		peekSize  = 64
   432  		seek      = 2048
   433  	)
   434  
   435  	h := md5.New()
   436  	buff := make([]byte, peekSize)
   437  
   438  	for i := 0; i < maxChunks; i++ {
   439  		if i > 0 {
   440  			_, err := r.Seek(seek, 0)
   441  			if err != nil {
   442  				if err == io.EOF {
   443  					break
   444  				}
   445  				return "", err
   446  			}
   447  		}
   448  
   449  		_, err := io.ReadAtLeast(r, buff, peekSize)
   450  		if err != nil {
   451  			if err == io.EOF || err == io.ErrUnexpectedEOF {
   452  				h.Write(buff)
   453  				break
   454  			}
   455  			return "", err
   456  		}
   457  		h.Write(buff)
   458  	}
   459  
   460  	return hex.EncodeToString(h.Sum(nil)), nil
   461  }
   462  
   463  // MD5FromReader creates a MD5 hash from the given reader.
   464  func MD5FromReader(r io.Reader) (string, error) {
   465  	h := md5.New()
   466  	if _, err := io.Copy(h, r); err != nil {
   467  		return "", nil
   468  	}
   469  	return hex.EncodeToString(h.Sum(nil)), nil
   470  }
   471  
   472  // IsWhitespace determines if the given rune is whitespace.
   473  func IsWhitespace(r rune) bool {
   474  	return r == ' ' || r == '\t' || r == '\n' || r == '\r'
   475  }
   476  
   477  // NormalizeHugoFlags facilitates transitions of Hugo command-line flags,
   478  // e.g. --baseUrl to --baseURL, --uglyUrls to --uglyURLs
   479  func NormalizeHugoFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
   480  	switch name {
   481  	case "baseUrl":
   482  		name = "baseURL"
   483  	case "uglyUrls":
   484  		name = "uglyURLs"
   485  	}
   486  	return pflag.NormalizedName(name)
   487  }
   488  
   489  // PrintFs prints the given filesystem to the given writer starting from the given path.
   490  // This is useful for debugging.
   491  func PrintFs(fs afero.Fs, path string, w io.Writer) {
   492  	if fs == nil {
   493  		return
   494  	}
   495  
   496  	afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
   497  		var filename string
   498  		var meta interface{}
   499  		if fim, ok := info.(hugofs.FileMetaInfo); ok {
   500  			filename = fim.Meta().Filename
   501  			meta = fim.Meta()
   502  		}
   503  		fmt.Fprintf(w, "    %q %q\t\t%v\n", path, filename, meta)
   504  		return nil
   505  	})
   506  }
   507  
   508  // HashString returns a hash from the given elements.
   509  // It will panic if the hash cannot be calculated.
   510  func HashString(elements ...interface{}) string {
   511  	var o interface{}
   512  	if len(elements) == 1 {
   513  		o = elements[0]
   514  	} else {
   515  		o = elements
   516  	}
   517  
   518  	hash, err := hashstructure.Hash(o, nil)
   519  	if err != nil {
   520  		panic(err)
   521  	}
   522  	return strconv.FormatUint(hash, 10)
   523  }