shithub: hugo

Download patch

ref: 92baa14fd3f45c0917c5988235cd1a0f8692f171
parent: a55640de8e3944d3b9f64b15155148a0e35cb31e
author: Bjørn Erik Pedersen <[email protected]>
date: Sat Mar 30 13:08:25 EDT 2019

hugolib: Allow page-relative aliases

Fixes #5757

--- a/docs/content/en/content-management/urls.md
+++ b/docs/content/en/content-management/urls.md
@@ -82,9 +82,13 @@
 
 ## 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
 
--- a/hugolib/alias.go
+++ b/hugolib/alias.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"html/template"
 	"io"
+	"path"
 	"path/filepath"
 	"runtime"
 	"strings"
@@ -28,8 +29,6 @@
 	"github.com/gohugoio/hugo/publisher"
 	"github.com/gohugoio/hugo/resources/page"
 	"github.com/gohugoio/hugo/tpl"
-
-	"github.com/gohugoio/hugo/helpers"
 )
 
 const (
@@ -132,13 +131,14 @@
 		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)
@@ -182,15 +182,12 @@
 	}
 
 	// 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"
+		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
 }
--- a/hugolib/alias_test.go
+++ b/hugolib/alias_test.go
@@ -25,7 +25,7 @@
 
 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.
 `
@@ -32,7 +32,7 @@
 
 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.
@@ -46,7 +46,7 @@
 	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))
@@ -53,9 +53,10 @@
 	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) {
@@ -64,7 +65,7 @@
 	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,
@@ -74,9 +75,9 @@
 	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; ")
@@ -135,7 +136,7 @@
 			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)
 		}
 	}
 }
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -16,6 +16,7 @@
 import (
 	"fmt"
 	"path"
+	"path/filepath"
 	"regexp"
 	"strings"
 	"time"
@@ -414,10 +415,11 @@
 			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":
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -303,7 +303,20 @@
 			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)
 				}
--- a/hugolib/site_stats_test.go
+++ b/hugolib/site_stats_test.go
@@ -55,7 +55,7 @@
 %s
 categories:
 %s
-aliases: [Ali%d]
+aliases: [/Ali%d]
 ---
 # Doc
 `
--- a/hugolib/site_url_test.go
+++ b/hugolib/site_url_test.go
@@ -26,7 +26,7 @@
 	"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