Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hugolib: Allow page-relative aliases #5802

Merged
merged 1 commit into from
Apr 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions docs/content/en/content-management/urls.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,13 @@ The following is a list of values that can be used in a `permalink` definition i

## 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

Expand Down
21 changes: 9 additions & 12 deletions hugolib/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"html/template"
"io"
"path"
"path/filepath"
"runtime"
"strings"
Expand All @@ -28,8 +29,6 @@ import (
"github.com/gohugoio/hugo/publisher"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/tpl"

"github.com/gohugoio/hugo/helpers"
)

const (
Expand Down Expand Up @@ -132,13 +131,14 @@ func (a aliasHandler) targetPathAlias(src string) (string, error) {
return "", fmt.Errorf("alias \"\" is an empty string")
}

alias := filepath.Clean(src)
components := strings.Split(alias, helpers.FilePathSeparator)
alias := path.Clean(filepath.ToSlash(src))

if !a.allowRoot && alias == helpers.FilePathSeparator {
if !a.allowRoot && alias == "/" {
return "", fmt.Errorf("alias \"%s\" resolves to website root directory", originalAlias)
}

components := strings.Split(alias, "/")

// Validate against directory traversal
if components[0] == ".." {
return "", fmt.Errorf("alias \"%s\" traverses outside the website root directory", originalAlias)
Expand Down Expand Up @@ -182,15 +182,12 @@ func (a aliasHandler) targetPathAlias(src string) (string, error) {
}

// Add the final touch
alias = strings.TrimPrefix(alias, helpers.FilePathSeparator)
if strings.HasSuffix(alias, helpers.FilePathSeparator) {
alias = strings.TrimPrefix(alias, "/")
if strings.HasSuffix(alias, "/") {
alias = alias + "index.html"
} else if !strings.HasSuffix(alias, ".html") {
alias = alias + helpers.FilePathSeparator + "index.html"
}
if originalAlias != alias {
a.log.INFO.Printf("Alias \"%s\" translated to \"%s\"\n", originalAlias, alias)
alias = alias + "/" + "index.html"
}

return alias, nil
return filepath.FromSlash(alias), nil
}
21 changes: 11 additions & 10 deletions hugolib/alias_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ import (

const pageWithAlias = `---
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.
`

const pageWithAliasMultipleOutputs = `---
title: Has Alias for HTML and AMP
aliases: ["foo/bar/"]
aliases: ["/foo/bar/"]
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.
Expand All @@ -46,16 +46,17 @@ func TestAlias(t *testing.T) {
assert := require.New(t)

b := newTestSitesBuilder(t)
b.WithSimpleConfigFile().WithContent("page.md", pageWithAlias)
b.WithSimpleConfigFile().WithContent("blog/page.md", pageWithAlias)
b.CreateSites().Build(BuildCfg{})

assert.Equal(1, len(b.H.Sites))
require.Len(t, b.H.Sites[0].RegularPages(), 1)

// the real page
b.AssertFileContent("public/page/index.html", "For some moments the old man")
// the alias redirector
b.AssertFileContent("public/blog/page/index.html", "For some moments the old man")
// the alias redirectors
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) {
Expand All @@ -64,7 +65,7 @@ func TestAliasMultipleOutputFormats(t *testing.T) {
assert := require.New(t)

b := newTestSitesBuilder(t)
b.WithSimpleConfigFile().WithContent("page.md", pageWithAliasMultipleOutputs)
b.WithSimpleConfigFile().WithContent("blog/page.md", pageWithAliasMultipleOutputs)

b.WithTemplates(
"_default/single.html", basicTemplate,
Expand All @@ -74,9 +75,9 @@ func TestAliasMultipleOutputFormats(t *testing.T) {
b.CreateSites().Build(BuildCfg{})

// the real pages
b.AssertFileContent("public/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/page/index.json", "For some moments the old man")
b.AssertFileContent("public/blog/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/blog/page/index.json", "For some moments the old man")

// the alias redirectors
b.AssertFileContent("public/foo/bar/index.html", "<meta http-equiv=\"refresh\" content=\"0; ")
Expand Down Expand Up @@ -135,7 +136,7 @@ func TestTargetPathHTMLRedirectAlias(t *testing.T) {
continue
}
if err == nil && path != test.expected {
t.Errorf("Expected: \"%s\", got: \"%s\"", test.expected, path)
t.Errorf("Expected: %q, got: %q", test.expected, path)
}
}
}
6 changes: 4 additions & 2 deletions hugolib/page__meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package hugolib
import (
"fmt"
"path"
"path/filepath"
"regexp"
"strings"
"time"
Expand Down Expand Up @@ -414,10 +415,11 @@ func (pm *pageMeta) setMetadata(p *pageState, frontmatter map[string]interface{}
pm.params[loki] = pm.weight
case "aliases":
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://") {
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
case "sitemap":
Expand Down
15 changes: 14 additions & 1 deletion hugolib/site_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,20 @@ func (s *Site) renderAliases() error {
f := of.Format

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.
a = path.Join(f.Path, a)
}
Expand Down
2 changes: 1 addition & 1 deletion hugolib/site_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ tags:
%s
categories:
%s
aliases: [Ali%d]
aliases: [/Ali%d]
---
# Doc
`
Expand Down
2 changes: 1 addition & 1 deletion hugolib/site_url_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"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 = `---
title: slug doc 2
Expand Down