Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions csbindgen-tests/src/field_casing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[repr(C)]
pub struct GeneratedStructFieldCasing {
pub this_is_snake_case: u32,
}
3 changes: 2 additions & 1 deletion csbindgen-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{
};

mod counter;
mod field_casing;

#[allow(dead_code)]
#[allow(non_snake_case)]
Expand Down Expand Up @@ -719,4 +720,4 @@ pub enum CResultStatus {
#[no_mangle]
pub extern "C" fn enum_test2(status: CResultStatus) -> i32 {
status as i32
}
}
1 change: 1 addition & 0 deletions csbindgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ repository = "https://github.com/Cysharp/csbindgen/"
[dependencies]
syn = { version = "2.0.68", features = ["full", "parsing"] }
regex = "1.10.5"
convert_case = "0.11.0"
11 changes: 10 additions & 1 deletion csbindgen/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
path::Path,
};
use std::convert::identity;

use crate::Case;
use crate::{generate, GenerateKind};

pub struct Builder {
Expand Down Expand Up @@ -35,6 +35,7 @@ pub struct BindgenOptions {
pub csharp_type_rename: fn(type_name: String) -> String,
pub csharp_file_header: String,
pub csharp_file_footer: String,
pub csharp_field_casing: Option<Case<'static>>,
pub always_included_types: Vec<String>,
}

Expand Down Expand Up @@ -63,6 +64,7 @@ impl Default for Builder {
csharp_type_rename: identity,
csharp_file_header: "".to_string(),
csharp_file_footer: "".to_string(),
csharp_field_casing: None,
always_included_types: vec![],
},
}
Expand Down Expand Up @@ -238,6 +240,13 @@ impl Builder {
self
}

/// configure the casing of the generated C# field names, default is to use the input field
/// names verbatim (ie. most likely snake_case)
pub fn csharp_field_casing(mut self, field_casing: Case<'static>) -> Builder {
self.options.csharp_field_casing = Some(field_casing);
self
}

pub fn generate_csharp_file<P: AsRef<Path>>(
&self,
csharp_output_path: P,
Expand Down
57 changes: 48 additions & 9 deletions csbindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use parser::*;
use std::{collections::HashSet, error::Error};
use type_meta::{ExternMethod, RustConst, RustEnum, RustStruct, RustType};

pub use convert_case::Case;

enum GenerateKind {
InputBindgen,
InputExtern,
Expand All @@ -39,15 +41,15 @@ pub(crate) fn generate(

for path in paths {
let file_content = std::fs::read_to_string(path)
.unwrap_or_else(|_| panic!("input file not found, path: {}", path.display()));
.unwrap_or_else(|_| panic!("input file not found, path: {}", std::path::absolute(path).unwrap().display()));
let file_ast = syn::parse_file(file_content.as_str())?;

match generate_kind {
GenerateKind::InputBindgen => collect_foreign_method(&file_ast, options, &mut methods),
GenerateKind::InputExtern => collect_extern_method(&file_ast, options, &mut methods),
};
collect_type_alias(&file_ast, &mut aliases);
collect_struct(&file_ast, &mut structs);
collect_struct(&file_ast, options, &mut structs);
collect_enum(&file_ast, &mut enums);

collect_const(&file_ast, &mut consts, options.csharp_generate_const_filter);
Expand Down Expand Up @@ -159,15 +161,17 @@ mod tests {
let path = std::env::current_dir().unwrap();
println!("starting dir: {}", path.display()); // csbindgen/csbindgen

std::env::set_current_dir(path.parent().unwrap()).unwrap();
// NOTE: no longer changing the cwd here since it causes race conditions with other tests
// running at the same time
// std::env::set_current_dir(path.parent().unwrap()).unwrap();

Builder::new()
.input_bindgen_file("csbindgen-tests/src/lz4.rs")
.input_bindgen_file("../csbindgen-tests/src/lz4.rs")
.csharp_class_name("LibLz4")
.csharp_dll_name("csbindgen_tests")
.generate_to_file(
"csbindgen-tests/src/lz4_ffi.rs",
"dotnet-sandbox/lz4_bindgen.cs",
"../csbindgen-tests/src/lz4_ffi.rs",
"../dotnet-sandbox/lz4_bindgen.cs",
)
.unwrap();
}
Expand Down Expand Up @@ -202,6 +206,41 @@ mod tests {
file.flush().unwrap();
}

#[test]
fn field_casing_original() {
let original_file_path = "../dotnet-sandbox/field_casing_original.cs";
let generated_file_path = "../dotnet-sandbox/field_casing_bindgen.cs";

Builder::new()
.always_included_types(["GeneratedStructFieldCasing"])
.input_bindgen_file("../csbindgen-tests/src/field_casing.rs")
.generate_csharp_file(generated_file_path)
.unwrap();

compare_and_delete_files(
original_file_path,
generated_file_path,
);
}

#[test]
fn field_casing_upper_camel() {
let original_file_path = "../dotnet-sandbox/field_casing_original_upper_camel.cs";
let generated_file_path = "../dotnet-sandbox/field_casing_bindgen_upper_camel.cs";

Builder::new()
.always_included_types(["GeneratedStructFieldCasing"])
.input_bindgen_file("../csbindgen-tests/src/field_casing.rs")
.csharp_field_casing(Case::UpperCamel)
.generate_csharp_file(generated_file_path)
.unwrap();

compare_and_delete_files(
original_file_path,
generated_file_path,
);
}

fn compare_and_delete_files(original_file_path: &str, generated_file_path: &str) {
let original = fs::read_to_string(original_file_path)
.expect("Should have been able to read original file");
Expand All @@ -216,15 +255,15 @@ mod tests {

// #[test]
// fn test_emit_without_class() {
// let generated_file_path = "dotnet-sandbox/only_enums_and_structs_bindgen.cs";
// let generated_file_path = "../dotnet-sandbox/only_enums_and_structs_bindgen.cs";
// Builder::new()
// .always_included_types(["Vec3", "Foo"])
// .input_bindgen_file("csbindgen-tests/src/only_enums_and_structs.rs")
// .input_bindgen_file("../csbindgen-tests/src/only_enums_and_structs.rs")
// .generate_csharp_file(generated_file_path)
// .unwrap();

// compare_and_delete_files(
// "dotnet-sandbox/only_enums_and_structs_original.cs",
// "../dotnet-sandbox/only_enums_and_structs_original.cs",
// generated_file_path,
// );
// }
Expand Down
16 changes: 11 additions & 5 deletions csbindgen/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::util::get_str_from_meta;
use crate::{alias_map::AliasMap, builder::BindgenOptions, field_map::FieldMap, type_meta::*};
use regex::Regex;
use std::collections::HashSet;
use convert_case::Casing;
use syn::{ForeignItem, Item, Pat, ReturnType};

enum FnItem {
Expand Down Expand Up @@ -268,12 +269,12 @@ pub fn collect_type_alias(ast: &syn::File, result: &mut AliasMap) {
}
}

pub fn collect_struct(ast: &syn::File, result: &mut Vec<RustStruct>) {
pub fn collect_struct(ast: &syn::File, options: &BindgenOptions, result: &mut Vec<RustStruct>) {
// collect union or struct
for item in depth_first_module_walk(&ast.items) {
if let Item::Union(t) = item {
let struct_name = t.ident.to_string();
let fields = collect_fields(&t.fields);
let fields = collect_fields(&t.fields, options);

result.push(RustStruct {
struct_name,
Expand All @@ -294,7 +295,7 @@ pub fn collect_struct(ast: &syn::File, result: &mut Vec<RustStruct>) {
if repr {
if let syn::Fields::Named(f) = &t.fields {
let struct_name = t.ident.to_string();
let fields = collect_fields(f);
let fields = collect_fields(f, options);
result.push(RustStruct {
struct_name,
fields,
Expand Down Expand Up @@ -335,14 +336,19 @@ pub fn collect_struct(ast: &syn::File, result: &mut Vec<RustStruct>) {
}
}

fn collect_fields(fields: &syn::FieldsNamed) -> Vec<FieldMember> {
fn collect_fields(fields: &syn::FieldsNamed, options: &BindgenOptions) -> Vec<FieldMember> {
let mut result = Vec::new();

for field in &fields.named {
if let Some(x) = &field.ident {
let name = match options.csharp_field_casing {
Some(case) => x.to_string().to_case(case),
None => x.to_string(),
};

let t = parse_type(&field.ty);
result.push(FieldMember {
name: x.to_string(),
name,
rust_type: t,
doc_comment: gather_docs(&field.attrs),
});
Expand Down
23 changes: 23 additions & 0 deletions dotnet-sandbox/field_casing_original.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;


namespace CsBindgen
{


[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct GeneratedStructFieldCasing
{
public uint this_is_snake_case;
}



}
23 changes: 23 additions & 0 deletions dotnet-sandbox/field_casing_original_upper_camel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;


namespace CsBindgen
{


[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct GeneratedStructFieldCasing
{
public uint ThisIsSnakeCase;
}



}