Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hugolib/page_paths.go8
-rw-r--r--hugolib/page_paths_test.go14
-rw-r--r--hugolib/site_output_test.go73
-rw-r--r--media/mediaType.go36
-rw-r--r--media/mediaType_test.go5
-rw-r--r--output/layout.go43
-rw-r--r--output/layout_test.go38
7 files changed, 183 insertions, 34 deletions
diff --git a/hugolib/page_paths.go b/hugolib/page_paths.go
index 8aa70b95b..73fd62278 100644
--- a/hugolib/page_paths.go
+++ b/hugolib/page_paths.go
@@ -164,7 +164,7 @@ func createTargetPath(d targetPathDescriptor) string {
if d.URL != "" {
pagePath = filepath.Join(pagePath, d.URL)
if strings.HasSuffix(d.URL, "/") || !strings.Contains(d.URL, ".") {
- pagePath = filepath.Join(pagePath, d.Type.BaseName+"."+d.Type.MediaType.Suffix)
+ pagePath = filepath.Join(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix())
}
} else {
if d.ExpandedPermalink != "" {
@@ -184,9 +184,9 @@ func createTargetPath(d targetPathDescriptor) string {
}
if isUgly {
- pagePath += "." + d.Type.MediaType.Suffix
+ pagePath += d.Type.MediaType.Delimiter + d.Type.MediaType.Suffix
} else {
- pagePath = filepath.Join(pagePath, d.Type.BaseName+"."+d.Type.MediaType.Suffix)
+ pagePath = filepath.Join(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix())
}
if d.LangPrefix != "" {
@@ -207,7 +207,7 @@ func createTargetPath(d targetPathDescriptor) string {
base = helpers.FilePathSeparator + d.Type.BaseName
}
- pagePath += base + "." + d.Type.MediaType.Suffix
+ pagePath += base + d.Type.MediaType.FullSuffix()
if d.LangPrefix != "" {
pagePath = filepath.Join(d.LangPrefix, pagePath)
diff --git a/hugolib/page_paths_test.go b/hugolib/page_paths_test.go
index 9a2db1192..80dc390cc 100644
--- a/hugolib/page_paths_test.go
+++ b/hugolib/page_paths_test.go
@@ -18,6 +18,8 @@ import (
"strings"
"testing"
+ "github.com/gohugoio/hugo/media"
+
"fmt"
"github.com/gohugoio/hugo/output"
@@ -27,6 +29,17 @@ func TestPageTargetPath(t *testing.T) {
pathSpec := newTestDefaultPathSpec()
+ noExtNoDelimMediaType := media.TextType
+ noExtNoDelimMediaType.Suffix = ""
+ noExtNoDelimMediaType.Delimiter = ""
+
+ // Netlify style _redirects
+ noExtDelimFormat := output.Format{
+ Name: "NER",
+ MediaType: noExtNoDelimMediaType,
+ BaseName: "_redirects",
+ }
+
for _, langPrefix := range []string{"", "no"} {
for _, uglyURLs := range []bool{false, true} {
t.Run(fmt.Sprintf("langPrefix=%q,uglyURLs=%t", langPrefix, uglyURLs),
@@ -40,6 +53,7 @@ func TestPageTargetPath(t *testing.T) {
{"JSON home", targetPathDescriptor{Kind: KindHome, Type: output.JSONFormat}, "/index.json"},
{"AMP home", targetPathDescriptor{Kind: KindHome, Type: output.AMPFormat}, "/amp/index.html"},
{"HTML home", targetPathDescriptor{Kind: KindHome, BaseName: "_index", Type: output.HTMLFormat}, "/index.html"},
+ {"Netlify redirects", targetPathDescriptor{Kind: KindHome, BaseName: "_index", Type: noExtDelimFormat}, "/_redirects"},
{"HTML section list", targetPathDescriptor{
Kind: KindSection,
Sections: []string{"sect1"},
diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go
index 6aff84397..8455a13f7 100644
--- a/hugolib/site_output_test.go
+++ b/hugolib/site_output_test.go
@@ -290,3 +290,76 @@ baseName = "feed"
require.Equal(t, "http://example.com/blog/feed.xml", s.Info.RSSLink)
}
+
+// Issue #3614
+func TestDotLessOutputFormat(t *testing.T) {
+ siteConfig := `
+baseURL = "http://example.com/blog"
+
+paginate = 1
+defaultContentLanguage = "en"
+
+disableKinds = ["page", "section", "taxonomy", "taxonomyTerm", "sitemap", "robotsTXT", "404"]
+
+[mediaTypes]
+[mediaTypes."text/nodot"]
+suffix = ""
+delimiter = ""
+[mediaTypes."text/defaultdelim"]
+suffix = "defd"
+[mediaTypes."text/nosuffix"]
+suffix = ""
+[mediaTypes."text/customdelim"]
+suffix = "del"
+delimiter = "_"
+
+[outputs]
+home = [ "DOTLESS", "DEF", "NOS", "CUS" ]
+
+[outputFormats]
+[outputFormats.DOTLESS]
+mediatype = "text/nodot"
+baseName = "_redirects" # This is how Netlify names their redirect files.
+[outputFormats.DEF]
+mediatype = "text/defaultdelim"
+baseName = "defaultdelimbase"
+[outputFormats.NOS]
+mediatype = "text/nosuffix"
+baseName = "nosuffixbase"
+[outputFormats.CUS]
+mediatype = "text/customdelim"
+baseName = "customdelimbase"
+
+`
+
+ mf := afero.NewMemMapFs()
+ writeToFs(t, mf, "content/foo.html", `foo`)
+ writeToFs(t, mf, "layouts/_default/list.dotless", `a dotless`)
+ writeToFs(t, mf, "layouts/_default/list.def.defd", `default delimim`)
+ writeToFs(t, mf, "layouts/_default/list.nos", `no suffix`)
+ writeToFs(t, mf, "layouts/_default/list.cus.del", `custom delim`)
+
+ th, h := newTestSitesFromConfig(t, mf, siteConfig)
+
+ err := h.Build(BuildCfg{})
+
+ require.NoError(t, err)
+
+ th.assertFileContent("public/_redirects", "a dotless")
+ th.assertFileContent("public/defaultdelimbase.defd", "default delimim")
+ // This looks weird, but the user has chosen this definition.
+ th.assertFileContent("public/nosuffixbase.", "no suffix")
+ th.assertFileContent("public/customdelimbase_del", "custom delim")
+
+ s := h.Sites[0]
+ home := s.getPage(KindHome)
+ require.NotNil(t, home)
+
+ outputs := home.OutputFormats()
+
+ require.Equal(t, "/blog/_redirects", outputs.Get("DOTLESS").RelPermalink())
+ require.Equal(t, "/blog/defaultdelimbase.defd", outputs.Get("DEF").RelPermalink())
+ require.Equal(t, "/blog/nosuffixbase.", outputs.Get("NOS").RelPermalink())
+ require.Equal(t, "/blog/customdelimbase_del", outputs.Get("CUS").RelPermalink())
+
+}
diff --git a/media/mediaType.go b/media/mediaType.go
index 6b6f90439..2f238ba23 100644
--- a/media/mediaType.go
+++ b/media/mediaType.go
@@ -22,6 +22,10 @@ import (
"github.com/mitchellh/mapstructure"
)
+const (
+ defaultDelimiter = "."
+)
+
// A media type (also known as MIME type and content type) is a two-part identifier for
// file formats and format contents transmitted on the Internet.
// For Hugo's use case, we use the top-level type name / subtype name + suffix.
@@ -29,9 +33,10 @@ import (
// If suffix is not provided, the sub type will be used.
// See // https://en.wikipedia.org/wiki/Media_type
type Type struct {
- MainType string // i.e. text
- SubType string // i.e. html
- Suffix string // i.e html
+ MainType string // i.e. text
+ SubType string // i.e. html
+ Suffix string // i.e html
+ Delimiter string // defaults to "."
}
// FromTypeString creates a new Type given a type sring on the form MainType/SubType and
@@ -54,7 +59,7 @@ func FromString(t string) (Type, error) {
suffix = subParts[1]
}
- return Type{MainType: mainType, SubType: subType, Suffix: suffix}, nil
+ return Type{MainType: mainType, SubType: subType, Suffix: suffix, Delimiter: defaultDelimiter}, nil
}
// Type returns a string representing the main- and sub-type of a media type, i.e. "text/css".
@@ -72,16 +77,21 @@ func (m Type) String() string {
return fmt.Sprintf("%s/%s", m.MainType, m.SubType)
}
+// FullSuffix returns the file suffix with any delimiter prepended.
+func (m Type) FullSuffix() string {
+ return m.Delimiter + m.Suffix
+}
+
var (
- CalendarType = Type{"text", "calendar", "ics"}
- CSSType = Type{"text", "css", "css"}
- CSVType = Type{"text", "csv", "csv"}
- HTMLType = Type{"text", "html", "html"}
- JavascriptType = Type{"application", "javascript", "js"}
- JSONType = Type{"application", "json", "json"}
- RSSType = Type{"application", "rss", "xml"}
- XMLType = Type{"application", "xml", "xml"}
- TextType = Type{"text", "plain", "txt"}
+ CalendarType = Type{"text", "calendar", "ics", defaultDelimiter}
+ CSSType = Type{"text", "css", "css", defaultDelimiter}
+ CSVType = Type{"text", "csv", "csv", defaultDelimiter}
+ HTMLType = Type{"text", "html", "html", defaultDelimiter}
+ JavascriptType = Type{"application", "javascript", "js", defaultDelimiter}
+ JSONType = Type{"application", "json", "json", defaultDelimiter}
+ RSSType = Type{"application", "rss", "xml", defaultDelimiter}
+ XMLType = Type{"application", "xml", "xml", defaultDelimiter}
+ TextType = Type{"text", "plain", "txt", defaultDelimiter}
)
var DefaultTypes = Types{
diff --git a/media/mediaType_test.go b/media/mediaType_test.go
index 8d83c19f8..a6b18d1d6 100644
--- a/media/mediaType_test.go
+++ b/media/mediaType_test.go
@@ -40,6 +40,7 @@ func TestDefaultTypes(t *testing.T) {
require.Equal(t, test.expectedMainType, test.tp.MainType)
require.Equal(t, test.expectedSubType, test.tp.SubType)
require.Equal(t, test.expectedSuffix, test.tp.Suffix)
+ require.Equal(t, defaultDelimiter, test.tp.Delimiter)
require.Equal(t, test.expectedType, test.tp.Type())
require.Equal(t, test.expectedString, test.tp.String())
@@ -66,11 +67,11 @@ func TestFromTypeString(t *testing.T) {
f, err = FromString("application/custom")
require.NoError(t, err)
- require.Equal(t, Type{MainType: "application", SubType: "custom", Suffix: "custom"}, f)
+ require.Equal(t, Type{MainType: "application", SubType: "custom", Suffix: "custom", Delimiter: defaultDelimiter}, f)
f, err = FromString("application/custom+pdf")
require.NoError(t, err)
- require.Equal(t, Type{MainType: "application", SubType: "custom", Suffix: "pdf"}, f)
+ require.Equal(t, Type{MainType: "application", SubType: "custom", Suffix: "pdf", Delimiter: defaultDelimiter}, f)
f, err = FromString("noslash")
require.Error(t, err)
diff --git a/output/layout.go b/output/layout.go
index 6dba7f3b4..cacb92b80 100644
--- a/output/layout.go
+++ b/output/layout.go
@@ -181,17 +181,37 @@ func resolveListTemplate(d LayoutDescriptor, f Format,
case "taxonomyTerm":
layouts = resolveTemplate(taxonomyTermLayouts, d, f)
}
-
return layouts
}
func resolveTemplate(templ string, d LayoutDescriptor, f Format) []string {
+ delim := "."
+ if f.MediaType.Delimiter == "" {
+ delim = ""
+ }
layouts := strings.Fields(replaceKeyValues(templ,
- "SUFFIX", f.MediaType.Suffix,
+ ".SUFFIX", delim+f.MediaType.Suffix,
"NAME", strings.ToLower(f.Name),
"SECTION", d.Section))
- return layouts
+ return filterDotLess(layouts)
+}
+
+func filterDotLess(layouts []string) []string {
+ var filteredLayouts []string
+
+ for _, l := range layouts {
+ // This may be constructed, but media types can be suffix-less, but can contain
+ // a delimiter.
+ l = strings.TrimSuffix(l, ".")
+ // If media type has no suffix, we have "index" type of layouts in this list, which
+ // doesn't make much sense.
+ if strings.Contains(l, ".") {
+ filteredLayouts = append(filteredLayouts, l)
+ }
+ }
+
+ return filteredLayouts
}
func prependTextPrefixIfNeeded(f Format, layouts ...string) []string {
@@ -220,7 +240,12 @@ func regularPageLayouts(types string, layout string, f Format) []string {
layout = "single"
}
- suffix := f.MediaType.Suffix
+ delimiter := "."
+ if f.MediaType.Delimiter == "" {
+ delimiter = ""
+ }
+
+ suffix := delimiter + f.MediaType.Suffix
name := strings.ToLower(f.Name)
if types != "" {
@@ -229,15 +254,15 @@ func regularPageLayouts(types string, layout string, f Format) []string {
// Add type/layout.html
for i := range t {
search := t[:len(t)-i]
- layouts = append(layouts, fmt.Sprintf("%s/%s.%s.%s", strings.ToLower(path.Join(search...)), layout, name, suffix))
- layouts = append(layouts, fmt.Sprintf("%s/%s.%s", strings.ToLower(path.Join(search...)), layout, suffix))
+ layouts = append(layouts, fmt.Sprintf("%s/%s.%s%s", strings.ToLower(path.Join(search...)), layout, name, suffix))
+ layouts = append(layouts, fmt.Sprintf("%s/%s%s", strings.ToLower(path.Join(search...)), layout, suffix))
}
}
// Add _default/layout.html
- layouts = append(layouts, fmt.Sprintf("_default/%s.%s.%s", layout, name, suffix))
- layouts = append(layouts, fmt.Sprintf("_default/%s.%s", layout, suffix))
+ layouts = append(layouts, fmt.Sprintf("_default/%s.%s%s", layout, name, suffix))
+ layouts = append(layouts, fmt.Sprintf("_default/%s%s", layout, suffix))
- return layouts
+ return filterDotLess(layouts)
}
diff --git a/output/layout_test.go b/output/layout_test.go
index 56aac00d5..9d4d2f6d5 100644
--- a/output/layout_test.go
+++ b/output/layout_test.go
@@ -21,14 +21,34 @@ import (
"github.com/stretchr/testify/require"
)
-var ampType = Format{
- Name: "AMP",
- MediaType: media.HTMLType,
- BaseName: "index",
-}
-
func TestLayout(t *testing.T) {
+ noExtNoDelimMediaType := media.TextType
+ noExtNoDelimMediaType.Suffix = ""
+ noExtNoDelimMediaType.Delimiter = ""
+
+ noExtMediaType := media.TextType
+ noExtMediaType.Suffix = ""
+
+ var (
+ ampType = Format{
+ Name: "AMP",
+ MediaType: media.HTMLType,
+ BaseName: "index",
+ }
+
+ noExtDelimFormat = Format{
+ Name: "NEM",
+ MediaType: noExtNoDelimMediaType,
+ BaseName: "_redirects",
+ }
+ noExt = Format{
+ Name: "NEX",
+ MediaType: noExtMediaType,
+ BaseName: "next",
+ }
+ )
+
for _, this := range []struct {
name string
d LayoutDescriptor
@@ -39,6 +59,12 @@ func TestLayout(t *testing.T) {
}{
{"Home", LayoutDescriptor{Kind: "home"}, true, "", ampType,
[]string{"index.amp.html", "index.html", "_default/list.amp.html", "_default/list.html", "theme/index.amp.html", "theme/index.html"}},
+ {"Home, no ext or delim", LayoutDescriptor{Kind: "home"}, true, "", noExtDelimFormat,
+ []string{"index.nem", "_default/list.nem"}},
+ {"Home, no ext", LayoutDescriptor{Kind: "home"}, true, "", noExt,
+ []string{"index.nex", "_default/list.nex"}},
+ {"Page, no ext or delim", LayoutDescriptor{Kind: "page"}, true, "", noExtDelimFormat,
+ []string{"_default/single.nem", "theme/_default/single.nem"}},
{"Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, false, "", ampType,
[]string{"section/sect1.amp.html", "section/sect1.html"}},
{"Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, false, "", ampType,