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: ""
|
||||
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
|
||||
|
|
|
@ -26,6 +26,8 @@ import (
|
|||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/jdkato/prose/transform"
|
||||
|
||||
bp "github.com/gohugoio/hugo/bufferpool"
|
||||
"github.com/spf13/cast"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
|
@ -194,6 +196,29 @@ func ReaderContains(r io.Reader, subslice []byte) bool {
|
|||
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.
|
||||
func HasStringsPrefix(s, prefix []string) bool {
|
||||
return len(s) >= len(prefix) && compareStringSlices(s[0:len(prefix)], prefix)
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGuessType(t *testing.T) {
|
||||
|
@ -173,6 +174,20 @@ func TestReaderContains(t *testing.T) {
|
|||
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) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
|
|
@ -101,6 +101,7 @@ func loadDefaultSettingsFor(v *viper.Viper) {
|
|||
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"})
|
||||
|
|
|
@ -132,6 +132,9 @@ type Site struct {
|
|||
// Logger etc.
|
||||
*deps.Deps `json:"-"`
|
||||
|
||||
// The func used to title case titles.
|
||||
titleFunc func(s string) string
|
||||
|
||||
siteStats *siteStats
|
||||
}
|
||||
|
||||
|
@ -172,6 +175,7 @@ func (s *Site) reset() *Site {
|
|||
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 @@ func newSite(cfg deps.DepsCfg) (*Site, error) {
|
|||
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 @@ func (s *Site) newTaxonomyPage(plural, key string) *Page {
|
|||
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) newSectionPage(name string) *Page {
|
|||
|
||||
func (s *Site) newTaxonomyTermsPage(plural string) *Page {
|
||||
p := s.newNodePage(KindTaxonomyTerm, plural)
|
||||
p.Title = strings.Title(plural)
|
||||
p.Title = s.titleFunc(plural)
|
||||
return p
|
||||
}
|
||||
|
|
|
@ -116,6 +116,7 @@ func init() {
|
|||
[]string{"title"},
|
||||
[][2]string{
|
||||
{`{{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/tpl/internal"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -26,7 +27,7 @@ func TestInit(t *testing.T) {
|
|||
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
|
||||
|
|
|
@ -27,13 +27,16 @@ import (
|
|||
|
||||
// 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.
|
||||
// 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 {
|
||||
titleFunc func(s string) string
|
||||
deps *deps.Deps
|
||||
}
|
||||
|
||||
|
@ -303,7 +306,7 @@ func (ns *Namespace) Title(s interface{}) (string, error) {
|
|||
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
|
||||
|
|
|
@ -19,11 +19,12 @@ import (
|
|||
"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{}
|
||||
|
||||
|
|
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
|
@ -159,6 +159,18 @@
|
|||
"revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75",
|
||||
"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",
|
||||
|
|
Loading…
Reference in a new issue