Change SummaryLength to be configurable (#3924)
authorBrendan Roy <br3ndanr@gmail.com>
Fri, 29 Sep 2017 07:04:55 +0000 (17:04 +1000)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 29 Sep 2017 07:04:55 +0000 (09:04 +0200)
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
helpers/content.go
helpers/content_test.go
hugolib/config.go
hugolib/page.go

index b81d878b356d4207a4a32550d8b94133c47bc575..2dafdb52b43518dc741aa00293135504cdd31466 100644 (file)
@@ -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
index 7f5975869fed159472fe52f8d9003e9691e2f27b..a79da090bd70cbd1bb32a364a8004727ff9c8230 100644 (file)
@@ -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 "<!--more-->".
 var SummaryDivider = []byte("<!--more-->")
 
@@ -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 {
index b0afb9cbd9ec916b2f5e016c50ad92c3975450c1..8f2d44cd9cfd92692e3e91015167fa831a5e551a 100644 (file)
@@ -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)
                }
index 2406ba77159cba82c41a086dc887ecdf5734b56f..d0ade018fca57abf2e8fb326a818eec60153ca9d 100644 (file)
@@ -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)
index d5c444ed6651bd0fc081b50dc4c9b5c8740d7a19..12bdf312b922d956830fa80018a398427eace567 100644 (file)
@@ -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