tsujikiri Documentation

tsujikiri (辻斬り — “cut through C++ bindings”) parses C++ headers via libclang-ng and generates binding code through a template-driven pipeline. Define what to expose, plug in a target format, and get ready-to-compile bindings.


What tsujikiri Does

tsujikiri reads a YAML configuration file (.input.yml) that describes which C++ headers to parse, which classes and methods to expose, and how to transform the declarations for the target binding system.

It parses each header using libclang-ng, building an Intermediate Representation (IR) — a pure Python data model of all classes, methods, fields, constructors, enums, and free functions. The IR is then processed through three configurable phases — filtering, attribute processing, and transforms — before being rendered via a Jinja2 template into the final output.

Built-in support for LuaBridge3 (Lua bindings), Lua Language Server (LuaLS type stubs), pybind11 (Python bindings), and Python type stubs (.pyi). Custom formats are first-class citizens — define a .output.yml file with your Jinja2 template and point tsujikiri at it.


Pipeline

C++ Header (.hpp)
       │
       ▼  libclang parse
Intermediate Representation (IR)
       │
       ├──▶  FilterEngine          namespaces / classes / methods / fields
       │     ↳ filtering.md
       │
       ├──▶  AttributeProcessor    [[tsujikiri::skip]], [[tsujikiri::keep]], [[tsujikiri::rename(...)]], [[tsujikiri::doc(...)]], etc.
       │     ↳ attributes.md
       │
       ├──▶  TransformPipeline     rename, suppress, inject, remap, modify
       │     ↳ transforms.md
       │
       ▼  Jinja2 template render
Target Binding Code (.cpp / .lua)
       │
       └──▶  Manifest (optional)   API snapshot for versioning and compat checks
             ↳ manifest-and-versioning.md

Each phase is independently configurable per input file and per output format. All configuration lives in a single .input.yml file.



Quick Example

Header (vec3.hpp):

namespace myproject {
class Vec3 {
public:
    Vec3() = default;
    Vec3(float x, float y, float z) : x_(x), y_(y), z_(z) {}
    float length() const;
    float dot(const Vec3& other) const;
public:
    float x_ = 0.0f, y_ = 0.0f, z_ = 0.0f;
};
}

Config (myproject.input.yml):

source:
  path: vec3.hpp
  parse_args: ["-std=c++17"]

filters:
  namespaces: ["myproject"]
  classes:
    whitelist: ["Vec3"]
  constructors:
    include: true

transforms:
  - stage: modify_field
    class: Vec3
    field: "(.+)_$"
    field_is_regex: true
    rename: "\\1"

generation:
  includes: ['"vec3.hpp"']

Command:

tsujikiri -i myproject.input.yml --target luabridge3 src/bindings.cpp

Output (excerpt):

void register_myproject(lua_State* L)
{
  luabridge::getGlobalNamespace(L)
    .beginNamespace("myproject")
      .beginClass<myproject::Vec3>("Vec3")
        .addConstructor<void (*)(float, float, float)>()
        .addFunction("length", &myproject::Vec3::length)
        .addFunction("dot", &myproject::Vec3::dot)
        .addProperty("x", &myproject::Vec3::x_)
        .addProperty("y", &myproject::Vec3::y_)
        .addProperty("z", &myproject::Vec3::z_)
      .endClass()
    .endNamespace();
}

See Getting Started for the full walkthrough.


Installation

Using pip:

pip install tsujikiri

Using uv:

uv pip install tsujikiri

Requirements: Python ≥ 3.12, libclang-ng ≥ 19 (Clang 19–22 supported).

To pin a specific Clang version use an extra:

pip install "tsujikiri[clang19]"   # Clang 19
pip install "tsujikiri[clang20]"   # Clang 20
pip install "tsujikiri[clang21]"   # Clang 21
pip install "tsujikiri[clang22]"   # Clang 22

Only one clangXX extra may be active at a time. See Getting Started for details.


License

MIT — see LICENSE.