diff options
Diffstat (limited to 'parser')
-rw-r--r-- | parser/pageparser/item.go | 5 | ||||
-rw-r--r-- | parser/pageparser/pagelexer.go | 53 | ||||
-rw-r--r-- | parser/pageparser/pageparser_shortcode_test.go | 8 |
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) { |