ref: 8fb594bfb090c017d4e5cbb2905780221e202c41
parent: 9b4170ce768717adfbe9d97c46e38ceaec2ce994
author: Bjørn Erik Pedersen <[email protected]>
date: Sun Jul 30 13:46:04 EDT 2017
Make the title case style guide configurable This works for the `title` func and the other places where Hugo makes title case. * AP style (new default) * Chicago style * Go style (what we have today) Fixes #989
--- a/docs/content/getting-started/configuration.md
+++ b/docs/content/getting-started/configuration.md
@@ -156,6 +156,10 @@
theme: ""
title: ""
# if true, use /filename.html instead of /filename/
+# Title Case style guide for the title func and other automatic title casing in Hugo.
+// Valid values are "AP" (default), "Chicago" and "Go" (which was what you had in Hugo <= 0.25.1).
+// See https://www.apstylebook.com/ and http://www.chicagomanualofstyle.org/home.html
+titleCaseStyle: "AP"
uglyURLs: false
# verbose output
verbose: false
--- a/helpers/general.go
+++ b/helpers/general.go
@@ -26,6 +26,8 @@
"unicode"
"unicode/utf8"
+ "github.com/jdkato/prose/transform"
+
bp "github.com/gohugoio/hugo/bufferpool"
"github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman"
@@ -192,6 +194,29 @@
}
}
return false
+}
+
+// GetTitleFunc returns a func that can be used to transform a string to
+// title case.
+//
+// The supported styles are
+//
+// - "Go" (strings.Title)
+// - "AP" (see https://www.apstylebook.com/)
+// - "Chicago" (see http://www.chicagomanualofstyle.org/home.html)
+//
+// If an unknown or empty style is provided, AP style is what you get.
+func GetTitleFunc(style string) func(s string) string {
+ switch strings.ToLower(style) {
+ case "go":
+ return strings.Title
+ case "chicago":
+ tc := transform.NewTitleConverter(transform.ChicagoStyle)
+ return tc.Title
+ default:
+ tc := transform.NewTitleConverter(transform.APStyle)
+ return tc.Title
+ }
}
// HasStringsPrefix tests whether the string slice s begins with prefix slice s.
--- a/helpers/general_test.go
+++ b/helpers/general_test.go
@@ -19,6 +19,7 @@
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestGuessType(t *testing.T) {
@@ -171,6 +172,20 @@
assert.False(t, ReaderContains(nil, []byte("a")))
assert.False(t, ReaderContains(nil, nil))
+}
+
+func TestGetTitleFunc(t *testing.T) {
+ title := "somewhere over the rainbow"
+ assert := require.New(t)
+
+ assert.Equal("Somewhere Over The Rainbow", GetTitleFunc("go")(title))
+ assert.Equal("Somewhere over the Rainbow", GetTitleFunc("chicago")(title), "Chicago style")
+ assert.Equal("Somewhere over the Rainbow", GetTitleFunc("Chicago")(title), "Chicago style")
+ assert.Equal("Somewhere Over the Rainbow", GetTitleFunc("ap")(title), "AP style")
+ assert.Equal("Somewhere Over the Rainbow", GetTitleFunc("ap")(title), "AP style")
+ assert.Equal("Somewhere Over the Rainbow", GetTitleFunc("")(title), "AP style")
+ assert.Equal("Somewhere Over the Rainbow", GetTitleFunc("unknown")(title), "AP style")
+
}
func BenchmarkReaderContains(b *testing.B) {
--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -101,6 +101,7 @@
v.SetDefault("canonifyURLs", false)
v.SetDefault("relativeURLs", false)
v.SetDefault("removePathAccents", false)
+ v.SetDefault("titleCaseStyle", "AP")
v.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
v.SetDefault("permalinks", make(PermalinkOverrides, 0))
v.SetDefault("sitemap", Sitemap{Priority: -1, Filename: "sitemap.xml"})
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -132,6 +132,9 @@
// Logger etc.
*deps.Deps `json:"-"`
+ // The func used to title case titles.
+ titleFunc func(s string) string
+
siteStats *siteStats
}
@@ -172,6 +175,7 @@
return &Site{Deps: s.Deps,
layoutHandler: output.NewLayoutHandler(s.PathSpec.ThemeSet()),
disabledKinds: s.disabledKinds,
+ titleFunc: s.titleFunc,
outputFormats: s.outputFormats,
outputFormatsConfig: s.outputFormatsConfig,
mediaTypesConfig: s.mediaTypesConfig,
@@ -227,11 +231,14 @@
return nil, err
}
+ titleFunc := helpers.GetTitleFunc(cfg.Language.GetString("titleCaseStyle"))
+
s := &Site{
PageCollections: c,
layoutHandler: output.NewLayoutHandler(cfg.Cfg.GetString("themesDir") != ""),
Language: cfg.Language,
disabledKinds: disabledKinds,
+ titleFunc: titleFunc,
outputFormats: outputFormats,
outputFormatsConfig: siteOutputFormatsConfig,
mediaTypesConfig: siteMediaTypesConfig,
@@ -2121,7 +2128,7 @@
p.Title = helpers.FirstUpper(key)
key = s.PathSpec.MakePathSanitized(key)
} else {
- p.Title = strings.Replace(strings.Title(key), "-", " ", -1)
+ p.Title = strings.Replace(s.titleFunc(key), "-", " ", -1)
}
return p
@@ -2141,6 +2148,6 @@
func (s *Site) newTaxonomyTermsPage(plural string) *Page {
p := s.newNodePage(KindTaxonomyTerm, plural)
- p.Title = strings.Title(plural)
+ p.Title = s.titleFunc(plural)
return p
}
--- a/tpl/strings/init.go
+++ b/tpl/strings/init.go
@@ -116,6 +116,7 @@
[]string{"title"},
[][2]string{
{`{{title "Bat man"}}`, `Bat Man`},
+ {`{{title "somewhere over the rainbow"}}`, `Somewhere Over the Rainbow`},
},
)
--- a/tpl/strings/init_test.go
+++ b/tpl/strings/init_test.go
@@ -18,6 +18,7 @@
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
+ "github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
@@ -26,7 +27,7 @@
var ns *internal.TemplateFuncsNamespace
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
- ns = nsf(&deps.Deps{})
+ ns = nsf(&deps.Deps{Cfg: viper.New()})
if ns.Name == name {
found = true
break
--- a/tpl/strings/strings.go
+++ b/tpl/strings/strings.go
@@ -27,7 +27,9 @@
// New returns a new instance of the strings-namespaced template functions.
func New(d *deps.Deps) *Namespace {
- return &Namespace{deps: d}
+ titleCaseStyle := d.Cfg.GetString("titleCaseStyle")
+ titleFunc := helpers.GetTitleFunc(titleCaseStyle)
+ return &Namespace{deps: d, titleFunc: titleFunc}
}
// Namespace provides template functions for the "strings" namespace.
@@ -34,7 +36,8 @@
// Most functions mimic the Go stdlib, but the order of the parameters may be
// different to ease their use in the Go template system.
type Namespace struct {
- deps *deps.Deps
+ titleFunc func(s string) string
+ deps *deps.Deps
}
// CountRunes returns the number of runes in s, excluding whitepace.
@@ -303,7 +306,7 @@
return "", err
}
- return _strings.Title(ss), nil
+ return ns.titleFunc(ss), nil
}
// ToLower returns a copy of the input s with all Unicode letters mapped to their
--- a/tpl/strings/strings_test.go
+++ b/tpl/strings/strings_test.go
@@ -19,11 +19,12 @@
"testing"
"github.com/gohugoio/hugo/deps"
+ "github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
-var ns = New(&deps.Deps{})
+var ns = New(&deps.Deps{Cfg: viper.New()})
type tstNoStringer struct{}
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -160,6 +160,18 @@
"revisionTime": "2014-10-17T20:07:13Z"
},
{
+ "checksumSHA1": "ywE9KA40kVq0qKcAIqLgpoA0su4=",
+ "path": "github.com/jdkato/prose/internal/util",
+ "revision": "c24611cae00c16858e611ef77226dd2f7502759f",
+ "revisionTime": "2017-07-29T20:17:14Z"
+ },
+ {
+ "checksumSHA1": "SpQ8EpkRvM9fAxEXQAy7Qy/L0Ig=",
+ "path": "github.com/jdkato/prose/transform",
+ "revision": "c24611cae00c16858e611ef77226dd2f7502759f",
+ "revisionTime": "2017-07-29T20:17:14Z"
+ },
+ {
"checksumSHA1": "gEjGS03N1eysvpQ+FCHTxPcbxXc=",
"path": "github.com/kardianos/osext",
"revision": "ae77be60afb1dcacde03767a8c37337fad28ac14",