From 8717a60cc030f4310c1779c0cdd51db37ad636cd Mon Sep 17 00:00:00 2001 From: Brendan Roy Date: Fri, 29 Sep 2017 17:04:55 +1000 Subject: [PATCH] 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 --- docs/content/getting-started/configuration.md | 4 +++ helpers/content.go | 29 +++++++++---------- helpers/content_test.go | 15 +++++++--- hugolib/config.go | 1 + hugolib/page.go | 4 +-- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/docs/content/getting-started/configuration.md b/docs/content/getting-started/configuration.md index b81d878b3..2dafdb52b 100644 --- a/docs/content/getting-started/configuration.md +++ b/docs/content/getting-started/configuration.md @@ -111,6 +111,8 @@ googleAnalytics: "" # if true, auto-detect Chinese/Japanese/Korean Languages in the content. (.Summary and .WordCount can work properly in CJKLanguage) hasCJKLanguage: false languageCode: "" +# the length of text to show in a .Summary +summaryLength: 70 layoutDir: "layouts" # Enable Logging 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) hasCJKLanguage = false languageCode = "" +# the length of text to show in a .Summary +summaryLength: 70 layoutDir = "layouts" # Enable Logging log = false diff --git a/helpers/content.go b/helpers/content.go index 7f5975869..a79da090b 100644 --- a/helpers/content.go +++ b/helpers/content.go @@ -36,9 +36,6 @@ import ( "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 "". var SummaryDivider = []byte("") @@ -47,6 +44,8 @@ type ContentSpec struct { blackfriday map[string]interface{} footnoteAnchorPrefix 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) defatultPygmentsOpts map[string]string @@ -61,6 +60,7 @@ func NewContentSpec(cfg config.Provider) (*ContentSpec, error) { blackfriday: cfg.GetStringMap("blackfriday"), footnoteAnchorPrefix: cfg.GetString("footnoteAnchorPrefix"), footnoteReturnLinkContents: cfg.GetString("footnoteReturnLinkContents"), + summaryLength: cfg.GetInt("summaryLength"), cfg: cfg, } @@ -480,20 +480,20 @@ func totalWordsOld(s string) int { } // TruncateWordsByRune truncates words by runes. -func TruncateWordsByRune(words []string, max int) (string, bool) { +func (c *ContentSpec) TruncateWordsByRune(words []string) (string, bool) { count := 0 for index, word := range words { - if count >= max { + if count >= c.summaryLength { return strings.Join(words[:index], " "), true } runeCount := utf8.RuneCountInString(word) if len(word) == runeCount { count++ - } else if count+runeCount < max { + } else if count+runeCount < c.summaryLength { count += runeCount } else { for ri := range word { - if count >= max { + if count >= c.summaryLength { truncatedWords := append(words[:index], word[:ri]) 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 // 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 ( wordCount = 0 lastWordIndex = -1 @@ -519,7 +518,7 @@ func TruncateWordsToWholeSentence(s string, max int) (string, bool) { wordCount++ lastWordIndex = i - if wordCount >= max { + if wordCount >= c.summaryLength { break } @@ -551,24 +550,24 @@ func isEndOfSentence(r rune) bool { } // Kept only for benchmark. -func truncateWordsToWholeSentenceOld(content string, max int) (string, bool) { +func (c *ContentSpec) truncateWordsToWholeSentenceOld(content string) (string, bool) { words := strings.Fields(content) - if max >= len(words) { + if c.summaryLength >= len(words) { return strings.Join(words, " "), false } - for counter, word := range words[max:] { + for counter, word := range words[c.summaryLength:] { if 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[:max], " "), true + return strings.Join(words[:c.summaryLength], " "), true } func getAsciidocExecPath() string { diff --git a/helpers/content_test.go b/helpers/content_test.go index b0afb9cbd..8f2d44cd9 100644 --- a/helpers/content_test.go +++ b/helpers/content_test.go @@ -76,20 +76,23 @@ func TestBytesToHTML(t *testing.T) { var benchmarkTruncateString = strings.Repeat("This is a sentence about nothing.", 20) func BenchmarkTestTruncateWordsToWholeSentence(b *testing.B) { + c := newTestContentSpec() b.ResetTimer() for i := 0; i < b.N; i++ { - TruncateWordsToWholeSentence(benchmarkTruncateString, SummaryLength) + c.TruncateWordsToWholeSentence(benchmarkTruncateString) } } func BenchmarkTestTruncateWordsToWholeSentenceOld(b *testing.B) { + c := newTestContentSpec() b.ResetTimer() for i := 0; i < b.N; i++ { - truncateWordsToWholeSentenceOld(benchmarkTruncateString, SummaryLength) + c.truncateWordsToWholeSentenceOld(benchmarkTruncateString) } } func TestTruncateWordsToWholeSentence(t *testing.T) { + c := newTestContentSpec() type test struct { input, expected string 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}, {" \nThis is not a sentence\nAnd this is another", "This is not a sentence", 4, true}, {"", "", 10, false}, + {"This... is a more difficult test?", "This... is a more difficult test?", 1, false}, } 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 { 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) { + c := newTestContentSpec() type test struct { input, expected string max int @@ -139,7 +145,8 @@ func TestTruncateWordsByRune(t *testing.T) { {" \nThis is not a sentence\n ", "This is not", 3, true}, } 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 { t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output) } diff --git a/hugolib/config.go b/hugolib/config.go index 2406ba771..d0ade018f 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -135,6 +135,7 @@ func loadDefaultSettingsFor(v *viper.Viper) error { v.SetDefault("newContentEditor", "") v.SetDefault("paginate", 10) v.SetDefault("paginatePath", "page") + v.SetDefault("summaryLength", 70) v.SetDefault("blackfriday", c.NewBlackfriday()) v.SetDefault("rSSUri", "index.xml") v.SetDefault("rssLimit", -1) diff --git a/hugolib/page.go b/hugolib/page.go index d5c444ed6..12bdf312b 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -677,9 +677,9 @@ func (p *Page) setAutoSummary() error { var summary string var truncated bool if p.isCJKLanguage { - summary, truncated = helpers.TruncateWordsByRune(p.PlainWords(), helpers.SummaryLength) + summary, truncated = p.s.ContentSpec.TruncateWordsByRune(p.PlainWords()) } else { - summary, truncated = helpers.TruncateWordsToWholeSentence(p.Plain(), helpers.SummaryLength) + summary, truncated = p.s.ContentSpec.TruncateWordsToWholeSentence(p.Plain()) } p.Summary = template.HTML(summary) p.Truncated = truncated