return AddTrailingSlash(AddLeadingSlash(path))
}
-// 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 after, ok := strings.CutPrefix(inPath, currentPath); ok {
return
}
-// GetRelativePath returns the relative path of a given path.
-func GetRelativePath(path, base string) (final string, err error) {
- if filepath.IsAbs(path) && base == "" {
- return "", errors.New("source: missing base directory")
- }
- name := filepath.Clean(path)
- base = filepath.Clean(base)
-
- name, err = filepath.Rel(base, name)
- if err != nil {
- return "", err
- }
-
- if strings.HasSuffix(filepath.FromSlash(path), FilePathSeparator) && !strings.HasSuffix(name, FilePathSeparator) {
- name += FilePathSeparator
- }
- return name, nil
-}
-
func prettifyPath(in string, b filepathPathBridge) string {
if filepath.Ext(in) == "" {
// /section/name/ -> /section/name/index.html
return b.Join(b.Dir(in), name, "index"+ext)
}
-// CommonDirPath returns the common directory of the given paths.
-func CommonDirPath(path1, path2 string) string {
- if path1 == "" || path2 == "" {
- return ""
- }
-
- hadLeadingSlash := strings.HasPrefix(path1, "/") || strings.HasPrefix(path2, "/")
-
- path1 = TrimLeading(path1)
- path2 = TrimLeading(path2)
-
- p1 := strings.Split(path1, "/")
- p2 := strings.Split(path2, "/")
-
- var common []string
-
- for i := 0; i < len(p1) && i < len(p2); i++ {
- if p1[i] == p2[i] {
- common = append(common, p1[i])
- } else {
- break
- }
- }
-
- s := strings.Join(common, "/")
-
- if hadLeadingSlash && s != "" {
- s = "/" + s
- }
-
- return s
-}
-
// Sanitize sanitizes string to be used in Hugo's file paths and URLs, allowing only
// a predefined set of special Unicode characters.
//
qt "github.com/frankban/quicktest"
)
-func TestGetRelativePath(t *testing.T) {
- tests := []struct {
- path string
- base string
- expect any
- }{
- {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
}
}
-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")
c.Assert(FieldsSlash(""), qt.DeepEquals, []string{})
}
-func TestCommonDirPath(t *testing.T) {
- c := qt.New(t)
-
- for _, this := range []struct {
- a, b, expected string
- }{
- {"/a/b/c", "/a/b/d", "/a/b"},
- {"/a/b/c", "a/b/d", "/a/b"},
- {"a/b/c", "/a/b/d", "/a/b"},
- {"a/b/c", "a/b/d", "a/b"},
- {"/a/b/c", "/a/b/c", "/a/b/c"},
- {"/a/b/c", "/a/b/c/d", "/a/b/c"},
- {"/a/b/c", "/a/b", "/a/b"},
- {"/a/b/c", "/a", "/a"},
- {"/a/b/c", "/d/e/f", ""},
- } {
- c.Assert(CommonDirPath(this.a, this.b), qt.Equals, this.expected, qt.Commentf("a: %s b: %s", this.a, this.b))
- }
-}
-
func TestIsSameFilePath(t *testing.T) {
c := qt.New(t)
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
- "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources/kinds"
)
return v
}
-// ParseIdentity parses component c with path s into a StringIdentity.
-func (pp *PathParser) ParseIdentity(c, s string) identity.StringIdentity {
- p := pp.parsePooled(c, s)
- defer putPath(p)
- return identity.StringIdentity(p.IdentifierBase())
-}
-
// ParseBaseAndBaseNameNoIdentifier parses component c with path s into a base and a base name without any identifier.
func (pp *PathParser) ParseBaseAndBaseNameNoIdentifier(c, s string) (string, string) {
p := pp.parsePooled(c, s)
return p.s[p.posContainerHigh:]
}
-// Name returns the last element of path without any language identifier.
-func (p *Path) NameNoLang() string {
- if len(p.posIdentifierPrefixLanguages) > 0 {
- // Prefix language: the wrapper is outside the name boundary, so Name() without
- // identifiers is the same as stripping all wrappers. Use the name + extension.
- if len(p.identifiersKnown) > 0 {
- return p.NameNoIdentifier() + p.s[p.identifiersKnown[0].Low-1:p.identifiersKnown[0].High]
- }
- return p.Name()
- }
- i := p.identifierIndex(p.posIdentifierLanguage)
- if i == -1 {
- return p.Name()
- }
- id := p.identifiersKnown[i]
- if id.Low-1 < p.posContainerHigh {
- return p.s[id.High:]
- }
- return p.s[p.posContainerHigh:id.Low-1] + p.s[id.High:]
-}
-
// BaseNameNoIdentifier returns the logical base name for a resource without any identifier (e.g. no extension).
// For bundles this will be the containing directory's name, e.g. "blog".
func (p *Path) BaseNameNoIdentifier() string {
return
}
-// BaseNoLeadingSlash returns the base path without the leading slash.
-func (p *Path) BaseNoLeadingSlash() string {
- return p.Base()[1:]
-}
-
func (p *Path) base(preserveExt, isBundle bool) string {
if len(p.identifiersKnown) == 0 {
return p.norm(p.s)
return p.identifiersAsStrings(p.posIdentifierPrefixLanguages)
}
-func (p *Path) Version() string {
- if len(p.posIdentifierVersions) > 0 {
- return p.identifierAsString(p.posIdentifierVersions[0])
- }
- return ""
-}
-
func (p *Path) Versions() []string {
return p.identifiersAsStrings(p.posIdentifierVersions)
}
-func (p *Path) Role() string {
- if len(p.posIdentifierRoles) > 0 {
- return p.identifierAsString(p.posIdentifierRoles[0])
- }
- return ""
-}
-
func (p *Path) Roles() []string {
return p.identifiersAsStrings(p.posIdentifierRoles)
}
return strings.TrimSuffix(strings.TrimPrefix(p.identifierAsString(p.posIdentifierCustom), identifierCustomWrapper), identifierCustomWrapper)
}
-func (p *Path) Identifier(i int) string {
- return p.identifierAsString(i)
-}
-
func (p *Path) Disabled() bool {
return p.disabled
}
func(c *qt.C, p *Path) {
c.Assert(p.Name(), qt.Equals, "b.md")
c.Assert(p.Base(), qt.Equals, "/a/b")
- c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b")
c.Assert(p.Section(), qt.Equals, "a")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
func(c *qt.C, p *Path) {
c.Assert(p.Name(), qt.Equals, "b.a.b.no.txt")
c.Assert(p.NameNoIdentifier(), qt.Equals, "b.a.b")
- c.Assert(p.NameNoLang(), qt.Equals, "b.a.b.txt")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"b", "a", "b"})
c.Assert(p.Base(), qt.Equals, "/a/b.a.b.txt")
- c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.a.b.txt")
c.Assert(p.Path(), qt.Equals, "/a/b.a.b.no.txt")
c.Assert(p.PathNoLang(), qt.Equals, "/a/b.a.b.txt")
c.Assert(p.Ext(), qt.Equals, "txt")
c.Assert(p.Lang(), qt.Equals, "")
c.Assert(p.NameNoExt(), qt.Equals, "index")
c.Assert(p.NameNoIdentifier(), qt.Equals, "index")
- c.Assert(p.NameNoLang(), qt.Equals, "index.md")
c.Assert(p.Section(), qt.Equals, "")
},
},
c.Assert(p.Lang(), qt.Equals, "no")
c.Assert(p.NameNoExt(), qt.Equals, "index.no")
c.Assert(p.NameNoIdentifier(), qt.Equals, "index")
- c.Assert(p.NameNoLang(), qt.Equals, "index.md")
c.Assert(p.Path(), qt.Equals, "/a/b/index.no.md")
c.Assert(p.PathNoLang(), qt.Equals, "/a/b/index.md")
c.Assert(p.Section(), qt.Equals, "a")
c.Assert(p.IsBundle(), qt.IsTrue)
c.Assert(p.IsLeafBundle(), qt.IsFalse)
c.Assert(p.NameNoExt(), qt.Equals, "_index.no")
- c.Assert(p.NameNoLang(), qt.Equals, "_index.md")
},
},
{
"/a/b/p1._version_v1_.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/p1")
- c.Assert(p.Version(), qt.Equals, "v1")
+ c.Assert(p.Versions(), qt.DeepEquals, []string{"v1"})
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "v1"})
},
"/a/b/p1._version_v1.0.0_.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/p1")
- c.Assert(p.Version(), qt.Equals, "v1.0.0")
+ c.Assert(p.Versions(), qt.DeepEquals, []string{"v1.0.0"})
c.Assert(p.Ext(), qt.Equals, "md")
},
},
"/a/b/p1._role_admin_.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/p1")
- c.Assert(p.Role(), qt.Equals, "admin")
+ c.Assert(p.Roles(), qt.DeepEquals, []string{"admin"})
c.Assert(p.Ext(), qt.Equals, "md")
},
},
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/p1")
c.Assert(p.Lang(), qt.Equals, "no")
- c.Assert(p.Role(), qt.Equals, "admin")
- c.Assert(p.Version(), qt.Equals, "v2")
+ c.Assert(p.Roles(), qt.DeepEquals, []string{"admin"})
+ c.Assert(p.Versions(), qt.DeepEquals, []string{"v2"})
c.Assert(p.Ext(), qt.Equals, "md")
},
},
"/a/b/p1._version_v1.0.0_._role_editor_.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/p1")
- c.Assert(p.Version(), qt.Equals, "v1.0.0")
- c.Assert(p.Role(), qt.Equals, "editor")
+ c.Assert(p.Versions(), qt.DeepEquals, []string{"v1.0.0"})
+ c.Assert(p.Roles(), qt.DeepEquals, []string{"editor"})
c.Assert(p.Ext(), qt.Equals, "md")
},
},
"/a/b/p1._version_v1_.no.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/p1")
- c.Assert(p.Version(), qt.Equals, "v1")
+ c.Assert(p.Versions(), qt.DeepEquals, []string{"v1"})
c.Assert(p.Lang(), qt.Equals, "no")
c.Assert(p.Ext(), qt.Equals, "md")
},
"/a/b/index._version_v1_.md",
func(c *qt.C, p *Path) {
c.Assert(p.IsLeafBundle(), qt.IsTrue)
- c.Assert(p.Version(), qt.Equals, "v1")
+ c.Assert(p.Versions(), qt.DeepEquals, []string{"v1"})
c.Assert(p.Base(), qt.Equals, "/a/b")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
},
"/a/b/_index._role_admin_.md",
func(c *qt.C, p *Path) {
c.Assert(p.IsBranchBundle(), qt.IsTrue)
- c.Assert(p.Role(), qt.Equals, "admin")
+ c.Assert(p.Roles(), qt.DeepEquals, []string{"admin"})
c.Assert(p.Base(), qt.Equals, "/a/b")
},
},
"/a/b/My Page._Version_V1_.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/my-page")
- c.Assert(p.Version(), qt.Equals, "v1")
+ c.Assert(p.Versions(), qt.DeepEquals, []string{"v1"})
pp := p.Unnormalized()
c.Assert(pp.BaseNameNoIdentifier(), qt.Equals, "My Page")
- c.Assert(pp.Version(), qt.Equals, "V1")
},
},
{
"/a/b/p1._role_guest_._role_admin_.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/p1")
- c.Assert(p.Role(), qt.Equals, "admin")
c.Assert(p.Roles(), qt.DeepEquals, []string{"admin", "guest"})
c.Assert(p.Ext(), qt.Equals, "md")
},
"/a/b/p1._version_v1_._version_v2_.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/p1")
- c.Assert(p.Version(), qt.Equals, "v2")
c.Assert(p.Versions(), qt.DeepEquals, []string{"v2", "v1"})
},
},
func(c *qt.C, p *Path) {
// Unknown prefix should be left in path and treated as custom wrapper.
c.Assert(p.Custom(), qt.Equals, "unknown_foo")
- c.Assert(p.Version(), qt.Equals, "")
- c.Assert(p.Role(), qt.Equals, "")
+ c.Assert(p.Versions(), qt.IsNil)
+ c.Assert(p.Roles(), qt.IsNil)
},
},
}
func(c *qt.C, p *Path) {
c.Assert(p.Lang(), qt.Equals, "")
c.Assert(p.Layout(), qt.Equals, "index")
- c.Assert(p.NameNoLang(), qt.Equals, "index.xy.html")
c.Assert(p.PathNoLang(), qt.Equals, "/foo/index.xy.html")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "index"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"xy"})
"Prefix version in layout",
"/page._version_v2_.html",
func(c *qt.C, p *Path) {
- c.Assert(p.Version(), qt.Equals, "v2")
+ c.Assert(p.Versions(), qt.DeepEquals, []string{"v2"})
c.Assert(p.Base(), qt.Equals, "/page.html")
},
},
"Prefix role in layout",
"/page._role_guest_.html",
func(c *qt.C, p *Path) {
- c.Assert(p.Role(), qt.Equals, "guest")
+ c.Assert(p.Roles(), qt.DeepEquals, []string{"guest"})
c.Assert(p.Base(), qt.Equals, "/page.html")
},
},
c.Assert(HasExt("/a/b.c/d"), qt.IsFalse)
}
-func BenchmarkParseIdentity(b *testing.B) {
- parser := newTestParser()
- for b.Loop() {
- parser.ParseIdentity(files.ComponentFolderAssets, "/a/b.css")
- }
-}
-
-func BenchmarkParseIdentityPrefixes(b *testing.B) {
- parser := newTestParser()
- for b.Loop() {
- parser.ParseIdentity(files.ComponentFolderLayouts, "/a/b._language_en_._version_v1_._role_admin_._layout_list_._kind_page_.html")
- }
-}
-
func TestSitesMatrixFromPath(t *testing.T) {
c := qt.New(t)
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)
-}
-
// URLEscape escapes unicode letters.
func URLEscape(uri string) string {
// escape unicode letters
import (
"testing"
-
- qt "github.com/frankban/quicktest"
)
func TestMakePermalink(t *testing.T) {
}
}
}
-
-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(""))
-}