mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
Split out the puthe path/filepath functions into common/paths
So they can be used from the config package without cyclic troubles. Updates #8654
This commit is contained in:
parent
5af045ebab
commit
93aad3c543
21 changed files with 956 additions and 591 deletions
|
@ -31,6 +31,8 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/livereload"
|
"github.com/gohugoio/hugo/livereload"
|
||||||
|
@ -275,7 +277,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
|
||||||
func getRootWatchDirsStr(baseDir string, watchDirs []string) string {
|
func getRootWatchDirsStr(baseDir string, watchDirs []string) string {
|
||||||
relWatchDirs := make([]string, len(watchDirs))
|
relWatchDirs := make([]string, len(watchDirs))
|
||||||
for i, dir := range watchDirs {
|
for i, dir := range watchDirs {
|
||||||
relWatchDirs[i], _ = helpers.GetRelativePath(dir, baseDir)
|
relWatchDirs[i], _ = paths.GetRelativePath(dir, baseDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(helpers.UniqueStringsSorted(helpers.ExtractRootPaths(relWatchDirs)), ",")
|
return strings.Join(helpers.UniqueStringsSorted(helpers.ExtractRootPaths(relWatchDirs)), ",")
|
||||||
|
|
312
common/paths/path.go
Normal file
312
common/paths/path.go
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
// 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"
|
||||||
|
"os"
|
||||||
|
"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
|
||||||
|
|
||||||
|
// ToSlashTrimLeading is just a filepath.ToSlash with an added / prefix trimmer.
|
||||||
|
func ToSlashTrimLeading(s string) string {
|
||||||
|
return strings.TrimPrefix(filepath.ToSlash(s), "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathPrep prepares the path using the uglify setting to create paths on
|
||||||
|
// either the form /section/name/index.html or /section/name.html.
|
||||||
|
func PathPrep(ugly bool, in string) string {
|
||||||
|
if ugly {
|
||||||
|
return Uglify(in)
|
||||||
|
}
|
||||||
|
return PrettifyPath(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrettifyPath is the same as PrettifyURLPath but for file paths.
|
||||||
|
// /section/name.html becomes /section/name/index.html
|
||||||
|
// /section/name/ becomes /section/name/index.html
|
||||||
|
// /section/name/index.html becomes /section/name/index.html
|
||||||
|
func PrettifyPath(in string) string {
|
||||||
|
return prettifyPath(in, fpb)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindCWD returns the current working directory from where the Hugo
|
||||||
|
// executable is run.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTrailingSlash adds a trailing Unix styled slash (/) if not already
|
||||||
|
// there.
|
||||||
|
func AddTrailingSlash(path string) string {
|
||||||
|
if !strings.HasSuffix(path, "/") {
|
||||||
|
path += "/"
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
252
common/paths/path_test.go
Normal file
252
common/paths/path_test.go
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
// 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 (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
qt "github.com/frankban/quicktest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetRelativePath(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
base string
|
||||||
|
expect interface{}
|
||||||
|
}{
|
||||||
|
{filepath.FromSlash("/a/b"), filepath.FromSlash("/a"), filepath.FromSlash("b")},
|
||||||
|
{filepath.FromSlash("/a/b/c/"), filepath.FromSlash("/a"), filepath.FromSlash("b/c/")},
|
||||||
|
{filepath.FromSlash("/c"), filepath.FromSlash("/a/b"), filepath.FromSlash("../../c")},
|
||||||
|
{filepath.FromSlash("/c"), "", false},
|
||||||
|
}
|
||||||
|
for i, this := range tests {
|
||||||
|
// ultimately a fancy wrapper around filepath.Rel
|
||||||
|
result, err := GetRelativePath(this.path, this.base)
|
||||||
|
|
||||||
|
if b, ok := this.expect.(bool); ok && !b {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("[%d] GetRelativePath didn't return an expected error", i)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] GetRelativePath failed: %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if result != this.expect {
|
||||||
|
t.Errorf("[%d] GetRelativePath got %v but expected %v", i, result, this.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakePathRelative(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
inPath, path1, path2, output string
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []test{
|
||||||
|
{"/abc/bcd/ab.css", "/abc/bcd", "/bbc/bcd", "/ab.css"},
|
||||||
|
{"/abc/bcd/ab.css", "/abcd/bcd", "/abc/bcd", "/ab.css"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range data {
|
||||||
|
output, _ := makePathRelative(d.inPath, d.path1, d.path2)
|
||||||
|
if d.output != output {
|
||||||
|
t.Errorf("Test #%d failed. Expected %q got %q", i, d.output, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, error := makePathRelative("a/b/c.ss", "/a/c", "/d/c", "/e/f")
|
||||||
|
|
||||||
|
if error == nil {
|
||||||
|
t.Errorf("Test failed, expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDottedRelativePath(t *testing.T) {
|
||||||
|
// on Windows this will receive both kinds, both country and western ...
|
||||||
|
for _, f := range []func(string) string{filepath.FromSlash, func(s string) string { return s }} {
|
||||||
|
doTestGetDottedRelativePath(f, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestGetDottedRelativePath(urlFixer func(string) string, t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
input, expected string
|
||||||
|
}
|
||||||
|
data := []test{
|
||||||
|
{"", "./"},
|
||||||
|
{urlFixer("/"), "./"},
|
||||||
|
{urlFixer("post"), "../"},
|
||||||
|
{urlFixer("/post"), "../"},
|
||||||
|
{urlFixer("post/"), "../"},
|
||||||
|
{urlFixer("tags/foo.html"), "../"},
|
||||||
|
{urlFixer("/tags/foo.html"), "../"},
|
||||||
|
{urlFixer("/post/"), "../"},
|
||||||
|
{urlFixer("////post/////"), "../"},
|
||||||
|
{urlFixer("/foo/bar/index.html"), "../../"},
|
||||||
|
{urlFixer("/foo/bar/foo/"), "../../../"},
|
||||||
|
{urlFixer("/foo/bar/foo"), "../../../"},
|
||||||
|
{urlFixer("foo/bar/foo/"), "../../../"},
|
||||||
|
{urlFixer("foo/bar/foo/bar"), "../../../../"},
|
||||||
|
{"404.html", "./"},
|
||||||
|
{"404.xml", "./"},
|
||||||
|
{"/404.html", "./"},
|
||||||
|
}
|
||||||
|
for i, d := range data {
|
||||||
|
output := GetDottedRelativePath(d.input)
|
||||||
|
if d.expected != output {
|
||||||
|
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeTitle(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
input, expected string
|
||||||
|
}
|
||||||
|
data := []test{
|
||||||
|
{"Make-Title", "Make Title"},
|
||||||
|
{"MakeTitle", "MakeTitle"},
|
||||||
|
{"make_title", "make_title"},
|
||||||
|
}
|
||||||
|
for i, d := range data {
|
||||||
|
output := MakeTitle(d.input)
|
||||||
|
if d.expected != output {
|
||||||
|
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace Extension is probably poorly named, but the intent of the
|
||||||
|
// function is to accept a path and return only the file name with a
|
||||||
|
// new extension. It's intentionally designed to strip out the path
|
||||||
|
// and only provide the name. We should probably rename the function to
|
||||||
|
// be more explicit at some point.
|
||||||
|
func TestReplaceExtension(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
input, newext, expected string
|
||||||
|
}
|
||||||
|
data := []test{
|
||||||
|
// These work according to the above definition
|
||||||
|
{"/some/random/path/file.xml", "html", "file.html"},
|
||||||
|
{"/banana.html", "xml", "banana.xml"},
|
||||||
|
{"./banana.html", "xml", "banana.xml"},
|
||||||
|
{"banana/pie/index.html", "xml", "index.xml"},
|
||||||
|
{"../pies/fish/index.html", "xml", "index.xml"},
|
||||||
|
// but these all fail
|
||||||
|
{"filename-without-an-ext", "ext", "filename-without-an-ext.ext"},
|
||||||
|
{"/filename-without-an-ext", "ext", "filename-without-an-ext.ext"},
|
||||||
|
{"/directory/mydir/", "ext", ".ext"},
|
||||||
|
{"mydir/", "ext", ".ext"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range data {
|
||||||
|
output := ReplaceExtension(filepath.FromSlash(d.input), d.newext)
|
||||||
|
if d.expected != output {
|
||||||
|
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtNoDelimiter(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
c.Assert(ExtNoDelimiter(filepath.FromSlash("/my/data.json")), qt.Equals, "json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilename(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
input, expected string
|
||||||
|
}
|
||||||
|
data := []test{
|
||||||
|
{"index.html", "index"},
|
||||||
|
{"./index.html", "index"},
|
||||||
|
{"/index.html", "index"},
|
||||||
|
{"index", "index"},
|
||||||
|
{"/tmp/index.html", "index"},
|
||||||
|
{"./filename-no-ext", "filename-no-ext"},
|
||||||
|
{"/filename-no-ext", "filename-no-ext"},
|
||||||
|
{"filename-no-ext", "filename-no-ext"},
|
||||||
|
{"directory/", ""}, // no filename case??
|
||||||
|
{"directory/.hidden.ext", ".hidden"},
|
||||||
|
{"./directory/../~/banana/gold.fish", "gold"},
|
||||||
|
{"../directory/banana.man", "banana"},
|
||||||
|
{"~/mydir/filename.ext", "filename"},
|
||||||
|
{"./directory//tmp/filename.ext", "filename"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range data {
|
||||||
|
output := Filename(filepath.FromSlash(d.input))
|
||||||
|
if d.expected != output {
|
||||||
|
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileAndExt(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
input, expectedFile, expectedExt string
|
||||||
|
}
|
||||||
|
data := []test{
|
||||||
|
{"index.html", "index", ".html"},
|
||||||
|
{"./index.html", "index", ".html"},
|
||||||
|
{"/index.html", "index", ".html"},
|
||||||
|
{"index", "index", ""},
|
||||||
|
{"/tmp/index.html", "index", ".html"},
|
||||||
|
{"./filename-no-ext", "filename-no-ext", ""},
|
||||||
|
{"/filename-no-ext", "filename-no-ext", ""},
|
||||||
|
{"filename-no-ext", "filename-no-ext", ""},
|
||||||
|
{"directory/", "", ""}, // no filename case??
|
||||||
|
{"directory/.hidden.ext", ".hidden", ".ext"},
|
||||||
|
{"./directory/../~/banana/gold.fish", "gold", ".fish"},
|
||||||
|
{"../directory/banana.man", "banana", ".man"},
|
||||||
|
{"~/mydir/filename.ext", "filename", ".ext"},
|
||||||
|
{"./directory//tmp/filename.ext", "filename", ".ext"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range data {
|
||||||
|
file, ext := fileAndExt(filepath.FromSlash(d.input), fpb)
|
||||||
|
if d.expectedFile != file {
|
||||||
|
t.Errorf("Test %d failed. Expected filename %q got %q.", i, d.expectedFile, file)
|
||||||
|
}
|
||||||
|
if d.expectedExt != ext {
|
||||||
|
t.Errorf("Test %d failed. Expected extension %q got %q.", i, d.expectedExt, ext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindCWD(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
expectedDir string
|
||||||
|
expectedErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// cwd, _ := os.Getwd()
|
||||||
|
data := []test{
|
||||||
|
//{cwd, nil},
|
||||||
|
// Commenting this out. It doesn't work properly.
|
||||||
|
// There's a good reason why we don't use os.Getwd(), it doesn't actually work the way we want it to.
|
||||||
|
// I really don't know a better way to test this function. - SPF 2014.11.04
|
||||||
|
}
|
||||||
|
for i, d := range data {
|
||||||
|
dir, err := FindCWD()
|
||||||
|
if d.expectedDir != dir {
|
||||||
|
t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedDir, dir)
|
||||||
|
}
|
||||||
|
if d.expectedErr != err {
|
||||||
|
t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
212
common/paths/url.go
Normal file
212
common/paths/url.go
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/purell"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pathBridge struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pathBridge) Base(in string) string {
|
||||||
|
return path.Base(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pathBridge) Clean(in string) string {
|
||||||
|
return path.Clean(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pathBridge) Dir(in string) string {
|
||||||
|
return path.Dir(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pathBridge) Ext(in string) string {
|
||||||
|
return path.Ext(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pathBridge) Join(elem ...string) string {
|
||||||
|
return path.Join(elem...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pathBridge) Separator() string {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pb pathBridge
|
||||||
|
|
||||||
|
func sanitizeURLWithFlags(in string, f purell.NormalizationFlags) string {
|
||||||
|
s, err := purell.NormalizeURLString(in, f)
|
||||||
|
if err != nil {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary workaround for the bug fix and resulting
|
||||||
|
// behavioral change in purell.NormalizeURLString():
|
||||||
|
// a leading '/' was inadvertently added to relative links,
|
||||||
|
// but no longer, see #878.
|
||||||
|
//
|
||||||
|
// I think the real solution is to allow Hugo to
|
||||||
|
// make relative URL with relative path,
|
||||||
|
// e.g. "../../post/hello-again/", as wished by users
|
||||||
|
// in issues #157, #622, etc., without forcing
|
||||||
|
// relative URLs to begin with '/'.
|
||||||
|
// Once the fixes are in, let's remove this kludge
|
||||||
|
// and restore SanitizeURL() to the way it was.
|
||||||
|
// -- @anthonyfok, 2015-02-16
|
||||||
|
//
|
||||||
|
// Begin temporary kludge
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if len(u.Path) > 0 && !strings.HasPrefix(u.Path, "/") {
|
||||||
|
u.Path = "/" + u.Path
|
||||||
|
}
|
||||||
|
return u.String()
|
||||||
|
// End temporary kludge
|
||||||
|
|
||||||
|
// return s
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// SanitizeURL sanitizes the input URL string.
|
||||||
|
func SanitizeURL(in string) string {
|
||||||
|
return sanitizeURLWithFlags(in, purell.FlagsSafe|purell.FlagRemoveTrailingSlash|purell.FlagRemoveDotSegments|purell.FlagRemoveDuplicateSlashes|purell.FlagRemoveUnnecessaryHostDots|purell.FlagRemoveEmptyPortSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SanitizeURLKeepTrailingSlash is the same as SanitizeURL, but will keep any trailing slash.
|
||||||
|
func SanitizeURLKeepTrailingSlash(in string) string {
|
||||||
|
return sanitizeURLWithFlags(in, purell.FlagsSafe|purell.FlagRemoveDotSegments|purell.FlagRemoveDuplicateSlashes|purell.FlagRemoveUnnecessaryHostDots|purell.FlagRemoveEmptyPortSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakePermalink combines base URL with content path to create full URL paths.
|
||||||
|
// Example
|
||||||
|
// base: http://spf13.com/
|
||||||
|
// path: post/how-i-blog
|
||||||
|
// result: http://spf13.com/post/how-i-blog
|
||||||
|
func MakePermalink(host, plink string) *url.URL {
|
||||||
|
base, err := url.Parse(host)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := url.Parse(plink)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Host != "" {
|
||||||
|
panic(fmt.Errorf("can't make permalink from absolute link %q", plink))
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Path = path.Join(base.Path, p.Path)
|
||||||
|
|
||||||
|
// path.Join will strip off the last /, so put it back if it was there.
|
||||||
|
hadTrailingSlash := (plink == "" && strings.HasSuffix(host, "/")) || strings.HasSuffix(p.Path, "/")
|
||||||
|
if hadTrailingSlash && !strings.HasSuffix(base.Path, "/") {
|
||||||
|
base.Path = base.Path + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAbsURL determines whether the given path points to an absolute URL.
|
||||||
|
func IsAbsURL(path string) bool {
|
||||||
|
url, err := url.Parse(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.IsAbs() || strings.HasPrefix(path, "//")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddContextRoot adds the context root to an URL if it's not already set.
|
||||||
|
// For relative URL entries on sites with a base url with a context root set (i.e. http://example.com/mysite),
|
||||||
|
// relative URLs must not include the context root if canonifyURLs is enabled. But if it's disabled, it must be set.
|
||||||
|
func AddContextRoot(baseURL, relativePath string) string {
|
||||||
|
url, err := url.Parse(baseURL)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newPath := path.Join(url.Path, relativePath)
|
||||||
|
|
||||||
|
// path strips trailing slash, ignore root path.
|
||||||
|
if newPath != "/" && strings.HasSuffix(relativePath, "/") {
|
||||||
|
newPath += "/"
|
||||||
|
}
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLizeAn
|
||||||
|
|
||||||
|
// PrettifyURL takes a URL string and returns a semantic, clean URL.
|
||||||
|
func PrettifyURL(in string) string {
|
||||||
|
x := PrettifyURLPath(in)
|
||||||
|
|
||||||
|
if path.Base(x) == "index.html" {
|
||||||
|
return path.Dir(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
if in == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrettifyURLPath takes a URL path to a content and converts it
|
||||||
|
// to enable pretty URLs.
|
||||||
|
// /section/name.html becomes /section/name/index.html
|
||||||
|
// /section/name/ becomes /section/name/index.html
|
||||||
|
// /section/name/index.html becomes /section/name/index.html
|
||||||
|
func PrettifyURLPath(in string) string {
|
||||||
|
return prettifyPath(in, pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uglify does the opposite of PrettifyURLPath().
|
||||||
|
// /section/name/index.html becomes /section/name.html
|
||||||
|
// /section/name/ becomes /section/name.html
|
||||||
|
// /section/name.html becomes /section/name.html
|
||||||
|
func Uglify(in string) string {
|
||||||
|
if path.Ext(in) == "" {
|
||||||
|
if len(in) < 2 {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
// /section/name/ -> /section/name.html
|
||||||
|
return path.Clean(in) + ".html"
|
||||||
|
}
|
||||||
|
|
||||||
|
name, ext := fileAndExt(in, pb)
|
||||||
|
if name == "index" {
|
||||||
|
// /section/name/index.html -> /section/name.html
|
||||||
|
d := path.Dir(in)
|
||||||
|
if len(d) > 1 {
|
||||||
|
return d + ext
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
// /.xml -> /index.xml
|
||||||
|
if name == "" {
|
||||||
|
return path.Dir(in) + "index" + ext
|
||||||
|
}
|
||||||
|
// /section/name.html -> /section/name.html
|
||||||
|
return path.Clean(in)
|
||||||
|
}
|
129
common/paths/url_test.go
Normal file
129
common/paths/url_test.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
// 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 (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
qt "github.com/frankban/quicktest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSanitizeURL(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"http://foo.bar/", "http://foo.bar"},
|
||||||
|
{"http://foo.bar", "http://foo.bar"}, // issue #1105
|
||||||
|
{"http://foo.bar/zoo/", "http://foo.bar/zoo"}, // issue #931
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
o1 := SanitizeURL(test.input)
|
||||||
|
o2 := SanitizeURLKeepTrailingSlash(test.input)
|
||||||
|
|
||||||
|
expected2 := test.expected
|
||||||
|
|
||||||
|
if strings.HasSuffix(test.input, "/") && !strings.HasSuffix(expected2, "/") {
|
||||||
|
expected2 += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
if o1 != test.expected {
|
||||||
|
t.Errorf("[%d] 1: Expected %#v, got %#v\n", i, test.expected, o1)
|
||||||
|
}
|
||||||
|
if o2 != expected2 {
|
||||||
|
t.Errorf("[%d] 2: Expected %#v, got %#v\n", i, expected2, o2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakePermalink(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
host, link, output string
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []test{
|
||||||
|
{"http://abc.com/foo", "post/bar", "http://abc.com/foo/post/bar"},
|
||||||
|
{"http://abc.com/foo/", "post/bar", "http://abc.com/foo/post/bar"},
|
||||||
|
{"http://abc.com", "post/bar", "http://abc.com/post/bar"},
|
||||||
|
{"http://abc.com", "bar", "http://abc.com/bar"},
|
||||||
|
{"http://abc.com/foo/bar", "post/bar", "http://abc.com/foo/bar/post/bar"},
|
||||||
|
{"http://abc.com/foo/bar", "post/bar/", "http://abc.com/foo/bar/post/bar/"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range data {
|
||||||
|
output := MakePermalink(d.host, d.link).String()
|
||||||
|
if d.output != output {
|
||||||
|
t.Errorf("Test #%d failed. Expected %q got %q", i, d.output, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddContextRoot(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
baseURL string
|
||||||
|
url string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"http://example.com/sub/", "/foo", "/sub/foo"},
|
||||||
|
{"http://example.com/sub/", "/foo/index.html", "/sub/foo/index.html"},
|
||||||
|
{"http://example.com/sub1/sub2", "/foo", "/sub1/sub2/foo"},
|
||||||
|
{"http://example.com", "/foo", "/foo"},
|
||||||
|
// cannot guess that the context root is already added int the example below
|
||||||
|
{"http://example.com/sub/", "/sub/foo", "/sub/sub/foo"},
|
||||||
|
{"http://example.com/тря", "/трям/", "/тря/трям/"},
|
||||||
|
{"http://example.com", "/", "/"},
|
||||||
|
{"http://example.com/bar", "//", "/bar/"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
output := AddContextRoot(test.baseURL, test.url)
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("Expected %#v, got %#v\n", test.expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPretty(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
c.Assert("/section/name/index.html", qt.Equals, PrettifyURLPath("/section/name.html"))
|
||||||
|
c.Assert("/section/sub/name/index.html", qt.Equals, PrettifyURLPath("/section/sub/name.html"))
|
||||||
|
c.Assert("/section/name/index.html", qt.Equals, PrettifyURLPath("/section/name/"))
|
||||||
|
c.Assert("/section/name/index.html", qt.Equals, PrettifyURLPath("/section/name/index.html"))
|
||||||
|
c.Assert("/index.html", qt.Equals, PrettifyURLPath("/index.html"))
|
||||||
|
c.Assert("/name/index.xml", qt.Equals, PrettifyURLPath("/name.xml"))
|
||||||
|
c.Assert("/", qt.Equals, PrettifyURLPath("/"))
|
||||||
|
c.Assert("/", qt.Equals, PrettifyURLPath(""))
|
||||||
|
c.Assert("/section/name", qt.Equals, PrettifyURL("/section/name.html"))
|
||||||
|
c.Assert("/section/sub/name", qt.Equals, PrettifyURL("/section/sub/name.html"))
|
||||||
|
c.Assert("/section/name", qt.Equals, PrettifyURL("/section/name/"))
|
||||||
|
c.Assert("/section/name", qt.Equals, PrettifyURL("/section/name/index.html"))
|
||||||
|
c.Assert("/", qt.Equals, PrettifyURL("/index.html"))
|
||||||
|
c.Assert("/name/index.xml", qt.Equals, PrettifyURL("/name.xml"))
|
||||||
|
c.Assert("/", qt.Equals, PrettifyURL("/"))
|
||||||
|
c.Assert("/", qt.Equals, PrettifyURL(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUgly(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
c.Assert("/section/name.html", qt.Equals, Uglify("/section/name.html"))
|
||||||
|
c.Assert("/section/sub/name.html", qt.Equals, Uglify("/section/sub/name.html"))
|
||||||
|
c.Assert("/section/name.html", qt.Equals, Uglify("/section/name/"))
|
||||||
|
c.Assert("/section/name.html", qt.Equals, Uglify("/section/name/index.html"))
|
||||||
|
c.Assert("/index.html", qt.Equals, Uglify("/index.html"))
|
||||||
|
c.Assert("/name.xml", qt.Equals, Uglify("/name.xml"))
|
||||||
|
c.Assert("/", qt.Equals, Uglify("/"))
|
||||||
|
c.Assert("/", qt.Equals, Uglify(""))
|
||||||
|
}
|
|
@ -21,6 +21,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/hexec"
|
"github.com/gohugoio/hugo/common/hexec"
|
||||||
|
@ -39,7 +41,7 @@ import (
|
||||||
func NewContent(
|
func NewContent(
|
||||||
sites *hugolib.HugoSites, kind, targetPath string) error {
|
sites *hugolib.HugoSites, kind, targetPath string) error {
|
||||||
targetPath = filepath.Clean(targetPath)
|
targetPath = filepath.Clean(targetPath)
|
||||||
ext := helpers.Ext(targetPath)
|
ext := paths.Ext(targetPath)
|
||||||
ps := sites.PathSpec
|
ps := sites.PathSpec
|
||||||
archetypeFs := ps.BaseFs.SourceFilesystems.Archetypes.Fs
|
archetypeFs := ps.BaseFs.SourceFilesystems.Archetypes.Fs
|
||||||
sourceFs := ps.Fs.Source
|
sourceFs := ps.Fs.Source
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
|
@ -129,7 +131,7 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, name, kind, targetPath, archety
|
||||||
|
|
||||||
// Reuse the Hugo template setup to get the template funcs properly set up.
|
// Reuse the Hugo template setup to get the template funcs properly set up.
|
||||||
templateHandler := s.Deps.Tmpl().(tpl.TemplateManager)
|
templateHandler := s.Deps.Tmpl().(tpl.TemplateManager)
|
||||||
templateName := helpers.Filename(archetypeFilename)
|
templateName := paths.Filename(archetypeFilename)
|
||||||
if err := templateHandler.AddTemplate("_text/"+templateName, string(archetypeTemplate)); err != nil {
|
if err := templateHandler.AddTemplate("_text/"+templateName, string(archetypeTemplate)); err != nil {
|
||||||
return nil, errors.Wrapf(err, "Failed to parse archetype file %q:", archetypeFilename)
|
return nil, errors.Wrapf(err, "Failed to parse archetype file %q:", archetypeFilename)
|
||||||
}
|
}
|
||||||
|
|
185
helpers/path.go
185
helpers/path.go
|
@ -18,7 +18,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -39,45 +38,6 @@ import (
|
||||||
// ErrThemeUndefined is returned when a theme has not be defined by the user.
|
// ErrThemeUndefined is returned when a theme has not be defined by the user.
|
||||||
var ErrThemeUndefined = errors.New("no theme set")
|
var ErrThemeUndefined = errors.New("no theme set")
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// MakePath takes a string with any characters and replace it
|
// MakePath takes a string with any characters and replace it
|
||||||
// so the string could be used in a path.
|
// so the string could be used in a path.
|
||||||
// It does so by creating a Unicode-sanitized string, with the spaces replaced,
|
// It does so by creating a Unicode-sanitized string, with the spaces replaced,
|
||||||
|
@ -159,13 +119,6 @@ func (p *PathSpec) UnicodeSanitize(s string) string {
|
||||||
return string(target)
|
return string(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
func makePathRelative(inPath string, possibleDirectories ...string) (string, error) {
|
||||||
for _, currentPath := range possibleDirectories {
|
for _, currentPath := range possibleDirectories {
|
||||||
if strings.HasPrefix(inPath, currentPath) {
|
if strings.HasPrefix(inPath, currentPath) {
|
||||||
|
@ -212,144 +165,6 @@ func GetDottedRelativePath(inPath string) string {
|
||||||
return 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathPrep prepares the path using the uglify setting to create paths on
|
|
||||||
// either the form /section/name/index.html or /section/name.html.
|
|
||||||
func PathPrep(ugly bool, in string) string {
|
|
||||||
if ugly {
|
|
||||||
return Uglify(in)
|
|
||||||
}
|
|
||||||
return PrettifyPath(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrettifyPath is the same as PrettifyURLPath but for file paths.
|
|
||||||
// /section/name.html becomes /section/name/index.html
|
|
||||||
// /section/name/ becomes /section/name/index.html
|
|
||||||
// /section/name/index.html becomes /section/name/index.html
|
|
||||||
func PrettifyPath(in string) string {
|
|
||||||
return prettifyPath(in, fpb)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
type NamedSlice struct {
|
||||||
Name string
|
Name string
|
||||||
Slice []string
|
Slice []string
|
||||||
|
|
|
@ -123,38 +123,6 @@ func TestMakePathSanitizedDisablePathToLower(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRelativePath(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
path string
|
|
||||||
base string
|
|
||||||
expect interface{}
|
|
||||||
}{
|
|
||||||
{filepath.FromSlash("/a/b"), filepath.FromSlash("/a"), filepath.FromSlash("b")},
|
|
||||||
{filepath.FromSlash("/a/b/c/"), filepath.FromSlash("/a"), filepath.FromSlash("b/c/")},
|
|
||||||
{filepath.FromSlash("/c"), filepath.FromSlash("/a/b"), filepath.FromSlash("../../c")},
|
|
||||||
{filepath.FromSlash("/c"), "", false},
|
|
||||||
}
|
|
||||||
for i, this := range tests {
|
|
||||||
// ultimately a fancy wrapper around filepath.Rel
|
|
||||||
result, err := GetRelativePath(this.path, this.base)
|
|
||||||
|
|
||||||
if b, ok := this.expect.(bool); ok && !b {
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("[%d] GetRelativePath didn't return an expected error", i)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("[%d] GetRelativePath failed: %s", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if result != this.expect {
|
|
||||||
t.Errorf("[%d] GetRelativePath got %v but expected %v", i, result, this.expect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakePathRelative(t *testing.T) {
|
func TestMakePathRelative(t *testing.T) {
|
||||||
type test struct {
|
type test struct {
|
||||||
inPath, path1, path2, output string
|
inPath, path1, path2, output string
|
||||||
|
@ -233,37 +201,6 @@ func TestMakeTitle(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace Extension is probably poorly named, but the intent of the
|
|
||||||
// function is to accept a path and return only the file name with a
|
|
||||||
// new extension. It's intentionally designed to strip out the path
|
|
||||||
// and only provide the name. We should probably rename the function to
|
|
||||||
// be more explicit at some point.
|
|
||||||
func TestReplaceExtension(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
input, newext, expected string
|
|
||||||
}
|
|
||||||
data := []test{
|
|
||||||
// These work according to the above definition
|
|
||||||
{"/some/random/path/file.xml", "html", "file.html"},
|
|
||||||
{"/banana.html", "xml", "banana.xml"},
|
|
||||||
{"./banana.html", "xml", "banana.xml"},
|
|
||||||
{"banana/pie/index.html", "xml", "index.xml"},
|
|
||||||
{"../pies/fish/index.html", "xml", "index.xml"},
|
|
||||||
// but these all fail
|
|
||||||
{"filename-without-an-ext", "ext", "filename-without-an-ext.ext"},
|
|
||||||
{"/filename-without-an-ext", "ext", "filename-without-an-ext.ext"},
|
|
||||||
{"/directory/mydir/", "ext", ".ext"},
|
|
||||||
{"mydir/", "ext", ".ext"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, d := range data {
|
|
||||||
output := ReplaceExtension(filepath.FromSlash(d.input), d.newext)
|
|
||||||
if d.expected != output {
|
|
||||||
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDirExists(t *testing.T) {
|
func TestDirExists(t *testing.T) {
|
||||||
type test struct {
|
type test struct {
|
||||||
input string
|
input string
|
||||||
|
@ -538,78 +475,6 @@ func TestAbsPathify(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtNoDelimiter(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
c.Assert(ExtNoDelimiter(filepath.FromSlash("/my/data.json")), qt.Equals, "json")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilename(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
input, expected string
|
|
||||||
}
|
|
||||||
data := []test{
|
|
||||||
{"index.html", "index"},
|
|
||||||
{"./index.html", "index"},
|
|
||||||
{"/index.html", "index"},
|
|
||||||
{"index", "index"},
|
|
||||||
{"/tmp/index.html", "index"},
|
|
||||||
{"./filename-no-ext", "filename-no-ext"},
|
|
||||||
{"/filename-no-ext", "filename-no-ext"},
|
|
||||||
{"filename-no-ext", "filename-no-ext"},
|
|
||||||
{"directory/", ""}, // no filename case??
|
|
||||||
{"directory/.hidden.ext", ".hidden"},
|
|
||||||
{"./directory/../~/banana/gold.fish", "gold"},
|
|
||||||
{"../directory/banana.man", "banana"},
|
|
||||||
{"~/mydir/filename.ext", "filename"},
|
|
||||||
{"./directory//tmp/filename.ext", "filename"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, d := range data {
|
|
||||||
output := Filename(filepath.FromSlash(d.input))
|
|
||||||
if d.expected != output {
|
|
||||||
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileAndExt(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
input, expectedFile, expectedExt string
|
|
||||||
}
|
|
||||||
data := []test{
|
|
||||||
{"index.html", "index", ".html"},
|
|
||||||
{"./index.html", "index", ".html"},
|
|
||||||
{"/index.html", "index", ".html"},
|
|
||||||
{"index", "index", ""},
|
|
||||||
{"/tmp/index.html", "index", ".html"},
|
|
||||||
{"./filename-no-ext", "filename-no-ext", ""},
|
|
||||||
{"/filename-no-ext", "filename-no-ext", ""},
|
|
||||||
{"filename-no-ext", "filename-no-ext", ""},
|
|
||||||
{"directory/", "", ""}, // no filename case??
|
|
||||||
{"directory/.hidden.ext", ".hidden", ".ext"},
|
|
||||||
{"./directory/../~/banana/gold.fish", "gold", ".fish"},
|
|
||||||
{"../directory/banana.man", "banana", ".man"},
|
|
||||||
{"~/mydir/filename.ext", "filename", ".ext"},
|
|
||||||
{"./directory//tmp/filename.ext", "filename", ".ext"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, d := range data {
|
|
||||||
file, ext := fileAndExt(filepath.FromSlash(d.input), fpb)
|
|
||||||
if d.expectedFile != file {
|
|
||||||
t.Errorf("Test %d failed. Expected filename %q got %q.", i, d.expectedFile, file)
|
|
||||||
}
|
|
||||||
if d.expectedExt != ext {
|
|
||||||
t.Errorf("Test %d failed. Expected extension %q got %q.", i, d.expectedExt, ext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathPrep(t *testing.T) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrettifyPath(t *testing.T) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtractAndGroupRootPaths(t *testing.T) {
|
func TestExtractAndGroupRootPaths(t *testing.T) {
|
||||||
in := []string{
|
in := []string{
|
||||||
filepath.FromSlash("/a/b/c/d"),
|
filepath.FromSlash("/a/b/c/d"),
|
||||||
|
|
153
helpers/url.go
153
helpers/url.go
|
@ -14,44 +14,16 @@
|
||||||
package helpers
|
package helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/purell"
|
"github.com/PuerkitoBio/purell"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pathBridge struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pathBridge) Base(in string) string {
|
|
||||||
return path.Base(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pathBridge) Clean(in string) string {
|
|
||||||
return path.Clean(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pathBridge) Dir(in string) string {
|
|
||||||
return path.Dir(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pathBridge) Ext(in string) string {
|
|
||||||
return path.Ext(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pathBridge) Join(elem ...string) string {
|
|
||||||
return path.Join(elem...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pathBridge) Separator() string {
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
var pb pathBridge
|
|
||||||
|
|
||||||
func sanitizeURLWithFlags(in string, f purell.NormalizationFlags) string {
|
func sanitizeURLWithFlags(in string, f purell.NormalizationFlags) string {
|
||||||
s, err := purell.NormalizeURLString(in, f)
|
s, err := purell.NormalizeURLString(in, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -123,37 +95,6 @@ func (p *PathSpec) URLEscape(uri string) string {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakePermalink combines base URL with content path to create full URL paths.
|
|
||||||
// Example
|
|
||||||
// base: http://spf13.com/
|
|
||||||
// path: post/how-i-blog
|
|
||||||
// result: http://spf13.com/post/how-i-blog
|
|
||||||
func MakePermalink(host, plink string) *url.URL {
|
|
||||||
base, err := url.Parse(host)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := url.Parse(plink)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Host != "" {
|
|
||||||
panic(fmt.Errorf("can't make permalink from absolute link %q", plink))
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Path = path.Join(base.Path, p.Path)
|
|
||||||
|
|
||||||
// path.Join will strip off the last /, so put it back if it was there.
|
|
||||||
hadTrailingSlash := (plink == "" && strings.HasSuffix(host, "/")) || strings.HasSuffix(p.Path, "/")
|
|
||||||
if hadTrailingSlash && !strings.HasSuffix(base.Path, "/") {
|
|
||||||
base.Path = base.Path + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
return base
|
|
||||||
}
|
|
||||||
|
|
||||||
// AbsURL creates an absolute URL from the relative path given and the BaseURL set in config.
|
// AbsURL creates an absolute URL from the relative path given and the BaseURL set in config.
|
||||||
func (p *PathSpec) AbsURL(in string, addLanguage bool) string {
|
func (p *PathSpec) AbsURL(in string, addLanguage bool) string {
|
||||||
url, err := url.Parse(in)
|
url, err := url.Parse(in)
|
||||||
|
@ -199,17 +140,7 @@ func (p *PathSpec) AbsURL(in string, addLanguage bool) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MakePermalink(baseURL, in).String()
|
return paths.MakePermalink(baseURL, in).String()
|
||||||
}
|
|
||||||
|
|
||||||
// IsAbsURL determines whether the given path points to an absolute URL.
|
|
||||||
func IsAbsURL(path string) bool {
|
|
||||||
url, err := url.Parse(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.IsAbs() || strings.HasPrefix(path, "//")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RelURL creates a URL relative to the BaseURL root.
|
// RelURL creates a URL relative to the BaseURL root.
|
||||||
|
@ -255,7 +186,7 @@ func (p *PathSpec) RelURL(in string, addLanguage bool) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canonifyURLs {
|
if !canonifyURLs {
|
||||||
u = AddContextRoot(baseURL, u)
|
u = paths.AddContextRoot(baseURL, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
if in == "" && !strings.HasSuffix(u, "/") && strings.HasSuffix(baseURL, "/") {
|
if in == "" && !strings.HasSuffix(u, "/") && strings.HasSuffix(baseURL, "/") {
|
||||||
|
@ -269,24 +200,6 @@ func (p *PathSpec) RelURL(in string, addLanguage bool) string {
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddContextRoot adds the context root to an URL if it's not already set.
|
|
||||||
// For relative URL entries on sites with a base url with a context root set (i.e. http://example.com/mysite),
|
|
||||||
// relative URLs must not include the context root if canonifyURLs is enabled. But if it's disabled, it must be set.
|
|
||||||
func AddContextRoot(baseURL, relativePath string) string {
|
|
||||||
url, err := url.Parse(baseURL)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newPath := path.Join(url.Path, relativePath)
|
|
||||||
|
|
||||||
// path strips trailing slash, ignore root path.
|
|
||||||
if newPath != "/" && strings.HasSuffix(relativePath, "/") {
|
|
||||||
newPath += "/"
|
|
||||||
}
|
|
||||||
return newPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrependBasePath prepends any baseURL sub-folder to the given resource
|
// PrependBasePath prepends any baseURL sub-folder to the given resource
|
||||||
func (p *PathSpec) PrependBasePath(rel string, isAbs bool) string {
|
func (p *PathSpec) PrependBasePath(rel string, isAbs bool) string {
|
||||||
basePath := p.GetBasePath(!isAbs)
|
basePath := p.GetBasePath(!isAbs)
|
||||||
|
@ -311,9 +224,9 @@ func (p *PathSpec) URLizeAndPrep(in string) string {
|
||||||
// URLPrep applies misc sanitation to the given URL.
|
// URLPrep applies misc sanitation to the given URL.
|
||||||
func (p *PathSpec) URLPrep(in string) string {
|
func (p *PathSpec) URLPrep(in string) string {
|
||||||
if p.UglyURLs {
|
if p.UglyURLs {
|
||||||
return Uglify(SanitizeURL(in))
|
return paths.Uglify(SanitizeURL(in))
|
||||||
}
|
}
|
||||||
pretty := PrettifyURL(SanitizeURL(in))
|
pretty := paths.PrettifyURL(SanitizeURL(in))
|
||||||
if path.Ext(pretty) == ".xml" {
|
if path.Ext(pretty) == ".xml" {
|
||||||
return pretty
|
return pretty
|
||||||
}
|
}
|
||||||
|
@ -323,57 +236,3 @@ func (p *PathSpec) URLPrep(in string) string {
|
||||||
}
|
}
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrettifyURL takes a URL string and returns a semantic, clean URL.
|
|
||||||
func PrettifyURL(in string) string {
|
|
||||||
x := PrettifyURLPath(in)
|
|
||||||
|
|
||||||
if path.Base(x) == "index.html" {
|
|
||||||
return path.Dir(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
if in == "" {
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrettifyURLPath takes a URL path to a content and converts it
|
|
||||||
// to enable pretty URLs.
|
|
||||||
// /section/name.html becomes /section/name/index.html
|
|
||||||
// /section/name/ becomes /section/name/index.html
|
|
||||||
// /section/name/index.html becomes /section/name/index.html
|
|
||||||
func PrettifyURLPath(in string) string {
|
|
||||||
return prettifyPath(in, pb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uglify does the opposite of PrettifyURLPath().
|
|
||||||
// /section/name/index.html becomes /section/name.html
|
|
||||||
// /section/name/ becomes /section/name.html
|
|
||||||
// /section/name.html becomes /section/name.html
|
|
||||||
func Uglify(in string) string {
|
|
||||||
if path.Ext(in) == "" {
|
|
||||||
if len(in) < 2 {
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
// /section/name/ -> /section/name.html
|
|
||||||
return path.Clean(in) + ".html"
|
|
||||||
}
|
|
||||||
|
|
||||||
name, ext := fileAndExt(in, pb)
|
|
||||||
if name == "index" {
|
|
||||||
// /section/name/index.html -> /section/name.html
|
|
||||||
d := path.Dir(in)
|
|
||||||
if len(d) > 1 {
|
|
||||||
return d + ext
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
// /.xml -> /index.xml
|
|
||||||
if name == "" {
|
|
||||||
return path.Dir(in) + "index" + ext
|
|
||||||
}
|
|
||||||
// /section/name.html -> /section/name.html
|
|
||||||
return path.Clean(in)
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/hugofs"
|
"github.com/gohugoio/hugo/hugofs"
|
||||||
"github.com/gohugoio/hugo/langs"
|
"github.com/gohugoio/hugo/langs"
|
||||||
)
|
)
|
||||||
|
@ -93,9 +92,8 @@ func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||||
{"/" + lang + "test", "http://base/", "http://base/" + lang + "/" + lang + "test"},
|
{"/" + lang + "test", "http://base/", "http://base/" + lang + "/" + lang + "test"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range newTests {
|
tests = append(tests, newTests...)
|
||||||
tests = append(tests, test)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -121,24 +119,6 @@ func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsAbsURL(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
|
|
||||||
for _, this := range []struct {
|
|
||||||
a string
|
|
||||||
b bool
|
|
||||||
}{
|
|
||||||
{"http://gohugo.io", true},
|
|
||||||
{"https://gohugo.io", true},
|
|
||||||
{"//gohugo.io", true},
|
|
||||||
{"http//gohugo.io", false},
|
|
||||||
{"/content", false},
|
|
||||||
{"content", false},
|
|
||||||
} {
|
|
||||||
c.Assert(IsAbsURL(this.a) == this.b, qt.Equals, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRelURL(t *testing.T) {
|
func TestRelURL(t *testing.T) {
|
||||||
for _, defaultInSubDir := range []bool{true, false} {
|
for _, defaultInSubDir := range []bool{true, false} {
|
||||||
for _, addLanguage := range []bool{true, false} {
|
for _, addLanguage := range []bool{true, false} {
|
||||||
|
@ -187,10 +167,7 @@ func doTestRelURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||||
{lang + "test", "http://base/", false, "/" + lang + "/" + lang + "test"},
|
{lang + "test", "http://base/", false, "/" + lang + "/" + lang + "test"},
|
||||||
{"/" + lang + "test", "http://base/", false, "/" + lang + "/" + lang + "test"},
|
{"/" + lang + "test", "http://base/", false, "/" + lang + "/" + lang + "test"},
|
||||||
}
|
}
|
||||||
|
tests = append(tests, newTests...)
|
||||||
for _, test := range newTests {
|
|
||||||
tests = append(tests, test)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
@ -247,28 +224,6 @@ func TestSanitizeURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMakePermalink(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
host, link, output string
|
|
||||||
}
|
|
||||||
|
|
||||||
data := []test{
|
|
||||||
{"http://abc.com/foo", "post/bar", "http://abc.com/foo/post/bar"},
|
|
||||||
{"http://abc.com/foo/", "post/bar", "http://abc.com/foo/post/bar"},
|
|
||||||
{"http://abc.com", "post/bar", "http://abc.com/post/bar"},
|
|
||||||
{"http://abc.com", "bar", "http://abc.com/bar"},
|
|
||||||
{"http://abc.com/foo/bar", "post/bar", "http://abc.com/foo/bar/post/bar"},
|
|
||||||
{"http://abc.com/foo/bar", "post/bar/", "http://abc.com/foo/bar/post/bar/"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, d := range data {
|
|
||||||
output := MakePermalink(d.host, d.link).String()
|
|
||||||
if d.output != output {
|
|
||||||
t.Errorf("Test #%d failed. Expected %q got %q", i, d.output, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestURLPrep(t *testing.T) {
|
func TestURLPrep(t *testing.T) {
|
||||||
type test struct {
|
type test struct {
|
||||||
ugly bool
|
ugly bool
|
||||||
|
@ -293,60 +248,3 @@ func TestURLPrep(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddContextRoot(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
baseURL string
|
|
||||||
url string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"http://example.com/sub/", "/foo", "/sub/foo"},
|
|
||||||
{"http://example.com/sub/", "/foo/index.html", "/sub/foo/index.html"},
|
|
||||||
{"http://example.com/sub1/sub2", "/foo", "/sub1/sub2/foo"},
|
|
||||||
{"http://example.com", "/foo", "/foo"},
|
|
||||||
// cannot guess that the context root is already added int the example below
|
|
||||||
{"http://example.com/sub/", "/sub/foo", "/sub/sub/foo"},
|
|
||||||
{"http://example.com/тря", "/трям/", "/тря/трям/"},
|
|
||||||
{"http://example.com", "/", "/"},
|
|
||||||
{"http://example.com/bar", "//", "/bar/"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
output := AddContextRoot(test.baseURL, test.url)
|
|
||||||
if output != test.expected {
|
|
||||||
t.Errorf("Expected %#v, got %#v\n", test.expected, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPretty(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
c.Assert("/section/name/index.html", qt.Equals, PrettifyURLPath("/section/name.html"))
|
|
||||||
c.Assert("/section/sub/name/index.html", qt.Equals, PrettifyURLPath("/section/sub/name.html"))
|
|
||||||
c.Assert("/section/name/index.html", qt.Equals, PrettifyURLPath("/section/name/"))
|
|
||||||
c.Assert("/section/name/index.html", qt.Equals, PrettifyURLPath("/section/name/index.html"))
|
|
||||||
c.Assert("/index.html", qt.Equals, PrettifyURLPath("/index.html"))
|
|
||||||
c.Assert("/name/index.xml", qt.Equals, PrettifyURLPath("/name.xml"))
|
|
||||||
c.Assert("/", qt.Equals, PrettifyURLPath("/"))
|
|
||||||
c.Assert("/", qt.Equals, PrettifyURLPath(""))
|
|
||||||
c.Assert("/section/name", qt.Equals, PrettifyURL("/section/name.html"))
|
|
||||||
c.Assert("/section/sub/name", qt.Equals, PrettifyURL("/section/sub/name.html"))
|
|
||||||
c.Assert("/section/name", qt.Equals, PrettifyURL("/section/name/"))
|
|
||||||
c.Assert("/section/name", qt.Equals, PrettifyURL("/section/name/index.html"))
|
|
||||||
c.Assert("/", qt.Equals, PrettifyURL("/index.html"))
|
|
||||||
c.Assert("/name/index.xml", qt.Equals, PrettifyURL("/name.xml"))
|
|
||||||
c.Assert("/", qt.Equals, PrettifyURL("/"))
|
|
||||||
c.Assert("/", qt.Equals, PrettifyURL(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUgly(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
c.Assert("/section/name.html", qt.Equals, Uglify("/section/name.html"))
|
|
||||||
c.Assert("/section/sub/name.html", qt.Equals, Uglify("/section/sub/name.html"))
|
|
||||||
c.Assert("/section/name.html", qt.Equals, Uglify("/section/name/"))
|
|
||||||
c.Assert("/section/name.html", qt.Equals, Uglify("/section/name/index.html"))
|
|
||||||
c.Assert("/index.html", qt.Equals, Uglify("/index.html"))
|
|
||||||
c.Assert("/name.xml", qt.Equals, Uglify("/name.xml"))
|
|
||||||
c.Assert("/", qt.Equals, Uglify("/"))
|
|
||||||
c.Assert("/", qt.Equals, Uglify(""))
|
|
||||||
}
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/gohugoio/hugo/common/types"
|
"github.com/gohugoio/hugo/common/types"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
|
cpaths "github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
"github.com/gobwas/glob"
|
||||||
hglob "github.com/gohugoio/hugo/hugofs/glob"
|
hglob "github.com/gohugoio/hugo/hugofs/glob"
|
||||||
|
@ -436,7 +437,7 @@ func (l configLoader) loadConfig(configName string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename string
|
var filename string
|
||||||
if helpers.ExtNoDelimiter(configName) != "" {
|
if cpaths.ExtNoDelimiter(configName) != "" {
|
||||||
exists, _ := helpers.Exists(baseFilename, l.Fs)
|
exists, _ := helpers.Exists(baseFilename, l.Fs)
|
||||||
if exists {
|
if exists {
|
||||||
filename = baseFilename
|
filename = baseFilename
|
||||||
|
@ -509,7 +510,7 @@ func (l configLoader) loadConfigFromConfigDir() ([]string, error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
name := helpers.Filename(filepath.Base(path))
|
name := cpaths.Filename(filepath.Base(path))
|
||||||
|
|
||||||
item, err := metadecoders.Default.UnmarshalFileToMap(sourceFs, path)
|
item, err := metadecoders.Default.UnmarshalFileToMap(sourceFs, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -520,7 +521,7 @@ func (l configLoader) loadConfigFromConfigDir() ([]string, error) {
|
||||||
|
|
||||||
if name != "config" {
|
if name != "config" {
|
||||||
// Can be params.jp, menus.en etc.
|
// Can be params.jp, menus.en etc.
|
||||||
name, lang := helpers.FileAndExtNoDelimiter(name)
|
name, lang := cpaths.FileAndExtNoDelimiter(name)
|
||||||
|
|
||||||
keyPath = []string{name}
|
keyPath = []string{name}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
"github.com/gohugoio/hugo/htesting/hqt"
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ func TestContentMap(t *testing.T) {
|
||||||
meta["lang"] = lang
|
meta["lang"] = lang
|
||||||
meta["path"] = meta.Filename()
|
meta["path"] = meta.Filename()
|
||||||
meta["classifier"] = files.ClassifyContentFile(fi.Name(), meta.GetOpener())
|
meta["classifier"] = files.ClassifyContentFile(fi.Name(), meta.GetOpener())
|
||||||
meta["translationBaseName"] = helpers.Filename(fi.Name())
|
meta["translationBaseName"] = paths.Filename(fi.Name())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/hugofs/files"
|
"github.com/gohugoio/hugo/hugofs/files"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
|
@ -187,7 +189,7 @@ func (c *PageCollections) getSectionOrPage(ref string) (*contentNode, string) {
|
||||||
langSuffix := "." + m.s.Lang()
|
langSuffix := "." + m.s.Lang()
|
||||||
|
|
||||||
// Trim both extension and any language code.
|
// Trim both extension and any language code.
|
||||||
name := helpers.PathNoExt(filename)
|
name := paths.PathNoExt(filename)
|
||||||
name = strings.TrimSuffix(name, langSuffix)
|
name = strings.TrimSuffix(name, langSuffix)
|
||||||
|
|
||||||
// These are reserved bundle names and will always be stored by their owning
|
// These are reserved bundle names and will always be stored by their owning
|
||||||
|
|
|
@ -29,6 +29,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/constants"
|
"github.com/gohugoio/hugo/common/constants"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
"github.com/gohugoio/hugo/common/loggers"
|
||||||
|
@ -1418,7 +1420,7 @@ func (s *SiteInfo) createNodeMenuEntryURL(in string) string {
|
||||||
menuEntryURL := in
|
menuEntryURL := in
|
||||||
menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(s.s.PathSpec.URLize(menuEntryURL))
|
menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(s.s.PathSpec.URLize(menuEntryURL))
|
||||||
if !s.canonifyURLs {
|
if !s.canonifyURLs {
|
||||||
menuEntryURL = helpers.AddContextRoot(s.s.PathSpec.BaseURL.String(), menuEntryURL)
|
menuEntryURL = paths.AddContextRoot(s.s.PathSpec.BaseURL.String(), menuEntryURL)
|
||||||
}
|
}
|
||||||
return menuEntryURL
|
return menuEntryURL
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,14 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/herrors"
|
"github.com/gohugoio/hugo/common/herrors"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/helpers"
|
|
||||||
"github.com/gohugoio/go-i18n/v2/i18n"
|
"github.com/gohugoio/go-i18n/v2/i18n"
|
||||||
|
"github.com/gohugoio/hugo/helpers"
|
||||||
toml "github.com/pelletier/go-toml"
|
toml "github.com/pelletier/go-toml"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
"github.com/gohugoio/hugo/deps"
|
||||||
|
@ -88,7 +90,7 @@ func addTranslationFile(bundle *i18n.Bundle, r source.File) error {
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
name := r.LogicalName()
|
name := r.LogicalName()
|
||||||
lang := helpers.Filename(name)
|
lang := paths.Filename(name)
|
||||||
tag := language.Make(lang)
|
tag := language.Make(lang)
|
||||||
if tag == language.Und {
|
if tag == language.Und {
|
||||||
name = artificialLangTagPrefix + name
|
name = artificialLangTagPrefix + name
|
||||||
|
|
|
@ -29,6 +29,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/disintegration/gift"
|
"github.com/disintegration/gift"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/cache/filecache"
|
"github.com/gohugoio/hugo/cache/filecache"
|
||||||
|
@ -365,7 +367,7 @@ func (i *imageResource) getImageMetaCacheTargetPath() string {
|
||||||
if fi := i.getFileInfo(); fi != nil {
|
if fi := i.getFileInfo(); fi != nil {
|
||||||
df.dir = filepath.Dir(fi.Meta().Path())
|
df.dir = filepath.Dir(fi.Meta().Path())
|
||||||
}
|
}
|
||||||
p1, _ := helpers.FileAndExt(df.file)
|
p1, _ := paths.FileAndExt(df.file)
|
||||||
h, _ := i.hash()
|
h, _ := i.hash()
|
||||||
idStr := helpers.HashString(h, i.size(), imageMetaVersionNumber, cfgHash)
|
idStr := helpers.HashString(h, i.size(), imageMetaVersionNumber, cfgHash)
|
||||||
p := path.Join(df.dir, fmt.Sprintf("%s_%s.json", p1, idStr))
|
p := path.Join(df.dir, fmt.Sprintf("%s_%s.json", p1, idStr))
|
||||||
|
@ -373,7 +375,7 @@ func (i *imageResource) getImageMetaCacheTargetPath() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *imageResource) relTargetPathFromConfig(conf images.ImageConfig) dirFile {
|
func (i *imageResource) relTargetPathFromConfig(conf images.ImageConfig) dirFile {
|
||||||
p1, p2 := helpers.FileAndExt(i.getResourcePaths().relTargetDirFile.file)
|
p1, p2 := paths.FileAndExt(i.getResourcePaths().relTargetDirFile.file)
|
||||||
if conf.TargetFormat != i.Format {
|
if conf.TargetFormat != i.Format {
|
||||||
p2 = conf.TargetFormat.DefaultExtension()
|
p2 = conf.TargetFormat.DefaultExtension()
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
|
||||||
"github.com/disintegration/gift"
|
"github.com/disintegration/gift"
|
||||||
|
@ -145,7 +147,7 @@ func TestImageTransformFormat(t *testing.T) {
|
||||||
assertExtWidthHeight := func(img resource.Image, ext string, w, h int) {
|
assertExtWidthHeight := func(img resource.Image, ext string, w, h int) {
|
||||||
c.Helper()
|
c.Helper()
|
||||||
c.Assert(img, qt.Not(qt.IsNil))
|
c.Assert(img, qt.Not(qt.IsNil))
|
||||||
c.Assert(helpers.Ext(img.RelPermalink()), qt.Equals, ext)
|
c.Assert(paths.Ext(img.RelPermalink()), qt.Equals, ext)
|
||||||
c.Assert(img.Width(), qt.Equals, w)
|
c.Assert(img.Width(), qt.Equals, w)
|
||||||
c.Assert(img.Height(), qt.Equals, h)
|
c.Assert(img.Height(), qt.Equals, h)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
"github.com/gohugoio/hugo/common/loggers"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
"github.com/gohugoio/hugo/resources/resource"
|
"github.com/gohugoio/hugo/resources/resource"
|
||||||
|
@ -118,7 +120,7 @@ func (f FrontMatterHandler) IsDateKey(key string) bool {
|
||||||
// This follows the format as outlined in Jekyll, https://jekyllrb.com/docs/posts/:
|
// This follows the format as outlined in Jekyll, https://jekyllrb.com/docs/posts/:
|
||||||
// "Where YEAR is a four-digit number, MONTH and DAY are both two-digit numbers"
|
// "Where YEAR is a four-digit number, MONTH and DAY are both two-digit numbers"
|
||||||
func dateAndSlugFromBaseFilename(name string) (time.Time, string) {
|
func dateAndSlugFromBaseFilename(name string) (time.Time, string) {
|
||||||
withoutExt, _ := helpers.FileAndExt(name)
|
withoutExt, _ := paths.FileAndExt(name)
|
||||||
|
|
||||||
if len(withoutExt) < 10 {
|
if len(withoutExt) < 10 {
|
||||||
// This can not be a date.
|
// This can not be a date.
|
||||||
|
|
|
@ -22,6 +22,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/resources/images/exif"
|
"github.com/gohugoio/hugo/resources/images/exif"
|
||||||
|
@ -136,13 +138,13 @@ func (ctx *ResourceTransformationCtx) PublishSourceMap(content string) error {
|
||||||
// extension, e.g. ".scss"
|
// extension, e.g. ".scss"
|
||||||
func (ctx *ResourceTransformationCtx) ReplaceOutPathExtension(newExt string) {
|
func (ctx *ResourceTransformationCtx) ReplaceOutPathExtension(newExt string) {
|
||||||
dir, file := path.Split(ctx.InPath)
|
dir, file := path.Split(ctx.InPath)
|
||||||
base, _ := helpers.PathAndExt(file)
|
base, _ := paths.PathAndExt(file)
|
||||||
ctx.OutPath = path.Join(dir, (base + newExt))
|
ctx.OutPath = path.Join(dir, (base + newExt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *ResourceTransformationCtx) addPathIdentifier(inPath, identifier string) string {
|
func (ctx *ResourceTransformationCtx) addPathIdentifier(inPath, identifier string) string {
|
||||||
dir, file := path.Split(inPath)
|
dir, file := path.Split(inPath)
|
||||||
base, ext := helpers.PathAndExt(file)
|
base, ext := paths.PathAndExt(file)
|
||||||
return path.Join(dir, (base + identifier + ext))
|
return path.Join(dir, (base + identifier + ext))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/hugofs/files"
|
"github.com/gohugoio/hugo/hugofs/files"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -263,7 +265,7 @@ func (sp *SourceSpec) NewFileInfo(fi hugofs.FileMetaInfo) (*FileInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), "."))
|
ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), "."))
|
||||||
baseName := helpers.Filename(name)
|
baseName := paths.Filename(name)
|
||||||
|
|
||||||
if translationBaseName == "" {
|
if translationBaseName == "" {
|
||||||
// This is usually provided by the filesystem. But this FileInfo is also
|
// This is usually provided by the filesystem. But this FileInfo is also
|
||||||
|
|
Loading…
Reference in a new issue