summaryrefslogtreecommitdiffstats
path: root/parser
diff options
context:
space:
mode:
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) {