From 21c5d236627010afb1dd6ae1704f616ce323b822 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Wed, 10 Oct 2018 01:47:27 -0400 Subject: [PATCH] Partial rebase of jamieconnolly:trim-trailing-slash See https://github.com/gohugoio/hugo/pull/3934 . I did not rebase some of the added tests because their merge conflicts were more complicated; this is a proof of concept, and there are apparently licensing issues that need to be resolved. --- commands/server.go | 9 +++++++++ helpers/url.go | 8 ++++++-- helpers/url_test.go | 27 +++++++++++++++++++++------ hugolib/config.go | 1 + hugolib/page__paths.go | 15 ++++++++------- hugolib/paths/paths.go | 2 ++ hugolib/site.go | 3 +++ resources/page/page_paths.go | 23 +++++++++++++++++++++-- 8 files changed, 71 insertions(+), 17 deletions(-) diff --git a/commands/server.go b/commands/server.go index 72884749277..93a7e16ff25 100644 --- a/commands/server.go +++ b/commands/server.go @@ -373,6 +373,15 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro } } + + if f.c.Cfg.GetBool("trimTrailingSlash") { + path := strings.Split(r.URL.Path, "/") + + if !strings.Contains(path[len(path)-1], ".") && !strings.HasSuffix(r.URL.Path, "/") { + r.URL.Path += ".html" + } + } + h.ServeHTTP(w, r) }) } diff --git a/helpers/url.go b/helpers/url.go index 6dbdea299e3..28c5e0bc18f 100644 --- a/helpers/url.go +++ b/helpers/url.go @@ -305,14 +305,18 @@ func (p *PathSpec) URLizeAndPrep(in string) string { // URLPrep applies misc sanitation to the given URL. func (p *PathSpec) URLPrep(in string) string { - if p.UglyURLs { + if p.UglyURLs && !p.TrimTrailingSlash { return Uglify(SanitizeURL(in)) } pretty := PrettifyURL(SanitizeURL(in)) if path.Ext(pretty) == ".xml" { return pretty } - url, err := purell.NormalizeURLString(pretty, purell.FlagAddTrailingSlash) + flag := purell.FlagAddTrailingSlash + if p.TrimTrailingSlash { + flag = purell.FlagRemoveTrailingSlash + } + url, err := purell.NormalizeURLString(pretty, flag) if err != nil { return pretty } diff --git a/helpers/url_test.go b/helpers/url_test.go index 9223ba2cd16..d2f51311ec7 100644 --- a/helpers/url_test.go +++ b/helpers/url_test.go @@ -242,19 +242,34 @@ func TestMakePermalink(t *testing.T) { func TestURLPrep(t *testing.T) { type test struct { - ugly bool - input string - output string + trimTrailingSlash bool + uglyURLs bool + input string + output string } data := []test{ - {false, "/section/name.html", "/section/name/"}, - {true, "/section/name/index.html", "/section/name.html"}, + // trimTrailingSlash=false, uglyURLs=false + {false, false, "/section/name.html", "/section/name/"}, + {false, false, "/section/name/index.html", "/section/name/"}, + + // trimTrailingSlash=false, uglyURLs=true + {false, true, "/section/name.html", "/section/name.html"}, + {false, true, "/section/name/index.html", "/section/name.html"}, + + // trimTrailingSlash=true, uglyURLs=false + {true, false, "/section/name.html", "/section/name"}, + {true, false, "/section/name/index.html", "/section/name"}, + + // trimTrailingSlash=true, uglyURLs=true + {true, true, "/section/name.html", "/section/name"}, + {true, true, "/section/name/index.html", "/section/name"}, } for i, d := range data { v := newTestCfg() - v.Set("uglyURLs", d.ugly) + v.Set("trimTrailingSlash", d.trimTrailingSlash) + v.Set("uglyURLs", d.uglyURLs) l := langs.NewDefaultLanguage(v) p, _ := NewPathSpec(hugofs.NewMem(v), l, nil) diff --git a/hugolib/config.go b/hugolib/config.go index 841bd5193a3..d1c04e86fd1 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -586,6 +586,7 @@ func loadDefaultSettingsFor(v *viper.Viper) error { v.SetDefault("ignoreCache", false) v.SetDefault("canonifyURLs", false) v.SetDefault("relativeURLs", false) + v.SetDefault("trimTrailingSlash", false) v.SetDefault("removePathAccents", false) v.SetDefault("titleCaseStyle", "AP") v.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"}) diff --git a/hugolib/page__paths.go b/hugolib/page__paths.go index a9152f44aba..ae40312f5e8 100644 --- a/hugolib/page__paths.go +++ b/hugolib/page__paths.go @@ -121,13 +121,14 @@ func createTargetPathDescriptor(s *Site, p page.Page, pm *pageMeta) (page.Target alwaysInSubDir := p.Kind() == kindSitemap desc := page.TargetPathDescriptor{ - PathSpec: d.PathSpec, - Kind: p.Kind(), - Sections: p.SectionsEntries(), - UglyURLs: s.Info.uglyURLs(p), - ForcePrefix: s.h.IsMultihost() || alwaysInSubDir, - Dir: dir, - URL: pm.urlPaths.URL, + PathSpec: d.PathSpec, + Kind: p.Kind(), + Sections: p.SectionsEntries(), + UglyURLs: s.Info.uglyURLs(p), + TrimTrailingSlash: s.Info.trimTrailingSlash, + ForcePrefix: s.h.IsMultihost() || alwaysInSubDir, + Dir: dir, + URL: pm.urlPaths.URL, } if pm.Slug() != "" { diff --git a/hugolib/paths/paths.go b/hugolib/paths/paths.go index 97d4f17bab7..8bc8d7fdf2e 100644 --- a/hugolib/paths/paths.go +++ b/hugolib/paths/paths.go @@ -59,6 +59,7 @@ type Paths struct { DisablePathToLower bool RemovePathAccents bool + TrimTrailingSlash bool UglyURLs bool CanonifyURLs bool @@ -156,6 +157,7 @@ func New(fs *hugofs.Fs, cfg config.Provider) (*Paths, error) { DisablePathToLower: cfg.GetBool("disablePathToLower"), RemovePathAccents: cfg.GetBool("removePathAccents"), UglyURLs: cfg.GetBool("uglyURLs"), + TrimTrailingSlash: cfg.GetBool("trimTrailingSlash"), CanonifyURLs: cfg.GetBool("canonifyURLs"), ThemesDir: cfg.GetString("themesDir"), diff --git a/hugolib/site.go b/hugolib/site.go index bbcbcd27aec..6a46eafcf77 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -546,6 +546,8 @@ type SiteInfo struct { relativeURLs bool uglyURLs func(p page.Page) bool + trimTrailingSlash bool + owner *HugoSites s *Site language *langs.Language @@ -1204,6 +1206,7 @@ func (s *Site) initializeSiteInfo() error { canonifyURLs: s.Cfg.GetBool("canonifyURLs"), relativeURLs: s.Cfg.GetBool("relativeURLs"), uglyURLs: uglyURLs, + trimTrailingSlash: s.Cfg.GetBool("trimTrailingSlash"), permalinks: permalinks, owner: s.h, s: s, diff --git a/resources/page/page_paths.go b/resources/page/page_paths.go index 247c4dfcbfc..e880912d2e2 100644 --- a/resources/page/page_paths.go +++ b/resources/page/page_paths.go @@ -68,6 +68,9 @@ type TargetPathDescriptor struct { // The expanded permalink if defined for the section, ready to use. ExpandedPermalink string + // Whether to trim the trailing slash from URLs + TrimTrailingSlash bool + // Some types cannot have uglyURLs, even if globally enabled, RSS being one example. UglyURLs bool } @@ -141,7 +144,7 @@ func CreateTargetPaths(d TargetPathDescriptor) (tp TargetPaths) { // the index base even when uglyURLs is enabled. needsBase := true - isUgly := d.UglyURLs && !d.Type.NoUgly + isUgly := (d.UglyURLs || d.TrimTrailingSlash) && !d.Type.NoUgly baseNameSameAsType := d.BaseName != "" && d.BaseName == d.Type.BaseName if d.ExpandedPermalink == "" && baseNameSameAsType { @@ -174,13 +177,21 @@ func CreateTargetPaths(d TargetPathDescriptor) (tp TargetPaths) { hasSlash := strings.HasSuffix(d.URL, slash) if hasSlash || !hasDot { - pagePath = pjoin(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix()) + if d.TrimTrailingSlash { + pagePath += d.Type.MediaType.FullSuffix() + } else { + pagePath = pjoin(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix()) + } } else if hasDot { pagePathDir = path.Dir(pagePathDir) } if !isHtmlIndex(pagePath) { link = pagePath + + if d.TrimTrailingSlash { + link = strings.TrimSuffix(link, ".html") + } } else if !hasSlash { link += slash } @@ -239,6 +250,10 @@ func CreateTargetPaths(d TargetPathDescriptor) (tp TargetPaths) { if !isHtmlIndex(pagePath) { link = pagePath + + if d.TrimTrailingSlash { + link = strings.TrimSuffix(link, ".html") + } } if d.PrefixFilePath != "" { @@ -278,6 +293,10 @@ func CreateTargetPaths(d TargetPathDescriptor) (tp TargetPaths) { if !isHtmlIndex(pagePath) { link = pagePath + + if d.TrimTrailingSlash { + link = strings.TrimSuffix(link, ".html") + } } else { link += slash }