/common/paths/path.go
https://gitlab.com/gohugo/hugo · Go · 265 lines · 159 code · 43 blank · 63 comment · 41 complexity · b01dc437a9b2c231069abf0412ef1aa7 MD5 · raw file
- // Copyright 2021 The Hugo Authors. All rights reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package paths
- import (
- "errors"
- "fmt"
- "path"
- "path/filepath"
- "regexp"
- "strings"
- )
- // FilePathSeparator as defined by os.Separator.
- const FilePathSeparator = string(filepath.Separator)
- // filepathPathBridge is a bridge for common functionality in filepath vs path
- type filepathPathBridge interface {
- Base(in string) string
- Clean(in string) string
- Dir(in string) string
- Ext(in string) string
- Join(elem ...string) string
- Separator() string
- }
- type filepathBridge struct{}
- func (filepathBridge) Base(in string) string {
- return filepath.Base(in)
- }
- func (filepathBridge) Clean(in string) string {
- return filepath.Clean(in)
- }
- func (filepathBridge) Dir(in string) string {
- return filepath.Dir(in)
- }
- func (filepathBridge) Ext(in string) string {
- return filepath.Ext(in)
- }
- func (filepathBridge) Join(elem ...string) string {
- return filepath.Join(elem...)
- }
- func (filepathBridge) Separator() string {
- return FilePathSeparator
- }
- var fpb filepathBridge
- // AbsPathify creates an absolute path if given a working dir and a relative path.
- // If already absolute, the path is just cleaned.
- func AbsPathify(workingDir, inPath string) string {
- if filepath.IsAbs(inPath) {
- return filepath.Clean(inPath)
- }
- return filepath.Join(workingDir, inPath)
- }
- // MakeTitle converts the path given to a suitable title, trimming whitespace
- // and replacing hyphens with whitespace.
- func MakeTitle(inpath string) string {
- return strings.Replace(strings.TrimSpace(inpath), "-", " ", -1)
- }
- // ReplaceExtension takes a path and an extension, strips the old extension
- // and returns the path with the new extension.
- func ReplaceExtension(path string, newExt string) string {
- f, _ := fileAndExt(path, fpb)
- return f + "." + newExt
- }
- func makePathRelative(inPath string, possibleDirectories ...string) (string, error) {
- for _, currentPath := range possibleDirectories {
- if strings.HasPrefix(inPath, currentPath) {
- return strings.TrimPrefix(inPath, currentPath), nil
- }
- }
- return inPath, errors.New("can't extract relative path, unknown prefix")
- }
- // Should be good enough for Hugo.
- var isFileRe = regexp.MustCompile(`.*\..{1,6}$`)
- // GetDottedRelativePath expects a relative path starting after the content directory.
- // It returns a relative path with dots ("..") navigating up the path structure.
- func GetDottedRelativePath(inPath string) string {
- inPath = filepath.Clean(filepath.FromSlash(inPath))
- if inPath == "." {
- return "./"
- }
- if !isFileRe.MatchString(inPath) && !strings.HasSuffix(inPath, FilePathSeparator) {
- inPath += FilePathSeparator
- }
- if !strings.HasPrefix(inPath, FilePathSeparator) {
- inPath = FilePathSeparator + inPath
- }
- dir, _ := filepath.Split(inPath)
- sectionCount := strings.Count(dir, FilePathSeparator)
- if sectionCount == 0 || dir == FilePathSeparator {
- return "./"
- }
- var dottedPath string
- for i := 1; i < sectionCount; i++ {
- dottedPath += "../"
- }
- return dottedPath
- }
- // ExtNoDelimiter takes a path and returns the extension, excluding the delimiter, i.e. "md".
- func ExtNoDelimiter(in string) string {
- return strings.TrimPrefix(Ext(in), ".")
- }
- // Ext takes a path and returns the extension, including the delimiter, i.e. ".md".
- func Ext(in string) string {
- _, ext := fileAndExt(in, fpb)
- return ext
- }
- // PathAndExt is the same as FileAndExt, but it uses the path package.
- func PathAndExt(in string) (string, string) {
- return fileAndExt(in, pb)
- }
- // FileAndExt takes a path and returns the file and extension separated,
- // the extension including the delimiter, i.e. ".md".
- func FileAndExt(in string) (string, string) {
- return fileAndExt(in, fpb)
- }
- // FileAndExtNoDelimiter takes a path and returns the file and extension separated,
- // the extension excluding the delimiter, e.g "md".
- func FileAndExtNoDelimiter(in string) (string, string) {
- file, ext := fileAndExt(in, fpb)
- return file, strings.TrimPrefix(ext, ".")
- }
- // Filename takes a file path, strips out the extension,
- // and returns the name of the file.
- func Filename(in string) (name string) {
- name, _ = fileAndExt(in, fpb)
- return
- }
- // PathNoExt takes a path, strips out the extension,
- // and returns the name of the file.
- func PathNoExt(in string) string {
- return strings.TrimSuffix(in, path.Ext(in))
- }
- // FileAndExt returns the filename and any extension of a file path as
- // two separate strings.
- //
- // If the path, in, contains a directory name ending in a slash,
- // then both name and ext will be empty strings.
- //
- // If the path, in, is either the current directory, the parent
- // directory or the root directory, or an empty string,
- // then both name and ext will be empty strings.
- //
- // If the path, in, represents the path of a file without an extension,
- // then name will be the name of the file and ext will be an empty string.
- //
- // If the path, in, represents a filename with an extension,
- // then name will be the filename minus any extension - including the dot
- // and ext will contain the extension - minus the dot.
- func fileAndExt(in string, b filepathPathBridge) (name string, ext string) {
- ext = b.Ext(in)
- base := b.Base(in)
- return extractFilename(in, ext, base, b.Separator()), ext
- }
- func extractFilename(in, ext, base, pathSeparator string) (name string) {
- // No file name cases. These are defined as:
- // 1. any "in" path that ends in a pathSeparator
- // 2. any "base" consisting of just an pathSeparator
- // 3. any "base" consisting of just an empty string
- // 4. any "base" consisting of just the current directory i.e. "."
- // 5. any "base" consisting of just the parent directory i.e. ".."
- if (strings.LastIndex(in, pathSeparator) == len(in)-1) || base == "" || base == "." || base == ".." || base == pathSeparator {
- name = "" // there is NO filename
- } else if ext != "" { // there was an Extension
- // return the filename minus the extension (and the ".")
- name = base[:strings.LastIndex(base, ".")]
- } else {
- // no extension case so just return base, which willi
- // be the filename
- name = base
- }
- return
- }
- // GetRelativePath returns the relative path of a given path.
- func GetRelativePath(path, base string) (final string, err error) {
- if filepath.IsAbs(path) && base == "" {
- return "", errors.New("source: missing base directory")
- }
- name := filepath.Clean(path)
- base = filepath.Clean(base)
- name, err = filepath.Rel(base, name)
- if err != nil {
- return "", err
- }
- if strings.HasSuffix(filepath.FromSlash(path), FilePathSeparator) && !strings.HasSuffix(name, FilePathSeparator) {
- name += FilePathSeparator
- }
- return name, nil
- }
- func prettifyPath(in string, b filepathPathBridge) string {
- if filepath.Ext(in) == "" {
- // /section/name/ -> /section/name/index.html
- if len(in) < 2 {
- return b.Separator()
- }
- return b.Join(in, "index.html")
- }
- name, ext := fileAndExt(in, b)
- if name == "index" {
- // /section/name/index.html -> /section/name/index.html
- return b.Clean(in)
- }
- // /section/name.html -> /section/name/index.html
- return b.Join(b.Dir(in), name, "index"+ext)
- }
- type NamedSlice struct {
- Name string
- Slice []string
- }
- func (n NamedSlice) String() string {
- if len(n.Slice) == 0 {
- return n.Name
- }
- return fmt.Sprintf("%s%s{%s}", n.Name, FilePathSeparator, strings.Join(n.Slice, ","))
- }