// Copyright 2015 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package helpers implements general utility functions that work with // and on content. The helper functions defined here lay down the // foundation of how Hugo works with files and filepaths, and perform // string operations on content. package helpers import ( "bytes" "fmt" "html/template" "os/exec" "unicode" "unicode/utf8" "github.com/chaseadamsio/goorgeous" bp "github.com/gohugoio/hugo/bufferpool" "github.com/gohugoio/hugo/config" "github.com/miekg/mmark" "github.com/mitchellh/mapstructure" "github.com/russross/blackfriday" jww "github.com/spf13/jwalterweatherman" "strings" ) // SummaryDivider denotes where content summarization should end. The default is "". var SummaryDivider = []byte("") // ContentSpec provides functionality to render markdown content. 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 cfg config.Provider } // NewContentSpec returns a ContentSpec initialized // with the appropriate fields from the given config.Provider. func NewContentSpec(cfg config.Provider) (*ContentSpec, error) { spec := &ContentSpec{ blackfriday: cfg.GetStringMap("blackfriday"), footnoteAnchorPrefix: cfg.GetString("footnoteAnchorPrefix"), footnoteReturnLinkContents: cfg.GetString("footnoteReturnLinkContents"), summaryLength: cfg.GetInt("summaryLength"), cfg: cfg, } // Highlighting setup options, err := parseDefaultPygmentsOpts(cfg) if err != nil { return nil, err } spec.defatultPygmentsOpts = options // Use the Pygmentize on path if present useClassic := false h := newHiglighters(spec) if cfg.GetBool("pygmentsUseClassic") { if !hasPygments() { jww.WARN.Println("Highlighting with pygmentsUseClassic set requires Pygments to be installed and in the path") } else { useClassic = true } } if useClassic { spec.Highlight = h.pygmentsHighlight } else { spec.Highlight = h.chromaHighlight } return spec, nil } // Blackfriday holds configuration values for Blackfriday rendering. type Blackfriday struct { Smartypants bool SmartypantsQuotesNBSP bool AngledQuotes bool Fractions bool HrefTargetBlank bool SmartDashes bool LatexDashes bool TaskLists bool PlainIDAnchors bool Extensions []string ExtensionsMask []string } // NewBlackfriday creates a new Blackfriday filled with site config or some sane defaults. func (c ContentSpec) NewBlackfriday() *Blackfriday { defaultParam := map[string]interface{}{ "smartypants": true, "angledQuotes": false, "smartypantsQuotesNBSP": false, "fractions": true, "hrefTargetBlank": false, "smartDashes": true, "latexDashes": true, "plainIDAnchors": true, "taskLists": true, } ToLowerMap(defaultParam) siteConfig := make(map[string]interface{}) for k, v := range defaultParam { siteConfig[k] = v } if c.blackfriday != nil { for k, v := range c.blackfriday { siteConfig[k] = v } } combinedConfig := &Blackfriday{} if err := mapstructure.Decode(siteConfig, combinedConfig); err != nil { jww.FATAL.Printf("Failed to get site rendering config\n%s", err.Error()) } return combinedConfig } var blackfridayExtensionMap = map[string]int{ "noIntraEmphasis": blackfriday.EXTENSION_NO_INTRA_EMPHASIS, "tables": blackfriday.EXTENSION_TABLES, "fencedCode": blackfriday.EXTENSION_FENCED_CODE, "autolink": blackfriday.EXTENSION_AUTOLINK, "strikethrough": blackfriday.EXTENSION_STRIKETHROUGH, "laxHtmlBlocks": blackfriday.EXTENSION_LAX_HTML_BLOCKS, "spaceHeaders": blackfriday.EXTENSION_SPACE_HEADERS, "hardLineBreak": blackfriday.EXTENSION_HARD_LINE_BREAK, "tabSizeEight": blackfriday.EXTENSION_TAB_SIZE_EIGHT, "footnotes": blackfriday.EXTENSION_FOOTNOTES, "noEmptyLineBeforeBlock": blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK, "headerIds": blackfriday.EXTENSION_HEADER_IDS, "titleblock": blackfriday.EXTENSION_TITLEBLOCK, "autoHeaderIds": blackfriday.EXTENSION_AUTO_HEADER_IDS, "backslashLineBreak": blackfriday.EXTENSION_BACKSLASH_LINE_BREAK, "definitionLists": blackfriday.EXTENSION_DEFINITION_LISTS, "joinLines": blackfriday.EXTENSION_JOIN_LINES, } var stripHTMLReplacer = strings.NewReplacer("\n", " ", "

", "\n", "
", "\n", "
", "\n") var mmarkExtensionMap = map[string]int{ "tables": mmark.EXTENSION_TABLES, "fencedCode": mmark.EXTENSION_FENCED_CODE, "autolink": mmark.EXTENSION_AUTOLINK, "laxHtmlBlocks": mmark.EXTENSION_LAX_HTML_BLOCKS, "spaceHeaders": mmark.EXTENSION_SPACE_HEADERS, "hardLineBreak": mmark.EXTENSION_HARD_LINE_BREAK, "footnotes": mmark.EXTENSION_FOOTNOTES, "noEmptyLineBeforeBlock": mmark.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK, "headerIds": mmark.EXTENSION_HEADER_IDS, "autoHeaderIds": mmark.EXTENSION_AUTO_HEADER_IDS, } // StripHTML accepts a string, strips out all HTML tags and returns it. func StripHTML(s string) string { // Shortcut strings with no tags in them if !strings.ContainsAny(s, "<>") { return s } s = stripHTMLReplacer.Replace(s) // Walk through the string removing all tags b := bp.GetBuffer() defer bp.PutBuffer(b) var inTag, isSpace, wasSpace bool for _, r := range s { if !inTag { isSpace = false } switch { case r == '<': inTag = true case r == '>': inTag = false case unicode.IsSpace(r): isSpace = true fallthrough default: if !inTag && (!isSpace || (isSpace && !wasSpace)) { b.WriteRune(r) } } wasSpace = isSpace } return b.String() } // stripEmptyNav strips out empty