csbinding_generator 0.5.0

this is a cli tool to generate c# bindings from a rust codebase.
use std::fs;
use std::path::Path;

use csharp_binder::{CSharpBuilder, CSharpConfiguration};

/// this function will open the file and make some basic checks to validate that the rust file does actually export functions and/or Structs/Enums
fn read_file(path: &Path) -> Result<String, String> {
    let content = fs::read_to_string(path).map_err(|x| x.to_string())?;
    if content.contains("extern \"C\"") || content.contains("\n#[repr(") {
        return Ok(content);
    }

    Err("There is nothing to export here".to_string())
}

fn capitalcase_word<S: AsRef<str>>(input: S) -> String {
    let first = input
        .as_ref()
        .chars()
        .next()
        .unwrap()
        .to_uppercase()
        .to_string();
    first
        .chars()
        .chain(input.as_ref().chars().skip(1))
        .collect()
}

fn capitalcase<S: AsRef<str>>(input: S) -> String {
    input.as_ref().split('_').map(capitalcase_word).collect()
}

pub fn write_file<S: AsRef<str>>(filename: S, folder: S, content: String) -> Result<(), String> {
    let new_filename = capitalcase(filename);
    let mut folder = Path::new(folder.as_ref()).to_path_buf();
    if !folder.as_path().exists() {
        fs::create_dir_all(folder.to_str().unwrap()).map_err(|x| x.to_string())?;
    }
    folder.push(&new_filename);
    folder.set_extension("cs");
    fs::write(folder, content).map_err(|x| x.to_string())
}

pub fn generate_csharp<S: AsRef<str>>(
    path: S,
    dll_name: S,
    namespace: &Option<String>,
) -> Result<String, String> {
    let path = Path::new(path.as_ref());
    let rust_file = read_file(&path)?;
    let classname = capitalcase(path.file_stem().unwrap().to_str().unwrap());

    let mut configuration = CSharpConfiguration::new(10);
    let mut builder = CSharpBuilder::new(&rust_file, dll_name.as_ref(), &mut configuration)
        .map_err(|x| x.to_string())?;
    if namespace.is_some() {
        builder.set_namespace(namespace.as_ref().unwrap());
    }
    builder.set_type(&classname);

    let script = builder.build().map_err(|x| x.to_string())?;
    Ok(script)
}

pub fn walk_folder<T: AsRef<str> + PartialEq>(
    root: T,
    excluded_files: &[String],
) -> Result<Vec<String>, String> {
    let p = Path::new(root.as_ref());
    if !p.is_dir() {
        return Err(format!(
            "string provided is not a valid folder: \"{}\"",
            root.as_ref()
        ));
    }

    let mut results = vec![];
    let mut folders_to_check = vec![];
    let mut handler = |p, folders_to_check: &mut Vec<_>| {
        for entry in fs::read_dir(p).unwrap() {
            let entry = entry.unwrap();
            let filename = entry.file_name().to_str().unwrap().to_string();
            if excluded_files.contains(&filename) {
                continue;
            }
            let e = entry.path().canonicalize().unwrap();
            // let e = entry.path();
            if e.is_file() && e.extension().unwrap_or_default().to_str().unwrap() == "rs" {
                results.push(e.to_str().unwrap().to_string())
            }
            if e.is_dir() {
                folders_to_check.push(e);
            }
        }
    };

    handler(p.into(), &mut folders_to_check);

    loop {
        let p = folders_to_check.pop();
        if p.is_none() {
            break;
        }
        handler(p.unwrap(), &mut folders_to_check);
    }

    Ok(results)
}