c_import 0.2.0

A c_import macro for Rust
Documentation
use proc_macro::TokenStream;
use std::hash::{Hash, Hasher};
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};

const FILESTEM: &str = "temphdr";
const C_ARGS: &[&str] = &[
    "--no-layout-tests",
    "--no-doc-comments",
    "--no-prepend-enum-name",
    "--disable-header-comment",
    "--",
    "-std=c17",
    "-I."
];
const CPP_ARGS: &[&str] = &[
    "--generate-inline-functions",
    "--no-layout-tests",
    "--no-doc-comments",
    "--no-prepend-enum-name",
    "--disable-header-comment",
    "--",
    "-xc++",
    "-std=c++17",
    "-I."
];

fn gen_header(input: String, is_cpp: bool) -> PathBuf {
    let mut hasher = std::collections::hash_map::DefaultHasher::new();
    input.hash(&mut hasher);
    let input_hash = hasher.finish();
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("Failed to get current time!");
    let filename = format!(
        "{:?}{}{}.{}",
        now.as_millis(),
        input_hash,
        FILESTEM,
        if is_cpp { "hpp" } else { "h" }
    );
    let f = std::env::temp_dir().join(filename);
    let input = "#pragma once\n".to_string() + &input;
    std::fs::write(&f, &input).expect("Failed to generate temporary header!");
    f
}

fn del_header(header: Option<PathBuf>) {
    if let Some(f) = header {
        if f.exists() {
            std::fs::remove_file(f).expect("Failed to delete temporary header!");
        }
    }
}

fn gen_command(header: String, args: &[&str], is_cpp: bool) -> (Command, Option<PathBuf>) {
    let mut cmd = Command::new("bindgen");
    let (path, header) = {
        let header = gen_header(header, is_cpp);
        let path = format!("{}", header.display());
        (path, Some(header))
    };
    let mut args = args.to_vec();
    args.insert(0, &path);
    cmd.args(args);
    (cmd, header)
}

pub(crate) fn common(input: TokenStream, is_cpp: bool) -> TokenStream {
    let input = input.to_string();
    let input: Vec<&str> = input.split(',').collect();
    let mut headers = vec![];
    let mut extra_args = vec![];
    for elem in input {
        let elem = elem.trim();
        if elem.starts_with("\"-") {
            extra_args.push(elem);
        } else if elem.starts_with("\"<") {
            headers.push(&elem[1..elem.len() - 1]);
        } else {
            headers.push(elem);
        }
    }
    let extra_args: Vec<String> = extra_args.iter().map(|s| s.replace('"', "")).collect();
    let mut args: Vec<&str> = if is_cpp {
        CPP_ARGS.to_vec()
    } else {
        C_ARGS.to_vec()
    };
    let header = headers
        .iter()
        .map(|s| format!("#include {}\n", s))
        .collect();
    args.append(&mut extra_args.iter().map(|s| s.trim()).collect());
    let (mut cmd, header) = gen_command(header, &args, is_cpp);
    let cmd = cmd.output().expect("Failed to invoke bindgen!");
    del_header(header);
    if !cmd.status.success() {
        std::io::stderr().write_all(&cmd.stderr).unwrap();
    }
    String::from_utf8(cmd.stdout)
        .expect("Failed to parse bindgen output")
        .parse()
        .unwrap()
}