summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--helpers/general.go11
-rw-r--r--helpers/general_test.go18
-rw-r--r--helpers/path.go6
-rw-r--r--hugolib/menu_test.go47
-rw-r--r--hugolib/page.go4
-rw-r--r--hugolib/page_permalink_test.go37
-rw-r--r--hugolib/site.go22
-rw-r--r--hugolib/site_test.go84
8 files changed, 187 insertions, 42 deletions
diff --git a/helpers/general.go b/helpers/general.go
index df08c887f..908751da0 100644
--- a/helpers/general.go
+++ b/helpers/general.go
@@ -25,6 +25,8 @@ import (
"reflect"
"strings"
"sync"
+ "unicode"
+ "unicode/utf8"
"github.com/spf13/cast"
bp "github.com/spf13/hugo/bufferpool"
@@ -78,6 +80,15 @@ func GuessType(in string) string {
return "unknown"
}
+// FirstUpper returns a string with the first character as upper case.
+func FirstUpper(s string) string {
+ if s == "" {
+ return ""
+ }
+ r, n := utf8.DecodeRuneInString(s)
+ return string(unicode.ToUpper(r)) + s[n:]
+}
+
// ReaderToBytes takes an io.Reader argument, reads from it
// and returns bytes.
func ReaderToBytes(lines io.Reader) []byte {
diff --git a/helpers/general_test.go b/helpers/general_test.go
index 31c478200..c801ad441 100644
--- a/helpers/general_test.go
+++ b/helpers/general_test.go
@@ -33,6 +33,24 @@ func TestGuessType(t *testing.T) {
}
}
+func TestFirstUpper(t *testing.T) {
+ for i, this := range []struct {
+ in string
+ expect string
+ }{
+ {"foo", "Foo"},
+ {"foo bar", "Foo bar"},
+ {"Foo Bar", "Foo Bar"},
+ {"", ""},
+ {"å", "Å"},
+ } {
+ result := FirstUpper(this.in)
+ if result != this.expect {
+ t.Errorf("[%d] got %s but expected %s", i, result, this.expect)
+ }
+ }
+}
+
func TestBytesToReader(t *testing.T) {
asBytes := ReaderToBytes(strings.NewReader("Hello World!"))
asReader := BytesToReader(asBytes)
diff --git a/helpers/path.go b/helpers/path.go
index 0dee8db4c..9f47549d2 100644
--- a/helpers/path.go
+++ b/helpers/path.go
@@ -92,7 +92,7 @@ func UnicodeSanitize(s string) string {
target := make([]rune, 0, len(source))
for _, r := range source {
- if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '.' || r == '/' || r == '_' || r == '-' || r == '#' {
+ if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '.' || r == '/' || r == '\\' || r == '_' || r == '-' || r == '#' {
target = append(target, r)
}
}
@@ -332,8 +332,8 @@ func GetRelativePath(path, base string) (final string, err error) {
return "", err
}
- if strings.HasSuffix(path, "/") && !strings.HasSuffix(name, "/") {
- name += "/"
+ if strings.HasSuffix(filepath.FromSlash(path), FilePathSeparator) && !strings.HasSuffix(name, FilePathSeparator) {
+ name += FilePathSeparator
}
return name, nil
}
diff --git a/hugolib/menu_test.go b/hugolib/menu_test.go
index 8c4c26b5b..86839b65b 100644
--- a/hugolib/menu_test.go
+++ b/hugolib/menu_test.go
@@ -92,6 +92,16 @@ weight = 3
+++
Front Matter with Menu Pages`)
+var MENU_PAGE_4 = []byte(`+++
+title = "Four"
+weight = 4
+[menu]
+ [menu.p_two]
+ Name = "Four"
+ Parent = "Three"
++++
+Front Matter with Menu Pages`)
+
var MENU_PAGE_SOURCES = []source.ByteSource{
{filepath.FromSlash("sect/doc1.md"), MENU_PAGE_1},
{filepath.FromSlash("sect/doc2.md"), MENU_PAGE_2},
@@ -102,6 +112,7 @@ var MENU_PAGE_SECTIONS_SOURCES = []source.ByteSource{
{filepath.FromSlash("first/doc1.md"), MENU_PAGE_1},
{filepath.FromSlash("first/doc2.md"), MENU_PAGE_2},
{filepath.FromSlash("second-section/doc3.md"), MENU_PAGE_3},
+ {filepath.FromSlash("Fish and Chips/doc4.md"), MENU_PAGE_4},
}
func tstCreateMenuPageWithNameTOML(title, menu, name string) []byte {
@@ -332,48 +343,64 @@ func doTestMenuWithUnicodeURLs(t *testing.T, canonifyURLs, uglyURLs bool) {
// Issue #1114
func TestSectionPagesMenu(t *testing.T) {
- viper.Reset()
- defer viper.Reset()
-
- viper.Set("SectionPagesMenu", "spm")
doTestSectionPagesMenu(true, t)
doTestSectionPagesMenu(false, t)
}
func doTestSectionPagesMenu(canonifyUrls bool, t *testing.T) {
+ viper.Reset()
+ defer viper.Reset()
+
+ viper.Set("SectionPagesMenu", "spm")
+
viper.Set("CanonifyURLs", canonifyUrls)
s := setupMenuTests(t, MENU_PAGE_SECTIONS_SOURCES)
- assert.Equal(t, 2, len(s.Sections))
+ assert.Equal(t, 3, len(s.Sections))
firstSectionPages := s.Sections["first"]
assert.Equal(t, 2, len(firstSectionPages))
secondSectionPages := s.Sections["second-section"]
assert.Equal(t, 1, len(secondSectionPages))
+ fishySectionPages := s.Sections["fish-and-chips"]
+ assert.Equal(t, 1, len(fishySectionPages))
- nodeFirst := s.newSectionListNode("first", firstSectionPages)
- nodeSecond := s.newSectionListNode("second-section", secondSectionPages)
-
+ nodeFirst := s.newSectionListNode("First", "first", firstSectionPages)
+ nodeSecond := s.newSectionListNode("Second Section", "second-section", secondSectionPages)
+ nodeFishy := s.newSectionListNode("Fish and Chips", "fish-and-chips", fishySectionPages)
firstSectionMenuEntry := findTestMenuEntryByID(s, "spm", "first")
secondSectionMenuEntry := findTestMenuEntryByID(s, "spm", "second-section")
+ fishySectionMenuEntry := findTestMenuEntryByID(s, "spm", "Fish and Chips")
assert.NotNil(t, firstSectionMenuEntry)
assert.NotNil(t, secondSectionMenuEntry)
assert.NotNil(t, nodeFirst)
assert.NotNil(t, nodeSecond)
+ assert.NotNil(t, fishySectionMenuEntry)
+ assert.NotNil(t, nodeFishy)
+
+ assert.True(t, nodeFirst.IsMenuCurrent("spm", firstSectionMenuEntry))
+ assert.False(t, nodeFirst.IsMenuCurrent("spm", secondSectionMenuEntry))
+ assert.False(t, nodeFirst.IsMenuCurrent("spm", fishySectionMenuEntry))
+ assert.True(t, nodeFishy.IsMenuCurrent("spm", fishySectionMenuEntry))
+ assert.Equal(t, "Fish and Chips", fishySectionMenuEntry.Name)
for _, p := range firstSectionPages {
assert.True(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry))
assert.False(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry))
- assert.True(t, nodeFirst.IsMenuCurrent("spm", firstSectionMenuEntry))
- assert.False(t, nodeFirst.IsMenuCurrent("spm", secondSectionMenuEntry))
}
for _, p := range secondSectionPages {
assert.False(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry))
assert.True(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry))
}
+
+ for _, p := range fishySectionPages {
+ assert.False(t, p.Page.HasMenuCurrent("spm", firstSectionMenuEntry))
+ assert.False(t, p.Page.HasMenuCurrent("spm", secondSectionMenuEntry))
+ assert.True(t, p.Page.HasMenuCurrent("spm", fishySectionMenuEntry))
+ }
}
func TestTaxonomyNodeMenu(t *testing.T) {
diff --git a/hugolib/page.go b/hugolib/page.go
index 391fedf38..8cb28e035 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -346,7 +346,7 @@ func (p *Page) analyzePage() {
func (p *Page) permalink() (*url.URL, error) {
baseURL := string(p.Site.BaseURL)
- dir := strings.TrimSpace(filepath.ToSlash(p.Source.Dir()))
+ dir := strings.TrimSpace(helpers.MakePath(filepath.ToSlash(strings.ToLower(p.Source.Dir()))))
pSlug := strings.TrimSpace(helpers.URLize(p.Slug))
pURL := strings.TrimSpace(helpers.URLize(p.URL))
var permalink string
@@ -837,5 +837,5 @@ func (p *Page) TargetPath() (outfile string) {
outfile = helpers.ReplaceExtension(p.Source.LogicalName(), p.Extension())
}
- return filepath.Join(p.Source.Dir(), strings.TrimSpace(outfile))
+ return filepath.Join(strings.ToLower(helpers.MakePath(p.Source.Dir())), strings.TrimSpace(outfile))
}
diff --git a/hugolib/page_permalink_test.go b/hugolib/page_permalink_test.go
index 7687c3fdd..2450300ad 100644
--- a/hugolib/page_permalink_test.go
+++ b/hugolib/page_permalink_test.go
@@ -15,7 +15,6 @@ func TestPermalink(t *testing.T) {
tests := []struct {
file string
- dir string
base template.URL
slug string
url string
@@ -24,27 +23,27 @@ func TestPermalink(t *testing.T) {
expectedAbs string
expectedRel string
}{
- {"x/y/z/boofar.md", "x/y/z", "", "", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"},
- {"x/y/z/boofar.md", "x/y/z/", "", "", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"},
+ {"x/y/z/boofar.md", "", "", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"},
+ {"x/y/z/boofar.md", "", "", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"},
// Issue #1174
- {"x/y/z/boofar.md", "x/y/z", "http://gopher.com/", "", "", false, true, "http://gopher.com/x/y/z/boofar/", "/x/y/z/boofar/"},
- {"x/y/z/boofar.md", "x/y/z/", "http://gopher.com/", "", "", true, true, "http://gopher.com/x/y/z/boofar.html", "/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "x/y/z/", "", "boofar", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"},
- {"x/y/z/boofar.md", "x/y/z", "http://barnew/", "", "", false, false, "http://barnew/x/y/z/boofar/", "/x/y/z/boofar/"},
- {"x/y/z/boofar.md", "x/y/z/", "http://barnew/", "boofar", "", false, false, "http://barnew/x/y/z/boofar/", "/x/y/z/boofar/"},
- {"x/y/z/boofar.md", "x/y/z", "", "", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "x/y/z/", "", "", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "x/y/z/", "", "boofar", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "x/y/z", "http://barnew/", "", "", true, false, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "x/y/z/", "http://barnew/", "boofar", "", true, false, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "x/y/z/", "http://barnew/boo/", "boofar", "", true, false, "http://barnew/boo/x/y/z/boofar.html", "/boo/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "x/y/z/", "http://barnew/boo/", "boofar", "", false, true, "http://barnew/boo/x/y/z/boofar/", "/x/y/z/boofar/"},
- {"x/y/z/boofar.md", "x/y/z/", "http://barnew/boo/", "boofar", "", false, false, "http://barnew/boo/x/y/z/boofar/", "/boo/x/y/z/boofar/"},
- {"x/y/z/boofar.md", "x/y/z/", "http://barnew/boo/", "boofar", "", true, true, "http://barnew/boo/x/y/z/boofar.html", "/x/y/z/boofar.html"},
- {"x/y/z/boofar.md", "x/y/z/", "http://barnew/boo", "boofar", "", true, true, "http://barnew/boo/x/y/z/boofar.html", "/x/y/z/boofar.html"},
+ {"x/y/z/boofar.md", "http://gopher.com/", "", "", false, true, "http://gopher.com/x/y/z/boofar/", "/x/y/z/boofar/"},
+ {"x/y/z/boofar.md", "http://gopher.com/", "", "", true, true, "http://gopher.com/x/y/z/boofar.html", "/x/y/z/boofar.html"},
+ {"x/y/z/boofar.md", "", "boofar", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"},
+ {"x/y/z/boofar.md", "http://barnew/", "", "", false, false, "http://barnew/x/y/z/boofar/", "/x/y/z/boofar/"},
+ {"x/y/z/boofar.md", "http://barnew/", "boofar", "", false, false, "http://barnew/x/y/z/boofar/", "/x/y/z/boofar/"},
+ {"x/y/z/boofar.md", "", "", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"},
+ {"x/y/z/boofar.md", "", "", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"},
+ {"x/y/z/boofar.md", "", "boofar", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"},
+ {"x/y/z/boofar.md", "http://barnew/", "", "", true, false, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"},
+ {"x/y/z/boofar.md", "http://barnew/", "boofar", "", true, false, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"},
+ {"x/y/z/boofar.md", "http://barnew/boo/", "boofar", "", true, false, "http://barnew/boo/x/y/z/boofar.html", "/boo/x/y/z/boofar.html"},
+ {"x/y/z/boofar.md", "http://barnew/boo/", "boofar", "", false, true, "http://barnew/boo/x/y/z/boofar/", "/x/y/z/boofar/"},
+ {"x/y/z/boofar.md", "http://barnew/boo/", "boofar", "", false, false, "http://barnew/boo/x/y/z/boofar/", "/boo/x/y/z/boofar/"},
+ {"x/y/z/boofar.md", "http://barnew/boo/", "boofar", "", true, true, "http://barnew/boo/x/y/z/boofar.html", "/x/y/z/boofar.html"},
+ {"x/y/z/boofar.md", "http://barnew/boo", "boofar", "", true, true, "http://barnew/boo/x/y/z/boofar.html", "/x/y/z/boofar.html"},
// test URL overrides
- {"x/y/z/boofar.md", "x/y/z", "", "", "/z/y/q/", false, false, "/z/y/q/", "/z/y/q/"},
+ {"x/y/z/boofar.md", "", "", "/z/y/q/", false, false, "/z/y/q/", "/z/y/q/"},
}
viper.Set("DefaultExtension", "html")
diff --git a/hugolib/site.go b/hugolib/site.go
index 1a596bdcf..1fe4cb4e3 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -774,7 +774,7 @@ func (s *Site) assembleMenus() {
if sectionPagesMenu != "" {
if _, ok := sectionPagesMenus[p.Section()]; !ok {
if p.Section() != "" {
- me := MenuEntry{Identifier: p.Section(), Name: helpers.MakeTitle(p.Section()), URL: s.Info.createNodeMenuEntryURL("/" + p.Section())}
+ me := MenuEntry{Identifier: p.Section(), Name: helpers.MakeTitle(helpers.FirstUpper(p.Section())), URL: s.Info.createNodeMenuEntryURL("/" + p.Section())}
if _, ok := flat[twoD{sectionPagesMenu, me.KeyName()}]; ok {
// menu with same id defined in config, let that one win
continue
@@ -1160,12 +1160,13 @@ func (s *Site) RenderListsOfTaxonomyTerms() (err error) {
return
}
-func (s *Site) newSectionListNode(section string, data WeightedPages) *Node {
+func (s *Site) newSectionListNode(sectionName, section string, data WeightedPages) *Node {
n := s.NewNode()
+ sectionName = helpers.FirstUpper(sectionName)
if viper.GetBool("PluralizeListTitles") {
- n.Title = strings.Title(inflect.Pluralize(section))
+ n.Title = inflect.Pluralize(sectionName)
} else {
- n.Title = strings.Title(section)
+ n.Title = sectionName
}
s.setURLs(n, section)
n.Date = data[0].Page.Date
@@ -1179,12 +1180,17 @@ func (s *Site) newSectionListNode(section string, data WeightedPages) *Node {
func (s *Site) RenderSectionLists() error {
for section, data := range s.Sections {
+ // section keys are lower case
+ // extract the original casing from the first page to get sensible titles.
+ sectionName := section
+ if len(data) > 0 {
+ sectionName = data[0].Page.Section()
+ }
layouts := s.appendThemeTemplates(
[]string{"section/" + section + ".html", "_default/section.html", "_default/list.html", "indexes/" + section + ".html", "_default/indexes.html"})
- n := s.newSectionListNode(section, data)
-
- if err := s.renderAndWritePage(fmt.Sprintf("section %s", section), fmt.Sprintf("/%s", section), n, s.appendThemeTemplates(layouts)...); err != nil {
+ n := s.newSectionListNode(sectionName, section, data)
+ if err := s.renderAndWritePage(fmt.Sprintf("section %s", section), section, n, s.appendThemeTemplates(layouts)...); err != nil {
return err
}
@@ -1203,7 +1209,7 @@ func (s *Site) RenderSectionLists() error {
continue
}
- sectionPagerNode := s.newSectionListNode(section, data)
+ sectionPagerNode := s.newSectionListNode(sectionName, section, data)
sectionPagerNode.paginator = pager
if pager.TotalPages() > 0 {
sectionPagerNode.Date = pager.Pages()[0].Date
diff --git a/hugolib/site_test.go b/hugolib/site_test.go
index 65ab241b7..22db1eaf2 100644
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -1,6 +1,7 @@
package hugolib
import (
+ "bitbucket.org/pkg/inflect"
"bytes"
"fmt"
"html/template"
@@ -277,6 +278,7 @@ func TestDraftAndFutureRender(t *testing.T) {
// Issue #957
func TestCrossrefs(t *testing.T) {
+ hugofs.DestinationFS = new(afero.MemMapFs)
for _, uglyUrls := range []bool{true, false} {
for _, relative := range []bool{true, false} {
doTestCrossrefs(t, relative, uglyUrls)
@@ -360,6 +362,7 @@ func doTestCrossrefs(t *testing.T, relative, uglyUrls bool) {
// Issue #939
func Test404ShouldAlwaysHaveUglyUrls(t *testing.T) {
+ hugofs.DestinationFS = new(afero.MemMapFs)
for _, uglyURLs := range []bool{true, false} {
doTest404ShouldAlwaysHaveUglyUrls(t, uglyURLs)
}
@@ -439,6 +442,87 @@ func doTest404ShouldAlwaysHaveUglyUrls(t *testing.T, uglyURLs bool) {
}
+// Issue #1176
+func TestSectionNaming(t *testing.T) {
+
+ for _, canonify := range []bool{true, false} {
+ for _, uglify := range []bool{true, false} {
+ for _, pluralize := range []bool{true, false} {
+ doTestSectionNaming(t, canonify, uglify, pluralize)
+ }
+ }
+ }
+}
+
+func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
+ hugofs.DestinationFS = new(afero.MemMapFs)
+ viper.Reset()
+ defer viper.Reset()
+ viper.Set("baseurl", "http://auth/sub/")
+ viper.Set("DefaultExtension", "html")
+ viper.Set("UglyURLs", uglify)
+ viper.Set("PluralizeListTitles", pluralize)
+ viper.Set("CanonifyURLs", canonify)
+
+ var expectedPathSuffix string
+
+ if uglify {
+ expectedPathSuffix = ".html"
+ } else {
+ expectedPathSuffix = "/index.html"
+ }
+
+ sources := []source.ByteSource{
+ {filepath.FromSlash("sect/doc1.html"), []byte("doc1")},
+ {filepath.FromSlash("Fish and Chips/doc2.html"), []byte("doc2")},
+ {filepath.FromSlash("ラーメン/doc3.html"), []byte("doc3")},
+ }
+
+ s := &Site{
+ Source: &source.InMemorySource{ByteSource: sources},
+ Targets: targetList{Page: &target.PagePub{UglyURLs: uglify}},
+ }
+
+ s.initializeSiteInfo()
+ templatePrep(s)
+
+ must(s.addTemplate("_default/single.html", "{{.Content}}"))
+ must(s.addTemplate("_default/list.html", "{{ .Title }}"))
+
+ createAndRenderPages(t, s)
+ s.RenderSectionLists()
+
+ tests := []struct {
+ doc string
+ pluralAware bool
+ expected string
+ }{
+ {filepath.FromSlash(fmt.Sprintf("sect/doc1%s", expectedPathSuffix)), false, "doc1"},
+ {filepath.FromSlash(fmt.Sprintf("sect%s", expectedPathSuffix)), true, "Sect"},
+ {filepath.FromSlash(fmt.Sprintf("fish-and-chips/doc2%s", expectedPathSuffix)), false, "doc2"},
+ {filepath.FromSlash(fmt.Sprintf("fish-and-chips%s", expectedPathSuffix)), true, "Fish and Chips"},
+ {filepath.FromSlash(fmt.Sprintf("ラーメン/doc3%s", expectedPathSuffix)), false, "doc3"},
+ {filepath.FromSlash(fmt.Sprintf("ラーメン%s", expectedPathSuffix)), true, "ラーメン"},
+ }
+
+ for _, test := range tests {
+ file, err := hugofs.DestinationFS.Open(test.doc)
+ if err != nil {
+ t.Fatalf("Did not find %s in target: %s", test.doc, err)
+ }
+
+ content := helpers.ReaderToString(file)
+
+ if test.pluralAware && pluralize {
+ test.expected = inflect.Pluralize(test.expected)
+ }
+
+ if content != test.expected {
+ t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content)
+ }
+ }
+
+}
func TestSkipRender(t *testing.T) {
viper.Reset()
defer viper.Reset()