# Rust bindgen helpers

[![GitHub repo](https://img.shields.io/badge/github-nyurik/bindgen_helpers-8da0cb?logo=github)](https://github.com/nyurik/bindgen_helpers)
[![crates.io version](https://img.shields.io/crates/v/bindgen_helpers)](https://crates.io/crates/bindgen_helpers)
[![crate usage](https://img.shields.io/crates/d/bindgen_helpers)](https://crates.io/crates/bindgen_helpers)
[![docs.rs status](https://img.shields.io/docsrs/bindgen_helpers)](https://docs.rs/bindgen_helpers)
[![crates.io license](https://img.shields.io/crates/l/bindgen_helpers)](https://github.com/nyurik/bindgen_helpers/blob/main/LICENSE-APACHE)
[![CI build status](https://github.com/nyurik/bindgen_helpers/actions/workflows/ci.yml/badge.svg)](https://github.com/nyurik/bindgen_helpers/actions)
[![Codecov](https://img.shields.io/codecov/c/github/nyurik/bindgen_helpers)](https://app.codecov.io/gh/nyurik/bindgen_helpers)

Utilities for shaping Rust code generated from C headers with [bindgen](https://rust-lang.github.io/rust-bindgen/).
This crate re-exports bindgen and adds a small wrapper for common cleanup tasks: renaming generated items, rustifying and renaming C enums, applying regex removals and case conversion to identifiers, and collecting matching integer `#define` constants into additional Rust enums.

The lower-level `Renamer` type implements bindgen parse callbacks directly for existing bindgen-style build scripts.
For new code, `BindingsBuilder` wraps an already configured bindgen `Builder`, installs the callbacks, runs generation, and writes the final bindings with any helper-generated enums appended.

## Usage

```toml
# Cargo.toml
[build-dependencies]
# Bindgen_helpers re-exports all of bindgen's public API at the root level
# Do not use bindgen directly to avoid some version conflicts
bindgen_helpers = "0.3"
```

```rust
// build.rs
use bindgen_helpers::{BindingsBuilder, Builder, define_enum, rename_enum};

fn main() {
  let builder = Builder::default()
    // in real code, use .header("path/to/header.h")
    .header_contents("test.h", r#"

struct my_struct {
    int a;
};

enum my_enum {
    I_SAID_YES_ENUM,
    I_SAID_NO_ENUM,
    I_SAID_MV_IT_ENUM,
    I_SAID_MV_IT2_ENUM,
};

#define ERR_FOO 1
#define ERR_BAR 2

"#);

  // Wrap the builder with the helper to handle all additional functionality
  // true to print debug info about the renames to stderr, false to be silent
  let mut helpers = BindingsBuilder::new(builder, true);

  // rename a single item, e.g. a struct, enum, or a typedef
  helpers.rename_item("my_struct", "MyStruct");

  // rename an enum and its values
  rename_enum!(
    helpers,
    "my_enum" => "MyEnum", // rename the enum itself
    remove: "^I_SAID_",    // optionally any number of "remove" regexes
    remove: "_ENUM$",
    case: Pascal,          // optionally set case convert, defaults to "PascalCase"
    "MV_IT" => "Value1",   // rename a specific value after pattern removal
    "MV_IT2" => "Value2",  // more specific value renames
  );

  // Collect matching #define constants into an additional Rust enum.
  // Make sure parameters are given in this order. Skipping is ok.
  define_enum!(
    helpers,
    ErrorCode,             // enum name
    r"^ERR_",              // include matching integer defines
    repr = u32,            // optionally override auto-computed repr type
    min: 0,                // optionally include only values >= min
    max: 999,              // optionally include only values <= max
    exclude: "_PRIVATE$",  // optionally exclude matching defines
    sort: Value,           // optionally sort by Name, Value, or ValueDesc
    derive: [Debug, Copy, Clone, PartialEq, Eq],  // optionally override derives
    remove: "^ERR_",       // optionally any number of "remove" regexes
    case: Pascal,          // optionally set case convert, defaults to "PascalCase"
    "FOOBAR" => "FooBar",  // rename specific values after pattern removal, e.g. to fix some special cases that don't follow the general pattern
  );

  let bindings = helpers.into_string().unwrap();
}

//
// This is the approximate code that would be generated by the above:
//

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MyStruct {
  pub a: ::std::os::raw::c_int,
}

#[repr(u32)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum MyEnum {
  Yes = 0,
  No = 1,
  Value1 = 2,
  Value2 = 3,
}

pub const ERR_FOO: u32 = 1;
pub const ERR_BAR: u32 = 2;

#[repr(u32)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum ErrorCode {
  Foo = (ERR_FOO as u32),
  Bar = (ERR_BAR as u32),
}

```

<!-- This code would generate the actual test output, but it is not stable enough to always run

  // Output the generated code to a string.
  // In real code, use .write_to_file("bindings.rs")
  let mut output = Vec::new();
  bindings.write(Box::new(&mut output)).unwrap();
  let output = String::from_utf8(output).unwrap();

  assert_eq!(output, r##"#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MyStruct {
    pub a: ::std::os::raw::c_int,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
    ["Size of MyStruct"][::std::mem::size_of::<MyStruct>() - 4usize];
    ["Alignment of MyStruct"][::std::mem::align_of::<MyStruct>() - 4usize];
    ["Offset of field: MyStruct::a"][::std::mem::offset_of!(MyStruct, a) - 0usize];
};
#[repr(u32)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum MyEnum {
    Yes = 0,
    No = 1,
    Value1 = 2,
    Value2 = 3,
}
"##);
-->

See the list of all [case variants](https://docs.rs/convert_case/latest/convert_case/enum.Case.html) supported by the `convert_case` crate.

## Development

* This project is easier to develop with [just](https://github.com/casey/just#readme), a modern alternative to `make`.
  Install it with `cargo install just`.
* To get a list of available commands, run `just`.
* To run tests, use `just test`.

## License

Licensed under either of

* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
  at your option.

### Contribution

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.
