Skip to content

carlos-sweb/zig-pug

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

227 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Espanol | English

zig-pug

A high-performance template engine inspired by Pug, implemented in Zig with full JavaScript support.

doctype html
html(lang="en")
  head
    title #{pageTitle.toUpperCase()}
  body
    h1.greeting Hello #{name}!
    p Age next year: #{age + 1}
    if isActive
      p.status Active user
    each item in items
      li= item

🎯 Features

  • Complete Pug syntax - Tags, attributes, classes, IDs, doctype
  • Full UTF-8 support - Accented characters (á, é, ñ, ü), emoji 🎉, all Unicode
  • JavaScript ES5.1 - Interpolations with methods, operators and expressions
  • Real JavaScript engine - Powered by mujs
  • Conditionals - if/else/else if/unless with full JavaScript expressions (>, <, >=, <=, ==, &&, ||)
  • Loops - each/while with array support
  • Mixins - Reusable components with arguments
  • Template inheritance - extends/block
  • JSON variables - Full support for strings, numbers, bools, arrays, and objects
  • Attribute expressions - Dynamic attribute values (class=myVar)
  • Buffered/unbuffered code - =, !=, and - operators
  • Documentation comments - //! for file metadata (ignored by parser)
  • Node.js addon - Native integration via N-API
  • Bun.js compatible - 2-5x faster than Node.js
  • C API - Full C-compatible API for FFI integration
  • C++ API - Modern C++ wrapper with RAII, exceptions, STL (C++11/17)
  • Editor support - VS Code, Sublime Text, CodeMirror
  • No dependencies - Only Zig 0.15.2 and embedded mujs
  • Fast - Native compilation in Zig with optimizations
  • Secure - HTML escaping and XSS prevention
  • Works on Termux/Android (CLI binary)
  • 87 unit tests - Comprehensive test coverage

Platform Notes:

  • Termux/Android: CLI binary works perfectly. Node.js addon compiles but cannot be loaded due to Android restrictions. See docs/en/TERMUX.md.
  • Windows: Pre-compiled binaries temporarily unavailable due to MinGW/MSVC toolchain incompatibility. Windows users can:

📦 Installation

Requirements

Clone and build

git clone https://github.com/carlos-sweb/zig-pug
cd zig-pug
zig build

Install (optional)

# Install system-wide
make install

# Uninstall
make uninstall

Run

# Run the compiled binary
./zig-out/bin/zpug template.zpug

# Or if installed
zpug template.zpug

CLI - Command Line Interface

zig-pug includes a powerful command line interface:

# Compile file to stdout
zpug template.zpug

# Compile with output file
zpug -i template.zpug -o output.html

# With variables (simple)
zpug template.zpug --var name=Alice --var age=25

# With arrays (CSV format)
zpug template.zpug --array items=apple,banana,orange

# With JSON (objects and complex structures)
zpug template.zpug --json user='{"name":"Alice","age":30}'

# With JSON file (arrays, objects, nested data)
zpug template.zpug --vars data.json

# Pretty-print with comments (development mode)
zpug -p template.zpug -o dev.html

# Pretty-print without comments (readable mode)
zpug -F template.zpug -o readable.html

# Minify output (production mode)
zpug -m template.zpug -o minified.html

# Default (production: no comments, minified)
zpug template.zpug -o output.html

# From stdin
cat template.zpug | zpug --stdin > output.html

Example with inline arrays and objects:

# CSV arrays (simple values)
zpug template.zpug --array fruits=apple,banana,orange --array scores=95,87,92

# JSON objects
zpug template.zpug --json user='{"name":"Alice","age":30,"email":"alice@example.com"}'

# JSON arrays (for complex data)
zpug template.zpug --json items='["apple","banana","orange"]'

# Mix all types
zpug template.zpug \
  --var title="Dashboard" \
  --array tags=prod,stable \
  --json user='{"name":"Alice","role":"admin"}' \
  --pretty

Example JSON variables file:

{
  "user": {
    "name": "Alice",
    "age": 30,
    "email": "alice@example.com"
  },
  "items": ["apple", "banana", "orange"],
  "active": true
}

See complete CLI documentation

🚀 Quick Start

Example: Complete Page

template.zpug:

doctype html
html(lang="en")
  head
    meta(charset="UTF-8")
    title #{pageTitle}
  body
    - var greeting = "Hello"
    h1.main-title= greeting + " " + userName

    if isLoggedIn
      p.status Welcome back, #{userName}!
      ul.menu
        each item in menuItems
          li
            a(href=item.url)= item.title
    else
      p Please log in

    mixin button(text, type)
      button(class=type)= text

    +button("Click me", "btn-primary")

Compile with:

zpug template.zpug --vars data.json -o output.html

Output:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>My Page</title></head><body><h1 class="main-title">Hello Alice</h1><p class="status">Welcome back, Alice!</p><ul class="menu"><li><a href="/home">Home</a></li><li><a href="/profile">Profile</a></li></ul><button class="btn-primary">Click me</button></body></html>

Supported Pug Syntax

Doctype

doctype html
// Output: <!DOCTYPE html>

doctype xml
// Output: <!DOCTYPE xml>

Tags and Attributes

// Simple tags
div
p Hello
span World

// Multiple classes (concatenated)
div.box.highlight.active
// Output: <div class="box highlight active">

// Classes and IDs
div.container
p#main-text
button.btn.btn-primary#submit

// Implicit divs (NEW in v4.0.0!)
.wrapper                    // <div class="wrapper">
#header                     // <div id="header">
(data-role="main")          // <div data-role="main">
#app.container(data-v="2")  // <div id="app" class="container" data-v="2">

// Attributes (static)
a(href="https://example.com" target="_blank") Link
input(type="text" name="username" required)

// Attributes (dynamic expressions)
- var myClass = "active"
- var myUrl = "/home"
button(class=myClass) Click
a(href=myUrl) Link
// Output: <button class="active">Click</button>

// Complex expressions (v4.0.0+)
- var alertType = "success"
- var userId = 42
div(class="alert alert-"+alertType)
div(id="user-"+userId)
// Output: <div class="alert alert-success">
//         <div id="user-42">

// Operators: +, -, ., [], <, >
a(href=user.profile.url) Profile
div(data-first=items[0]) First Item

// Multiple lines
div(
  class="card"
  id="user-card"
  data-user-id="123"
)

Buffered and Unbuffered Code

// Unbuffered code (executes but doesn't output)
- var name = "Alice"
- var age = 30
- var doubled = age * 2

// Buffered code inline (tag= syntax)
p= name
// Output: <p>Alice</p>

h1= name.toUpperCase()
// Output: <h1>ALICE</h1>

// Unescaped buffered code (tag!=)
- var html = "<strong>Bold</strong>"
div= html
// Output: <div>&lt;strong&gt;Bold&lt;/strong&gt;</div>

div!= html
// Output: <div><strong>Bold</strong></div>

JavaScript Interpolation

// Simple variables
p Hello #{name}

// String methods
p #{name.toUpperCase()}
p #{email.toLowerCase()}

// Arithmetic
p Age: #{age}
p Next year: #{age + 1}
p Double: #{age * 2}

// Objects (from JSON)
p Name: #{user.name}
p Email: #{user.email}
p Age: #{user.age}

// Arrays (from JSON)
p First: #{items[0]}
p Count: #{items.length}

// Complex expressions
p Full: #{firstName + ' ' + lastName}
p Status: #{age >= 18 ? 'Adult' : 'Minor'}

// Math
p Max: #{Math.max(10, 20)}
p Random: #{Math.floor(Math.random() * 100)}

HTML Escaping (XSS Security)

All interpolations are automatically escaped for security:

// Escaped by default (safe)
p #{userInput}
// Input: <script>alert('xss')</script>
// Output: <p>&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;</p>

// Unescaped (for trusted HTML only)
p !{trustedHtml}
// Input: <strong>Bold</strong>
// Output: <p><strong>Bold</strong></p>

Escaped characters: & < > " '

⚠️ Security: Only use !{} with HTML you control. Never with user input.

Conditionals

Full support for if, else, else if, and unless with complete JavaScript expressions:

// Basic if/else
if isLoggedIn
  p Welcome back!
else
  p Please log in

// Multiple else if (unlimited chaining)
if score > 90
  p Grade A
else if score > 80
  p Grade B
else if score > 70
  p Grade C
else
  p Grade F

// unless (negation)
unless isAdmin
  p Access denied

// Property access
if user.isPremium
  p Premium features enabled
else
  p Upgrade to premium

// Comparison operators (>, <, >=, <=, ==)
if age >= 18
  p Adult content
else if age >= 13
  p Teen content
else
  p Kids content

// Logical operators (&&, ||)
if age >= 18 && hasLicense
  p Can drive
else
  p Cannot drive

// String equality
if status == "active"
  p Account active
else if status == "pending"
  p Pending approval
else
  p Account inactive

// Complex expressions
if (isAdmin || isModerator) && user.isActive
  p Administrative access
else
  p Regular access

// Array length checks
if items.length > 0
  p Cart has #{items.length} items
else
  p Cart is empty

// Nested conditions
if user.isActive
  if user.isPremium
    p Premium active user
  else
    p Regular active user
else
  p Inactive account

Supported features:

  • ✅ Property access: user.isPremium, array.length
  • ✅ Comparison operators: >, <, >=, <=, ==
  • ✅ Logical operators: && (AND), || (OR)
  • ✅ String equality: status == "active"
  • ✅ Unlimited else if chaining
  • ✅ Nested conditionals
  • ✅ Complex combined expressions

Loops

// Each with arrays
each item in items
  li= item

// Each with index
each item, i in items
  li #{i}: #{item}

// Optional chaining (NEW!) - Works everywhere
// Safely access properties that may not exist

// In interpolations
p #{user?.name}
p #{user?.profile?.bio}

// In buffered code
h1= user?.name
p= product?.description

// In attributes
a(href=user?.website)
div(class=item?.theme)

// In loops
each tag in product?.tags
  span.tag= tag

// Nested optional chaining
each item in data?.products?.featured
  li= item

// While loops
- var count = 0
while count < 5
  p Count: #{count}
  - count = count + 1

Optional Chaining (?.): Works in all contexts (interpolations, buffered code, attributes, loops). Eliminates manual hasOwnProperty checks. If the property doesn't exist, returns empty value—no errors thrown.

Mixins with Arguments

// Define mixin
mixin greeting(name)
  p Hello, #{name}!

mixin button(text, type)
  button(class=type)= text

// Use mixins
+greeting("World")
+greeting("Alice")

+button("Click me", "btn-primary")
+button("Cancel", "btn-secondary")

// Output:
// <p>Hello, World!</p>
// <p>Hello, Alice!</p>
// <button class="btn-primary">Click me</button>
// <button class="btn-secondary">Cancel</button>

Template Inheritance (Extends/Block)

Build reusable layouts with template inheritance using extends and block:

// layout.zpug - Base layout
doctype html
html
  head
    title
      block title
        | Default Title
  body
    header
      h1 My Website
    main
      block content
        p Default content
    footer
      block footer
        p © 2024

// page.zpug - Extends layout
extends layout.zpug

block title
  | Home Page

block content
  h2 Welcome
  p This replaces the default block content

// Output:
// <!DOCTYPE html>
// <html>
//   <head><title>Home Page</title></head>
//   <body>
//     <header><h1>My Website</h1></header>
//     <main>
//       <h2>Welcome</h2>
//       <p>This replaces the default block content</p>
//     </main>
//     <footer><p>© 2024</p></footer>
//   </body>
// </html>

Block Modes:

// Replace (default) - Replaces parent block completely
block content
  p New content

// Append - Adds after parent block
block append content
  p Added after default

// Prepend - Adds before parent block
block prepend content
  p Added before default

Path Syntax:

// Unquoted paths (recommended)
extends layout.zpug
extends ../layouts/base.zpug

// Quoted paths
extends "layout.zpug"
extends "../layouts/base.zpug"

See examples/extends/ for complete working examples.

Comments

//! Documentation comment (completely ignored)
//! Can appear before doctype declarations

// Buffered comment (visible in HTML with --pretty)
// This appears only in development mode

//- Unbuffered comment (never in HTML)
//- This is only in source, never compiled

// Security: Comments are escaped
// Comment with --> injection attempt
// Output: <!-- Comment with - -> injection attempt -->

Comment Types:

Syntax Name Processed? In HTML? Use Case
//! Documentation ❌ No ❌ No File metadata, author notes
// Buffered ✅ Yes ✅ Yes (--pretty only) Development debugging
//- Unbuffered ✅ Yes ❌ No Code comments

Comment Behavior:

  • Documentation comments (//!): Completely ignored by tokenizer (can appear before doctype)
  • Production mode (default): All buffered comments (//) are stripped for minimal file size
  • Development mode (--pretty): Buffered comments (//) are included for debugging
  • Readable mode (--format): Pretty-print without comments
  • Unbuffered comments (//-): Always stripped in all modes
# Production: no comments, minified
zpug template.zpug -o output.html

# Development: with comments and indentation
zpug --pretty template.zpug -o output.html

# Readable: indentation without comments
zpug --format template.zpug -o output.html

This matches industry standards (Pug, HTML minifiers) where production output is optimized and development output is readable.

UTF-8 Support

Full Unicode support for international characters in all template elements:

doctype html
html(lang="es")
  head
    title Página en Español
  body
    h1 Bienvenido 🎉

    // Spanish
    p.información Este es un párrafo con acentos: José, María, Ángel

    // Portuguese
    p.português Programação em português com ã, õ, ç

    // French
    p.français Génération française avec é, è, ê, ç

    // German
    p#größe Deutsche Größe mit ä, ö, ü, ß

    // Emoji and symbols
    p Symbols: © ™ € £ ¥ • Emoji: 🚀 ✨ 💻 🌍

Supported:

  • ✅ Accented characters in text: á é í ó ú ñ ü ç
  • ✅ Accented characters in class names: .información
  • ✅ Accented characters in IDs: #descripción
  • ✅ Accented characters in comments: // útil
  • ✅ Emoji and Unicode symbols: 🎉 © ™ €
  • ✅ All UTF-8 sequences (1-4 bytes)

Programming API (Zig)

Complete Example

const std = @import("std");
const parser = @import("parser.zig");
const compiler = @import("compiler.zig");
const runtime = @import("runtime.zig");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Create JavaScript runtime
    var js_runtime = try runtime.JsRuntime.init(allocator);
    defer js_runtime.deinit();

    // Set variables
    try js_runtime.setString("name", "Alice");
    try js_runtime.setNumber("age", 25);
    try js_runtime.setBool("active", true);

    // Parse template
    const source =
        \\doctype html
        \\html
        \\  body
        \\    h1= name
        \\    p Age: #{age}
    ;

    var pars = try parser.Parser.init(allocator, source);
    defer pars.deinit();
    const tree = try pars.parse();

    // Compile to HTML
    var comp = try compiler.Compiler.init(allocator, js_runtime);
    defer comp.deinit();
    const html = try comp.compile(tree);
    defer allocator.free(html);

    std.debug.print("{s}\n", .{html});
}

Working with JSON Data

// Set arrays
const items = [_][]const u8{ "apple", "banana", "orange" };
for (items) |item| {
    // Arrays are set via JavaScript code
    _ = try js_runtime.eval("var items = ['apple', 'banana', 'orange']");
}

// Set objects
_ = try js_runtime.eval("var user = {name: 'Bob', age: 30}");

// Access properties
const name = try js_runtime.eval("user.name");
defer allocator.free(name);

Complete Documentation

Getting Started

Integration

Advanced

Testing

# Run all tests
zig build test

# View detailed results
zig build test --summary all

Test status: ✅ All 87 tests passing

See docs/tests/ for detailed test documentation.

Architecture

zig-pug uses a two-phase architecture for processing templates:

Source (*.zpug)
      ↓
  Tokenizer (lexical analysis)
      ↓
   Parser (syntactic analysis)
      ↓
     AST (abstract syntax tree)
      ↓
  Compiler ← JS Runtime (mujs)
      ↓
    HTML (output)

Conditional Expression Evaluation

zig-pug uses a separation of concerns approach:

Phase 1: Parser (Zig)

  • Recognizes operators as tokens (>=, &&, ||, etc.)
  • Reconstructs JavaScript expressions as strings
  • Does NOT evaluate expressions

Phase 2: mujs (C)

  • Evaluates JavaScript expressions
  • Handles all operators, property access, methods
  • Returns results to the compiler

Example:

if age >= 18 && hasLicense
  p Can drive

Parser produces: "age>=18&&hasLicense" (string) mujs evaluates: 25 >= 18 && truetrue (result)

This architecture provides:

  • ✅ Complete JavaScript ES5.1 support automatically
  • ✅ Simple parser code (just string concatenation)
  • ✅ Reliable evaluation (mujs is battle-tested)
  • ✅ Same approach as Pug.js (delegates to JavaScript engine)

For details: See docs/en/ARCHITECTURE.md

Performance Optimizations

  • HTML escaping - Pre-calculated buffer size (single allocation)
  • JS code generation - Pre-allocated buffers for arrays/objects
  • mujs - Compiled with -O2 optimization
  • Release builds - Forced ReleaseFast for mujs compatibility

JavaScript Engine

zig-pug uses mujs as its JavaScript engine:

  • Version: mujs 1.3.8
  • Standard: ES5.1 compliant
  • Size: 590 KB
  • Dependencies: None (only libm)
  • Used by: MuPDF, Ghostscript

Supported JavaScript (ES5.1)

Fully supported:

  • String methods, Number methods, Array methods
  • Object property access, Arithmetic/Comparison/Logical operators
  • Ternary operator, Math object, JSON object

Not supported (ES6+):

  • Arrow functions, Template literals, let/const, Async/await, Classes

For template engines, ES5.1 is completely sufficient.

Project Status

✅ Completed (v0.2.0)

Phase 1: Critical Bugs

  • Multiple classes concatenation
  • Loop iterator parsing
  • Mixin arguments
  • Comment escaping (security)

Phase 2: API & Variables

  • JSON arrays support (--vars)
  • JSON objects support
  • Attribute expressions (class=myVar)
  • Unbuffered code (- lines)

Phase 3: UX Improvements

  • Error messages with line numbers and hints
  • Tag= and tag!= syntax
  • Doctype support

Phase 4: Performance

  • Optimized HTML escaping
  • Optimized JS code generation

Phase 5: Testing

  • 87 comprehensive unit tests
  • Test documentation

Phase 6: Production Features

  • Comment handling (production vs development modes)
  • Pretty printing (HTML indentation)

🚧 In Progress

  • Watch mode (-w)

📋 Roadmap

See PLAN.md for the complete development plan.

📚 Documentation

Technical Documentation

Compilation Pipeline:

  • Tokenizer (Español) - Lexical analysis: state machine, token types, indentation handling
  • Parser (Español) - Syntax analysis: recursive descent, AST building, error handling
  • AST (Español) - Abstract Syntax Tree: node types, structure, visitor pattern
  • Compiler (Español) - HTML generation: JavaScript evaluation, security, template features

Language APIs:

Additional Resources:

Language-Specific Guides

Contributing

Contributions are welcome! Please:

  1. Fork the project
  2. Create a branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

MIT License - see LICENSE for details

Acknowledgments

Support


Made with ❤️ using Zig 0.15.2 and mujs

About

Engine Template

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors