mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
parent
a55640de8e
commit
92baa14fd3
7 changed files with 46 additions and 29 deletions
|
@ -82,9 +82,13 @@ The following is a list of values that can be used in a `permalink` definition i
|
||||||
|
|
||||||
## Aliases
|
## Aliases
|
||||||
|
|
||||||
For people migrating existing published content to Hugo, there's a good chance you need a mechanism to handle redirecting old URLs.
|
Aliases can be used to create redirects to your page from other URLs.
|
||||||
|
|
||||||
Luckily, redirects can be handled easily with **aliases** in Hugo.
|
|
||||||
|
Aliases comes in two forms:
|
||||||
|
|
||||||
|
1. Starting with a `/` meaning they are relative to the `BaseURL`, e.g. `/posts/my-blogpost/`
|
||||||
|
2. They are relative to the `Page` they're defined in, e.g. `my-blogpost` or even something like `../blog/my-blogpost` (new in Hugo 0.55).
|
||||||
|
|
||||||
### Example: Aliases
|
### Example: Aliases
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -28,8 +29,6 @@ import (
|
||||||
"github.com/gohugoio/hugo/publisher"
|
"github.com/gohugoio/hugo/publisher"
|
||||||
"github.com/gohugoio/hugo/resources/page"
|
"github.com/gohugoio/hugo/resources/page"
|
||||||
"github.com/gohugoio/hugo/tpl"
|
"github.com/gohugoio/hugo/tpl"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/helpers"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -132,13 +131,14 @@ func (a aliasHandler) targetPathAlias(src string) (string, error) {
|
||||||
return "", fmt.Errorf("alias \"\" is an empty string")
|
return "", fmt.Errorf("alias \"\" is an empty string")
|
||||||
}
|
}
|
||||||
|
|
||||||
alias := filepath.Clean(src)
|
alias := path.Clean(filepath.ToSlash(src))
|
||||||
components := strings.Split(alias, helpers.FilePathSeparator)
|
|
||||||
|
|
||||||
if !a.allowRoot && alias == helpers.FilePathSeparator {
|
if !a.allowRoot && alias == "/" {
|
||||||
return "", fmt.Errorf("alias \"%s\" resolves to website root directory", originalAlias)
|
return "", fmt.Errorf("alias \"%s\" resolves to website root directory", originalAlias)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
components := strings.Split(alias, "/")
|
||||||
|
|
||||||
// Validate against directory traversal
|
// Validate against directory traversal
|
||||||
if components[0] == ".." {
|
if components[0] == ".." {
|
||||||
return "", fmt.Errorf("alias \"%s\" traverses outside the website root directory", originalAlias)
|
return "", fmt.Errorf("alias \"%s\" traverses outside the website root directory", originalAlias)
|
||||||
|
@ -182,15 +182,12 @@ func (a aliasHandler) targetPathAlias(src string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the final touch
|
// Add the final touch
|
||||||
alias = strings.TrimPrefix(alias, helpers.FilePathSeparator)
|
alias = strings.TrimPrefix(alias, "/")
|
||||||
if strings.HasSuffix(alias, helpers.FilePathSeparator) {
|
if strings.HasSuffix(alias, "/") {
|
||||||
alias = alias + "index.html"
|
alias = alias + "index.html"
|
||||||
} else if !strings.HasSuffix(alias, ".html") {
|
} else if !strings.HasSuffix(alias, ".html") {
|
||||||
alias = alias + helpers.FilePathSeparator + "index.html"
|
alias = alias + "/" + "index.html"
|
||||||
}
|
|
||||||
if originalAlias != alias {
|
|
||||||
a.log.INFO.Printf("Alias \"%s\" translated to \"%s\"\n", originalAlias, alias)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return alias, nil
|
return filepath.FromSlash(alias), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,14 @@ import (
|
||||||
|
|
||||||
const pageWithAlias = `---
|
const pageWithAlias = `---
|
||||||
title: Has Alias
|
title: Has Alias
|
||||||
aliases: ["foo/bar/"]
|
aliases: ["/foo/bar/", "rel"]
|
||||||
---
|
---
|
||||||
For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.
|
For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.
|
||||||
`
|
`
|
||||||
|
|
||||||
const pageWithAliasMultipleOutputs = `---
|
const pageWithAliasMultipleOutputs = `---
|
||||||
title: Has Alias for HTML and AMP
|
title: Has Alias for HTML and AMP
|
||||||
aliases: ["foo/bar/"]
|
aliases: ["/foo/bar/"]
|
||||||
outputs: ["HTML", "AMP", "JSON"]
|
outputs: ["HTML", "AMP", "JSON"]
|
||||||
---
|
---
|
||||||
For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.
|
For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.
|
||||||
|
@ -46,16 +46,17 @@ func TestAlias(t *testing.T) {
|
||||||
assert := require.New(t)
|
assert := require.New(t)
|
||||||
|
|
||||||
b := newTestSitesBuilder(t)
|
b := newTestSitesBuilder(t)
|
||||||
b.WithSimpleConfigFile().WithContent("page.md", pageWithAlias)
|
b.WithSimpleConfigFile().WithContent("blog/page.md", pageWithAlias)
|
||||||
b.CreateSites().Build(BuildCfg{})
|
b.CreateSites().Build(BuildCfg{})
|
||||||
|
|
||||||
assert.Equal(1, len(b.H.Sites))
|
assert.Equal(1, len(b.H.Sites))
|
||||||
require.Len(t, b.H.Sites[0].RegularPages(), 1)
|
require.Len(t, b.H.Sites[0].RegularPages(), 1)
|
||||||
|
|
||||||
// the real page
|
// the real page
|
||||||
b.AssertFileContent("public/page/index.html", "For some moments the old man")
|
b.AssertFileContent("public/blog/page/index.html", "For some moments the old man")
|
||||||
// the alias redirector
|
// the alias redirectors
|
||||||
b.AssertFileContent("public/foo/bar/index.html", "<meta http-equiv=\"refresh\" content=\"0; ")
|
b.AssertFileContent("public/foo/bar/index.html", "<meta http-equiv=\"refresh\" content=\"0; ")
|
||||||
|
b.AssertFileContent("public/blog/rel/index.html", "<meta http-equiv=\"refresh\" content=\"0; ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAliasMultipleOutputFormats(t *testing.T) {
|
func TestAliasMultipleOutputFormats(t *testing.T) {
|
||||||
|
@ -64,7 +65,7 @@ func TestAliasMultipleOutputFormats(t *testing.T) {
|
||||||
assert := require.New(t)
|
assert := require.New(t)
|
||||||
|
|
||||||
b := newTestSitesBuilder(t)
|
b := newTestSitesBuilder(t)
|
||||||
b.WithSimpleConfigFile().WithContent("page.md", pageWithAliasMultipleOutputs)
|
b.WithSimpleConfigFile().WithContent("blog/page.md", pageWithAliasMultipleOutputs)
|
||||||
|
|
||||||
b.WithTemplates(
|
b.WithTemplates(
|
||||||
"_default/single.html", basicTemplate,
|
"_default/single.html", basicTemplate,
|
||||||
|
@ -74,9 +75,9 @@ func TestAliasMultipleOutputFormats(t *testing.T) {
|
||||||
b.CreateSites().Build(BuildCfg{})
|
b.CreateSites().Build(BuildCfg{})
|
||||||
|
|
||||||
// the real pages
|
// the real pages
|
||||||
b.AssertFileContent("public/page/index.html", "For some moments the old man")
|
b.AssertFileContent("public/blog/page/index.html", "For some moments the old man")
|
||||||
b.AssertFileContent("public/amp/page/index.html", "For some moments the old man")
|
b.AssertFileContent("public/amp/blog/page/index.html", "For some moments the old man")
|
||||||
b.AssertFileContent("public/page/index.json", "For some moments the old man")
|
b.AssertFileContent("public/blog/page/index.json", "For some moments the old man")
|
||||||
|
|
||||||
// the alias redirectors
|
// the alias redirectors
|
||||||
b.AssertFileContent("public/foo/bar/index.html", "<meta http-equiv=\"refresh\" content=\"0; ")
|
b.AssertFileContent("public/foo/bar/index.html", "<meta http-equiv=\"refresh\" content=\"0; ")
|
||||||
|
@ -135,7 +136,7 @@ func TestTargetPathHTMLRedirectAlias(t *testing.T) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err == nil && path != test.expected {
|
if err == nil && path != test.expected {
|
||||||
t.Errorf("Expected: \"%s\", got: \"%s\"", test.expected, path)
|
t.Errorf("Expected: %q, got: %q", test.expected, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ package hugolib
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -414,10 +415,11 @@ func (pm *pageMeta) setMetadata(p *pageState, frontmatter map[string]interface{}
|
||||||
pm.params[loki] = pm.weight
|
pm.params[loki] = pm.weight
|
||||||
case "aliases":
|
case "aliases":
|
||||||
pm.aliases = cast.ToStringSlice(v)
|
pm.aliases = cast.ToStringSlice(v)
|
||||||
for _, alias := range pm.aliases {
|
for i, alias := range pm.aliases {
|
||||||
if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") {
|
if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") {
|
||||||
return fmt.Errorf("only relative aliases are supported, %v provided", alias)
|
return fmt.Errorf("http* aliases not supported: %q", alias)
|
||||||
}
|
}
|
||||||
|
pm.aliases[i] = filepath.ToSlash(alias)
|
||||||
}
|
}
|
||||||
pm.params[loki] = pm.aliases
|
pm.params[loki] = pm.aliases
|
||||||
case "sitemap":
|
case "sitemap":
|
||||||
|
|
|
@ -303,7 +303,20 @@ func (s *Site) renderAliases() error {
|
||||||
f := of.Format
|
f := of.Format
|
||||||
|
|
||||||
for _, a := range p.Aliases() {
|
for _, a := range p.Aliases() {
|
||||||
if f.Path != "" {
|
isRelative := !strings.HasPrefix(a, "/")
|
||||||
|
|
||||||
|
if isRelative {
|
||||||
|
// Make alias relative, where "." will be on the
|
||||||
|
// same directory level as the current page.
|
||||||
|
// TODO(bep) ugly URLs doesn't seem to be supported in
|
||||||
|
// aliases, I'm not sure why not.
|
||||||
|
basePath := of.RelPermalink()
|
||||||
|
if strings.HasSuffix(basePath, "/") {
|
||||||
|
basePath = path.Join(basePath, "..")
|
||||||
|
}
|
||||||
|
a = path.Join(basePath, a)
|
||||||
|
|
||||||
|
} else if f.Path != "" {
|
||||||
// Make sure AMP and similar doesn't clash with regular aliases.
|
// Make sure AMP and similar doesn't clash with regular aliases.
|
||||||
a = path.Join(f.Path, a)
|
a = path.Join(f.Path, a)
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ tags:
|
||||||
%s
|
%s
|
||||||
categories:
|
categories:
|
||||||
%s
|
%s
|
||||||
aliases: [Ali%d]
|
aliases: [/Ali%d]
|
||||||
---
|
---
|
||||||
# Doc
|
# Doc
|
||||||
`
|
`
|
||||||
|
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const slugDoc1 = "---\ntitle: slug doc 1\nslug: slug-doc-1\naliases:\n - sd1/foo/\n - sd2\n - sd3/\n - sd4.html\n---\nslug doc 1 content\n"
|
const slugDoc1 = "---\ntitle: slug doc 1\nslug: slug-doc-1\naliases:\n - /sd1/foo/\n - /sd2\n - /sd3/\n - /sd4.html\n---\nslug doc 1 content\n"
|
||||||
|
|
||||||
const slugDoc2 = `---
|
const slugDoc2 = `---
|
||||||
title: slug doc 2
|
title: slug doc 2
|
||||||
|
|
Loading…
Reference in a new issue