fix: add scoped error logging for lexer/parser errors

- Add std.log.scoped(.pugz) to template.zig and view_engine.zig
- Log detailed error info (code, line, column, message) when parsing fails
- Log template path context in ViewEngine on parse errors
- Remove debug print from lexer, use proper scoped logging instead
- Move benchmarks, docs, examples, playground, tests out of src/ to project root
- Update build.zig and documentation paths accordingly
- Bump version to 0.3.1
This commit is contained in:
2026-01-25 17:10:02 +05:30
parent 9d3b729c6c
commit aaf6a1af2d
1148 changed files with 57 additions and 330 deletions

View File

@@ -0,0 +1,3 @@
- var avatar = '219b77f9d21de75e81851b6b886057c7'
div.avatar-div(style=`background-image: url(https://www.gravatar.com/avatar/${avatar})`)

View File

@@ -0,0 +1,7 @@
- var user = { name: 'tobi' }
foo(data-user=user)
foo(data-items=[1,2,3])
foo(data-username='tobi')
foo(data-escaped={message: "Let's rock!"})
foo(data-ampersand={message: "a quote: " this & that"})
foo(data-epoc=new Date(0))

View File

@@ -0,0 +1,22 @@
- var id = 5
- function answer() { return 42; }
a(href='/user/' + id, class='button')
a(href = '/user/' + id, class = 'button')
meta(key='answer', value=answer())
a(class = ['class1', 'class2'])
a.tag-class(class = ['class1', 'class2'])
a(href='/user/' + id class='button')
a(href = '/user/' + id class = 'button')
meta(key='answer' value=answer())
a(class = ['class1', 'class2'])
a.tag-class(class = ['class1', 'class2'])
div(id=id)&attributes({foo: 'bar'})
- var bar = null
div(foo=null bar=bar)&attributes({baz: 'baz'})
div(...object)
div(...object after="after")
div(before="before" ...object)
div(before="before" ...object after="after")

View File

@@ -0,0 +1,43 @@
a(href='/contact') contact
a(href='/save').button save
a(foo, bar, baz)
a(foo='foo, bar, baz', bar=1)
a(foo='((foo))', bar= (1) ? 1 : 0 )
select
option(value='foo', selected) Foo
option(selected, value='bar') Bar
a(foo="class:")
input(pattern='\\S+')
a(href='/contact') contact
a(href='/save').button save
a(foo bar baz)
a(foo='foo, bar, baz' bar=1)
a(foo='((foo))' bar= (1) ? 1 : 0 )
select
option(value='foo' selected) Foo
option(selected value='bar') Bar
a(foo="class:")
input(pattern='\\S+')
foo(terse="true")
foo(date=new Date(0))
foo(abc
,def)
foo(abc,
def)
foo(abc,
def)
foo(abc
,def)
foo(abc
def)
foo(abc
def)
- var attrs = {foo: 'bar', bar: '<baz>'}
div&attributes(attrs)
a(foo='foo' "bar"="bar")
a(foo='foo' 'bar'='bar')

View File

@@ -0,0 +1,3 @@
script(type='text/x-template')
div(id!='user-<%= user.id %>')
h1 <%= user.title %>

View File

@@ -0,0 +1,3 @@
html
body
h1 Title

View File

@@ -0,0 +1,8 @@
ul
li foo
li bar
li baz

View File

@@ -0,0 +1,12 @@
-
list = ["uno", "dos", "tres",
"cuatro", "cinco", "seis"];
//- Without a block, the element is accepted and no code is generated
-
each item in list
-
string = item.charAt(0)
.toUpperCase() +
item.slice(1);
li= string

View File

@@ -0,0 +1,5 @@
ul
li: a(href='#') foo
li: a(href='#') bar
p baz

View File

@@ -0,0 +1,2 @@
ul
li.list-item: .foo: #bar baz

View File

@@ -0,0 +1,4 @@
figure
blockquote
| Try to define yourself by what you do, and you&#8217;ll burnout every time. You are. That is enough. I rest in that.
figcaption from @thefray at 1:43pm on May 10

View File

@@ -0,0 +1,4 @@
extends ./auxiliary/blocks-in-blocks-layout.pug
block body
h1 Page 2

View File

@@ -0,0 +1,19 @@
//- see https://github.com/pugjs/pug/issues/1589
-var ajax = true
-if( ajax )
//- return only contents if ajax requests
block contents
p ajax contents
-else
//- return all html
doctype html
html
head
meta( charset='utf8' )
title sample
body
block contents
p all contetns

View File

@@ -0,0 +1,10 @@
html
body
- var friends = 1
case friends
when 0
p you have no friends
when 1
p you have a friend
default
p you have #{friends} friends

View File

@@ -0,0 +1,19 @@
html
body
- var friends = 1
case friends
when 0: p you have no friends
when 1: p you have a friend
default: p you have #{friends} friends
- var friends = 0
case friends
when 0
when 1
p you have very few friends
default
p you have #{friends} friends
- var friend = 'Tim:G'
case friend
when 'Tim:G': p Friend is a string
when {tim: 'g'}: p Friend is an object

View File

@@ -0,0 +1,3 @@
a(class='')
a(class=null)
a(class=undefined)

View File

@@ -0,0 +1,14 @@
a(class=['foo', 'bar', 'baz'])
a.foo(class='bar').baz
a.foo-bar_baz
a(class={foo: true, bar: false, baz: true})
a.-foo
a.3foo

View File

@@ -0,0 +1,43 @@
- if (true)
p foo
- else
p bar
- if (true) {
p foo
- } else {
p bar
- }
if true
p foo
p bar
p baz
else
p bar
unless true
p foo
else
p bar
if 'nested'
if 'works'
p yay
//- allow empty blocks
if false
else
.bar
if true
.bar
else
.bing
if false
.bing
else if false
.bar
else
.foo

View File

@@ -0,0 +1,2 @@
p= '<script>'
p!= '<script>'

View File

@@ -0,0 +1,35 @@
- var items = [1,2,3]
ul
- items.forEach(function(item){
li= item
- })
- var items = [1,2,3]
ul
for item, i in items
li(class='item-' + i)= item
ul
each item, i in items
li= item
ul
each $item in items
li= $item
- var nums = [1, 2, 3]
- var letters = ['a', 'b', 'c']
ul
for l in letters
for n in nums
li #{n}: #{l}
- var count = 1
- var counter = function() { return [count++, count++, count++] }
ul
for n in counter()
li #{n}

View File

@@ -0,0 +1,10 @@
p= null
p= undefined
p= ''
p= 0
p= false
p(foo=null)
p(foo=undefined)
p(foo='')
p(foo=0)
p(foo=false)

View File

@@ -0,0 +1,10 @@
doctype html
html
body
- var s = 'this'
case s
//- Comment
when 'this'
p It's this!
when 'that'
p It's that!

View File

@@ -0,0 +1,29 @@
// foo
ul
// bar
li one
// baz
li two
//
ul
li foo
// block
// inline follow
li three
// block
// inline followed by tags
ul
li four
//if IE lt 9
// inline
script(src='/lame.js')
// end-inline
p five
.foo // not a comment

View File

@@ -0,0 +1,9 @@
//-
s/s.
//- test/cases/comments.source.pug
//-
test/cases/comments.source.pug
when
()

View File

@@ -0,0 +1 @@
doctype custom stuff

View File

@@ -0,0 +1,4 @@
doctype
html
body
h1 Title

View File

@@ -0,0 +1 @@
doctype html

View File

@@ -0,0 +1,52 @@
- var users = []
ul
for user in users
li= user.name
else
li no users!
- var users = [{ name: 'tobi', friends: ['loki'] }, { name: 'loki' }]
if users
ul
for user in users
li= user.name
else
li no users!
- var user = { name: 'tobi', age: 10 }
ul
each val, key in user
li #{key}: #{val}
else
li user has no details!
- var user = {}
ul
each prop, key in user
li #{key}: #{val}
else
li user has no details!
- var user = Object.create(null)
- user.name = 'tobi'
ul
each val, key in user
li #{key}: #{val}
else
li user has no details!
- var ofKeyword = [{ name: 'tobi', friends: ['loki'] }, { name: 'loki' }]
ul
each val of ofKeyword
li= user.name
ul
each val of ["variable with of keyword"]
li= val

View File

@@ -0,0 +1,2 @@
script.
var re = /\d+/;

View File

@@ -0,0 +1,8 @@
doctype html
html
head
title escape-test
body
textarea
- var txt = '<param name="flashvars" value="a=&quot;value_a&quot;&b=&quot;value_b&quot;&c=3"/>'
| #{txt}

View File

@@ -0,0 +1,6 @@
foo(attr="<%= bar %>")
foo(class="<%= bar %>")
foo(attr!="<%= bar %>")
foo(class!="<%= bar %>")
foo(class!="<%= bar %> lol rofl")
foo(class!="<%= bar %> lol rofl <%= lmao %>")

View File

@@ -0,0 +1 @@
include ./auxiliary/filter-in-include.pug

View File

@@ -0,0 +1,6 @@
- var users = [{ name: 'tobi', age: 2 }]
fb:users
for user in users
fb:user(age=user.age)
:cdata

View File

@@ -0,0 +1,6 @@
script(type='text/javascript')
:coffee-script
regexp = /\n/
:coffee-script(minify=true)
math =
square: (value) -> value * value

View File

@@ -0,0 +1,7 @@
html
body
:custom(opt='val' num=2)
Line 1
Line 2
Line 4

View File

@@ -0,0 +1,4 @@
html
body
pre
include:custom(opt='val' num=2) filters.include.custom.pug

View File

@@ -0,0 +1,7 @@
html
body
include:markdown-it some.md
script
include:coffee-script(minify=true) include-filter-coffee.coffee
script
include:cdata:coffee-script(minify=false) include-filter-coffee.coffee

View File

@@ -0,0 +1 @@
p before #[:cdata inside] after

View File

@@ -0,0 +1,8 @@
html
head
style(type="text/css")
:less
@pad: 15px;
body {
padding: @pad;
}

View File

@@ -0,0 +1,5 @@
html
body
:markdown-it
This is _some_ awesome **markdown**
whoop.

View File

@@ -0,0 +1,10 @@
script
:cdata:uglify-js
(function() {
console.log('test')
})()
script
:cdata:uglify-js:coffee-script
(->
console.log 'test'
)()

View File

@@ -0,0 +1,7 @@
html
head
style(type="text/css")
:stylus
body
padding: 50px
body

View File

@@ -0,0 +1,6 @@
html
div
:verbatim
filters are applied at compile time
with no #[b interpolation] at #{all}

View File

@@ -0,0 +1,13 @@
- var version = 1449104952939
<ul>
<li>foo</li>
<li>bar</li>
<li>baz</li>
</ul>
<!--build:js /js/app.min.js?v=#{version}-->
<!--endbuild-->
p You can <em>embed</em> html as well.
p: <strong>Even</strong> as the body of a block expansion.

View File

@@ -0,0 +1,4 @@
doctype html
input(type='checkbox', checked)
input(type='checkbox', checked=true)
input(type='checkbox', checked=false)

View File

@@ -0,0 +1 @@
include /auxiliary/extends-from-root.pug

View File

@@ -0,0 +1,2 @@
include auxiliary/extends-empty-block-1.pug
include auxiliary/extends-empty-block-2.pug

View File

@@ -0,0 +1 @@
include ../cases/auxiliary/extends-relative.pug

View File

@@ -0,0 +1,3 @@
| The message is "
yield
| "

View File

@@ -0,0 +1,5 @@
html
body
p
include include-only-text-body.pug
em hello world

View File

@@ -0,0 +1,3 @@
head
script(type='text/javascript').
alert('hello world');

View File

@@ -0,0 +1,4 @@
html
include include-with-text-head.pug
script(src='/caustic.js')
script(src='/app.js')

View File

@@ -0,0 +1,2 @@
script#pet-template(type='text/x-template')
include auxiliary/pet.pug

View File

@@ -0,0 +1,4 @@
include auxiliary/yield-nested.pug
p some content
p and some more

View File

@@ -0,0 +1,3 @@
pre
code
include javascript-new-lines.js

View File

@@ -0,0 +1,10 @@
include auxiliary/mixins.pug
+foo
body
include auxiliary/smile.html
include auxiliary/escapes.html
script(type="text/javascript")
include:verbatim auxiliary/includable.js

View File

@@ -0,0 +1,6 @@
extends auxiliary/dialog.pug
block content
h1 Alert!
p I'm an alert!

View File

@@ -0,0 +1,6 @@
html
head
block head
script(src='jquery.js')
script(src='keymaster.js')
script(src='caustic.js')

View File

@@ -0,0 +1,13 @@
extend auxiliary/layout.include.pug
block head
script(src='jquery.js')
block content
h2 Page
p Some content
block window-content
h2 Awesome
p Now we can extend included blocks!

View File

@@ -0,0 +1,4 @@
extend auxiliary/inheritance.extend.mixin.block.pug
block content
p Hello World!

View File

@@ -0,0 +1,11 @@
extend auxiliary/layout.pug
mixin article(title)
if title
h1= title
block
block content
+article("The meaning of life")
p Foo bar baz!

View File

@@ -0,0 +1,9 @@
extend auxiliary/layout.pug
block head
script(src='jquery.js')
block content
h2 Page
p Some content

View File

@@ -0,0 +1,4 @@
extends /auxiliary/inheritance.extend.recursive-parent.pug
block parent
h4 child

View File

@@ -0,0 +1,13 @@
extend auxiliary/layout.pug
block head
script(src='jquery.js')
block content
h2 Page
p Some content

View File

@@ -0,0 +1,9 @@
extends auxiliary/layout.pug
block head
script(src='jquery.js')
block content
h2 Page
p Some content

View File

@@ -0,0 +1,3 @@
block content // Main content goes here
append content // adding something to content
prepend content // adding something to other end of content

View File

@@ -0,0 +1,19 @@
p bing #[strong foo] bong
p.
bing
#[strong foo]
#[strong= '[foo]']
#[- var foo = 'foo]']
bong
p
| bing
| #[strong foo]
| #[strong= '[foo]']
| #[- var foo = 'foo]']
| bong
p.
\#[strong escaped]
\#[#[strong escaped]

View File

@@ -0,0 +1,3 @@
p #[a.rho(href='#', class='rho--modifier') with inline link]
p Some text #[a.rho(href='#', class='rho--modifier')]
p Some text #[a.rho(href='#', class='rho--modifier') with inline link]

View File

@@ -0,0 +1,4 @@
mixin linkit(url)
a(href=url)= url
p This also works #[+linkit('http://www.bing.com')] so hurrah for Pug

View File

@@ -0,0 +1,6 @@
- var id = 42;
foo
| some
| \#{text}
| \!{here}
| My ID #{"is {" + id + "}"}

View File

@@ -0,0 +1 @@
var x = '\n here is some \n new lined text';

View File

@@ -0,0 +1,6 @@
extends ../fixtures/append/app-layout.pug
block append head
script(src='foo.js')
script(src='bar.js')

View File

@@ -0,0 +1,6 @@
extends ../fixtures/append-without-block/app-layout.pug
append head
script(src='foo.js')
script(src='bar.js')

View File

@@ -0,0 +1,19 @@
extends ../fixtures/multi-append-prepend-block/redefine.pug
append content
p.first.append Something appended to content
prepend content
p.first.prepend Something prepended to content
append content
p.last.append Last append must be most last
prepend content
p.last.prepend Last prepend must appear at top
append head
script(src='jquery.js')
prepend head
script(src='foo.js')

View File

@@ -0,0 +1,6 @@
extends ../fixtures/prepend/app-layout.pug
block prepend head
script(src='foo.js')
script(src='bar.js')

View File

@@ -0,0 +1,6 @@
extends ../fixtures/prepend-without-block/app-layout.pug
prepend head
script(src='foo.js')
script(src='bar.js')

View File

@@ -0,0 +1,4 @@
include ./auxiliary/mixin-at-end-of-file.pug
+slide()
p some awesome content

View File

@@ -0,0 +1,6 @@
mixin m(id)
div
block
+m()
| This text should appear

View File

@@ -0,0 +1,7 @@
mixin foo()
h1= title
html
body
+foo

View File

@@ -0,0 +1,5 @@
//- regression test for https://github.com/pugjs/pug/issues/1435
include ../fixtures/mixin-include.pug
+bang

View File

@@ -0,0 +1,59 @@
mixin centered(title)
div.centered(id=attributes.id)
- if (title)
h1(class=attributes.class)= title
block
- if (attributes.href)
.footer
a(href=attributes.href) Back
mixin main(title)
div.stretch
+centered(title).highlight&attributes(attributes)
block
mixin bottom
div.bottom&attributes(attributes)
block
body
+centered#First Hello World
+centered('Section 1')#Second
p Some important content.
+centered('Section 2')#Third.foo(href='menu.html', class='bar')
p Even more important content.
+main('Section 3')(href='#')
p Last content.
+bottom.foo(class='bar', name='end', id='Last', data-attr='baz')
p Some final words.
+bottom(class=['class1', 'class2'])
mixin foo
div.thing(attr1='foo', attr2='bar')&attributes(attributes)
- var val = '<biz>'
- var classes = ['foo', 'bar']
+foo(attr3='baz' data-foo=val data-bar!=val class=classes).thunk
//- Regression test for #1424
mixin work_filmstrip_item(work)
div&attributes(attributes)= work
+work_filmstrip_item('work')("data-profile"='profile', "data-creator-name"='name')
mixin my-mixin(arg1, arg2, arg3, arg4)
p= arg1
p= arg2
p= arg3
p= arg4
+foo(
attr3="qux"
class="baz"
)
+my-mixin(
'1',
'2',
'3',
'4'
)

View File

@@ -0,0 +1,24 @@
mixin article(name)
section.article
h1= name
block
html
body
+article('Foo'): p I'm article foo
mixin article(name)
section.article
h1= name
p
block
html
body
+article('Something').
I'm a much longer
text-only article,
but you can still
inline html tags
in me if you want.

View File

@@ -0,0 +1,44 @@
mixin form(method, action)
form(method=method, action=action)
- var csrf_token_from_somewhere = 'hey'
input(type='hidden', name='_csrf', value=csrf_token_from_somewhere)
block
html
body
+form('GET', '/search')
input(type='text', name='query', placeholder='Search')
input(type='submit', value='Search')
html
body
+form('POST', '/search')
input(type='text', name='query', placeholder='Search')
input(type='submit', value='Search')
html
body
+form('POST', '/search')
mixin bar()
#bar
block
mixin foo()
#foo
+bar
block
+foo
p one
p two
p three
mixin baz
#baz
block
+baz()= '123'

View File

@@ -0,0 +1,15 @@
mixin foo
p.bar&attributes(attributes) One
p.baz.quux&attributes(attributes) Two
p&attributes(attributes) Three
p.bar&attributes(attributes)(class="baz") Four
body
+foo.hello
+foo#world
+foo.hello#world
+foo.hello.world
+foo(class="hello")
+foo.hello(class="world")
+foo
+foo&attributes({class: "hello"})

View File

@@ -0,0 +1,3 @@
mixin never-called
.wtf This isn't something we ever want to output
body

View File

@@ -0,0 +1,32 @@
mixin comment(title, str)
.comment
h2= title
p.body= str
mixin comment (title, str)
.comment
h2= title
p.body= str
#user
h1 Tobi
.comments
+comment('This',
(('is regular, javascript')))
mixin list
ul
li foo
li bar
li baz
body
+list()
+ list()
mixin foobar(str)
div#interpolation= str + 'interpolated'
- var suffix = "bar"
+#{'foo' + suffix}('This is ')

View File

@@ -0,0 +1,6 @@
mixin list(tag, ...items)
#{tag}
each item in items
li= item
+list('ul', 1, 2, 3, 4)

View File

@@ -0,0 +1,2 @@
fb:user:role Something
foo(fb:foo='bar')

View File

@@ -0,0 +1,8 @@
ul
li a
li b
li
ul
li c
li d
li e

View File

@@ -0,0 +1,4 @@
//
.foo
.bar
.hey

View File

@@ -0,0 +1,4 @@
:markdown-it
code sample
# Heading

View File

@@ -0,0 +1,3 @@
pre.
what
is #{'going'} #[| #{'on'}]

View File

@@ -0,0 +1,10 @@
pre.
foo
bar
baz
pre
code.
foo
bar
baz

View File

@@ -0,0 +1,2 @@
p "foo"
p 'foo'

View File

@@ -0,0 +1,4 @@
extends ./auxiliary/1794-extends.pug
block content
include ./auxiliary/1794-include.pug

View File

@@ -0,0 +1,2 @@
- var url = 'http://www.google.com'
.url #{url.replace('http://', '').replace(/^www\./, '')}

View File

@@ -0,0 +1,6 @@
script.
if (foo) {
bar();
}

View File

@@ -0,0 +1,9 @@
script#user-template(type='text/template')
#user
h1 <%= user.name %>
p <%= user.description %>
script#user-template(type='text/template').
if (foo) {
bar();
}

View File

@@ -0,0 +1,8 @@
script.
if (foo) {
bar();
}
script!= 'foo()'
script foo()
script
div

View File

@@ -0,0 +1,4 @@
doctype html
html
body
br/

Some files were not shown because too many files have changed in this diff Show More