mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
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
This commit is contained in:
parent
9b4170ce76
commit
8fb594bfb0
10 changed files with 77 additions and 7 deletions
|
@ -156,6 +156,10 @@ themesDir: "themes"
|
||||||
theme: ""
|
theme: ""
|
||||||
title: ""
|
title: ""
|
||||||
# if true, use /filename.html instead of /filename/
|
# 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
|
uglyURLs: false
|
||||||
# verbose output
|
# verbose output
|
||||||
verbose: false
|
verbose: false
|
||||||
|
|
|
@ -26,6 +26,8 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/jdkato/prose/transform"
|
||||||
|
|
||||||
bp "github.com/gohugoio/hugo/bufferpool"
|
bp "github.com/gohugoio/hugo/bufferpool"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
jww "github.com/spf13/jwalterweatherman"
|
jww "github.com/spf13/jwalterweatherman"
|
||||||
|
@ -194,6 +196,29 @@ func ReaderContains(r io.Reader, subslice []byte) bool {
|
||||||
return false
|
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.
|
// HasStringsPrefix tests whether the string slice s begins with prefix slice s.
|
||||||
func HasStringsPrefix(s, prefix []string) bool {
|
func HasStringsPrefix(s, prefix []string) bool {
|
||||||
return len(s) >= len(prefix) && compareStringSlices(s[0:len(prefix)], prefix)
|
return len(s) >= len(prefix) && compareStringSlices(s[0:len(prefix)], prefix)
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGuessType(t *testing.T) {
|
func TestGuessType(t *testing.T) {
|
||||||
|
@ -173,6 +174,20 @@ func TestReaderContains(t *testing.T) {
|
||||||
assert.False(t, ReaderContains(nil, nil))
|
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) {
|
func BenchmarkReaderContains(b *testing.B) {
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|
|
@ -101,6 +101,7 @@ func loadDefaultSettingsFor(v *viper.Viper) {
|
||||||
v.SetDefault("canonifyURLs", false)
|
v.SetDefault("canonifyURLs", false)
|
||||||
v.SetDefault("relativeURLs", false)
|
v.SetDefault("relativeURLs", false)
|
||||||
v.SetDefault("removePathAccents", false)
|
v.SetDefault("removePathAccents", false)
|
||||||
|
v.SetDefault("titleCaseStyle", "AP")
|
||||||
v.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
|
v.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
|
||||||
v.SetDefault("permalinks", make(PermalinkOverrides, 0))
|
v.SetDefault("permalinks", make(PermalinkOverrides, 0))
|
||||||
v.SetDefault("sitemap", Sitemap{Priority: -1, Filename: "sitemap.xml"})
|
v.SetDefault("sitemap", Sitemap{Priority: -1, Filename: "sitemap.xml"})
|
||||||
|
|
|
@ -132,6 +132,9 @@ type Site struct {
|
||||||
// Logger etc.
|
// Logger etc.
|
||||||
*deps.Deps `json:"-"`
|
*deps.Deps `json:"-"`
|
||||||
|
|
||||||
|
// The func used to title case titles.
|
||||||
|
titleFunc func(s string) string
|
||||||
|
|
||||||
siteStats *siteStats
|
siteStats *siteStats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,6 +175,7 @@ func (s *Site) reset() *Site {
|
||||||
return &Site{Deps: s.Deps,
|
return &Site{Deps: s.Deps,
|
||||||
layoutHandler: output.NewLayoutHandler(s.PathSpec.ThemeSet()),
|
layoutHandler: output.NewLayoutHandler(s.PathSpec.ThemeSet()),
|
||||||
disabledKinds: s.disabledKinds,
|
disabledKinds: s.disabledKinds,
|
||||||
|
titleFunc: s.titleFunc,
|
||||||
outputFormats: s.outputFormats,
|
outputFormats: s.outputFormats,
|
||||||
outputFormatsConfig: s.outputFormatsConfig,
|
outputFormatsConfig: s.outputFormatsConfig,
|
||||||
mediaTypesConfig: s.mediaTypesConfig,
|
mediaTypesConfig: s.mediaTypesConfig,
|
||||||
|
@ -227,11 +231,14 @@ func newSite(cfg deps.DepsCfg) (*Site, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
titleFunc := helpers.GetTitleFunc(cfg.Language.GetString("titleCaseStyle"))
|
||||||
|
|
||||||
s := &Site{
|
s := &Site{
|
||||||
PageCollections: c,
|
PageCollections: c,
|
||||||
layoutHandler: output.NewLayoutHandler(cfg.Cfg.GetString("themesDir") != ""),
|
layoutHandler: output.NewLayoutHandler(cfg.Cfg.GetString("themesDir") != ""),
|
||||||
Language: cfg.Language,
|
Language: cfg.Language,
|
||||||
disabledKinds: disabledKinds,
|
disabledKinds: disabledKinds,
|
||||||
|
titleFunc: titleFunc,
|
||||||
outputFormats: outputFormats,
|
outputFormats: outputFormats,
|
||||||
outputFormatsConfig: siteOutputFormatsConfig,
|
outputFormatsConfig: siteOutputFormatsConfig,
|
||||||
mediaTypesConfig: siteMediaTypesConfig,
|
mediaTypesConfig: siteMediaTypesConfig,
|
||||||
|
@ -2121,7 +2128,7 @@ func (s *Site) newTaxonomyPage(plural, key string) *Page {
|
||||||
p.Title = helpers.FirstUpper(key)
|
p.Title = helpers.FirstUpper(key)
|
||||||
key = s.PathSpec.MakePathSanitized(key)
|
key = s.PathSpec.MakePathSanitized(key)
|
||||||
} else {
|
} else {
|
||||||
p.Title = strings.Replace(strings.Title(key), "-", " ", -1)
|
p.Title = strings.Replace(s.titleFunc(key), "-", " ", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
@ -2141,6 +2148,6 @@ func (s *Site) newSectionPage(name string) *Page {
|
||||||
|
|
||||||
func (s *Site) newTaxonomyTermsPage(plural string) *Page {
|
func (s *Site) newTaxonomyTermsPage(plural string) *Page {
|
||||||
p := s.newNodePage(KindTaxonomyTerm, plural)
|
p := s.newNodePage(KindTaxonomyTerm, plural)
|
||||||
p.Title = strings.Title(plural)
|
p.Title = s.titleFunc(plural)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,7 @@ func init() {
|
||||||
[]string{"title"},
|
[]string{"title"},
|
||||||
[][2]string{
|
[][2]string{
|
||||||
{`{{title "Bat man"}}`, `Bat Man`},
|
{`{{title "Bat man"}}`, `Bat Man`},
|
||||||
|
{`{{title "somewhere over the rainbow"}}`, `Somewhere Over the Rainbow`},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
"github.com/gohugoio/hugo/deps"
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
"github.com/gohugoio/hugo/tpl/internal"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ func TestInit(t *testing.T) {
|
||||||
var ns *internal.TemplateFuncsNamespace
|
var ns *internal.TemplateFuncsNamespace
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
||||||
ns = nsf(&deps.Deps{})
|
ns = nsf(&deps.Deps{Cfg: viper.New()})
|
||||||
if ns.Name == name {
|
if ns.Name == name {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
|
|
|
@ -27,13 +27,16 @@ import (
|
||||||
|
|
||||||
// New returns a new instance of the strings-namespaced template functions.
|
// New returns a new instance of the strings-namespaced template functions.
|
||||||
func New(d *deps.Deps) *Namespace {
|
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.
|
// Namespace provides template functions for the "strings" namespace.
|
||||||
// Most functions mimic the Go stdlib, but the order of the parameters may be
|
// Most functions mimic the Go stdlib, but the order of the parameters may be
|
||||||
// different to ease their use in the Go template system.
|
// different to ease their use in the Go template system.
|
||||||
type Namespace struct {
|
type Namespace struct {
|
||||||
|
titleFunc func(s string) string
|
||||||
deps *deps.Deps
|
deps *deps.Deps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +306,7 @@ func (ns *Namespace) Title(s interface{}) (string, error) {
|
||||||
return "", err
|
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
|
// ToLower returns a copy of the input s with all Unicode letters mapped to their
|
||||||
|
|
|
@ -19,11 +19,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
"github.com/gohugoio/hugo/deps"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ns = New(&deps.Deps{})
|
var ns = New(&deps.Deps{Cfg: viper.New()})
|
||||||
|
|
||||||
type tstNoStringer struct{}
|
type tstNoStringer struct{}
|
||||||
|
|
||||||
|
|
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
|
@ -159,6 +159,18 @@
|
||||||
"revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75",
|
"revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75",
|
||||||
"revisionTime": "2014-10-17T20:07:13Z"
|
"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=",
|
"checksumSHA1": "gEjGS03N1eysvpQ+FCHTxPcbxXc=",
|
||||||
"path": "github.com/kardianos/osext",
|
"path": "github.com/kardianos/osext",
|
||||||
|
|
Loading…
Reference in a new issue