diff --git a/.gitignore b/.gitignore index 6e92f57..415675a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ tags +**/*.qtpl.go +*.qtpl.go \ No newline at end of file diff --git a/qtc/main.go b/qtc/main.go index 7216a0a..007f2ef 100644 --- a/qtc/main.go +++ b/qtc/main.go @@ -20,16 +20,38 @@ var ( file = flag.String("file", "", "Path to template file to compile.\n"+ "Flags -dir and -ext are ignored if file is set.\n"+ "The compiled file will be placed near the original file with .go extension added.") - ext = flag.String("ext", "qtpl", "Only files with this extension are compiled") + ext = flag.String("ext", "qtpl", "Only files with this extension are compiled") + startTag = flag.String("sTag", "{%", "tag starting delimiter (2 chars)") + endTag = flag.String("eTag", "%}", "tag ending delimiter (2 chars)") ) var logger = log.New(os.Stderr, "qtc: ", log.LstdFlags) var filesCompiled int +func checkDelimiters() { + start := *startTag + end := *endTag + + if len(start) != 2 { + logger.Fatalf("tag starting delimiter '%s' must be 2 chars length", start) + } + if len(end) != 2 { + logger.Fatalf("tag ending delimiter '%s' must be 2 chars length", end) + } + + if start[1] != end[0] { + logger.Fatalf("starting delimiter last char ('%s') and ending delimiter first char ('%s') must be the same", string(start[1]), string(end[0])) + } + +} + func main() { + flag.Parse() + checkDelimiters() + if len(*file) > 0 { compileSingleFile(*file) return @@ -122,7 +144,13 @@ func compileFile(infile string) { if err != nil { logger.Fatalf("cannot determine package name for %q: %s", infile, err) } - if err = parse(outf, inf, infile, packageName); err != nil { + + var scannerConfig = &config{ + TagStartingDelimiter: *startTag, + TagEndingDelimiter: *endTag, + } + + if err = parse(outf, inf, infile, packageName, scannerConfig); err != nil { logger.Fatalf("error when parsing file %q: %s", infile, err) } if err = outf.Close(); err != nil { diff --git a/qtc/parser.go b/qtc/parser.go index e313205..648da84 100644 --- a/qtc/parser.go +++ b/qtc/parser.go @@ -25,11 +25,25 @@ type parser struct { packageNameEmitted bool } -func parse(w io.Writer, r io.Reader, filePath, packageName string) error { - p := &parser{ - s: newScanner(r, filePath), - w: w, - packageName: packageName, +type config struct { + TagStartingDelimiter string + TagEndingDelimiter string +} + +func parse(w io.Writer, r io.Reader, filePath, packageName string, parserConfig *config) error { + var p *parser + if parserConfig == nil { + p = &parser{ + s: newScanner(r, filePath), + w: w, + packageName: packageName, + } + } else { + p = &parser{ + s: newScannerWithTagConf(r, filePath, parserConfig.TagStartingDelimiter, parserConfig.TagEndingDelimiter), + w: w, + packageName: packageName, + } } return p.parseTemplate() } diff --git a/qtc/parser_test.go b/qtc/parser_test.go index b34cc0c..707270f 100644 --- a/qtc/parser_test.go +++ b/qtc/parser_test.go @@ -549,7 +549,7 @@ else func testParseFailure(t *testing.T, str string) { r := bytes.NewBufferString(str) w := &bytes.Buffer{} - if err := parse(w, r, "./foobar.tpl", "memory"); err == nil { + if err := parse(w, r, "./foobar.tpl", "memory", nil); err == nil { t.Fatalf("expecting error when parsing %q", str) } } @@ -557,7 +557,7 @@ func testParseFailure(t *testing.T, str string) { func testParseSuccess(t *testing.T, str string) { r := bytes.NewBufferString(str) w := &bytes.Buffer{} - if err := parse(w, r, "./foobar.tpl", "memory"); err != nil { + if err := parse(w, r, "./foobar.tpl", "memory", nil); err != nil { t.Fatalf("unexpected error when parsing %q: %s", str, err) } } @@ -576,7 +576,7 @@ func TestParseFile(t *testing.T) { } w := quicktemplate.AcquireByteBuffer() - if err := parse(w, f, filename, packageName); err != nil { + if err := parse(w, f, filename, packageName, nil); err != nil { t.Fatalf("unexpected error: %s", err) } code, err := format.Source(w.B) diff --git a/qtc/scanner.go b/qtc/scanner.go index f48f84a..d3ee56a 100644 --- a/qtc/scanner.go +++ b/qtc/scanner.go @@ -49,10 +49,12 @@ func (t *token) String() string { } type scanner struct { - r *bufio.Reader - t token - c byte - err error + openingTag []byte + closingTag []byte + r *bufio.Reader + t token + c byte + err error filePath string @@ -69,13 +71,19 @@ type scanner struct { rewind bool } -func newScanner(r io.Reader, filePath string) *scanner { +func newScannerWithTagConf(r io.Reader, filePath string, openTag string, closeTag string) *scanner { return &scanner{ - r: bufio.NewReader(r), - filePath: filePath, + r: bufio.NewReader(r), + filePath: filePath, + openingTag: []byte(openTag), + closingTag: []byte(closeTag), } } +func newScanner(r io.Reader, filePath string) *scanner { + return newScannerWithTagConf(r, filePath, "{%", "%}") +} + func (s *scanner) Rewind() { if s.rewind { panic("BUG: duplicate Rewind call") @@ -177,14 +185,14 @@ func (s *scanner) readPlain() bool { v := s.stopCapture() s.t.init(text, startLine, startPos) if ok { - n := bytes.LastIndex(v, strTagOpen) + n := bytes.LastIndex(v, s.openingTag) v = v[:n] s.t.Value = append(s.t.Value[:0], v...) } return ok } -var strTagOpen = []byte("{%") +//var strTagOpen = openingTag //[]byte("{%") func (s *scanner) skipComment() bool { if !s.readTagContents() { @@ -199,13 +207,13 @@ func (s *scanner) skipUntilTag(tagName string) bool { if !s.nextByte() { break } - if s.c != '{' { + if s.c != s.openingTag[0] { continue } if !s.nextByte() { break } - if s.c != '%' { + if s.c != s.openingTag[1] { s.unreadByte('~') continue } @@ -247,7 +255,7 @@ func (s *scanner) readText() bool { ok = (len(s.t.Value) > 0) break } - if s.c != '{' { + if s.c != s.openingTag[0] { s.appendByte() continue } @@ -256,12 +264,12 @@ func (s *scanner) readText() bool { ok = true break } - if s.c == '%' { + if s.c == s.openingTag[1] { s.nextTokenID = tagName ok = true break } - s.unreadByte('{') + s.unreadByte(s.openingTag[0]) s.appendByte() } if s.stripSpaceDepth > 0 { @@ -276,8 +284,8 @@ func (s *scanner) readTagName() bool { s.skipSpace() s.t.init(tagName, s.line, s.pos()) for { - if s.isSpace() || s.c == '%' { - if s.c == '%' { + if s.isSpace() || s.c == s.closingTag[0] { + if s.c == s.closingTag[0] { s.unreadByte('~') } s.nextTokenID = tagContents @@ -300,7 +308,7 @@ func (s *scanner) readTagContents() bool { s.skipSpace() s.t.init(tagContents, s.line, s.pos()) for { - if s.c != '%' { + if s.c != s.closingTag[0] { s.appendByte() if !s.nextByte() { return false @@ -311,12 +319,12 @@ func (s *scanner) readTagContents() bool { s.appendByte() return false } - if s.c == '}' { + if s.c == s.closingTag[1] { s.nextTokenID = text s.t.Value = stripTrailingSpace(s.t.Value) return true } - s.unreadByte('%') + s.unreadByte(s.closingTag[0]) s.appendByte() if !s.nextByte() { return false diff --git a/qtc/scanner_test.go b/qtc/scanner_test.go index 26f333a..dea1943 100644 --- a/qtc/scanner_test.go +++ b/qtc/scanner_test.go @@ -220,6 +220,71 @@ func testScannerSuccess(t *testing.T, str string, expectedTokens []tt) { } } +// func TestConfigurationScannerSuccess(t *testing.T) { +// testConfigurationScannerSuccess(t, "", "[%", "%]", nil) +// testConfigurationScannerSuccess(t, "a%]{foo}bar", "[%", "%]", []tt{ +// {ID: text, Value: "a%]{foo}bar"}, +// }) +// testConfigurationScannerSuccess(t, "[% foo bar baz(a, b, 123) %]", "[%", "%]", []tt{ +// {ID: tagName, Value: "foo"}, +// {ID: tagContents, Value: "bar baz(a, b, 123)"}, +// }) +// testConfigurationScannerSuccess(t, "foo[%bar%]baz", "[%", "%]", []tt{ +// {ID: text, Value: "foo"}, +// {ID: tagName, Value: "bar"}, +// {ID: tagContents, Value: ""}, +// {ID: text, Value: "baz"}, +// }) +// testConfigurationScannerSuccess(t, "{{[%\n\r\tfoo bar\n\rbaz%%\n \r %]}", "[%", "%]", []tt{ +// {ID: text, Value: "{{"}, +// {ID: tagName, Value: "foo"}, +// {ID: tagContents, Value: "bar\n\rbaz%%"}, +// {ID: text, Value: "}"}, +// }) +// testConfigurationScannerSuccess(t, "[%%]", "[%", "%]", []tt{ +// {ID: tagName, Value: ""}, +// {ID: tagContents, Value: ""}, +// }) +// testConfigurationScannerSuccess(t, "[%%aaa bb%]", "[%", "%]", []tt{ +// {ID: tagName, Value: ""}, +// {ID: tagContents, Value: "%aaa bb"}, +// }) +// testConfigurationScannerSuccess(t, "foo[% bar %][% baz aa (123)%]321", "[%", "%]", []tt{ +// {ID: text, Value: "foo"}, +// {ID: tagName, Value: "bar"}, +// {ID: tagContents, Value: ""}, +// {ID: tagName, Value: "baz"}, +// {ID: tagContents, Value: "aa (123)"}, +// {ID: text, Value: "321"}, +// }) +// testConfigurationScannerSuccess(t, " aa\n\t [%stripspace%] \t\n f\too \n b ar \n\r\t [% bar baz asd %]\n\nbaz \n\t \taaa \n[%endstripspace%] bb ", "[%", "%]", []tt{ +// {ID: text, Value: " aa\n\t "}, +// {ID: text, Value: "f\toob ar"}, +// {ID: tagName, Value: "bar"}, +// {ID: tagContents, Value: "baz asd"}, +// {ID: text, Value: "bazaaa"}, +// {ID: text, Value: " bb "}, +// }) +// } + +// func testConfigurationScannerSuccess(t *testing.T, str string, startTag string, endTag string, expectedTokens []tt) { +// r := bytes.NewBufferString(str) +// s := newScannerWithTagConf(r, "memory", startTag, endTag) +// var tokens []tt +// for s.Next() { +// tokens = append(tokens, tt{ +// ID: s.Token().ID, +// Value: string(s.Token().Value), +// }) +// } +// if err := s.LastError(); err != nil { +// t.Fatalf("unexpected error: %s. str=%q", err, str) +// } +// if !reflect.DeepEqual(tokens, expectedTokens) { +// t.Fatalf("unexpected tokens %v. Expecting %v. str=%q", tokens, expectedTokens, str) +// } +// } + type tt struct { ID int Value string diff --git a/qtc/scanner_with_conf_test.go b/qtc/scanner_with_conf_test.go new file mode 100644 index 0000000..d3d8014 --- /dev/null +++ b/qtc/scanner_with_conf_test.go @@ -0,0 +1,349 @@ +package main + +import ( + "bytes" + "fmt" + "reflect" + "testing" +) + +/*TestScannerConfigurationTagNameWithDotAndEqual ..." + */ +func TestScannerConfigurationTagNameWithDotAndEqual(t *testing.T) { + testScannerConfigurationSuccess(t, "[%foo.bar.34 baz aaa%] awer[% aa= %]", + []confToken{ + {ID: tagName, Value: "foo.bar.34"}, + {ID: tagContents, Value: "baz aaa"}, + {ID: text, Value: " awer"}, + {ID: tagName, Value: "aa="}, + {ID: tagContents, Value: ""}, + }) +} + +/*TestScannerConfigurationStripspaceSuccess ... + */ +func TestScannerConfigurationStripspaceSuccess(t *testing.T) { + testScannerConfigurationSuccess(t, " aa\n\t [%stripspace%] \t\n f\too \n b ar \n\r\t [% bar baz asd %]\n\nbaz \n\t \taaa \n[%endstripspace%] bb ", []confToken{ + {ID: text, Value: " aa\n\t "}, + {ID: text, Value: "f\toob ar"}, + {ID: tagName, Value: "bar"}, + {ID: tagContents, Value: "baz asd"}, + {ID: text, Value: "bazaaa"}, + {ID: text, Value: " bb "}, + }) + testScannerConfigurationSuccess(t, "[%stripspace %][% stripspace fobar %] [%space%] a\taa\n\r\t bb b [%endstripspace %] [%endstripspace baz%]", []confToken{ + {ID: text, Value: " "}, + {ID: text, Value: "a\taabb b"}, + }) + + // sripspace wins over collapsespace + testScannerConfigurationSuccess(t, "[%stripspace%] [%collapsespace%]foo\n\t bar[%endcollapsespace%] \r\n\t [%endstripspace%]", []confToken{ + {ID: text, Value: "foobar"}, + }) +} + +/*TestScannerConfigurationStripspaceFailure ... + */ +func TestScannerConfigurationStripspaceFailure(t *testing.T) { + // incomplete stripspace tag + testScannerConfigurationFailure(t, "[%stripspace ") + + // incomplete endstripspace tag + testScannerConfigurationFailure(t, "[%stripspace%]aaa[%endstripspace") + + // missing endstripspace + testScannerConfigurationFailure(t, "[%stripspace%] foobar") + + // missing stripspace + testScannerConfigurationFailure(t, "aaa[%endstripspace%]") + + // missing the second endstripspace + testScannerConfigurationFailure(t, "[%stripspace%][%stripspace%]aaaa[%endstripspace%]") +} + +/*TestScannerConfigurationCollapsespaceSuccess ... + */ +func TestScannerConfigurationCollapsespaceSuccess(t *testing.T) { + testScannerConfigurationSuccess(t, " aa\n\t [%collapsespace%] \t\n foo \n bar[% bar baz asd %]\n\nbaz \n \n[%endcollapsespace%] bb ", []confToken{ + {ID: text, Value: " aa\n\t "}, + {ID: text, Value: " foo bar"}, + {ID: tagName, Value: "bar"}, + {ID: tagContents, Value: "baz asd"}, + {ID: text, Value: " baz "}, + {ID: text, Value: " bb "}, + }) + testScannerConfigurationSuccess(t, "[%collapsespace %][% collapsespace fobar %] [%space%] aaa\n\r\t bbb [%endcollapsespace %] [%endcollapsespace baz%]", []confToken{ + {ID: text, Value: " "}, + {ID: text, Value: " "}, + {ID: text, Value: " aaa bbb "}, + {ID: text, Value: " "}, + }) +} + +/*TestScannerCollapsespaceFailure ... + */ +func TestScannerConfigurationCollapsespaceFailure(t *testing.T) { + // incomplete collapsespace tag + testScannerConfigurationFailure(t, "[%collapsespace ") + + // incomplete endcollapsespace tag + testScannerConfigurationFailure(t, "[%collapsespace%]aaa[%endcollapsespace") + + // missing endcollapsespace + testScannerConfigurationFailure(t, "[%collapsespace%] foobar") + + // missing collapsespace + testScannerConfigurationFailure(t, "aaa[%endcollapsespace%]") + + // missing the second endcollapsespace + testScannerConfigurationFailure(t, "[%collapsespace%][%collapsespace%]aaaa[%endcollapsespace%]") +} + +/*TestScannerPlainSuccess ... + */ +func TestScannerConfigurationPlainSuccess(t *testing.T) { + testScannerConfigurationSuccess(t, "[%plain%][%endplain%]", nil) + testScannerConfigurationSuccess(t, "[%plain%][%foo bar%]asdf[%endplain%]", []confToken{ + {ID: text, Value: "[%foo bar%]asdf"}, + }) + testScannerConfigurationSuccess(t, "[%plain%][%foo[%endplain%]", []confToken{ + {ID: text, Value: "[%foo"}, + }) + testScannerConfigurationSuccess(t, "aa[%plain%]bbb[%cc%][%endplain%][%plain%]dsff[%endplain%]", []confToken{ + {ID: text, Value: "aa"}, + {ID: text, Value: "bbb[%cc%]"}, + {ID: text, Value: "dsff"}, + }) + testScannerConfigurationSuccess(t, "mmm[%plain%]aa[% bar [%%% }baz[%endplain%]nnn", []confToken{ + {ID: text, Value: "mmm"}, + {ID: text, Value: "aa[% bar [%%% }baz"}, + {ID: text, Value: "nnn"}, + }) + testScannerConfigurationSuccess(t, "[% plain dsd %]0[%comment%]123[%endcomment%]45[% endplain aaa %]", []confToken{ + {ID: text, Value: "0[%comment%]123[%endcomment%]45"}, + }) +} + +/*TestScannerPlainFailure ... + */ +func TestScannerConfigurationPlainFailure(t *testing.T) { + testScannerConfigurationFailure(t, "[%plain%]sdfds") + testScannerConfigurationFailure(t, "[%plain%]aaaa%[%endplain") + testScannerConfigurationFailure(t, "[%plain%][%endplain%") +} + +/*TestScannerCommentSuccess ... + */ +func TestScannerConfigurationCommentSuccess(t *testing.T) { + testScannerConfigurationSuccess(t, "[%comment%][%endcomment%]", nil) + testScannerConfigurationSuccess(t, "[%comment%]foo[%endcomment%]", nil) + testScannerConfigurationSuccess(t, "[%comment%]foo[%endcomment%][%comment%]sss[%endcomment%]", nil) + testScannerConfigurationSuccess(t, "[%comment%]foo[%bar%][%endcomment%]", nil) + testScannerConfigurationSuccess(t, "[%comment%]foo[%bar [%endcomment%]", nil) + testScannerConfigurationSuccess(t, "[%comment%]foo[%bar&^[%endcomment%]", nil) + testScannerConfigurationSuccess(t, "[%comment%]foo[% bar\n\rs%[%endcomment%]", nil) + testScannerConfigurationSuccess(t, "xx[%x%]www[% comment aux data %]aaa[% comment %][% endcomment %]yy", []confToken{ + {ID: text, Value: "xx"}, + {ID: tagName, Value: "x"}, + {ID: tagContents, Value: ""}, + {ID: text, Value: "www"}, + {ID: text, Value: "yy"}, + }) +} + +/*TestScannerCommentFailure ... + */ +func TestScannerConfigurationCommentFailure(t *testing.T) { + testScannerConfigurationFailure(t, "[%comment%]...no endcomment") + testScannerConfigurationFailure(t, "[% comment %]foobar[% endcomment") +} + +func TestScannerConfigurationSuccess(t *testing.T) { + testScannerConfigurationSuccess(t, "", nil) + testScannerConfigurationSuccess(t, "a%]{foo}bar", []confToken{ + {ID: text, Value: "a%]{foo}bar"}, + }) + testScannerConfigurationSuccess(t, "[% foo bar baz(a, b, 123) %]", []confToken{ + {ID: tagName, Value: "foo"}, + {ID: tagContents, Value: "bar baz(a, b, 123)"}, + }) + testScannerConfigurationSuccess(t, "foo[%bar%]baz", []confToken{ + {ID: text, Value: "foo"}, + {ID: tagName, Value: "bar"}, + {ID: tagContents, Value: ""}, + {ID: text, Value: "baz"}, + }) + testScannerConfigurationSuccess(t, "{{[%\n\r\tfoo bar\n\rbaz%%\n \r %]}", []confToken{ + {ID: text, Value: "{{"}, + {ID: tagName, Value: "foo"}, + {ID: tagContents, Value: "bar\n\rbaz%%"}, + {ID: text, Value: "}"}, + }) + testScannerConfigurationSuccess(t, "[%%]", []confToken{ + {ID: tagName, Value: ""}, + {ID: tagContents, Value: ""}, + }) + testScannerConfigurationSuccess(t, "[%%aaa bb%]", []confToken{ + {ID: tagName, Value: ""}, + {ID: tagContents, Value: "%aaa bb"}, + }) + testScannerConfigurationSuccess(t, "foo[% bar %][% baz aa (123)%]321", []confToken{ + {ID: text, Value: "foo"}, + {ID: tagName, Value: "bar"}, + {ID: tagContents, Value: ""}, + {ID: tagName, Value: "baz"}, + {ID: tagContents, Value: "aa (123)"}, + {ID: text, Value: "321"}, + }) +} + +/*TestScannerConfigurationFailure ... + */ +func TestScannerConfigurationFailure(t *testing.T) { + testScannerConfigurationFailure(t, "a[%") + testScannerConfigurationFailure(t, "a[%foo") + testScannerConfigurationFailure(t, "a[%% }foo") + testScannerConfigurationFailure(t, "a[% foo %") + testScannerConfigurationFailure(t, "b[% fo() %]bar") + testScannerConfigurationFailure(t, "aa[% foo bar") +} + +func testScannerConfigurationFailure(t *testing.T, str string) { + r := bytes.NewBufferString(str) + s := newScannerWithTagConf(r, "memory", "[%", "%]") + var tokens []confToken + for s.Next() { + id := s.Token().ID + val := string(s.Token().Value) + fmt.Printf("%d %s", id, val) + tokens = append(tokens, confToken{ + ID: s.Token().ID, + Value: string(s.Token().Value), + }) + } + if err := s.LastError(); err == nil { + t.Fatalf("expecting error when scanning %q. got tokens %v", str, tokens) + } +} + +func testScannerConfigurationSuccess(t *testing.T, str string, expectedTokens []confToken) { + r := bytes.NewBufferString(str) + s := newScannerWithTagConf(r, "memory", "[%", "%]") + var tokens []confToken + for s.Next() { + tokens = append(tokens, confToken{ + ID: s.Token().ID, + Value: string(s.Token().Value), + }) + } + if err := s.LastError(); err != nil { + t.Fatalf("unexpected error: %s. str=%q", err, str) + } + if !reflect.DeepEqual(tokens, expectedTokens) { + t.Fatalf("unexpected tokens %v. Expecting %v. str=%q", tokens, expectedTokens, str) + } +} + +// /*TestScannerConfigurationSuccessTest ... +// */ +// func TestScannerConfigurationSuccess(t *testing.T) { +// testScannerConfigurationSuccess(t, "", nil) +// testScannerConfigurationSuccess(t, "a%]{foo}bar", []confToken{ +// {ID: text, Value: "a%]{foo}bar"}, +// }) +// testScannerConfigurationSuccess(t, "[% foo bar baz(a, b, 123) %]", []confToken{ +// {ID: tagName, Value: "foo"}, +// {ID: tagContents, Value: "bar baz(a, b, 123)"}, +// }) +// testScannerConfigurationSuccess(t, "foo[%bar%]baz", []confToken{ +// {ID: text, Value: "foo"}, +// {ID: tagName, Value: "bar"}, +// {ID: tagContents, Value: ""}, +// {ID: text, Value: "baz"}, +// }) +// testScannerConfigurationSuccess(t, "{{[%\n\r\tfoo bar\n\rbaz%%\n \r %]}", []confToken{ +// {ID: text, Value: "{{"}, +// {ID: tagName, Value: "foo"}, +// {ID: tagContents, Value: "bar\n\rbaz%%"}, +// {ID: text, Value: "}"}, +// }) +// testScannerConfigurationSuccess(t, "[%%]", []confToken{ +// {ID: tagName, Value: ""}, +// {ID: tagContents, Value: ""}, +// }) +// testScannerConfigurationSuccess(t, "[%%aaa bb%]", []confToken{ +// {ID: tagName, Value: ""}, +// {ID: tagContents, Value: "%aaa bb"}, +// }) +// testScannerConfigurationSuccess(t, "foo[% bar %][% baz aa (123)%]321", []confToken{ +// {ID: text, Value: "foo"}, +// {ID: tagName, Value: "bar"}, +// {ID: tagContents, Value: ""}, +// {ID: tagName, Value: "baz"}, +// {ID: tagContents, Value: "aa (123)"}, +// {ID: text, Value: "321"}, +// }) +// testScannerConfigurationSuccess(t, " aa\n\t [%stripspace%] \t\n f\too \n b ar \n\r\t [% bar baz asd %]\n\nbaz \n\t \taaa \n[%endstripspace%] bb ", []confToken{ +// {ID: text, Value: " aa\n\t "}, +// {ID: text, Value: "f\toob ar"}, +// {ID: tagName, Value: "bar"}, +// {ID: tagContents, Value: "baz asd"}, +// {ID: text, Value: "bazaaa"}, +// {ID: text, Value: " bb "}, +// }) +// } + +/*TestCrash ... + */ +func TestCrash(t *testing.T) { + + var source = `This is a base page template. All the other template pages implement this interface. + +{% interface +Page { + Title() + Body() +} +%] + + +Page prints a page implementing Page interface. +[% func PageTemplate(p Page) %] + + + {%= p.Title() %] + + +
+ return to main page +
+ [%= p.Body() %] + + +[% endfunc %] + + +Base page implementation. Other pages may inherit from it if they need +overriding only certain Page methods +[% code type BasePage struct {} %] +[% func (p *BasePage) Title() %]This is a base title[% endfunc %] +[% func (p *BasePage) Body() %]This is a base body[% endfunc %] +}` + r := bytes.NewBufferString(source) + s := newScannerWithTagConf(r, "memory", "[%", "%]") + var tokens []confToken + for s.Next() { + tokens = append(tokens, confToken{ + ID: s.Token().ID, + Value: string(s.Token().Value), + }) + } + if err := s.LastError(); err != nil { + t.Fatalf("unexpected error: %s. str=%q", err, source) + } +} + +type confToken struct { + ID int + Value string +}