Templating

CWeb Templating

1. What the system actually is

This is not a runtime template loader. .cweb files are translated into normal C by the cweb CLI (cweb build) before the actual app build, and are then compiled together like ordinary C source files.

Concrete flow:

  1. cweb build recursively scans app/templates for .cweb files.
  2. Each file is translated into build/<basename>.c and build/<basename>.h.
  3. In addition, build/auto_routes.c, build/build.h, and build/sources.conf are generated.

The .cweb file itself is no longer opened at runtime. The runtime path only uses the generated C functions.

Minimal workflow for new templates

  1. Create the file app/templates/<name>.template.cweb.
  2. In <%header ... %>, declare your structs/includes and char* <name>_template(...);.
  3. Write the template body using HTML, {...} output, <% ... %>, <cif>, <cloop>, or <cstyle>.
  4. Run cweb build.
  5. Include the generated header file from build/ in the route.
  6. Populate the data structure in C.
  7. Call the generated function directly.
  8. If using external <cstyle>, additionally expose <name>_template_css(...) and wire it into the HTML using cweb_setAssetLink(...).

Important details:

  • Only app/templates is scanned, not arbitrary other template directories.
  • The output name is derived from the filename without the last extension.
  • home.template.cweb therefore becomes build/home.template.c and build/home.template.h.
  • Depending on your workflow, you may need to run cweb build manually again after template changes.

2. Structure of a .cweb file

The github repo app example app/templates/home.template.cweb shows the full pattern.

2.1 The header block is practically required

At the top there is a special header block:

<%header
typedef struct {
    const char *page_title;
    ...
} page_data_t;

char* home_template(page_data_t *data);
%>

This block is evaluated separately by the generator.

It has two roles:

  1. It provides types, includes, typedefs, and declarations for the generated header file.
  2. It provides the signature of the actual render function.

Important:

  • The render function must return char*.
  • The signature must be inside the header block.
  • If the parser cannot find a matching char* function there, generation of the .c file fails.

2.2 What gets copied into the generated header

The content of the header block is copied almost directly into the generated .h. In addition, the generator injects standard includes like stdio.h, stdlib.h, string.h, time.h, and stdbool.h.

If the template contains external CSS via <cstyle>, the generator also automatically adds an extra declaration char* <template>_css(<params>); unless you already declared it yourself in the header.

3. How data is passed into the template

The system is not data-driven like JSON- or dictionary-based templates. It is purely C-typed. in Future version there will be also json option.

The app example does it like this:

  1. A typedef struct is defined in the .cweb header.
  2. The render function takes a pointer to exactly that type.
  3. The route builds a real C struct.
  4. The route calls the generated function directly.

Example from home.page.c:

page_data_t data = {0};
data.page_title = "CWeb Template Engine Demo";
...
char *html = home_template(&data);

this means:

  • If the struct field does not exist, C compilation fails.
  • If you write data->foo in the template even though foo is not part of the struct, the generated .c fails to compile.
  • IDE support works well precisely because you are working with real C types.

4. How the route gets access to the generated files

The route includes the generated header file directly:

#include "../../build/home.template.h"

After that, page_data_t, home_template(...), and for external CSS also home_template_css(...) are known.

5. Variables

The variable syntax is:

{expression}
{expression:type}


## 6. Code Code inside .cweb template files

Inside `<% ... %>` you write C code directly. That code is copied almost unchanged into the generated function.

Examples:

```c
<% if (data->is_admin) { %>
   <p>you are admin</p>
...
<% } %>

or:

<%
for (size_t i = 0; i < data->menu_count; i++) {
    cweb_output_raw("<li>");
    cweb_output_html(data->menu_items[i]);
    cweb_output_raw("</li>");
}
%>

Important:

  • Inside such blocks, you can directly use cweb_output_raw(...) and cweb_output_html(...).
  • This is the lowest-level and most flexible part of the system.

c code or template hhtml elements

you can choose if you want to write c code or use template html elements like <cif> or <cloop>

7 Security filter for C blocks

<% ... %> is not completely unrestricted. The lexer rejects certain patterns, for example:

  • system(
  • exec(
  • popen(
  • fork(
  • unlink(
  • remove(
  • rmdir(
  • #include
  • #define
  • #pragma

Practical consequence:

  • Includes and type definitions belong in the <%header ... %> block.
  • The body is intended for render logic, not preprocessor directives.

8. Why {...} inside CSS and JavaScript is not treated as a variable

The lexer deliberately suppresses variable detection inside <style>, <cstyle>, and <script> so that normal CSS and JS syntax with curly braces does not break.

Practical consequence:

  • {data->...} interpolation does not work inside CSS and JavaScript the same way it does in the HTML body.
  • For dynamic CSS/JS, you must use <% ... %> there.

YES you can mix css & js with c code and variables