Permalink
Browse files

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
  • Loading branch information...
bep committed Jul 30, 2017
1 parent 9b4170c commit 8fb594bfb090c017d4e5cbb2905780221e202c41
@@ -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
View
@@ -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)
View
@@ -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++ {
View
@@ -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"})
View
@@ -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
}
View
@@ -116,6 +116,7 @@ func init() {
[]string{"title"},
[][2]string{
{`{{title "Bat man"}}`, `Bat Man`},
{`{{title "somewhere over the rainbow"}}`, `Somewhere Over the Rainbow`},
},
)
View
@@ -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
View
@@ -27,14 +27,17 @@ 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 {
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 @@ 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{}
View
@@ -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",

0 comments on commit 8fb594b

Please sign in to comment.