Repository: CloudyKit/jet
Pull request #213 (2024-02-18 17:55:18)

Allow DevelopmentMode to be set on or off.
Allowing DevelopmentMode to be set on or off via a variable is a common pattern I missed.


Patch:
diff --git a/constructors.go b/constructors.go
index d0f023b..49e934f 100644
--- a/constructors.go
+++ b/constructors.go
@@ -86,12 +86,12 @@ func (t *Template) newNil(pos Pos) *NilNode {
 	return &NilNode{NodeBase: NodeBase{TemplatePath: t.Name, NodeType: NodeNil, Pos: pos}}
 }
 
-func (t *Template) newField(pos Pos, ident string) *FieldNode {
-	return &FieldNode{NodeBase: NodeBase{TemplatePath: t.Name, NodeType: NodeField, Pos: pos}, Ident: strings.Split(ident[1:], ".")} //[1:] to drop leading period
+func (t *Template) newField(pos Pos, line int, ident string) *FieldNode {
+	return &FieldNode{NodeBase: NodeBase{TemplatePath: t.Name, NodeType: NodeField, Pos: pos, Line: line}, Ident: strings.Split(ident[1:], ".")} //[1:] to drop leading period
 }
 
-func (t *Template) newChain(pos Pos, node Node) *ChainNode {
-	return &ChainNode{NodeBase: NodeBase{TemplatePath: t.Name, NodeType: NodeChain, Pos: pos}, Node: node}
+func (t *Template) newChain(pos Pos, line int, node Node) *ChainNode {
+	return &ChainNode{NodeBase: NodeBase{TemplatePath: t.Name, NodeType: NodeChain, Pos: pos, Line: line}, Node: node}
 }
 
 func (t *Template) newBool(pos Pos, true bool) *BoolNode {
diff --git a/examples/asset_packaging/main.go b/examples/asset_packaging/main.go
index 75eb2ad..04dda68 100644
--- a/examples/asset_packaging/main.go
+++ b/examples/asset_packaging/main.go
@@ -42,7 +42,7 @@ func init() {
 
 	views = jet.NewSet(
 		httpfsLoader,
-		jet.InDevelopmentMode(), // remove in production
+		jet.DevelopmentMode(true), // remove or set false in production
 	)
 }
 
diff --git a/examples/todos/main.go b/examples/todos/main.go
index f312255..e1e79e8 100644
--- a/examples/todos/main.go
+++ b/examples/todos/main.go
@@ -30,7 +30,7 @@ import (
 
 var views = jet.NewSet(
 	jet.NewOSFileSystemLoader("./views"),
-	jet.InDevelopmentMode(), // remove in production
+	jet.DevelopmentMode(true), // remove or set false in production
 )
 
 type tTODO struct {
diff --git a/lex.go b/lex.go
index dc37b6c..3711bfa 100644
--- a/lex.go
+++ b/lex.go
@@ -141,8 +141,8 @@ const eof = -1
 const (
 	defaultLeftDelim  = "{{"
 	defaultRightDelim = "}}"
-	leftComment       = "{*"
-	rightComment      = "*}"
+	defaultLeftComment       = "{*"
+	defaultRightComment      = "*}"
 	leftTrimMarker    = "- "
 	rightTrimMarker   = " -"
 	trimMarkerLen     = Pos(len(leftTrimMarker))
@@ -165,6 +165,8 @@ type lexer struct {
 	lastType       itemType
 	leftDelim      string
 	rightDelim     string
+	leftComment      string
+	rightComment     string
 	trimRightDelim string
 }
 
@@ -177,6 +179,15 @@ func (l *lexer) setDelimiters(leftDelim, rightDelim string) {
 	}
 }
 
+func (l *lexer) setCommentDelimiters(leftDelim, rightDelim string) {
+	if leftDelim != "" {
+		l.leftComment = leftDelim
+	}
+	if rightDelim != "" {
+		l.rightComment = rightDelim
+	}
+}
+
 // next returns the next rune in the input.
 func (l *lexer) next() rune {
 	if int(l.pos) >= len(l.input) {
@@ -266,6 +277,8 @@ func lex(name, input string, run bool) *lexer {
 		items:          make(chan item),
 		leftDelim:      defaultLeftDelim,
 		rightDelim:     defaultRightDelim,
+		leftComment:    defaultLeftComment,
+		rightComment:   defaultRightComment,
 		trimRightDelim: rightTrimMarker + defaultRightDelim,
 	}
 	if run {
@@ -289,7 +302,7 @@ func lexText(l *lexer) stateFn {
 	for {
 		// without breaking the API, this seems like a reasonable workaround to correctly parse comments
 		i := strings.IndexByte(l.input[l.pos:], l.leftDelim[0])  // index of suspected left delimiter
-		ic := strings.IndexByte(l.input[l.pos:], leftComment[0]) // index of suspected left comment marker
+		ic := strings.IndexByte(l.input[l.pos:], l.leftComment[0]) // index of suspected left comment marker
 		if ic > -1 && ic < i {                                   // use whichever is lower for future lexing
 			i = ic
 		}
@@ -313,7 +326,7 @@ func lexText(l *lexer) stateFn {
 				l.ignore()
 				return lexLeftDelim
 			}
-			if strings.HasPrefix(l.input[l.pos:], leftComment) {
+			if strings.HasPrefix(l.input[l.pos:], l.leftComment) {
 				if l.pos > l.start {
 					l.emit(itemText)
 				}
@@ -346,12 +359,12 @@ func lexLeftDelim(l *lexer) stateFn {
 
 // lexComment scans a comment. The left comment marker is known to be present.
 func lexComment(l *lexer) stateFn {
-	l.pos += Pos(len(leftComment))
-	i := strings.Index(l.input[l.pos:], rightComment)
+	l.pos += Pos(len(l.leftComment))
+	i := strings.Index(l.input[l.pos:], l.rightComment)
 	if i < 0 {
 		return l.errorf("unclosed comment")
 	}
-	l.pos += Pos(i + len(rightComment))
+	l.pos += Pos(i + len(l.rightComment))
 	l.ignore()
 	return lexText
 }
diff --git a/parse.go b/parse.go
index 4f0166d..f1f315d 100644
--- a/parse.go
+++ b/parse.go
@@ -225,6 +225,7 @@ func (s *Set) parse(name, text string, cacheAfterParsing bool) (t *Template, err
 
 	lexer := lex(name, text, false)
 	lexer.setDelimiters(s.leftDelim, s.rightDelim)
+	lexer.setCommentDelimiters(s.leftComment, s.rightComment)
 	lexer.run()
 	t.startParse(lexer)
 	t.parseTemplate(cacheAfterParsing)
@@ -792,7 +793,7 @@ func (t *Template) operand(context string) Expression {
 	}
 RESET:
 	if t.peek().typ == itemField {
-		chain := t.newChain(t.peek().pos, node)
+		chain := t.newChain(t.peek().pos, t.lex.lineNumber(), node)
 		for t.peekNonSpace().typ == itemField {
 			chain.Add(t.next().val)
 		}
@@ -803,7 +804,7 @@ RESET:
 		// More complex error cases will have to be handled at execution time.
 		switch node.Type() {
 		case NodeField:
-			node = t.newField(chain.Position(), chain.String())
+			node = t.newField(chain.Position(), t.lex.lineNumber(), chain.String())
 		case NodeBool, NodeString, NodeNumber, NodeNil:
 			t.errorf("unexpected . after term %q", node.String())
 		default:
@@ -1032,7 +1033,7 @@ func (t *Template) term() Node {
 	case itemNil:
 		return t.newNil(token.pos)
 	case itemField:
-		return t.newField(token.pos, token.val)
+		return t.newField(token.pos, t.lex.lineNumber(), token.val)
 	case itemBool:
 		return t.newBool(token.pos, token.val == "true")
 	case itemCharConstant, itemComplex, itemNumber:
diff --git a/set.go b/set.go
index 67b7871..b55f1b6 100644
--- a/set.go
+++ b/set.go
@@ -23,6 +23,8 @@ type Set struct {
 	developmentMode bool
 	leftDelim       string
 	rightDelim      string
+	leftComment       string
+	rightComment     string
 }
 
 // Option is the type of option functions that can be used in NewSet().
@@ -84,6 +86,15 @@ func WithDelims(left, right string) Option {
 	}
 }
 
+// WithCommentDelims returns an option function that sets the comment delimiters to the specified strings.
+// Parsed templates will inherit the settings. Not setting them leaves them at the default: `{*` and `*}`.
+func WithCommentDelims(left, right string) Option {
+	return func(s *Set) {
+		s.leftComment = left
+		s.rightComment = right
+	}
+}
+
 // WithTemplateNameExtensions returns an option function that sets the extensions to try when looking
 // up template names in the cache or loader. Default extensions are `""` (no extension), `".jet"`,
 // `".html.jet"`, `".jet.html"`. Extensions will be tried in the order they are defined in the slice.
@@ -100,8 +111,14 @@ func WithTemplateNameExtensions(extensions []string) Option {
 // InDevelopmentMode returns an option function that toggles development mode on, meaning the cache will
 // always be bypassed and every template lookup will go to the loader.
 func InDevelopmentMode() Option {
+	return DevelopmentMode(true)
+}
+
+// DevelopmentMode returns an option function that sets development mode on or off. "On" means the cache will
+// always be bypassed and every template lookup will go to the loader.
+func DevelopmentMode(mode bool) Option {
 	return func(s *Set) {
-		s.developmentMode = true
+		s.developmentMode = mode
 	}
 }
 


Test patch:
diff --git a/parse_test.go b/parse_test.go
index a6baf34..a78993b 100644
--- a/parse_test.go
+++ b/parse_test.go
@@ -131,6 +131,7 @@ func TestParseTemplateWithCustomDelimiters(t *testing.T) {
 		NewOSFileSystemLoader("./testData"),
 		WithSafeWriter(nil),
 		WithDelims("[[", "]]"),
+		WithCommentDelims("[*", "*]"),
 	)
 	p := ParserTestCase{T: t, set: set}
 	p.TestPrintFile("custom_delimiters.jet")
diff --git a/testData/custom_delimiters.jet b/testData/custom_delimiters.jet
index 00d92ad..739d761 100644
--- a/testData/custom_delimiters.jet
+++ b/testData/custom_delimiters.jet
@@ -1,4 +1,4 @@
-{* comment *}[[ . ]]
+[* comment *][[ . ]]
 [[ singleValue ]]
 [[ nil ]]
 [[ "" ]]