Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Writing a Plugin

This guide walks you through creating a custom typewriter plugin.

1. Create a Crate

cargo new --lib typewriter-plugin-mylang

2. Cargo.toml

[package]
name = "typewriter-plugin-mylang"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
typewriter-plugin = "0.1.0"
typewriter-core = "0.5.2"

The cdylib crate type is required for dynamic loading.

3. Implement TypeMapper

#![allow(unused)]
fn main() {
use typewriter_plugin::prelude::*;

struct MyLangMapper;

impl TypeMapper for MyLangMapper {
    fn map_primitive(&self, ty: &PrimitiveType) -> String {
        match ty {
            PrimitiveType::String => "string".into(),
            PrimitiveType::Bool => "boolean".into(),
            PrimitiveType::U32 => "uint32".into(),
            // ... implement all primitives
            _ => "any".into(),
        }
    }

    fn map_option(&self, inner: &TypeKind) -> String {
        format!("{}?", self.map_type(inner))
    }

    fn map_vec(&self, inner: &TypeKind) -> String {
        format!("List<{}>", self.map_type(inner))
    }

    fn map_hashmap(&self, key: &TypeKind, value: &TypeKind) -> String {
        format!("Map<{}, {}>", self.map_type(key), self.map_type(value))
    }

    fn map_tuple(&self, elements: &[TypeKind]) -> String {
        let inner: Vec<String> = elements.iter().map(|e| self.map_type(e)).collect();
        format!("({})", inner.join(", "))
    }

    fn map_named(&self, name: &str) -> String { name.into() }

    fn emit_struct(&self, def: &StructDef) -> String {
        // Your struct rendering logic
        todo!()
    }

    fn emit_enum(&self, def: &EnumDef) -> String {
        // Your enum rendering logic
        todo!()
    }

    fn file_header(&self, type_name: &str) -> String {
        format!("// Generated by typewriter. Source: {}\n\n", type_name)
    }

    fn file_extension(&self) -> &str { "mylang" }

    fn file_naming(&self, type_name: &str) -> String {
        to_file_style(type_name, FileStyle::SnakeCase)
    }
}
}

4. Implement EmitterPlugin

#![allow(unused)]
fn main() {
struct MyLangPlugin;

impl MyLangPlugin {
    fn new() -> Self { Self }
}

impl EmitterPlugin for MyLangPlugin {
    fn language_id(&self) -> &str { "mylang" }
    fn language_name(&self) -> &str { "My Language" }
    fn version(&self) -> &str { "0.1.0" }
    fn default_output_dir(&self) -> &str { "./generated/mylang" }
    fn file_extension(&self) -> &str { "mylang" }

    fn mapper(&self, _config: &PluginConfig) -> Box<dyn TypeMapper> {
        Box::new(MyLangMapper)
    }
}

declare_plugin!(MyLangPlugin);
}

5. Build and Install

cargo build --release
cp target/release/libtypewriter_plugin_mylang.so ~/.typewriter/plugins/

6. Validate

typebridge plugin validate ./target/release/libtypewriter_plugin_mylang.so

7. Use

#![allow(unused)]
fn main() {
#[derive(TypeWriter)]
#[sync_to(mylang)]
pub struct User { ... }
}
typebridge generate --all