hugo

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

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

path.go (7824B)

    1 // Copyright 2021 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 paths
   15 
   16 import (
   17 	"errors"
   18 	"fmt"
   19 	"path"
   20 	"path/filepath"
   21 	"regexp"
   22 	"strings"
   23 )
   24 
   25 // FilePathSeparator as defined by os.Separator.
   26 const FilePathSeparator = string(filepath.Separator)
   27 
   28 // filepathPathBridge is a bridge for common functionality in filepath vs path
   29 type filepathPathBridge interface {
   30 	Base(in string) string
   31 	Clean(in string) string
   32 	Dir(in string) string
   33 	Ext(in string) string
   34 	Join(elem ...string) string
   35 	Separator() string
   36 }
   37 
   38 type filepathBridge struct{}
   39 
   40 func (filepathBridge) Base(in string) string {
   41 	return filepath.Base(in)
   42 }
   43 
   44 func (filepathBridge) Clean(in string) string {
   45 	return filepath.Clean(in)
   46 }
   47 
   48 func (filepathBridge) Dir(in string) string {
   49 	return filepath.Dir(in)
   50 }
   51 
   52 func (filepathBridge) Ext(in string) string {
   53 	return filepath.Ext(in)
   54 }
   55 
   56 func (filepathBridge) Join(elem ...string) string {
   57 	return filepath.Join(elem...)
   58 }
   59 
   60 func (filepathBridge) Separator() string {
   61 	return FilePathSeparator
   62 }
   63 
   64 var fpb filepathBridge
   65 
   66 // AbsPathify creates an absolute path if given a working dir and a relative path.
   67 // If already absolute, the path is just cleaned.
   68 func AbsPathify(workingDir, inPath string) string {
   69 	if filepath.IsAbs(inPath) {
   70 		return filepath.Clean(inPath)
   71 	}
   72 	return filepath.Join(workingDir, inPath)
   73 }
   74 
   75 // MakeTitle converts the path given to a suitable title, trimming whitespace
   76 // and replacing hyphens with whitespace.
   77 func MakeTitle(inpath string) string {
   78 	return strings.Replace(strings.TrimSpace(inpath), "-", " ", -1)
   79 }
   80 
   81 // ReplaceExtension takes a path and an extension, strips the old extension
   82 // and returns the path with the new extension.
   83 func ReplaceExtension(path string, newExt string) string {
   84 	f, _ := fileAndExt(path, fpb)
   85 	return f + "." + newExt
   86 }
   87 
   88 func makePathRelative(inPath string, possibleDirectories ...string) (string, error) {
   89 	for _, currentPath := range possibleDirectories {
   90 		if strings.HasPrefix(inPath, currentPath) {
   91 			return strings.TrimPrefix(inPath, currentPath), nil
   92 		}
   93 	}
   94 	return inPath, errors.New("can't extract relative path, unknown prefix")
   95 }
   96 
   97 // Should be good enough for Hugo.
   98 var isFileRe = regexp.MustCompile(`.*\..{1,6}$`)
   99 
  100 // GetDottedRelativePath expects a relative path starting after the content directory.
  101 // It returns a relative path with dots ("..") navigating up the path structure.
  102 func GetDottedRelativePath(inPath string) string {
  103 	inPath = filepath.Clean(filepath.FromSlash(inPath))
  104 
  105 	if inPath == "." {
  106 		return "./"
  107 	}
  108 
  109 	if !isFileRe.MatchString(inPath) && !strings.HasSuffix(inPath, FilePathSeparator) {
  110 		inPath += FilePathSeparator
  111 	}
  112 
  113 	if !strings.HasPrefix(inPath, FilePathSeparator) {
  114 		inPath = FilePathSeparator + inPath
  115 	}
  116 
  117 	dir, _ := filepath.Split(inPath)
  118 
  119 	sectionCount := strings.Count(dir, FilePathSeparator)
  120 
  121 	if sectionCount == 0 || dir == FilePathSeparator {
  122 		return "./"
  123 	}
  124 
  125 	var dottedPath string
  126 
  127 	for i := 1; i < sectionCount; i++ {
  128 		dottedPath += "../"
  129 	}
  130 
  131 	return dottedPath
  132 }
  133 
  134 // ExtNoDelimiter takes a path and returns the extension, excluding the delimiter, i.e. "md".
  135 func ExtNoDelimiter(in string) string {
  136 	return strings.TrimPrefix(Ext(in), ".")
  137 }
  138 
  139 // Ext takes a path and returns the extension, including the delimiter, i.e. ".md".
  140 func Ext(in string) string {
  141 	_, ext := fileAndExt(in, fpb)
  142 	return ext
  143 }
  144 
  145 // PathAndExt is the same as FileAndExt, but it uses the path package.
  146 func PathAndExt(in string) (string, string) {
  147 	return fileAndExt(in, pb)
  148 }
  149 
  150 // FileAndExt takes a path and returns the file and extension separated,
  151 // the extension including the delimiter, i.e. ".md".
  152 func FileAndExt(in string) (string, string) {
  153 	return fileAndExt(in, fpb)
  154 }
  155 
  156 // FileAndExtNoDelimiter takes a path and returns the file and extension separated,
  157 // the extension excluding the delimiter, e.g "md".
  158 func FileAndExtNoDelimiter(in string) (string, string) {
  159 	file, ext := fileAndExt(in, fpb)
  160 	return file, strings.TrimPrefix(ext, ".")
  161 }
  162 
  163 // Filename takes a file path, strips out the extension,
  164 // and returns the name of the file.
  165 func Filename(in string) (name string) {
  166 	name, _ = fileAndExt(in, fpb)
  167 	return
  168 }
  169 
  170 // PathNoExt takes a path, strips out the extension,
  171 // and returns the name of the file.
  172 func PathNoExt(in string) string {
  173 	return strings.TrimSuffix(in, path.Ext(in))
  174 }
  175 
  176 // FileAndExt returns the filename and any extension of a file path as
  177 // two separate strings.
  178 //
  179 // If the path, in, contains a directory name ending in a slash,
  180 // then both name and ext will be empty strings.
  181 //
  182 // If the path, in, is either the current directory, the parent
  183 // directory or the root directory, or an empty string,
  184 // then both name and ext will be empty strings.
  185 //
  186 // If the path, in, represents the path of a file without an extension,
  187 // then name will be the name of the file and ext will be an empty string.
  188 //
  189 // If the path, in, represents a filename with an extension,
  190 // then name will be the filename minus any extension - including the dot
  191 // and ext will contain the extension - minus the dot.
  192 func fileAndExt(in string, b filepathPathBridge) (name string, ext string) {
  193 	ext = b.Ext(in)
  194 	base := b.Base(in)
  195 
  196 	return extractFilename(in, ext, base, b.Separator()), ext
  197 }
  198 
  199 func extractFilename(in, ext, base, pathSeparator string) (name string) {
  200 	// No file name cases. These are defined as:
  201 	// 1. any "in" path that ends in a pathSeparator
  202 	// 2. any "base" consisting of just an pathSeparator
  203 	// 3. any "base" consisting of just an empty string
  204 	// 4. any "base" consisting of just the current directory i.e. "."
  205 	// 5. any "base" consisting of just the parent directory i.e. ".."
  206 	if (strings.LastIndex(in, pathSeparator) == len(in)-1) || base == "" || base == "." || base == ".." || base == pathSeparator {
  207 		name = "" // there is NO filename
  208 	} else if ext != "" { // there was an Extension
  209 		// return the filename minus the extension (and the ".")
  210 		name = base[:strings.LastIndex(base, ".")]
  211 	} else {
  212 		// no extension case so just return base, which willi
  213 		// be the filename
  214 		name = base
  215 	}
  216 	return
  217 }
  218 
  219 // GetRelativePath returns the relative path of a given path.
  220 func GetRelativePath(path, base string) (final string, err error) {
  221 	if filepath.IsAbs(path) && base == "" {
  222 		return "", errors.New("source: missing base directory")
  223 	}
  224 	name := filepath.Clean(path)
  225 	base = filepath.Clean(base)
  226 
  227 	name, err = filepath.Rel(base, name)
  228 	if err != nil {
  229 		return "", err
  230 	}
  231 
  232 	if strings.HasSuffix(filepath.FromSlash(path), FilePathSeparator) && !strings.HasSuffix(name, FilePathSeparator) {
  233 		name += FilePathSeparator
  234 	}
  235 	return name, nil
  236 }
  237 
  238 func prettifyPath(in string, b filepathPathBridge) string {
  239 	if filepath.Ext(in) == "" {
  240 		// /section/name/  -> /section/name/index.html
  241 		if len(in) < 2 {
  242 			return b.Separator()
  243 		}
  244 		return b.Join(in, "index.html")
  245 	}
  246 	name, ext := fileAndExt(in, b)
  247 	if name == "index" {
  248 		// /section/name/index.html -> /section/name/index.html
  249 		return b.Clean(in)
  250 	}
  251 	// /section/name.html -> /section/name/index.html
  252 	return b.Join(b.Dir(in), name, "index"+ext)
  253 }
  254 
  255 type NamedSlice struct {
  256 	Name  string
  257 	Slice []string
  258 }
  259 
  260 func (n NamedSlice) String() string {
  261 	if len(n.Slice) == 0 {
  262 		return n.Name
  263 	}
  264 	return fmt.Sprintf("%s%s{%s}", n.Name, FilePathSeparator, strings.Join(n.Slice, ","))
  265 }