mirror of
https://github.com/gohugoio/hugo.git
synced 2025-04-28 06:30:33 +03:00

FileAndExt has now be completely rewritten and now works as may reasonably be expected. The test cases for: ReplaceExtension Filename FileAndExt now all pass. The problem was the way path.Base was being used. Firstly Base returns "." if the directory is empty, but it can also return "." for the current directory, or ".." for the parent directory, if these are the last elements in the path. Simply detecting the presence of a "." in the returned string and truncating before the "." does not therefore always result in a valid filename. Secondly, Base strips any trailing slashes making is more difficult to detect when the path does not end in a filename but in a directory name i.e. a no filename case. Not detecting this incorrectly results in the last directory name being returned as the filename. The code has been updated to take account of of both situations so that: 1) An empty string for both the filename and extension is returned if the path does not contain a filename (plus an optional extension). This includes both the empty path case, and the ends in a directory case. 2) If the path represents a filename with an extension then both the filename and the extension (minus any dots) are returned as non empty strings. 3) If the path represents a filename without an extension then filename will be returned as a non empty string (minus any dot) and the extension will be returned as an empty string.
347 lines
8.6 KiB
Go
347 lines
8.6 KiB
Go
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
|
//
|
|
// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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 helpers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/spf13/afero"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
var sanitizeRegexp = regexp.MustCompile("[^a-zA-Z0-9./_-]")
|
|
|
|
// Take a string with any characters and replace it so the string could be used in a path.
|
|
// MakePath creates a Unicode sanitized string, with the spaces replaced, whilst
|
|
// preserving the original casing of the string.
|
|
// E.g. Social Media -> Social-Media
|
|
func MakePath(s string) string {
|
|
return UnicodeSanitize(strings.Replace(strings.TrimSpace(s), " ", "-", -1))
|
|
}
|
|
|
|
// MakePathToLower creates a Unicode santized string, with the spaces replaced,
|
|
// and transformed to lower case.
|
|
// E.g. Social Media -> social-media
|
|
func MakePathToLower(s string) string {
|
|
return strings.ToLower(MakePath(s))
|
|
}
|
|
|
|
func MakeTitle(inpath string) string {
|
|
return strings.Replace(strings.TrimSpace(inpath), "-", " ", -1)
|
|
}
|
|
|
|
// unused
|
|
//func Sanitize(s string) string {
|
|
// return sanitizeRegexp.ReplaceAllString(s, "")
|
|
//}
|
|
|
|
func UnicodeSanitize(s string) string {
|
|
source := []rune(s)
|
|
target := make([]rune, 0, len(source))
|
|
|
|
for _, r := range source {
|
|
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '.' || r == '/' || r == '_' || r == '-' {
|
|
target = append(target, r)
|
|
}
|
|
}
|
|
|
|
return string(target)
|
|
}
|
|
|
|
func ReplaceExtension(path string, newExt string) string {
|
|
f, _ := FileAndExt(path)
|
|
return f + "." + newExt
|
|
}
|
|
|
|
// Check if Exists && is Directory
|
|
func DirExists(path string, fs afero.Fs) (bool, error) {
|
|
fi, err := fs.Stat(path)
|
|
if err == nil && fi.IsDir() {
|
|
return true, nil
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
func IsDir(path string, fs afero.Fs) (bool, error) {
|
|
fi, err := fs.Stat(path)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return fi.IsDir(), nil
|
|
}
|
|
|
|
func IsEmpty(path string, fs afero.Fs) (bool, error) {
|
|
if b, _ := Exists(path, fs); !b {
|
|
return false, fmt.Errorf("%q path does not exist", path)
|
|
}
|
|
fi, err := fs.Stat(path)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if fi.IsDir() {
|
|
f, err := os.Open(path)
|
|
// FIX: Resource leak - f.close() should be called here by defer or is missed
|
|
// if the err != nil branch is taken.
|
|
defer f.Close()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
list, err := f.Readdir(-1)
|
|
// f.Close() - see bug fix above
|
|
return len(list) == 0, nil
|
|
} else {
|
|
return fi.Size() == 0, nil
|
|
}
|
|
}
|
|
|
|
// Check if File / Directory Exists
|
|
func Exists(path string, fs afero.Fs) (bool, error) {
|
|
_, err := fs.Stat(path)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
func AbsPathify(inPath string) string {
|
|
if filepath.IsAbs(inPath) {
|
|
return filepath.Clean(inPath)
|
|
}
|
|
|
|
return filepath.Clean(filepath.Join(viper.GetString("WorkingDir"), inPath))
|
|
}
|
|
|
|
func MakeStaticPathRelative(inPath string) (string, error) {
|
|
staticDir := AbsPathify(viper.GetString("StaticDir"))
|
|
themeStaticDir := AbsPathify("themes/"+viper.GetString("theme")) + "/static/"
|
|
|
|
return MakePathRelative(inPath, staticDir, themeStaticDir)
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
func Filename(in string) (name string) {
|
|
name, _ = FileAndExt(in)
|
|
return
|
|
}
|
|
|
|
// FileAndExt returns the filename and any extension of a file path as
|
|
// two separate strings.
|
|
// If 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
|
|
// 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) (name string, ext string) {
|
|
ext = path.Ext(in)
|
|
base := path.Base(in) // path.Base strips any trailing slash!
|
|
|
|
// No file name cases. These are defined as:
|
|
// 1. any "in" path that ends in a os.PathSeparator i.e. "/" on linux
|
|
// 2. any "base" consisting of just an os.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, string(os.PathSeparator)) == len(in)-1) || base == "" || base == "." || base == ".." || base == string(os.PathListSeparator) {
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
name = filepath.ToSlash(name)
|
|
return name, nil
|
|
}
|
|
|
|
// Given a source path, determine the section
|
|
func GuessSection(in string) string {
|
|
parts := strings.Split(in, "/")
|
|
|
|
if len(parts) == 0 {
|
|
return ""
|
|
}
|
|
|
|
// trim filename
|
|
if !strings.HasSuffix(in, "/") {
|
|
parts = parts[:len(parts)-1]
|
|
}
|
|
|
|
if len(parts) == 0 {
|
|
return ""
|
|
}
|
|
|
|
// if first directory is "content", return second directory
|
|
section := ""
|
|
|
|
if parts[0] == "content" && len(parts) > 1 {
|
|
section = parts[1]
|
|
} else {
|
|
section = parts[0]
|
|
}
|
|
|
|
if section == "." {
|
|
return ""
|
|
}
|
|
|
|
return section
|
|
}
|
|
|
|
func PathPrep(ugly bool, in string) string {
|
|
if ugly {
|
|
return Uglify(in)
|
|
} else {
|
|
return PrettifyPath(in)
|
|
}
|
|
}
|
|
|
|
// /section/name.html -> /section/name/index.html
|
|
// /section/name/ -> /section/name/index.html
|
|
// /section/name/index.html -> /section/name/index.html
|
|
func PrettifyPath(in string) string {
|
|
if path.Ext(in) == "" {
|
|
// /section/name/ -> /section/name/index.html
|
|
if len(in) < 2 {
|
|
return "/"
|
|
}
|
|
return path.Join(path.Clean(in), "index.html")
|
|
} else {
|
|
name, ext := FileAndExt(in)
|
|
if name == "index" {
|
|
// /section/name/index.html -> /section/name/index.html
|
|
return path.Clean(in)
|
|
} else {
|
|
// /section/name.html -> /section/name/index.html
|
|
return path.Join(path.Dir(in), name, "index"+ext)
|
|
}
|
|
}
|
|
}
|
|
|
|
func FindCWD() (string, error) {
|
|
serverFile, err := filepath.Abs(os.Args[0])
|
|
|
|
if err != nil {
|
|
return "", fmt.Errorf("Can't get absolute path for executable: %v", err)
|
|
}
|
|
|
|
path := filepath.Dir(serverFile)
|
|
realFile, err := filepath.EvalSymlinks(serverFile)
|
|
|
|
if err != nil {
|
|
if _, err = os.Stat(serverFile + ".exe"); err == nil {
|
|
realFile = filepath.Clean(serverFile + ".exe")
|
|
}
|
|
}
|
|
|
|
if err == nil && realFile != serverFile {
|
|
path = filepath.Dir(realFile)
|
|
}
|
|
|
|
return path, nil
|
|
}
|
|
|
|
func SafeWriteToDisk(inpath string, r io.Reader, fs afero.Fs) (err error) {
|
|
dir, _ := filepath.Split(inpath)
|
|
ospath := filepath.FromSlash(dir)
|
|
|
|
if ospath != "" {
|
|
err = fs.MkdirAll(ospath, 0777) // rwx, rw, r
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
exists, err := Exists(inpath, fs)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if exists {
|
|
return fmt.Errorf("%v already exists", inpath)
|
|
}
|
|
|
|
file, err := fs.Create(inpath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
_, err = io.Copy(file, r)
|
|
return
|
|
}
|
|
|
|
func WriteToDisk(inpath string, r io.Reader, fs afero.Fs) (err error) {
|
|
dir, _ := filepath.Split(inpath)
|
|
ospath := filepath.FromSlash(dir)
|
|
|
|
if ospath != "" {
|
|
err = fs.MkdirAll(ospath, 0777) // rwx, rw, r
|
|
if err != nil {
|
|
if err != os.ErrExist {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
file, err := fs.Create(inpath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
_, err = io.Copy(file, r)
|
|
return
|
|
}
|