| Name | xvcl JSON |
| Version |
2.5.0
JSON |
| download |
| home_page | None |
| Summary | Extended VCL compiler with metaprogramming features for Fastly VCL |
| upload_time | 2025-10-11 09:16:25 |
| maintainer | None |
| docs_url | None |
| author | None |
| requires_python | >=3.9 |
| license | MIT |
| keywords |
compiler
fastly
preprocessor
varnish
vcl
|
| VCS |
|
| bugtrack_url |
|
| requirements |
No requirements were recorded.
|
| Travis-CI |
No Travis.
|
| coveralls test coverage |
No coveralls.
|
# xvcl
Supercharge your Fastly VCL with programming constructs like loops, functions, constants, and more.
**📖 [Quick Reference Guide](xvcl-quick-reference.md)** - One-page syntax reference for all xvcl features
## Table of Contents
- [Introduction](#introduction)
- [Why Use xvcl?](#why-use-xvcl)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Features](#core-features)
- [Constants](#constants)
- [Template Expressions](#template-expressions)
- [For Loops](#for-loops)
- [Conditionals](#conditionals)
- [Variables](#variables)
- [File Includes](#file-includes)
- [Advanced Features](#advanced-features)
- [Inline Macros](#inline-macros)
- [Functions](#functions)
- [Command-Line Usage](#command-line-usage)
- [Integration with Falco](#integration-with-falco)
- [Best Practices](#best-practices)
- [Troubleshooting](#troubleshooting)
## Introduction
xvcl is VCL transpiler that extends Fastly VCL with programming constructs that generate standard VCL code.
Think of it as a build step for your VCL: write enhanced VCL source files, run xvcl, and get clean, valid VCL output.
> **💡 Tip:** For a quick syntax reference, see the [xvcl Quick Reference Guide](xvcl-quick-reference.md).
**What you can do:**
- Define constants once, use them everywhere
- Generate repetitive code with for loops
- Create reusable functions with return values
- Build zero-overhead macros for common patterns
- Conditionally compile code for different environments
- Split large VCL files into modular includes
**What you get:**
- Standard VCL output that works with Fastly
- Compile-time safety and error checking
- Reduced code duplication
- Better maintainability
## Why Use xvcl?
VCL is powerful but limited by design. You can't define functions with return values, you can't use loops, and managing constants means find-and-replace. This leads to:
- **Copy-paste errors:** Similar backends? Copy, paste, modify, repeat, make mistakes
- **Magic numbers:** Hardcoded values scattered throughout your code
- **Duplication:** Same logic repeated in multiple subroutines
- **Poor maintainability:** Change one thing, update it in 20 places
xvcl solves these problems by adding programming constructs that compile down to clean VCL.
### Real-World Example
**Without xvcl (manual, error-prone):**
```vcl
backend web1 {
.host = "web1.example.com";
.port = "80";
}
backend web2 {
.host = "web2.example.com";
.port = "80";
}
backend web3 {
.host = "web3.example.com";
.port = "80";
}
sub vcl_recv {
if (req.http.Host == "web1.example.com") {
set req.backend = web1;
}
if (req.http.Host == "web2.example.com") {
set req.backend = web2;
}
if (req.http.Host == "web3.example.com") {
set req.backend = web3;
}
}
```
**With xvcl (clean, maintainable):**
```vcl
#const BACKENDS = ["web1", "web2", "web3"]
#for backend in BACKENDS
backend {{backend}} {
.host = "{{backend}}.example.com";
.port = "80";
}
#endfor
sub vcl_recv {
#for backend in BACKENDS
if (req.http.Host == "{{backend}}.example.com") {
set req.backend = {{backend}};
}
#endfor
}
```
Adding a new backend? Just update the list. xvcl generates all the code.
## Installation
xvcl is a Python package. You need Python 3.9 or later.
```bash
# Using pip
pip install xvcl
# Or install from source
pip install .
# Development installation with dev dependencies
pip install -e ".[dev]"
# Using uv (recommended for faster installation)
uv pip install xvcl
```
After installation, the `xvcl` command is available globally.
**No external dependencies** - uses only Python standard library.
## Quick Start
Create an xvcl source file (use `.xvcl` extension by convention):
**`hello.xvcl`:**
```vcl
#const MESSAGE = "Hello from xvcl!"
sub vcl_recv {
set req.http.X-Message = "{{MESSAGE}}";
}
```
Run xvcl:
```bash
xvcl hello.xvcl -o hello.vcl
```
Output **`hello.vcl`:**
```vcl
sub vcl_recv {
set req.http.X-Message = "Hello from xvcl!";
}
```
Validate with Falco:
```bash
falco lint hello.vcl
```
## Core Features
### Constants
Define named constants with type checking. Constants are evaluated at preprocessing time and substituted into your code.
**Syntax:**
```vcl
#const NAME TYPE = value
```
**Supported types:**
- `INTEGER` - Whole numbers
- `STRING` - Text strings
- `FLOAT` - Decimal numbers
- `BOOL` - True/False
**Example:**
```vcl
#const MAX_AGE INTEGER = 3600
#const ORIGIN STRING = "origin.example.com"
#const PRODUCTION BOOL = True
#const CACHE_VERSION FLOAT = 1.5
```
**Using constants in templates:**
```vcl
#const TTL = 300
#const BACKEND_HOST = "api.example.com"
backend F_api {
.host = "{{BACKEND_HOST}}";
.port = "443";
}
sub vcl_fetch {
set beresp.ttl = {{TTL}}s;
}
```
**Why use constants?**
- Single source of truth for configuration values
- Easy to update across entire VCL
- Type safety prevents mistakes
- Self-documenting code
### Template Expressions
Embed Python expressions in your VCL using `{{expression}}` syntax. Expressions are evaluated at preprocessing time.
**Example:**
```vcl
#const PORT = 8080
sub vcl_recv {
set req.http.X-Port = "{{PORT}}";
set req.http.X-Double = "{{PORT * 2}}";
set req.http.X-Hex = "{{hex(PORT)}}";
}
```
**Output:**
```vcl
sub vcl_recv {
set req.http.X-Port = "8080";
set req.http.X-Double = "16160";
set req.http.X-Hex = "0x1f90";
}
```
**Available functions:**
- `range(n)` - Generate number sequences
- `len(list)` - Get list length
- `str(x)`, `int(x)` - Type conversions
- `hex(n)` - Hexadecimal conversion
- `format(x, fmt)` - Format values
- `enumerate(iterable)` - Enumerate with indices
- `min(...)`, `max(...)` - Min/max values
- `abs(n)` - Absolute value
**String formatting:**
```vcl
#const REGION = "us-east"
#const INDEX = 1
set req.backend = F_backend_{{REGION}}_{{INDEX}};
```
### For Loops
Generate repetitive VCL code by iterating over ranges or lists.
**Syntax:**
```vcl
#for variable in iterable
// Code to repeat
#endfor
```
**Example 1: Range-based loop**
```vcl
#for i in range(5)
backend web{{i}} {
.host = "web{{i}}.example.com";
.port = "80";
}
#endfor
```
**Output:**
```vcl
backend web0 {
.host = "web0.example.com";
.port = "80";
}
backend web1 {
.host = "web1.example.com";
.port = "80";
}
// ... continues through web4
```
**Example 2: List iteration**
```vcl
#const REGIONS = ["us-east", "us-west", "eu-west"]
#for region in REGIONS
backend F_{{region}} {
.host = "{{region}}.example.com";
.port = "443";
.ssl = true;
}
#endfor
```
**Example 3: Nested loops**
```vcl
#const REGIONS = ["us", "eu"]
#const ENVS = ["prod", "staging"]
#for region in REGIONS
#for env in ENVS
backend {{region}}_{{env}} {
.host = "{{env}}.{{region}}.example.com";
.port = "443";
}
#endfor
#endfor
```
**Why use for loops?**
- Generate multiple similar backends
- Create ACL entries from lists
- Build routing logic programmatically
- Reduce copy-paste errors
### Conditionals
Conditionally include or exclude code based on compile-time conditions.
**Syntax:**
```vcl
#if condition
// Code when true
#else
// Code when false (optional)
#endif
```
**Example 1: Environment-specific configuration**
```vcl
#const PRODUCTION = True
#const DEBUG = False
sub vcl_recv {
#if PRODUCTION
set req.http.X-Environment = "production";
unset req.http.X-Debug-Info;
#else
set req.http.X-Environment = "development";
set req.http.X-Debug-Info = "Enabled";
#endif
#if DEBUG
set req.http.X-Request-ID = randomstr(16, "0123456789abcdef");
#endif
}
```
**Example 2: Feature flags**
```vcl
#const ENABLE_NEW_ROUTING = True
#const ENABLE_RATE_LIMITING = False
sub vcl_recv {
#if ENABLE_NEW_ROUTING
call new_routing_logic;
#else
call legacy_routing_logic;
#endif
#if ENABLE_RATE_LIMITING
if (ratelimit.check_rate("client_" + client.ip, 1, 100, 60s, 1000s)) {
error 429 "Too Many Requests";
}
#endif
}
```
**Why use conditionals?**
- Single codebase for multiple environments
- Easy feature flag management
- Dead code elimination (code in false branches isn't generated)
- Compile-time optimization
### Variables
Declare and initialize local variables in one step.
**Syntax:**
```vcl
#let name TYPE = expression;
```
**Example:**
```vcl
sub vcl_recv {
#let timestamp STRING = std.time(now, now);
#let cache_key STRING = req.url.path + req.http.Host;
set req.http.X-Timestamp = var.timestamp;
set req.hash = var.cache_key;
}
```
**Expands to:**
```vcl
sub vcl_recv {
declare local var.timestamp STRING;
set var.timestamp = std.time(now, now);
declare local var.cache_key STRING;
set var.cache_key = req.url.path + req.http.Host;
set req.http.X-Timestamp = var.timestamp;
set req.hash = var.cache_key;
}
```
**Why use #let?**
- Shorter syntax than separate declare + set
- Clear initialization point
- Reduces boilerplate
### File Includes
Split large VCL files into modular, reusable components.
**Syntax:**
```vcl
#include "path/to/file.xvcl"
```
**Example project structure:**
```
vcl/
├── main.xvcl
├── includes/
│ ├── backends.xvcl
│ ├── security.xvcl
│ └── routing.xvcl
```
**`main.xvcl`:**
```vcl
#include "includes/backends.xvcl"
#include "includes/security.xvcl"
#include "includes/routing.xvcl"
sub vcl_recv {
call security_checks;
call routing_logic;
}
```
**`includes/backends.xvcl`:**
```vcl
#const BACKENDS = ["web1", "web2", "web3"]
#for backend in BACKENDS
backend F_{{backend}} {
.host = "{{backend}}.example.com";
.port = "443";
}
#endfor
```
**Include path resolution:**
1. Relative to the current file
2. Relative to include paths specified with `-I`
**Run with include paths:**
```bash
xvcl main.xvcl -o main.vcl -I ./vcl/includes
```
**Features:**
- **Include-once semantics:** Files are only included once even if referenced multiple times
- **Cycle detection:** Prevents circular includes
- **Shared constants:** Constants defined in included files are available to the parent
**Why use includes?**
- Organize large VCL projects
- Share common configurations across multiple VCL files
- Team collaboration (different files for different concerns)
- Reusable components library
## Advanced Features
### Inline Macros
Create zero-overhead text substitution macros. Unlike functions, macros are expanded inline at compile time with no runtime cost.
**Syntax:**
```vcl
#inline macro_name(param1, param2, ...)
expression
#endinline
```
**Example 1: String concatenation**
```vcl
#inline add_prefix(s)
"prefix-" + s
#endinline
#inline add_suffix(s)
s + "-suffix"
#endinline
sub vcl_recv {
set req.http.X-Modified = add_prefix("test");
set req.http.X-Both = add_prefix(add_suffix("middle"));
}
```
**Output:**
```vcl
sub vcl_recv {
set req.http.X-Modified = "prefix-" + "test";
set req.http.X-Both = "prefix-" + "middle" + "-suffix";
}
```
**Example 2: Common patterns**
```vcl
#inline normalize_host(host)
std.tolower(regsub(host, "^www\.", ""))
#endinline
#inline cache_key(url, host)
digest.hash_md5(url + "|" + host)
#endinline
sub vcl_recv {
set req.http.X-Normalized = normalize_host(req.http.Host);
set req.hash = cache_key(req.url, req.http.Host);
}
```
**Output:**
```vcl
sub vcl_recv {
set req.http.X-Normalized = std.tolower(regsub(req.http.Host, "^www\.", ""));
set req.hash = digest.hash_md5(req.url + "|" + req.http.Host);
}
```
**Example 3: Operator precedence handling**
xvcl automatically handles operator precedence:
```vcl
#inline double(x)
x + x
#endinline
sub vcl_recv {
declare local var.result INTEGER;
set var.result = double(5) * 10; // Correctly expands to (5 + 5) * 10
}
```
**Macros vs Functions:**
| Feature | Macros | Functions |
| ------------- | ------------------- | ----------------------------- |
| Expansion | Compile-time inline | Runtime subroutine call |
| Overhead | None | Subroutine call + global vars |
| Return values | Expression only | Single or tuple |
| Use case | Simple expressions | Complex logic |
**When to use macros:**
- String manipulation patterns
- Simple calculations
- Common expressions repeated throughout code
- When you need zero runtime overhead
**When to use functions:**
- Complex logic with multiple statements
- Need to return multiple values
- Conditional logic or loops inside the reusable code
### Functions
Define reusable functions with parameters and return values. Functions are compiled into VCL subroutines.
**Syntax:**
```vcl
#def function_name(param1 TYPE, param2 TYPE, ...) -> RETURN_TYPE
// Function body
return value;
#enddef
```
**Example 1: Simple function**
```vcl
#def add(a INTEGER, b INTEGER) -> INTEGER
declare local var.sum INTEGER;
set var.sum = a + b;
return var.sum;
#enddef
sub vcl_recv {
declare local var.result INTEGER;
set var.result = add(5, 10);
set req.http.X-Sum = var.result;
}
```
**Example 2: String processing**
```vcl
#def normalize_path(path STRING) -> STRING
declare local var.result STRING;
set var.result = std.tolower(path);
set var.result = regsub(var.result, "/$", "");
return var.result;
#enddef
sub vcl_recv {
declare local var.clean_path STRING;
set var.clean_path = normalize_path(req.url.path);
set req.url = var.clean_path;
}
```
**Example 3: Functions with conditionals**
```vcl
#def should_cache(url STRING) -> BOOL
declare local var.cacheable BOOL;
if (url ~ "^/api/") {
set var.cacheable = false;
} else if (url ~ "\.(jpg|png|css|js)$") {
set var.cacheable = true;
} else {
set var.cacheable = false;
}
return var.cacheable;
#enddef
sub vcl_recv {
declare local var.can_cache BOOL;
set var.can_cache = should_cache(req.url.path);
if (var.can_cache) {
return(lookup);
} else {
return(pass);
}
}
```
**Example 4: Tuple returns (multiple values)**
```vcl
#def parse_user_agent(ua STRING) -> (STRING, STRING)
declare local var.browser STRING;
declare local var.os STRING;
if (ua ~ "Chrome") {
set var.browser = "chrome";
} else if (ua ~ "Firefox") {
set var.browser = "firefox";
} else {
set var.browser = "other";
}
if (ua ~ "Windows") {
set var.os = "windows";
} else if (ua ~ "Mac") {
set var.os = "macos";
} else {
set var.os = "other";
}
return var.browser, var.os;
#enddef
sub vcl_recv {
declare local var.browser STRING;
declare local var.os STRING;
set var.browser, var.os = parse_user_agent(req.http.User-Agent);
set req.http.X-Browser = var.browser;
set req.http.X-OS = var.os;
}
```
**Behind the scenes:**
Functions are compiled into VCL subroutines using global headers for parameter passing:
```vcl
// Your code:
set var.result = add(5, 10);
// Becomes:
set req.http.X-Func-add-a = std.itoa(5);
set req.http.X-Func-add-b = std.itoa(10);
call add;
set var.result = std.atoi(req.http.X-Func-add-Return);
```
xvcl generates the `sub add { ... }` implementation and handles all type conversions automatically.
**Function features:**
- **Type safety:** Parameters and returns are type-checked
- **Multiple returns:** Use tuple syntax to return multiple values
- **Automatic conversions:** INTEGER/FLOAT/BOOL are converted to/from STRING automatically
- **Scope annotations:** Generated subroutines work in all VCL scopes
**Why use functions?**
- Reusable complex logic
- Reduce code duplication
- Easier testing (test the function once)
- Better code organization
## Command-Line Usage
**Basic usage:**
```bash
xvcl input.xvcl -o output.vcl
```
**Options:**
| Option | Description |
| --------------- | --------------------------------------------------- |
| `input` | Input xvcl source file (required) |
| `-o, --output` | Output VCL file (default: replaces .xvcl with .vcl) |
| `-I, --include` | Add include search path (repeatable) |
| `--debug` | Enable debug output with expansion traces |
| `--source-maps` | Add source map comments to output |
| `-v, --verbose` | Verbose output (alias for --debug) |
**Examples:**
```bash
# Basic compilation
xvcl main.xvcl -o main.vcl
# With include paths
xvcl main.xvcl -o main.vcl \
-I ./includes \
-I ./shared
# Debug mode (see expansion traces)
xvcl main.xvcl -o main.vcl --debug
# With source maps (track generated code origin)
xvcl main.xvcl -o main.vcl --source-maps
```
**Automatic output naming:**
If you don't specify `-o`, xvcl replaces `.xvcl` with `.vcl`:
```bash
# These are equivalent:
xvcl main.xvcl
xvcl main.xvcl -o main.vcl
```
**Debug mode output:**
```bash
$ xvcl example.xvcl --debug
[DEBUG] Processing file: example.xvcl
[DEBUG] Pass 1: Extracting constants
[DEBUG] Defined constant: MAX_AGE = 3600
[DEBUG] Pass 2: Processing includes
[DEBUG] Pass 3: Extracting inline macros
[DEBUG] Defined macro: add_prefix(s)
[DEBUG] Pass 4: Extracting functions
[DEBUG] Pass 5: Processing directives and generating code
[DEBUG] Processing #for at line 10
[DEBUG] Loop iterating 3 times
[DEBUG] Iteration 0: backend = web1
[DEBUG] Iteration 1: backend = web2
[DEBUG] Iteration 2: backend = web3
[DEBUG] Pass 6: Generating function subroutines
✓ Compiled example.xvcl -> example.vcl
Constants: 1
Macros: 1 (add_prefix)
Functions: 0
```
## Integration with Falco
xvcl generates standard VCL that you can use with Falco's full toolset.
**Recommended workflow:**
```bash
# 1. Write your xvcl source
vim main.xvcl
# 2. Compile with xvcl
xvcl main.xvcl -o main.vcl
# 3. Lint with Falco
falco lint main.vcl
# 4. Test with Falco
falco test main.vcl
# 5. Simulate with Falco
falco simulate main.vcl
```
**Makefile integration:**
```makefile
# Makefile
.PHONY: build lint test clean
XVCL = xvcl
SOURCES = $(wildcard *.xvcl)
OUTPUTS = $(SOURCES:.xvcl=.vcl)
build: $(OUTPUTS)
%.vcl: %.xvcl
$(XVCL) $< -o $@ -I ./includes
lint: build
falco lint *.vcl
test: build
falco test *.vcl
clean:
rm -f $(OUTPUTS)
```
**CI/CD integration:**
```yaml
# .github/workflows/vcl.yml
name: VCL CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install Falco
run: |
wget https://github.com/ysugimoto/falco/releases/latest/download/falco_linux_amd64
chmod +x falco_linux_amd64
sudo mv falco_linux_amd64 /usr/local/bin/falco
- name: Compile xvcl
run: |
xvcl main.xvcl -o main.vcl
- name: Lint VCL
run: falco lint main.vcl
- name: Test VCL
run: falco test main.vcl
```
**Testing compiled VCL:**
You can write Falco tests for your generated VCL:
**`main.test.vcl`:**
```vcl
// @suite: Backend routing tests
// @test: Should route to correct backend
sub test_backend_routing {
set req.http.Host = "web1.example.com";
call vcl_recv;
assert.equal(req.backend, "web1");
}
```
Run tests after compilation:
```bash
xvcl main.xvcl -o main.vcl
falco test main.vcl
```
## Best Practices
### 1. Use the `.xvcl` extension
Makes it clear which files are xvcl source files:
```
✓ main.xvcl → main.vcl
✗ main.vcl → main.vcl.processed
```
### 2. Keep constants at the top
```vcl
// Good: Constants first, easy to find
#const MAX_BACKENDS = 10
#const PRODUCTION = True
#for i in range(MAX_BACKENDS)
// ... use constant
#endfor
```
### 3. Use descriptive constant names
```vcl
// Good
#const CACHE_TTL_SECONDS = 3600
#const API_BACKEND_HOST = "api.example.com"
// Bad
#const X = 3600
#const B = "api.example.com"
```
### 4. Comment your macros and functions
```vcl
// Normalizes a hostname by removing www prefix and converting to lowercase
#inline normalize_host(host)
std.tolower(regsub(host, "^www\.", ""))
#endinline
// Parses User-Agent and returns (browser, os) tuple
#def parse_user_agent(ua STRING) -> (STRING, STRING)
// ...
#enddef
```
### 5. Prefer macros for simple expressions, functions for complex logic
```vcl
// Good: Simple expression = macro
#inline cache_key(url, host)
digest.hash_md5(url + "|" + host)
#endinline
// Good: Complex logic = function
#def should_cache(url STRING, method STRING) -> BOOL
declare local var.result BOOL;
if (method != "GET" && method != "HEAD") {
set var.result = false;
} else if (url ~ "^/api/") {
set var.result = false;
} else {
set var.result = true;
}
return var.result;
#enddef
```
### 6. Use includes for organization
```
vcl/
├── main.xvcl # Main entry point
├── config.xvcl # Constants and configuration
├── includes/
│ ├── backends.xvcl
│ ├── security.xvcl
│ ├── routing.xvcl
│ └── caching.xvcl
```
### 7. Version control both source and output
```gitignore
# Include both in git
*.xvcl
*.vcl
# But gitignore generated files in CI
# (if you regenerate on deploy)
```
**Or** only version control source files and regenerate on deployment:
```gitignore
# Version control xvcl source only
*.xvcl
# Ignore generated VCL
*.vcl
```
Choose based on your deployment process.
### 8. Add source maps in development
```bash
# Development: easier debugging
xvcl main.xvcl -o main.vcl --source-maps
# Production: cleaner output
xvcl main.xvcl -o main.vcl
```
Source maps add comments like:
```vcl
// BEGIN INCLUDE: includes/backends.xvcl
backend F_web1 { ... }
// END INCLUDE: includes/backends.xvcl
```
### 9. Test incrementally
Don't write a massive source file and compile once. Test as you go:
```bash
# Write a bit
vim main.xvcl
# Compile
xvcl main.xvcl
# Check output
cat main.vcl
# Lint
falco lint main.vcl
# Repeat
```
### 10. Use debug mode when things go wrong
```bash
xvcl main.xvcl --debug
```
Shows exactly what xvcl is doing.
## Troubleshooting
### Error: "Name 'X' is not defined"
**Problem:** You're using a variable or constant that doesn't exist.
```vcl
#const PORT = 8080
set req.http.X-Value = "{{PROT}}"; // Typo!
```
**Error:**
```
Error at main.xvcl:3:
Name 'PROT' is not defined
Did you mean: PORT?
```
**Solution:** Check spelling. xvcl suggests similar names.
### Error: "Invalid #const syntax"
**Problem:** Malformed constant declaration.
```vcl
#const PORT 8080 // Missing = sign
#const = 8080 // Missing name
#const PORT = STRING // Missing value
```
**Solution:** Use correct syntax:
```vcl
#const PORT INTEGER = 8080
```
### Error: "No matching #endfor for #for"
**Problem:** Missing closing keyword.
```vcl
#for i in range(10)
backend web{{i}} { ... }
// Missing #endfor
```
**Solution:** Add the closing keyword:
```vcl
#for i in range(10)
backend web{{i}} { ... }
#endfor
```
### Error: "Circular include detected"
**Problem:** File A includes file B which includes file A.
```
main.xvcl includes util.xvcl
util.xvcl includes main.xvcl
```
**Solution:** Restructure your includes. Create a shared file:
```
main.xvcl includes shared.xvcl
util.xvcl includes shared.xvcl
```
### Error: "Cannot find included file"
**Problem:** Include path is wrong or file doesn't exist.
```vcl
#include "includes/backends.xvcl"
```
**Solution:** Check path and use `-I` flag:
```bash
xvcl main.xvcl -o main.vcl -I ./includes
```
### Generated VCL has syntax errors
**Problem:** xvcl generated invalid VCL.
**Solution:**
1. Check the generated output:
```bash
cat main.vcl
```
2. Find the problematic section
3. Trace back to source with `--source-maps`:
```bash
xvcl main.xvcl -o main.vcl --source-maps
```
4. Fix the source file
### Macro expansion issues
**Problem:** Macro expands incorrectly.
```vcl
#inline double(x)
x + x
#endinline
set var.result = double(1 + 2);
// Expands to: (1 + 2) + (1 + 2) ✓ Correct
```
xvcl automatically adds parentheses when needed.
**If you see issues:** Check operator precedence in your macro definition.
### Function calls not working
**Problem:** Function call doesn't get replaced.
```vcl
#def add(a INTEGER, b INTEGER) -> INTEGER
return a + b;
#enddef
set var.result = add(5, 10);
```
**Common causes:**
1. **Missing semicolon:** Function calls must end with `;`
```vcl
set var.result = add(5, 10); // ✓ Correct
set var.result = add(5, 10) // ✗ Won't match
```
2. **Wrong number of arguments:**
```vcl
set var.result = add(5); // ✗ Expects 2 args
```
3. **Typo in function name:**
```vcl
set var.result = addr(5, 10); // ✗ Function 'addr' not defined
```
### Performance issues
**Problem:** Compilation is slow.
**Common causes:**
1. **Large loops:** `#for i in range(10000)` generates 10,000 copies
2. **Deep nesting:** Multiple nested loops or includes
3. **Complex macros:** Heavily nested macro expansions
**Solutions:**
1. Reduce loop iterations if possible
2. Use functions instead of generating everything inline
3. Split into multiple source files
4. Profile with `--debug` to see what's slow
### Getting help
**Check the error context:**
Errors show surrounding lines:
```
Error at main.xvcl:15:
Invalid #for syntax: #for in range(10)
Context:
13: sub vcl_recv {
14: // Generate backends
→ 15: #for in range(10)
16: backend web{{i}} { ... }
17: #endfor
18: }
```
**Enable debug mode:**
```bash
xvcl main.xvcl --debug
```
**Validate generated VCL:**
```bash
falco lint main.vcl -vv
```
The `-vv` flag shows detailed Falco errors.
---
## Examples Gallery
Here are complete, working examples you can use as starting points.
### Example 1: Multi-region backends
**`multi-region.xvcl`:**
```vcl
#const REGIONS = ["us-east", "us-west", "eu-west", "ap-south"]
#const DEFAULT_REGION = "us-east"
#for region in REGIONS
backend F_origin_{{region}} {
.host = "origin-{{region}}.example.com";
.port = "443";
.ssl = true;
.connect_timeout = 5s;
.first_byte_timeout = 30s;
.between_bytes_timeout = 10s;
}
#endfor
sub vcl_recv {
declare local var.region STRING;
// Detect region from client IP or header
if (req.http.X-Region) {
set var.region = req.http.X-Region;
} else {
set var.region = "{{DEFAULT_REGION}}";
}
// Route to appropriate backend
#for region in REGIONS
if (var.region == "{{region}}") {
set req.backend = F_origin_{{region}};
}
#endfor
}
```
### Example 2: Feature flag system
**`feature-flags.xvcl`:**
```vcl
#const ENABLE_NEW_CACHE_POLICY = True
#const ENABLE_WEBP_CONVERSION = True
#const ENABLE_ANALYTICS = False
#const ENABLE_DEBUG_HEADERS = False
sub vcl_recv {
#if ENABLE_NEW_CACHE_POLICY
// New cache policy with fine-grained control
if (req.url.path ~ "\.(jpg|png|gif|css|js)$") {
set req.http.X-Cache-Policy = "static";
} else {
set req.http.X-Cache-Policy = "dynamic";
}
#else
// Legacy cache policy
set req.http.X-Cache-Policy = "default";
#endif
#if ENABLE_WEBP_CONVERSION
if (req.http.Accept ~ "image/webp") {
set req.http.X-Image-Format = "webp";
}
#endif
#if ENABLE_ANALYTICS
set req.http.X-Analytics-ID = uuid.generate();
#endif
}
sub vcl_deliver {
#if ENABLE_DEBUG_HEADERS
set resp.http.X-Cache-Status = resp.http.X-Cache;
set resp.http.X-Backend = req.backend;
set resp.http.X-Region = req.http.X-Region;
#endif
}
```
### Example 3: URL normalization library
**`url-utils.xvcl`:**
```vcl
// Inline macros for common URL operations
#inline strip_www(host)
regsub(host, "^www\.", "")
#endinline
#inline lowercase_host(host)
std.tolower(host)
#endinline
#inline normalize_host(host)
lowercase_host(strip_www(host))
#endinline
#inline remove_trailing_slash(path)
regsub(path, "/$", "")
#endinline
#inline remove_query_string(url)
regsub(url, "\?.*$", "")
#endinline
// Function for complex normalization
#def normalize_url(url STRING, host STRING) -> STRING
declare local var.result STRING;
declare local var.clean_host STRING;
declare local var.clean_path STRING;
set var.clean_host = normalize_host(host);
set var.clean_path = remove_trailing_slash(url);
set var.result = "https://" + var.clean_host + var.clean_path;
return var.result;
#enddef
sub vcl_recv {
declare local var.canonical_url STRING;
set var.canonical_url = normalize_url(req.url.path, req.http.Host);
set req.http.X-Canonical-URL = var.canonical_url;
}
```
### Example 4: A/B testing framework
**`ab-testing.xvcl`:**
```vcl
#const EXPERIMENTS = [
("homepage_hero", 50),
("checkout_flow", 30),
("pricing_page", 25)
]
#def assign_experiment(exp_id STRING, percentage INTEGER) -> BOOL
declare local var.hash STRING;
declare local var.value INTEGER;
declare local var.assigned BOOL;
set var.hash = digest.hash_md5(client.ip + exp_id);
set var.value = std.atoi(substr(var.hash, 0, 2)) % 100;
set var.assigned = (var.value < percentage);
return var.assigned;
#enddef
sub vcl_recv {
declare local var.in_experiment BOOL;
#for exp_id, percentage in EXPERIMENTS
set var.in_experiment = assign_experiment("{{exp_id}}", {{percentage}});
if (var.in_experiment) {
set req.http.X-Experiment-{{exp_id}} = "variant";
} else {
set req.http.X-Experiment-{{exp_id}} = "control";
}
#endfor
}
```
---
## Summary
xvcl extends Fastly VCL with powerful programming constructs:
**Core features:**
- Constants - Single source of truth for configuration
- Template expressions - Dynamic value substitution
- For loops - Generate repetitive code
- Conditionals - Environment-specific builds
- Variables - Cleaner local variable syntax
- Includes - Modular code organization
**Advanced features:**
- Inline macros - Zero-overhead text substitution
- Functions - Reusable logic with return values
**Benefits:**
- Less code duplication
- Fewer copy-paste errors
- Better maintainability
- Easier testing
- Faster development
**Integration:**
- Works with Falco's full toolset
- Standard VCL output
- No runtime overhead
- Easy CI/CD integration
Start simple, add complexity as needed. xvcl grows with your VCL projects.
Raw data
{
"_id": null,
"home_page": null,
"name": "xvcl",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "compiler, fastly, preprocessor, varnish, vcl",
"author": null,
"author_email": "Frank Denis <github@pureftpd.org>",
"download_url": "https://files.pythonhosted.org/packages/46/15/599be23003f62ffbf09a37488ca781c8a816529a1f81ce393a90b51bc58a/xvcl-2.5.0.tar.gz",
"platform": null,
"description": "# xvcl\n\nSupercharge your Fastly VCL with programming constructs like loops, functions, constants, and more.\n\n**\ud83d\udcd6 [Quick Reference Guide](xvcl-quick-reference.md)** - One-page syntax reference for all xvcl features\n\n## Table of Contents\n\n- [Introduction](#introduction)\n- [Why Use xvcl?](#why-use-xvcl)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Features](#core-features)\n - [Constants](#constants)\n - [Template Expressions](#template-expressions)\n - [For Loops](#for-loops)\n - [Conditionals](#conditionals)\n - [Variables](#variables)\n - [File Includes](#file-includes)\n- [Advanced Features](#advanced-features)\n - [Inline Macros](#inline-macros)\n - [Functions](#functions)\n- [Command-Line Usage](#command-line-usage)\n- [Integration with Falco](#integration-with-falco)\n- [Best Practices](#best-practices)\n- [Troubleshooting](#troubleshooting)\n\n## Introduction\n\nxvcl is VCL transpiler that extends Fastly VCL with programming constructs that generate standard VCL code.\n\nThink of it as a build step for your VCL: write enhanced VCL source files, run xvcl, and get clean, valid VCL output.\n\n> **\ud83d\udca1 Tip:** For a quick syntax reference, see the [xvcl Quick Reference Guide](xvcl-quick-reference.md).\n\n**What you can do:**\n\n- Define constants once, use them everywhere\n- Generate repetitive code with for loops\n- Create reusable functions with return values\n- Build zero-overhead macros for common patterns\n- Conditionally compile code for different environments\n- Split large VCL files into modular includes\n\n**What you get:**\n\n- Standard VCL output that works with Fastly\n- Compile-time safety and error checking\n- Reduced code duplication\n- Better maintainability\n\n## Why Use xvcl?\n\nVCL is powerful but limited by design. You can't define functions with return values, you can't use loops, and managing constants means find-and-replace. This leads to:\n\n- **Copy-paste errors:** Similar backends? Copy, paste, modify, repeat, make mistakes\n- **Magic numbers:** Hardcoded values scattered throughout your code\n- **Duplication:** Same logic repeated in multiple subroutines\n- **Poor maintainability:** Change one thing, update it in 20 places\n\nxvcl solves these problems by adding programming constructs that compile down to clean VCL.\n\n### Real-World Example\n\n**Without xvcl (manual, error-prone):**\n\n```vcl\nbackend web1 {\n .host = \"web1.example.com\";\n .port = \"80\";\n}\n\nbackend web2 {\n .host = \"web2.example.com\";\n .port = \"80\";\n}\n\nbackend web3 {\n .host = \"web3.example.com\";\n .port = \"80\";\n}\n\nsub vcl_recv {\n if (req.http.Host == \"web1.example.com\") {\n set req.backend = web1;\n }\n if (req.http.Host == \"web2.example.com\") {\n set req.backend = web2;\n }\n if (req.http.Host == \"web3.example.com\") {\n set req.backend = web3;\n }\n}\n```\n\n**With xvcl (clean, maintainable):**\n\n```vcl\n#const BACKENDS = [\"web1\", \"web2\", \"web3\"]\n\n#for backend in BACKENDS\nbackend {{backend}} {\n .host = \"{{backend}}.example.com\";\n .port = \"80\";\n}\n#endfor\n\nsub vcl_recv {\n#for backend in BACKENDS\n if (req.http.Host == \"{{backend}}.example.com\") {\n set req.backend = {{backend}};\n }\n#endfor\n}\n```\n\nAdding a new backend? Just update the list. xvcl generates all the code.\n\n## Installation\n\nxvcl is a Python package. You need Python 3.9 or later.\n\n```bash\n# Using pip\npip install xvcl\n\n# Or install from source\npip install .\n\n# Development installation with dev dependencies\npip install -e \".[dev]\"\n\n# Using uv (recommended for faster installation)\nuv pip install xvcl\n```\n\nAfter installation, the `xvcl` command is available globally.\n\n**No external dependencies** - uses only Python standard library.\n\n## Quick Start\n\nCreate an xvcl source file (use `.xvcl` extension by convention):\n\n**`hello.xvcl`:**\n\n```vcl\n#const MESSAGE = \"Hello from xvcl!\"\n\nsub vcl_recv {\n set req.http.X-Message = \"{{MESSAGE}}\";\n}\n```\n\nRun xvcl:\n\n```bash\nxvcl hello.xvcl -o hello.vcl\n```\n\nOutput **`hello.vcl`:**\n\n```vcl\nsub vcl_recv {\n set req.http.X-Message = \"Hello from xvcl!\";\n}\n```\n\nValidate with Falco:\n\n```bash\nfalco lint hello.vcl\n```\n\n## Core Features\n\n### Constants\n\nDefine named constants with type checking. Constants are evaluated at preprocessing time and substituted into your code.\n\n**Syntax:**\n\n```vcl\n#const NAME TYPE = value\n```\n\n**Supported types:**\n- `INTEGER` - Whole numbers\n- `STRING` - Text strings\n- `FLOAT` - Decimal numbers\n- `BOOL` - True/False\n\n**Example:**\n\n```vcl\n#const MAX_AGE INTEGER = 3600\n#const ORIGIN STRING = \"origin.example.com\"\n#const PRODUCTION BOOL = True\n#const CACHE_VERSION FLOAT = 1.5\n```\n\n**Using constants in templates:**\n\n```vcl\n#const TTL = 300\n#const BACKEND_HOST = \"api.example.com\"\n\nbackend F_api {\n .host = \"{{BACKEND_HOST}}\";\n .port = \"443\";\n}\n\nsub vcl_fetch {\n set beresp.ttl = {{TTL}}s;\n}\n```\n\n**Why use constants?**\n\n- Single source of truth for configuration values\n- Easy to update across entire VCL\n- Type safety prevents mistakes\n- Self-documenting code\n\n### Template Expressions\n\nEmbed Python expressions in your VCL using `{{expression}}` syntax. Expressions are evaluated at preprocessing time.\n\n**Example:**\n\n```vcl\n#const PORT = 8080\n\nsub vcl_recv {\n set req.http.X-Port = \"{{PORT}}\";\n set req.http.X-Double = \"{{PORT * 2}}\";\n set req.http.X-Hex = \"{{hex(PORT)}}\";\n}\n```\n\n**Output:**\n\n```vcl\nsub vcl_recv {\n set req.http.X-Port = \"8080\";\n set req.http.X-Double = \"16160\";\n set req.http.X-Hex = \"0x1f90\";\n}\n```\n\n**Available functions:**\n\n- `range(n)` - Generate number sequences\n- `len(list)` - Get list length\n- `str(x)`, `int(x)` - Type conversions\n- `hex(n)` - Hexadecimal conversion\n- `format(x, fmt)` - Format values\n- `enumerate(iterable)` - Enumerate with indices\n- `min(...)`, `max(...)` - Min/max values\n- `abs(n)` - Absolute value\n\n**String formatting:**\n\n```vcl\n#const REGION = \"us-east\"\n#const INDEX = 1\n\nset req.backend = F_backend_{{REGION}}_{{INDEX}};\n```\n\n### For Loops\n\nGenerate repetitive VCL code by iterating over ranges or lists.\n\n**Syntax:**\n\n```vcl\n#for variable in iterable\n // Code to repeat\n#endfor\n```\n\n**Example 1: Range-based loop**\n\n```vcl\n#for i in range(5)\nbackend web{{i}} {\n .host = \"web{{i}}.example.com\";\n .port = \"80\";\n}\n#endfor\n```\n\n**Output:**\n\n```vcl\nbackend web0 {\n .host = \"web0.example.com\";\n .port = \"80\";\n}\nbackend web1 {\n .host = \"web1.example.com\";\n .port = \"80\";\n}\n// ... continues through web4\n```\n\n**Example 2: List iteration**\n\n```vcl\n#const REGIONS = [\"us-east\", \"us-west\", \"eu-west\"]\n\n#for region in REGIONS\nbackend F_{{region}} {\n .host = \"{{region}}.example.com\";\n .port = \"443\";\n .ssl = true;\n}\n#endfor\n```\n\n**Example 3: Nested loops**\n\n```vcl\n#const REGIONS = [\"us\", \"eu\"]\n#const ENVS = [\"prod\", \"staging\"]\n\n#for region in REGIONS\n #for env in ENVS\nbackend {{region}}_{{env}} {\n .host = \"{{env}}.{{region}}.example.com\";\n .port = \"443\";\n}\n #endfor\n#endfor\n```\n\n**Why use for loops?**\n\n- Generate multiple similar backends\n- Create ACL entries from lists\n- Build routing logic programmatically\n- Reduce copy-paste errors\n\n### Conditionals\n\nConditionally include or exclude code based on compile-time conditions.\n\n**Syntax:**\n\n```vcl\n#if condition\n // Code when true\n#else\n // Code when false (optional)\n#endif\n```\n\n**Example 1: Environment-specific configuration**\n\n```vcl\n#const PRODUCTION = True\n#const DEBUG = False\n\nsub vcl_recv {\n#if PRODUCTION\n set req.http.X-Environment = \"production\";\n unset req.http.X-Debug-Info;\n#else\n set req.http.X-Environment = \"development\";\n set req.http.X-Debug-Info = \"Enabled\";\n#endif\n\n#if DEBUG\n set req.http.X-Request-ID = randomstr(16, \"0123456789abcdef\");\n#endif\n}\n```\n\n**Example 2: Feature flags**\n\n```vcl\n#const ENABLE_NEW_ROUTING = True\n#const ENABLE_RATE_LIMITING = False\n\nsub vcl_recv {\n#if ENABLE_NEW_ROUTING\n call new_routing_logic;\n#else\n call legacy_routing_logic;\n#endif\n\n#if ENABLE_RATE_LIMITING\n if (ratelimit.check_rate(\"client_\" + client.ip, 1, 100, 60s, 1000s)) {\n error 429 \"Too Many Requests\";\n }\n#endif\n}\n```\n\n**Why use conditionals?**\n\n- Single codebase for multiple environments\n- Easy feature flag management\n- Dead code elimination (code in false branches isn't generated)\n- Compile-time optimization\n\n### Variables\n\nDeclare and initialize local variables in one step.\n\n**Syntax:**\n\n```vcl\n#let name TYPE = expression;\n```\n\n**Example:**\n\n```vcl\nsub vcl_recv {\n #let timestamp STRING = std.time(now, now);\n #let cache_key STRING = req.url.path + req.http.Host;\n\n set req.http.X-Timestamp = var.timestamp;\n set req.hash = var.cache_key;\n}\n```\n\n**Expands to:**\n\n```vcl\nsub vcl_recv {\n declare local var.timestamp STRING;\n set var.timestamp = std.time(now, now);\n declare local var.cache_key STRING;\n set var.cache_key = req.url.path + req.http.Host;\n\n set req.http.X-Timestamp = var.timestamp;\n set req.hash = var.cache_key;\n}\n```\n\n**Why use #let?**\n\n- Shorter syntax than separate declare + set\n- Clear initialization point\n- Reduces boilerplate\n\n### File Includes\n\nSplit large VCL files into modular, reusable components.\n\n**Syntax:**\n\n```vcl\n#include \"path/to/file.xvcl\"\n```\n\n**Example project structure:**\n\n```\nvcl/\n\u251c\u2500\u2500 main.xvcl\n\u251c\u2500\u2500 includes/\n\u2502 \u251c\u2500\u2500 backends.xvcl\n\u2502 \u251c\u2500\u2500 security.xvcl\n\u2502 \u2514\u2500\u2500 routing.xvcl\n```\n\n**`main.xvcl`:**\n\n```vcl\n#include \"includes/backends.xvcl\"\n#include \"includes/security.xvcl\"\n#include \"includes/routing.xvcl\"\n\nsub vcl_recv {\n call security_checks;\n call routing_logic;\n}\n```\n\n**`includes/backends.xvcl`:**\n\n```vcl\n#const BACKENDS = [\"web1\", \"web2\", \"web3\"]\n\n#for backend in BACKENDS\nbackend F_{{backend}} {\n .host = \"{{backend}}.example.com\";\n .port = \"443\";\n}\n#endfor\n```\n\n**Include path resolution:**\n\n1. Relative to the current file\n2. Relative to include paths specified with `-I`\n\n**Run with include paths:**\n\n```bash\nxvcl main.xvcl -o main.vcl -I ./vcl/includes\n```\n\n**Features:**\n\n- **Include-once semantics:** Files are only included once even if referenced multiple times\n- **Cycle detection:** Prevents circular includes\n- **Shared constants:** Constants defined in included files are available to the parent\n\n**Why use includes?**\n\n- Organize large VCL projects\n- Share common configurations across multiple VCL files\n- Team collaboration (different files for different concerns)\n- Reusable components library\n\n## Advanced Features\n\n### Inline Macros\n\nCreate zero-overhead text substitution macros. Unlike functions, macros are expanded inline at compile time with no runtime cost.\n\n**Syntax:**\n\n```vcl\n#inline macro_name(param1, param2, ...)\nexpression\n#endinline\n```\n\n**Example 1: String concatenation**\n\n```vcl\n#inline add_prefix(s)\n\"prefix-\" + s\n#endinline\n\n#inline add_suffix(s)\ns + \"-suffix\"\n#endinline\n\nsub vcl_recv {\n set req.http.X-Modified = add_prefix(\"test\");\n set req.http.X-Both = add_prefix(add_suffix(\"middle\"));\n}\n```\n\n**Output:**\n\n```vcl\nsub vcl_recv {\n set req.http.X-Modified = \"prefix-\" + \"test\";\n set req.http.X-Both = \"prefix-\" + \"middle\" + \"-suffix\";\n}\n```\n\n**Example 2: Common patterns**\n\n```vcl\n#inline normalize_host(host)\nstd.tolower(regsub(host, \"^www\\.\", \"\"))\n#endinline\n\n#inline cache_key(url, host)\ndigest.hash_md5(url + \"|\" + host)\n#endinline\n\nsub vcl_recv {\n set req.http.X-Normalized = normalize_host(req.http.Host);\n set req.hash = cache_key(req.url, req.http.Host);\n}\n```\n\n**Output:**\n\n```vcl\nsub vcl_recv {\n set req.http.X-Normalized = std.tolower(regsub(req.http.Host, \"^www\\.\", \"\"));\n set req.hash = digest.hash_md5(req.url + \"|\" + req.http.Host);\n}\n```\n\n**Example 3: Operator precedence handling**\n\nxvcl automatically handles operator precedence:\n\n```vcl\n#inline double(x)\nx + x\n#endinline\n\nsub vcl_recv {\n declare local var.result INTEGER;\n set var.result = double(5) * 10; // Correctly expands to (5 + 5) * 10\n}\n```\n\n**Macros vs Functions:**\n\n| Feature | Macros | Functions |\n| ------------- | ------------------- | ----------------------------- |\n| Expansion | Compile-time inline | Runtime subroutine call |\n| Overhead | None | Subroutine call + global vars |\n| Return values | Expression only | Single or tuple |\n| Use case | Simple expressions | Complex logic |\n\n**When to use macros:**\n\n- String manipulation patterns\n- Simple calculations\n- Common expressions repeated throughout code\n- When you need zero runtime overhead\n\n**When to use functions:**\n\n- Complex logic with multiple statements\n- Need to return multiple values\n- Conditional logic or loops inside the reusable code\n\n### Functions\n\nDefine reusable functions with parameters and return values. Functions are compiled into VCL subroutines.\n\n**Syntax:**\n\n```vcl\n#def function_name(param1 TYPE, param2 TYPE, ...) -> RETURN_TYPE\n // Function body\n return value;\n#enddef\n```\n\n**Example 1: Simple function**\n\n```vcl\n#def add(a INTEGER, b INTEGER) -> INTEGER\n declare local var.sum INTEGER;\n set var.sum = a + b;\n return var.sum;\n#enddef\n\nsub vcl_recv {\n declare local var.result INTEGER;\n set var.result = add(5, 10);\n set req.http.X-Sum = var.result;\n}\n```\n\n**Example 2: String processing**\n\n```vcl\n#def normalize_path(path STRING) -> STRING\n declare local var.result STRING;\n set var.result = std.tolower(path);\n set var.result = regsub(var.result, \"/$\", \"\");\n return var.result;\n#enddef\n\nsub vcl_recv {\n declare local var.clean_path STRING;\n set var.clean_path = normalize_path(req.url.path);\n set req.url = var.clean_path;\n}\n```\n\n**Example 3: Functions with conditionals**\n\n```vcl\n#def should_cache(url STRING) -> BOOL\n declare local var.cacheable BOOL;\n\n if (url ~ \"^/api/\") {\n set var.cacheable = false;\n } else if (url ~ \"\\.(jpg|png|css|js)$\") {\n set var.cacheable = true;\n } else {\n set var.cacheable = false;\n }\n\n return var.cacheable;\n#enddef\n\nsub vcl_recv {\n declare local var.can_cache BOOL;\n set var.can_cache = should_cache(req.url.path);\n\n if (var.can_cache) {\n return(lookup);\n } else {\n return(pass);\n }\n}\n```\n\n**Example 4: Tuple returns (multiple values)**\n\n```vcl\n#def parse_user_agent(ua STRING) -> (STRING, STRING)\n declare local var.browser STRING;\n declare local var.os STRING;\n\n if (ua ~ \"Chrome\") {\n set var.browser = \"chrome\";\n } else if (ua ~ \"Firefox\") {\n set var.browser = \"firefox\";\n } else {\n set var.browser = \"other\";\n }\n\n if (ua ~ \"Windows\") {\n set var.os = \"windows\";\n } else if (ua ~ \"Mac\") {\n set var.os = \"macos\";\n } else {\n set var.os = \"other\";\n }\n\n return var.browser, var.os;\n#enddef\n\nsub vcl_recv {\n declare local var.browser STRING;\n declare local var.os STRING;\n\n set var.browser, var.os = parse_user_agent(req.http.User-Agent);\n\n set req.http.X-Browser = var.browser;\n set req.http.X-OS = var.os;\n}\n```\n\n**Behind the scenes:**\n\nFunctions are compiled into VCL subroutines using global headers for parameter passing:\n\n```vcl\n// Your code:\nset var.result = add(5, 10);\n\n// Becomes:\nset req.http.X-Func-add-a = std.itoa(5);\nset req.http.X-Func-add-b = std.itoa(10);\ncall add;\nset var.result = std.atoi(req.http.X-Func-add-Return);\n```\n\nxvcl generates the `sub add { ... }` implementation and handles all type conversions automatically.\n\n**Function features:**\n\n- **Type safety:** Parameters and returns are type-checked\n- **Multiple returns:** Use tuple syntax to return multiple values\n- **Automatic conversions:** INTEGER/FLOAT/BOOL are converted to/from STRING automatically\n- **Scope annotations:** Generated subroutines work in all VCL scopes\n\n**Why use functions?**\n\n- Reusable complex logic\n- Reduce code duplication\n- Easier testing (test the function once)\n- Better code organization\n\n## Command-Line Usage\n\n**Basic usage:**\n\n```bash\nxvcl input.xvcl -o output.vcl\n```\n\n**Options:**\n\n| Option | Description |\n| --------------- | --------------------------------------------------- |\n| `input` | Input xvcl source file (required) |\n| `-o, --output` | Output VCL file (default: replaces .xvcl with .vcl) |\n| `-I, --include` | Add include search path (repeatable) |\n| `--debug` | Enable debug output with expansion traces |\n| `--source-maps` | Add source map comments to output |\n| `-v, --verbose` | Verbose output (alias for --debug) |\n\n**Examples:**\n\n```bash\n# Basic compilation\nxvcl main.xvcl -o main.vcl\n\n# With include paths\nxvcl main.xvcl -o main.vcl \\\n -I ./includes \\\n -I ./shared\n\n# Debug mode (see expansion traces)\nxvcl main.xvcl -o main.vcl --debug\n\n# With source maps (track generated code origin)\nxvcl main.xvcl -o main.vcl --source-maps\n```\n\n**Automatic output naming:**\n\nIf you don't specify `-o`, xvcl replaces `.xvcl` with `.vcl`:\n\n```bash\n# These are equivalent:\nxvcl main.xvcl\nxvcl main.xvcl -o main.vcl\n```\n\n**Debug mode output:**\n\n```bash\n$ xvcl example.xvcl --debug\n\n[DEBUG] Processing file: example.xvcl\n[DEBUG] Pass 1: Extracting constants\n[DEBUG] Defined constant: MAX_AGE = 3600\n[DEBUG] Pass 2: Processing includes\n[DEBUG] Pass 3: Extracting inline macros\n[DEBUG] Defined macro: add_prefix(s)\n[DEBUG] Pass 4: Extracting functions\n[DEBUG] Pass 5: Processing directives and generating code\n[DEBUG] Processing #for at line 10\n[DEBUG] Loop iterating 3 times\n[DEBUG] Iteration 0: backend = web1\n[DEBUG] Iteration 1: backend = web2\n[DEBUG] Iteration 2: backend = web3\n[DEBUG] Pass 6: Generating function subroutines\n\u2713 Compiled example.xvcl -> example.vcl\n Constants: 1\n Macros: 1 (add_prefix)\n Functions: 0\n```\n\n## Integration with Falco\n\nxvcl generates standard VCL that you can use with Falco's full toolset.\n\n**Recommended workflow:**\n\n```bash\n# 1. Write your xvcl source\nvim main.xvcl\n\n# 2. Compile with xvcl\nxvcl main.xvcl -o main.vcl\n\n# 3. Lint with Falco\nfalco lint main.vcl\n\n# 4. Test with Falco\nfalco test main.vcl\n\n# 5. Simulate with Falco\nfalco simulate main.vcl\n```\n\n**Makefile integration:**\n\n```makefile\n# Makefile\n.PHONY: build lint test clean\n\nXVCL = xvcl\nSOURCES = $(wildcard *.xvcl)\nOUTPUTS = $(SOURCES:.xvcl=.vcl)\n\nbuild: $(OUTPUTS)\n\n%.vcl: %.xvcl\n\t$(XVCL) $< -o $@ -I ./includes\n\nlint: build\n\tfalco lint *.vcl\n\ntest: build\n\tfalco test *.vcl\n\nclean:\n\trm -f $(OUTPUTS)\n```\n\n**CI/CD integration:**\n\n```yaml\n# .github/workflows/vcl.yml\nname: VCL CI\n\non: [push, pull_request]\n\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v3\n\n - name: Set up Python\n uses: actions/setup-python@v4\n with:\n python-version: '3.10'\n\n - name: Install Falco\n run: |\n wget https://github.com/ysugimoto/falco/releases/latest/download/falco_linux_amd64\n chmod +x falco_linux_amd64\n sudo mv falco_linux_amd64 /usr/local/bin/falco\n\n - name: Compile xvcl\n run: |\n xvcl main.xvcl -o main.vcl\n\n - name: Lint VCL\n run: falco lint main.vcl\n\n - name: Test VCL\n run: falco test main.vcl\n```\n\n**Testing compiled VCL:**\n\nYou can write Falco tests for your generated VCL:\n\n**`main.test.vcl`:**\n\n```vcl\n// @suite: Backend routing tests\n\n// @test: Should route to correct backend\nsub test_backend_routing {\n set req.http.Host = \"web1.example.com\";\n call vcl_recv;\n\n assert.equal(req.backend, \"web1\");\n}\n```\n\nRun tests after compilation:\n\n```bash\nxvcl main.xvcl -o main.vcl\nfalco test main.vcl\n```\n\n## Best Practices\n\n### 1. Use the `.xvcl` extension\n\nMakes it clear which files are xvcl source files:\n\n```\n\u2713 main.xvcl \u2192 main.vcl\n\u2717 main.vcl \u2192 main.vcl.processed\n```\n\n### 2. Keep constants at the top\n\n```vcl\n// Good: Constants first, easy to find\n#const MAX_BACKENDS = 10\n#const PRODUCTION = True\n\n#for i in range(MAX_BACKENDS)\n // ... use constant\n#endfor\n```\n\n### 3. Use descriptive constant names\n\n```vcl\n// Good\n#const CACHE_TTL_SECONDS = 3600\n#const API_BACKEND_HOST = \"api.example.com\"\n\n// Bad\n#const X = 3600\n#const B = \"api.example.com\"\n```\n\n### 4. Comment your macros and functions\n\n```vcl\n// Normalizes a hostname by removing www prefix and converting to lowercase\n#inline normalize_host(host)\nstd.tolower(regsub(host, \"^www\\.\", \"\"))\n#endinline\n\n// Parses User-Agent and returns (browser, os) tuple\n#def parse_user_agent(ua STRING) -> (STRING, STRING)\n // ...\n#enddef\n```\n\n### 5. Prefer macros for simple expressions, functions for complex logic\n\n```vcl\n// Good: Simple expression = macro\n#inline cache_key(url, host)\ndigest.hash_md5(url + \"|\" + host)\n#endinline\n\n// Good: Complex logic = function\n#def should_cache(url STRING, method STRING) -> BOOL\n declare local var.result BOOL;\n if (method != \"GET\" && method != \"HEAD\") {\n set var.result = false;\n } else if (url ~ \"^/api/\") {\n set var.result = false;\n } else {\n set var.result = true;\n }\n return var.result;\n#enddef\n```\n\n### 6. Use includes for organization\n\n```\nvcl/\n\u251c\u2500\u2500 main.xvcl # Main entry point\n\u251c\u2500\u2500 config.xvcl # Constants and configuration\n\u251c\u2500\u2500 includes/\n\u2502 \u251c\u2500\u2500 backends.xvcl\n\u2502 \u251c\u2500\u2500 security.xvcl\n\u2502 \u251c\u2500\u2500 routing.xvcl\n\u2502 \u2514\u2500\u2500 caching.xvcl\n```\n\n### 7. Version control both source and output\n\n```gitignore\n# Include both in git\n*.xvcl\n*.vcl\n\n# But gitignore generated files in CI\n# (if you regenerate on deploy)\n```\n\n**Or** only version control source files and regenerate on deployment:\n\n```gitignore\n# Version control xvcl source only\n*.xvcl\n\n# Ignore generated VCL\n*.vcl\n```\n\nChoose based on your deployment process.\n\n### 8. Add source maps in development\n\n```bash\n# Development: easier debugging\nxvcl main.xvcl -o main.vcl --source-maps\n\n# Production: cleaner output\nxvcl main.xvcl -o main.vcl\n```\n\nSource maps add comments like:\n\n```vcl\n// BEGIN INCLUDE: includes/backends.xvcl\nbackend F_web1 { ... }\n// END INCLUDE: includes/backends.xvcl\n```\n\n### 9. Test incrementally\n\nDon't write a massive source file and compile once. Test as you go:\n\n```bash\n# Write a bit\nvim main.xvcl\n\n# Compile\nxvcl main.xvcl\n\n# Check output\ncat main.vcl\n\n# Lint\nfalco lint main.vcl\n\n# Repeat\n```\n\n### 10. Use debug mode when things go wrong\n\n```bash\nxvcl main.xvcl --debug\n```\n\nShows exactly what xvcl is doing.\n\n## Troubleshooting\n\n### Error: \"Name 'X' is not defined\"\n\n**Problem:** You're using a variable or constant that doesn't exist.\n\n```vcl\n#const PORT = 8080\n\nset req.http.X-Value = \"{{PROT}}\"; // Typo!\n```\n\n**Error:**\n\n```\nError at main.xvcl:3:\n Name 'PROT' is not defined\n Did you mean: PORT?\n```\n\n**Solution:** Check spelling. xvcl suggests similar names.\n\n### Error: \"Invalid #const syntax\"\n\n**Problem:** Malformed constant declaration.\n\n```vcl\n#const PORT 8080 // Missing = sign\n#const = 8080 // Missing name\n#const PORT = STRING // Missing value\n```\n\n**Solution:** Use correct syntax:\n\n```vcl\n#const PORT INTEGER = 8080\n```\n\n### Error: \"No matching #endfor for #for\"\n\n**Problem:** Missing closing keyword.\n\n```vcl\n#for i in range(10)\n backend web{{i}} { ... }\n// Missing #endfor\n```\n\n**Solution:** Add the closing keyword:\n\n```vcl\n#for i in range(10)\n backend web{{i}} { ... }\n#endfor\n```\n\n### Error: \"Circular include detected\"\n\n**Problem:** File A includes file B which includes file A.\n\n```\nmain.xvcl includes util.xvcl\nutil.xvcl includes main.xvcl\n```\n\n**Solution:** Restructure your includes. Create a shared file:\n\n```\nmain.xvcl includes shared.xvcl\nutil.xvcl includes shared.xvcl\n```\n\n### Error: \"Cannot find included file\"\n\n**Problem:** Include path is wrong or file doesn't exist.\n\n```vcl\n#include \"includes/backends.xvcl\"\n```\n\n**Solution:** Check path and use `-I` flag:\n\n```bash\nxvcl main.xvcl -o main.vcl -I ./includes\n```\n\n### Generated VCL has syntax errors\n\n**Problem:** xvcl generated invalid VCL.\n\n**Solution:**\n\n1. Check the generated output:\n ```bash\n cat main.vcl\n ```\n\n2. Find the problematic section\n\n3. Trace back to source with `--source-maps`:\n\n ```bash\n xvcl main.xvcl -o main.vcl --source-maps\n ```\n\n4. Fix the source file\n\n### Macro expansion issues\n\n**Problem:** Macro expands incorrectly.\n\n```vcl\n#inline double(x)\nx + x\n#endinline\n\nset var.result = double(1 + 2);\n// Expands to: (1 + 2) + (1 + 2) \u2713 Correct\n```\n\nxvcl automatically adds parentheses when needed.\n\n**If you see issues:** Check operator precedence in your macro definition.\n\n### Function calls not working\n\n**Problem:** Function call doesn't get replaced.\n\n```vcl\n#def add(a INTEGER, b INTEGER) -> INTEGER\n return a + b;\n#enddef\n\nset var.result = add(5, 10);\n```\n\n**Common causes:**\n\n1. **Missing semicolon:** Function calls must end with `;`\n ```vcl\n set var.result = add(5, 10); // \u2713 Correct\n set var.result = add(5, 10) // \u2717 Won't match\n ```\n\n2. **Wrong number of arguments:**\n ```vcl\n set var.result = add(5); // \u2717 Expects 2 args\n ```\n\n3. **Typo in function name:**\n ```vcl\n set var.result = addr(5, 10); // \u2717 Function 'addr' not defined\n ```\n\n### Performance issues\n\n**Problem:** Compilation is slow.\n\n**Common causes:**\n\n1. **Large loops:** `#for i in range(10000)` generates 10,000 copies\n2. **Deep nesting:** Multiple nested loops or includes\n3. **Complex macros:** Heavily nested macro expansions\n\n**Solutions:**\n\n1. Reduce loop iterations if possible\n2. Use functions instead of generating everything inline\n3. Split into multiple source files\n4. Profile with `--debug` to see what's slow\n\n### Getting help\n\n**Check the error context:**\n\nErrors show surrounding lines:\n\n```\nError at main.xvcl:15:\n Invalid #for syntax: #for in range(10)\n\n Context:\n 13: sub vcl_recv {\n 14: // Generate backends\n \u2192 15: #for in range(10)\n 16: backend web{{i}} { ... }\n 17: #endfor\n 18: }\n```\n\n**Enable debug mode:**\n\n```bash\nxvcl main.xvcl --debug\n```\n\n**Validate generated VCL:**\n\n```bash\nfalco lint main.vcl -vv\n```\n\nThe `-vv` flag shows detailed Falco errors.\n\n---\n\n## Examples Gallery\n\nHere are complete, working examples you can use as starting points.\n\n### Example 1: Multi-region backends\n\n**`multi-region.xvcl`:**\n\n```vcl\n#const REGIONS = [\"us-east\", \"us-west\", \"eu-west\", \"ap-south\"]\n#const DEFAULT_REGION = \"us-east\"\n\n#for region in REGIONS\nbackend F_origin_{{region}} {\n .host = \"origin-{{region}}.example.com\";\n .port = \"443\";\n .ssl = true;\n .connect_timeout = 5s;\n .first_byte_timeout = 30s;\n .between_bytes_timeout = 10s;\n}\n#endfor\n\nsub vcl_recv {\n declare local var.region STRING;\n\n // Detect region from client IP or header\n if (req.http.X-Region) {\n set var.region = req.http.X-Region;\n } else {\n set var.region = \"{{DEFAULT_REGION}}\";\n }\n\n // Route to appropriate backend\n#for region in REGIONS\n if (var.region == \"{{region}}\") {\n set req.backend = F_origin_{{region}};\n }\n#endfor\n}\n```\n\n### Example 2: Feature flag system\n\n**`feature-flags.xvcl`:**\n\n```vcl\n#const ENABLE_NEW_CACHE_POLICY = True\n#const ENABLE_WEBP_CONVERSION = True\n#const ENABLE_ANALYTICS = False\n#const ENABLE_DEBUG_HEADERS = False\n\nsub vcl_recv {\n#if ENABLE_NEW_CACHE_POLICY\n // New cache policy with fine-grained control\n if (req.url.path ~ \"\\.(jpg|png|gif|css|js)$\") {\n set req.http.X-Cache-Policy = \"static\";\n } else {\n set req.http.X-Cache-Policy = \"dynamic\";\n }\n#else\n // Legacy cache policy\n set req.http.X-Cache-Policy = \"default\";\n#endif\n\n#if ENABLE_WEBP_CONVERSION\n if (req.http.Accept ~ \"image/webp\") {\n set req.http.X-Image-Format = \"webp\";\n }\n#endif\n\n#if ENABLE_ANALYTICS\n set req.http.X-Analytics-ID = uuid.generate();\n#endif\n}\n\nsub vcl_deliver {\n#if ENABLE_DEBUG_HEADERS\n set resp.http.X-Cache-Status = resp.http.X-Cache;\n set resp.http.X-Backend = req.backend;\n set resp.http.X-Region = req.http.X-Region;\n#endif\n}\n```\n\n### Example 3: URL normalization library\n\n**`url-utils.xvcl`:**\n\n```vcl\n// Inline macros for common URL operations\n#inline strip_www(host)\nregsub(host, \"^www\\.\", \"\")\n#endinline\n\n#inline lowercase_host(host)\nstd.tolower(host)\n#endinline\n\n#inline normalize_host(host)\nlowercase_host(strip_www(host))\n#endinline\n\n#inline remove_trailing_slash(path)\nregsub(path, \"/$\", \"\")\n#endinline\n\n#inline remove_query_string(url)\nregsub(url, \"\\?.*$\", \"\")\n#endinline\n\n// Function for complex normalization\n#def normalize_url(url STRING, host STRING) -> STRING\n declare local var.result STRING;\n declare local var.clean_host STRING;\n declare local var.clean_path STRING;\n\n set var.clean_host = normalize_host(host);\n set var.clean_path = remove_trailing_slash(url);\n set var.result = \"https://\" + var.clean_host + var.clean_path;\n\n return var.result;\n#enddef\n\nsub vcl_recv {\n declare local var.canonical_url STRING;\n set var.canonical_url = normalize_url(req.url.path, req.http.Host);\n set req.http.X-Canonical-URL = var.canonical_url;\n}\n```\n\n### Example 4: A/B testing framework\n\n**`ab-testing.xvcl`:**\n\n```vcl\n#const EXPERIMENTS = [\n (\"homepage_hero\", 50),\n (\"checkout_flow\", 30),\n (\"pricing_page\", 25)\n]\n\n#def assign_experiment(exp_id STRING, percentage INTEGER) -> BOOL\n declare local var.hash STRING;\n declare local var.value INTEGER;\n declare local var.assigned BOOL;\n\n set var.hash = digest.hash_md5(client.ip + exp_id);\n set var.value = std.atoi(substr(var.hash, 0, 2)) % 100;\n set var.assigned = (var.value < percentage);\n\n return var.assigned;\n#enddef\n\nsub vcl_recv {\n declare local var.in_experiment BOOL;\n\n#for exp_id, percentage in EXPERIMENTS\n set var.in_experiment = assign_experiment(\"{{exp_id}}\", {{percentage}});\n if (var.in_experiment) {\n set req.http.X-Experiment-{{exp_id}} = \"variant\";\n } else {\n set req.http.X-Experiment-{{exp_id}} = \"control\";\n }\n#endfor\n}\n```\n\n---\n\n## Summary\n\nxvcl extends Fastly VCL with powerful programming constructs:\n\n**Core features:**\n\n- Constants - Single source of truth for configuration\n- Template expressions - Dynamic value substitution\n- For loops - Generate repetitive code\n- Conditionals - Environment-specific builds\n- Variables - Cleaner local variable syntax\n- Includes - Modular code organization\n\n**Advanced features:**\n\n- Inline macros - Zero-overhead text substitution\n- Functions - Reusable logic with return values\n\n**Benefits:**\n\n- Less code duplication\n- Fewer copy-paste errors\n- Better maintainability\n- Easier testing\n- Faster development\n\n**Integration:**\n\n- Works with Falco's full toolset\n- Standard VCL output\n- No runtime overhead\n- Easy CI/CD integration\n\nStart simple, add complexity as needed. xvcl grows with your VCL projects.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Extended VCL compiler with metaprogramming features for Fastly VCL",
"version": "2.5.0",
"project_urls": null,
"split_keywords": [
"compiler",
" fastly",
" preprocessor",
" varnish",
" vcl"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "d8ffcc8473b6131648a1a7a1b8efd85d6273a6e510d76ec3e7f0842fea353a7f",
"md5": "4e8f8d176e21180bb6c35021b0d70c45",
"sha256": "fcc72f42eff445c450bac2de7e880a0ee6414a29b83bba290754427f24f2cc74"
},
"downloads": -1,
"filename": "xvcl-2.5.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "4e8f8d176e21180bb6c35021b0d70c45",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 24000,
"upload_time": "2025-10-11T09:16:24",
"upload_time_iso_8601": "2025-10-11T09:16:24.316292Z",
"url": "https://files.pythonhosted.org/packages/d8/ff/cc8473b6131648a1a7a1b8efd85d6273a6e510d76ec3e7f0842fea353a7f/xvcl-2.5.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "4615599be23003f62ffbf09a37488ca781c8a816529a1f81ce393a90b51bc58a",
"md5": "02a42d53085e6790be5b382986f4210d",
"sha256": "7d2192a11c2ed37ec805cd6f3504ce08bb057f2bc9bdf56bab77d737c1fb2aa3"
},
"downloads": -1,
"filename": "xvcl-2.5.0.tar.gz",
"has_sig": false,
"md5_digest": "02a42d53085e6790be5b382986f4210d",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 56245,
"upload_time": "2025-10-11T09:16:25",
"upload_time_iso_8601": "2025-10-11T09:16:25.500066Z",
"url": "https://files.pythonhosted.org/packages/46/15/599be23003f62ffbf09a37488ca781c8a816529a1f81ce393a90b51bc58a/xvcl-2.5.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-11 09:16:25",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "xvcl"
}