hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

general.go (13120B)

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