summaryrefslogtreecommitdiffhomepage
path: root/parser
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2018-11-26 11:01:27 +0100
committerBjørn Erik Pedersen <[email protected]>2018-11-27 16:14:09 +0100
commitbc337e6ab5a75f1f1bfe3a83f3786d0afdb6346c (patch)
tree1f3b087822337acbde696147f18e86b2c9f1d8eb /parser
parent112461fded0d7970817ce7bf476c4763922ad314 (diff)
downloadhugo-bc337e6ab5a75f1f1bfe3a83f3786d0afdb6346c.tar.gz
hugo-bc337e6ab5a75f1f1bfe3a83f3786d0afdb6346c.zip
Add inline shortcode support
An inline shortcode's name must end with `.inline`, all lowercase. E.g.: ```bash {{< time.inline >}}{{ now }}{{< /time.inline >}} ``` The above will print the current date and time. Note that an inline shortcode's inner content is parsed and executed as a Go text template with the same context as a regular shortcode template. This means that the current page can be accessed via `.Page.Title` etc. This also means that there are no concept of "nested inline shortcodes". The same inline shortcode can be reused later in the same content file, with different params if needed, using the self-closing syntax: ``` {{< time.inline />}} ``` Fixes #4011
Diffstat (limited to 'parser')
-rw-r--r--parser/pageparser/item.go5
-rw-r--r--parser/pageparser/pagelexer.go53
-rw-r--r--parser/pageparser/pageparser_shortcode_test.go8
3 files changed, 65 insertions, 1 deletions
diff --git a/parser/pageparser/item.go b/parser/pageparser/item.go
index 0567bd8b9..644c20e87 100644
--- a/parser/pageparser/item.go
+++ b/parser/pageparser/item.go
@@ -42,6 +42,10 @@ func (i Item) IsShortcodeName() bool {
return i.Type == tScName
}
+func (i Item) IsInlineShortcodeName() bool {
+ return i.Type == tScNameInline
+}
+
func (i Item) IsLeftShortcodeDelim() bool {
return i.Type == tLeftDelimScWithMarkup || i.Type == tLeftDelimScNoMarkup
}
@@ -119,6 +123,7 @@ const (
tRightDelimScWithMarkup
tScClose
tScName
+ tScNameInline
tScParam
tScParamVal
diff --git a/parser/pageparser/pagelexer.go b/parser/pageparser/pagelexer.go
index 8106758a9..94c1ff26b 100644
--- a/parser/pageparser/pagelexer.go
+++ b/parser/pageparser/pagelexer.go
@@ -32,6 +32,7 @@ type stateFunc func(*pageLexer) stateFunc
type lexerShortcodeState struct {
currLeftDelimItem ItemType
currRightDelimItem ItemType
+ isInline bool
currShortcodeName string // is only set when a shortcode is in opened state
closingState int // > 0 = on its way to be closed
elementStepNum int // step number in element
@@ -224,6 +225,19 @@ func lexMainSection(l *pageLexer) stateFunc {
for {
if l.isShortCodeStart() {
+ if l.isInline {
+ // If we're inside an inline shortcode, the only valid shortcode markup is
+ // the markup which closes it.
+ b := l.input[l.pos+3:]
+ end := indexNonWhiteSpace(b, '/')
+ if end != len(l.input)-1 {
+ b = bytes.TrimSpace(b[end+1:])
+ if end == -1 || !bytes.HasPrefix(b, []byte(l.currShortcodeName+" ")) {
+ return l.errorf("inline shortcodes do not support nesting")
+ }
+ }
+ }
+
if l.pos > l.start {
l.emit(tText)
}
@@ -266,6 +280,14 @@ func lexMainSection(l *pageLexer) stateFunc {
func (l *pageLexer) isShortCodeStart() bool {
return l.hasPrefix(leftDelimScWithMarkup) || l.hasPrefix(leftDelimScNoMarkup)
+
+}
+
+func (l *pageLexer) posFirstNonWhiteSpace() int {
+ f := func(c rune) bool {
+ return !unicode.IsSpace(c)
+ }
+ return bytes.IndexFunc(l.input[l.pos:], f)
}
func lexIntroSection(l *pageLexer) stateFunc {
@@ -611,6 +633,9 @@ Loop:
return lexInsideShortcode
}
+// Inline shortcodes has the form {{< myshortcode.inline >}}
+var inlineIdentifier = []byte("inline ")
+
// scans an alphanumeric inside shortcode
func lexIdentifierInShortcode(l *pageLexer) stateFunc {
lookForEnd := false
@@ -620,6 +645,11 @@ Loop:
case isAlphaNumericOrHyphen(r):
// Allow forward slash inside names to make it possible to create namespaces.
case r == '/':
+ case r == '.':
+ l.isInline = l.hasPrefix(inlineIdentifier)
+ if !l.isInline {
+ return l.errorf("period in shortcode name only allowed for inline identifiers")
+ }
default:
l.backup()
word := string(l.input[l.start:l.pos])
@@ -634,7 +664,11 @@ Loop:
l.currShortcodeName = word
l.openShortcodes[word] = true
l.elementStepNum++
- l.emit(tScName)
+ if l.isInline {
+ l.emit(tScNameInline)
+ } else {
+ l.emit(tScName)
+ }
break Loop
}
}
@@ -646,6 +680,7 @@ Loop:
}
func lexEndOfShortcode(l *pageLexer) stateFunc {
+ l.isInline = false
if l.hasPrefix(l.currentRightShortcodeDelim()) {
return lexShortcodeRightDelim
}
@@ -747,6 +782,22 @@ func minIndex(indices ...int) int {
return min
}
+func indexNonWhiteSpace(s []byte, in rune) int {
+ idx := bytes.IndexFunc(s, func(r rune) bool {
+ return !unicode.IsSpace(r)
+ })
+
+ if idx == -1 {
+ return -1
+ }
+
+ r, _ := utf8.DecodeRune(s[idx:])
+ if r == in {
+ return idx
+ }
+ return -1
+}
+
func isSpace(r rune) bool {
return r == ' ' || r == '\t'
}
diff --git a/parser/pageparser/pageparser_shortcode_test.go b/parser/pageparser/pageparser_shortcode_test.go
index efef6fca2..c52840b58 100644
--- a/parser/pageparser/pageparser_shortcode_test.go
+++ b/parser/pageparser/pageparser_shortcode_test.go
@@ -23,12 +23,14 @@ var (
tstRightMD = nti(tRightDelimScWithMarkup, "%}}")
tstSCClose = nti(tScClose, "/")
tstSC1 = nti(tScName, "sc1")
+ tstSC1Inline = nti(tScNameInline, "sc1.inline")
tstSC2 = nti(tScName, "sc2")
tstSC3 = nti(tScName, "sc3")
tstSCSlash = nti(tScName, "sc/sub")
tstParam1 = nti(tScParam, "param1")
tstParam2 = nti(tScParam, "param2")
tstVal = nti(tScParamVal, "Hello World")
+ tstText = nti(tText, "Hello World")
)
var shortCodeLexerTests = []lexerTest{
@@ -146,6 +148,12 @@ var shortCodeLexerTests = []lexerTest{
nti(tError, "comment must be closed")}},
{"commented out, misplaced close", `{{</* sc1 >}}*/`, []Item{
nti(tError, "comment must be closed")}},
+ // Inline shortcodes
+ {"basic inline", `{{< sc1.inline >}}Hello World{{< /sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF}},
+ {"basic inline with space", `{{< sc1.inline >}}Hello World{{< / sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF}},
+ {"inline self closing", `{{< sc1.inline >}}Hello World{{< /sc1.inline >}}Hello World{{< sc1.inline />}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSC1Inline, tstSCClose, tstRightNoMD, tstEOF}},
+ {"inline with nested shortcode (not supported)", `{{< sc1.inline >}}Hello World{{< sc1 >}}{{< /sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, nti(tError, "inline shortcodes do not support nesting")}},
+ {"inline case mismatch", `{{< sc1.Inline >}}Hello World{{< /sc1.Inline >}}`, []Item{tstLeftNoMD, nti(tError, "period in shortcode name only allowed for inline identifiers")}},
}
func TestShortcodeLexer(t *testing.T) {