mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
Change SummaryLength to be configurable (#3924)
Move SummaryLength into the ContentSpec struct and refactor the relevant summary functions to be methods of ContentSpec. The new summaryLength struct member is configurable by the summaryLength config value, and the default remains 70. Also updates hugolib/page to use the refactored methods. Resolves #3734
This commit is contained in:
parent
2818878994
commit
8717a60cc0
5 changed files with 32 additions and 21 deletions
|
@ -111,6 +111,8 @@ googleAnalytics: ""
|
||||||
# if true, auto-detect Chinese/Japanese/Korean Languages in the content. (.Summary and .WordCount can work properly in CJKLanguage)
|
# if true, auto-detect Chinese/Japanese/Korean Languages in the content. (.Summary and .WordCount can work properly in CJKLanguage)
|
||||||
hasCJKLanguage: false
|
hasCJKLanguage: false
|
||||||
languageCode: ""
|
languageCode: ""
|
||||||
|
# the length of text to show in a .Summary
|
||||||
|
summaryLength: 70
|
||||||
layoutDir: "layouts"
|
layoutDir: "layouts"
|
||||||
# Enable Logging
|
# Enable Logging
|
||||||
log: false
|
log: false
|
||||||
|
@ -252,6 +254,8 @@ googleAnalytics = ""
|
||||||
# if true, auto-detect Chinese/Japanese/Korean Languages in the content. (.Summary and .WordCount can work properly in CJKLanguage)
|
# if true, auto-detect Chinese/Japanese/Korean Languages in the content. (.Summary and .WordCount can work properly in CJKLanguage)
|
||||||
hasCJKLanguage = false
|
hasCJKLanguage = false
|
||||||
languageCode = ""
|
languageCode = ""
|
||||||
|
# the length of text to show in a .Summary
|
||||||
|
summaryLength: 70
|
||||||
layoutDir = "layouts"
|
layoutDir = "layouts"
|
||||||
# Enable Logging
|
# Enable Logging
|
||||||
log = false
|
log = false
|
||||||
|
|
|
@ -36,9 +36,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SummaryLength is the length of the summary that Hugo extracts from a content.
|
|
||||||
var SummaryLength = 70
|
|
||||||
|
|
||||||
// SummaryDivider denotes where content summarization should end. The default is "<!--more-->".
|
// SummaryDivider denotes where content summarization should end. The default is "<!--more-->".
|
||||||
var SummaryDivider = []byte("<!--more-->")
|
var SummaryDivider = []byte("<!--more-->")
|
||||||
|
|
||||||
|
@ -47,6 +44,8 @@ type ContentSpec struct {
|
||||||
blackfriday map[string]interface{}
|
blackfriday map[string]interface{}
|
||||||
footnoteAnchorPrefix string
|
footnoteAnchorPrefix string
|
||||||
footnoteReturnLinkContents string
|
footnoteReturnLinkContents string
|
||||||
|
// SummaryLength is the length of the summary that Hugo extracts from a content.
|
||||||
|
summaryLength int
|
||||||
|
|
||||||
Highlight func(code, lang, optsStr string) (string, error)
|
Highlight func(code, lang, optsStr string) (string, error)
|
||||||
defatultPygmentsOpts map[string]string
|
defatultPygmentsOpts map[string]string
|
||||||
|
@ -61,6 +60,7 @@ func NewContentSpec(cfg config.Provider) (*ContentSpec, error) {
|
||||||
blackfriday: cfg.GetStringMap("blackfriday"),
|
blackfriday: cfg.GetStringMap("blackfriday"),
|
||||||
footnoteAnchorPrefix: cfg.GetString("footnoteAnchorPrefix"),
|
footnoteAnchorPrefix: cfg.GetString("footnoteAnchorPrefix"),
|
||||||
footnoteReturnLinkContents: cfg.GetString("footnoteReturnLinkContents"),
|
footnoteReturnLinkContents: cfg.GetString("footnoteReturnLinkContents"),
|
||||||
|
summaryLength: cfg.GetInt("summaryLength"),
|
||||||
|
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
@ -480,20 +480,20 @@ func totalWordsOld(s string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TruncateWordsByRune truncates words by runes.
|
// TruncateWordsByRune truncates words by runes.
|
||||||
func TruncateWordsByRune(words []string, max int) (string, bool) {
|
func (c *ContentSpec) TruncateWordsByRune(words []string) (string, bool) {
|
||||||
count := 0
|
count := 0
|
||||||
for index, word := range words {
|
for index, word := range words {
|
||||||
if count >= max {
|
if count >= c.summaryLength {
|
||||||
return strings.Join(words[:index], " "), true
|
return strings.Join(words[:index], " "), true
|
||||||
}
|
}
|
||||||
runeCount := utf8.RuneCountInString(word)
|
runeCount := utf8.RuneCountInString(word)
|
||||||
if len(word) == runeCount {
|
if len(word) == runeCount {
|
||||||
count++
|
count++
|
||||||
} else if count+runeCount < max {
|
} else if count+runeCount < c.summaryLength {
|
||||||
count += runeCount
|
count += runeCount
|
||||||
} else {
|
} else {
|
||||||
for ri := range word {
|
for ri := range word {
|
||||||
if count >= max {
|
if count >= c.summaryLength {
|
||||||
truncatedWords := append(words[:index], word[:ri])
|
truncatedWords := append(words[:index], word[:ri])
|
||||||
return strings.Join(truncatedWords, " "), true
|
return strings.Join(truncatedWords, " "), true
|
||||||
}
|
}
|
||||||
|
@ -507,8 +507,7 @@ func TruncateWordsByRune(words []string, max int) (string, bool) {
|
||||||
|
|
||||||
// TruncateWordsToWholeSentence takes content and truncates to whole sentence
|
// TruncateWordsToWholeSentence takes content and truncates to whole sentence
|
||||||
// limited by max number of words. It also returns whether it is truncated.
|
// limited by max number of words. It also returns whether it is truncated.
|
||||||
func TruncateWordsToWholeSentence(s string, max int) (string, bool) {
|
func (c *ContentSpec) TruncateWordsToWholeSentence(s string) (string, bool) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
wordCount = 0
|
wordCount = 0
|
||||||
lastWordIndex = -1
|
lastWordIndex = -1
|
||||||
|
@ -519,7 +518,7 @@ func TruncateWordsToWholeSentence(s string, max int) (string, bool) {
|
||||||
wordCount++
|
wordCount++
|
||||||
lastWordIndex = i
|
lastWordIndex = i
|
||||||
|
|
||||||
if wordCount >= max {
|
if wordCount >= c.summaryLength {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,24 +550,24 @@ func isEndOfSentence(r rune) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kept only for benchmark.
|
// Kept only for benchmark.
|
||||||
func truncateWordsToWholeSentenceOld(content string, max int) (string, bool) {
|
func (c *ContentSpec) truncateWordsToWholeSentenceOld(content string) (string, bool) {
|
||||||
words := strings.Fields(content)
|
words := strings.Fields(content)
|
||||||
|
|
||||||
if max >= len(words) {
|
if c.summaryLength >= len(words) {
|
||||||
return strings.Join(words, " "), false
|
return strings.Join(words, " "), false
|
||||||
}
|
}
|
||||||
|
|
||||||
for counter, word := range words[max:] {
|
for counter, word := range words[c.summaryLength:] {
|
||||||
if strings.HasSuffix(word, ".") ||
|
if strings.HasSuffix(word, ".") ||
|
||||||
strings.HasSuffix(word, "?") ||
|
strings.HasSuffix(word, "?") ||
|
||||||
strings.HasSuffix(word, ".\"") ||
|
strings.HasSuffix(word, ".\"") ||
|
||||||
strings.HasSuffix(word, "!") {
|
strings.HasSuffix(word, "!") {
|
||||||
upper := max + counter + 1
|
upper := c.summaryLength + counter + 1
|
||||||
return strings.Join(words[:upper], " "), (upper < len(words))
|
return strings.Join(words[:upper], " "), (upper < len(words))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(words[:max], " "), true
|
return strings.Join(words[:c.summaryLength], " "), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAsciidocExecPath() string {
|
func getAsciidocExecPath() string {
|
||||||
|
|
|
@ -76,20 +76,23 @@ func TestBytesToHTML(t *testing.T) {
|
||||||
var benchmarkTruncateString = strings.Repeat("This is a sentence about nothing.", 20)
|
var benchmarkTruncateString = strings.Repeat("This is a sentence about nothing.", 20)
|
||||||
|
|
||||||
func BenchmarkTestTruncateWordsToWholeSentence(b *testing.B) {
|
func BenchmarkTestTruncateWordsToWholeSentence(b *testing.B) {
|
||||||
|
c := newTestContentSpec()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
TruncateWordsToWholeSentence(benchmarkTruncateString, SummaryLength)
|
c.TruncateWordsToWholeSentence(benchmarkTruncateString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTestTruncateWordsToWholeSentenceOld(b *testing.B) {
|
func BenchmarkTestTruncateWordsToWholeSentenceOld(b *testing.B) {
|
||||||
|
c := newTestContentSpec()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
truncateWordsToWholeSentenceOld(benchmarkTruncateString, SummaryLength)
|
c.truncateWordsToWholeSentenceOld(benchmarkTruncateString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTruncateWordsToWholeSentence(t *testing.T) {
|
func TestTruncateWordsToWholeSentence(t *testing.T) {
|
||||||
|
c := newTestContentSpec()
|
||||||
type test struct {
|
type test struct {
|
||||||
input, expected string
|
input, expected string
|
||||||
max int
|
max int
|
||||||
|
@ -104,9 +107,11 @@ func TestTruncateWordsToWholeSentence(t *testing.T) {
|
||||||
{"To be. Or not to be. That's the question.", "To be.", 1, true},
|
{"To be. Or not to be. That's the question.", "To be.", 1, true},
|
||||||
{" \nThis is not a sentence\nAnd this is another", "This is not a sentence", 4, true},
|
{" \nThis is not a sentence\nAnd this is another", "This is not a sentence", 4, true},
|
||||||
{"", "", 10, false},
|
{"", "", 10, false},
|
||||||
|
{"This... is a more difficult test?", "This... is a more difficult test?", 1, false},
|
||||||
}
|
}
|
||||||
for i, d := range data {
|
for i, d := range data {
|
||||||
output, truncated := TruncateWordsToWholeSentence(d.input, d.max)
|
c.summaryLength = d.max
|
||||||
|
output, truncated := c.TruncateWordsToWholeSentence(d.input)
|
||||||
if d.expected != output {
|
if d.expected != output {
|
||||||
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
||||||
}
|
}
|
||||||
|
@ -118,6 +123,7 @@ func TestTruncateWordsToWholeSentence(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTruncateWordsByRune(t *testing.T) {
|
func TestTruncateWordsByRune(t *testing.T) {
|
||||||
|
c := newTestContentSpec()
|
||||||
type test struct {
|
type test struct {
|
||||||
input, expected string
|
input, expected string
|
||||||
max int
|
max int
|
||||||
|
@ -139,7 +145,8 @@ func TestTruncateWordsByRune(t *testing.T) {
|
||||||
{" \nThis is not a sentence\n ", "This is not", 3, true},
|
{" \nThis is not a sentence\n ", "This is not", 3, true},
|
||||||
}
|
}
|
||||||
for i, d := range data {
|
for i, d := range data {
|
||||||
output, truncated := TruncateWordsByRune(strings.Fields(d.input), d.max)
|
c.summaryLength = d.max
|
||||||
|
output, truncated := c.TruncateWordsByRune(strings.Fields(d.input))
|
||||||
if d.expected != output {
|
if d.expected != output {
|
||||||
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,7 @@ func loadDefaultSettingsFor(v *viper.Viper) error {
|
||||||
v.SetDefault("newContentEditor", "")
|
v.SetDefault("newContentEditor", "")
|
||||||
v.SetDefault("paginate", 10)
|
v.SetDefault("paginate", 10)
|
||||||
v.SetDefault("paginatePath", "page")
|
v.SetDefault("paginatePath", "page")
|
||||||
|
v.SetDefault("summaryLength", 70)
|
||||||
v.SetDefault("blackfriday", c.NewBlackfriday())
|
v.SetDefault("blackfriday", c.NewBlackfriday())
|
||||||
v.SetDefault("rSSUri", "index.xml")
|
v.SetDefault("rSSUri", "index.xml")
|
||||||
v.SetDefault("rssLimit", -1)
|
v.SetDefault("rssLimit", -1)
|
||||||
|
|
|
@ -677,9 +677,9 @@ func (p *Page) setAutoSummary() error {
|
||||||
var summary string
|
var summary string
|
||||||
var truncated bool
|
var truncated bool
|
||||||
if p.isCJKLanguage {
|
if p.isCJKLanguage {
|
||||||
summary, truncated = helpers.TruncateWordsByRune(p.PlainWords(), helpers.SummaryLength)
|
summary, truncated = p.s.ContentSpec.TruncateWordsByRune(p.PlainWords())
|
||||||
} else {
|
} else {
|
||||||
summary, truncated = helpers.TruncateWordsToWholeSentence(p.Plain(), helpers.SummaryLength)
|
summary, truncated = p.s.ContentSpec.TruncateWordsToWholeSentence(p.Plain())
|
||||||
}
|
}
|
||||||
p.Summary = template.HTML(summary)
|
p.Summary = template.HTML(summary)
|
||||||
p.Truncated = truncated
|
p.Truncated = truncated
|
||||||
|
|
Loading…
Reference in a new issue