Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#37 delimiters configuration #50

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
tags
**/*.qtpl.go
*.qtpl.go
32 changes: 30 additions & 2 deletions qtc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 19 additions & 5 deletions qtc/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
6 changes: 3 additions & 3 deletions qtc/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,15 +549,15 @@ 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)
}
}

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)
}
}
Expand All @@ -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)
Expand Down
46 changes: 27 additions & 19 deletions qtc/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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")
Expand Down Expand Up @@ -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() {
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
65 changes: 65 additions & 0 deletions qtc/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading