Type-safe, import-aware, width-aware code generation for multiple languages.
sigil-stitch combines JavaPoet's builder + CodeBlock
model with Wadler-Lindig pretty printing
and multi-language support. Reference types with %T in format strings, and the library
tracks every import for you, resolves naming conflicts, and emits width-aware formatted output.
cargo add sigil-stitchRequires Rust edition 2024, MSRV 1.88.0. Runtime dependencies: pretty (Wadler-Lindig formatting), serde with derive (every spec type implements Serialize/Deserialize out of the box, so you can round-trip specs as JSON or YAML), and snafu (structured errors).
sigil-stitch offers two ways to build code. Both produce the same CodeBlock with
the same import tracking and rendering.
Builder API -- programmatic, good for dynamic code generation:
use sigil_stitch::prelude::*;
use sigil_stitch::code_block::StringLitArg;
let user_type = TypeName::importable_type("./models", "User");
let mut cb = CodeBlock::builder();
cb.add_statement(
"const user: %T = await getUser(%S)",
(user_type.clone(), StringLitArg("id".into())),
);
cb.add_statement("return user", ());
let body = cb.build().unwrap();
let file = FileSpec::builder("user.ts")
.add_code(body)
.build()
.unwrap();
let output = file.render(80).unwrap();
assert!(output.contains("import type { User } from './models'"));
assert!(output.contains("const user: User = await getUser('id');"));sigil_quote! macro -- inline target-language code, less ceremony:
use sigil_stitch::prelude::*;
use sigil_stitch::lang::typescript::TypeScript;
let user_type = TypeName::importable_type("./models", "User");
let body = sigil_quote!(TypeScript {
const user: $T(user_type) = await getUser($S("id"));
if (!user) {
throw new Error($S("not found"));
}
return user;
}).unwrap();The macro uses $T/$S/$N/$L/$C/$V/$W interpolation markers that expand to
the equivalent %T/%S/%N/%L/%V format specifiers at compile time. It also
supports $V("@{expr}") for compile-time interpolation in verbatim strings,
$attr("text") for structural annotations with language-specific rendering,
$T_join(sep, iter) for type-name joins with per-item import tracking,
$C_each for splicing iterables of code blocks, $if/$else_if/$else
for meta-conditionals, $for for compile-time iteration, $let for Rust-level
variable bindings, $join for separator-joined lists, and $+ for line
continuation in multi-line expressions. Go const (, var (, import (, and
type ( paren-delimited blocks are recognized as structural blocks so $for,
$if, and $C_each expand inside them.
| Specifier | Name | Argument Type | Purpose |
|---|---|---|---|
%T |
Type | TypeName |
Emit type reference, track import |
%N |
Name | NameArg |
Emit identifier name, escape reserved words |
%S |
String | StringLitArg |
Emit escaped string literal |
%V |
Verbatim | VerbatimStrArg |
Emit string with interpolation preserved |
%L |
Literal | &str, number, CodeBlock |
Emit raw value or nested block |
%W |
Wrap | (none) | Soft line break point |
%> |
Indent | (none) | Increase indent level |
%< |
Dedent | (none) | Decrease indent level |
%[ |
Statement begin | (none) | Start of statement |
%] |
Statement end | (none) | End of statement (appends ; if needed) |
Bare &str maps to %L. Use NameArg for %N, StringLitArg for %S, and VerbatimStrArg for %V.
See the Format Specifiers chapter for the full deep dive.
Build structured declarations with the spec builders:
| Spec | Purpose |
|---|---|
| ParameterSpec | Function parameter (name + type + default + variadic + property) |
| FieldSpec | Struct field / class property (visibility, static, readonly) |
| FunSpec | Function or method (params, return type, body, async, abstract) |
| TypeSpec | Class, struct, interface, trait, enum, type alias, newtype, embedded types |
| PropertySpec | Computed property with getter/setter |
| AnnotationSpec | @Override, #[derive(...)], [[nodiscard]] |
| EnumVariantSpec | Enum variant with a discriminant, constructor arguments, or positional/record payload |
| ImportSpec | Explicit imports (aliased, side-effect, wildcard) |
| FileSpec | Top-level file with automatic import resolution |
| ProjectSpec | Multi-file project generation |
| CodeTemplate | Reusable parameterized templates with named parameters |
Specs record declaration intent. At render time the selected language validates
that intent and lowers it to structured CodeBlocks, so type references remain
available to import tracking and alias resolution.
See Building Functions & Fields, Building Types & Enums, and Files & Projects for examples and the full API.
| Language | Extension | Semicolons | Import Style |
|---|---|---|---|
| TypeScript | .ts |
yes | ES modules |
| JavaScript | .js |
yes | ES modules |
| Rust | .rs |
yes | use paths |
| Go | .go |
no | package imports |
| Python | .py |
no | import/from |
| Java | .java |
yes | package imports |
| Kotlin | .kt |
no | package imports |
| Swift | .swift |
no | import module |
| Dart | .dart |
yes | package imports |
| Scala | .scala |
no | import paths |
| Haskell | .hs |
no | import module |
| OCaml | .ml |
no | open module |
| C | .c |
yes | #include |
| C++ | .cpp |
yes | #include/using |
| C# | .cs |
yes | using namespace |
| Lua | .lua |
no | require() |
| Bash | .bash |
no | source |
| Zsh | .zsh |
no | source |
The sigil-stitch book covers everything in depth:
User Guide:
- Introduction -- what it is and how the pieces fit together
- Getting Started -- first CodeBlock, first FileSpec, first output
- Format Specifiers -- deep dive on
%T,%N,%S,%V,%L,%W, and friends - TypeName -- type references, import tracking, cross-language rendering
- Building Functions & Fields -- ParameterSpec, FieldSpec, FunSpec
- Building Types & Enums -- TypeSpec, PropertySpec, AnnotationSpec, EnumVariantSpec
- Files & Projects -- ImportSpec, FileSpec, ProjectSpec
- sigil_quote! Macro -- inline code with
$T/$S/$N/$L/$V/$C_each/$if/$for/$let/$join/$+/$attr/$T_joininterpolation, plus Go paren-block support - Code Templates -- reusable
#{name:K}templates - Language Cookbook -- idiomatic recipes per language
Development Guide:
- Architecture -- ownership, materialization, rendering, and import resolution
- Design -- accepted seams and design invariants
- Declaration Specs and Language Lowering -- declaration intent, capabilities, and language-owned grammar
- Type Presentation -- data-driven cross-language type rendering
- Adding a Language -- implementing the CodeLang trait step by step
The minimum supported Rust version is 1.88.0 (edition 2024, let-chains).
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.