herb 0.8.3

Rust bindings for Herb
Documentation
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;

fn main() {
  let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
  let vendor_src_dir = manifest_dir.join("vendor/libherb/src");
  let vendor_include_dir = manifest_dir.join("vendor/libherb/src/include");

  let (src_dir, include_dir, root_dir) = if vendor_src_dir.exists() {
    (vendor_src_dir, vendor_include_dir, manifest_dir.clone())
  } else {
    let root = manifest_dir.parent().unwrap();
    (
      root.join("src"),
      root.join("src/include"),
      root.to_path_buf(),
    )
  };

  let vendor_prism_src = manifest_dir.join("vendor/prism/src");
  let vendor_prism_include = manifest_dir.join("vendor/prism/include");

  let prism_include = if vendor_prism_src.exists() {
    let mut prism_sources = Vec::new();
    for path in glob::glob(vendor_prism_src.join("**/*.c").to_str().unwrap())
      .unwrap()
      .flatten()
    {
      prism_sources.push(path);
    }

    let mut prism_build = cc::Build::new();
    prism_build
      .flag("-std=c99")
      .flag("-fPIC")
      .opt_level(2)
      .include(&vendor_prism_include)
      .files(&prism_sources)
      .warnings(false);

    prism_build.compile("prism");

    vendor_prism_include
  } else {
    let prism_path = get_prism_path(&root_dir);
    let prism_build = prism_path.join("build");
    println!("cargo:rustc-link-search=native={}", prism_build.display());
    println!("cargo:rustc-link-lib=static=prism");
    prism_path.join("include")
  };

  let mut c_sources = Vec::new();

  for path in glob::glob(src_dir.join("**/*.c").to_str().unwrap())
    .unwrap()
    .flatten()
  {
    if !path.ends_with("main.c") {
      c_sources.push(path);
    }
  }

  let mut build = cc::Build::new();
  build
    .flag("-std=c99")
    .flag("-Wall")
    .flag("-Wextra")
    .flag("-Wno-unused-parameter")
    .flag("-fPIC")
    .opt_level(2)
    .include(&include_dir)
    .include(&prism_include)
    .files(&c_sources);

  build.compile("herb");

  let bindings = bindgen::Builder::default()
    .header(include_dir.join("analyze.h").to_str().unwrap())
    .header(include_dir.join("herb.h").to_str().unwrap())
    .header(include_dir.join("ast_nodes.h").to_str().unwrap())
    .header(include_dir.join("errors.h").to_str().unwrap())
    .header(include_dir.join("element_source.h").to_str().unwrap())
    .header(include_dir.join("token_struct.h").to_str().unwrap())
    .header(include_dir.join("util/hb_string.h").to_str().unwrap())
    .header(include_dir.join("util/hb_array.h").to_str().unwrap())
    .clang_arg(format!("-I{}", include_dir.display()))
    .clang_arg(format!("-I{}", prism_include.display()))
    .allowlist_function("herb_.*")
    .allowlist_function("hb_array_.*")
    .allowlist_function("token_type_to_string")
    .allowlist_function("ast_node_free")
    .allowlist_function("element_source_to_string")
    .allowlist_type("AST_.*")
    .allowlist_type("ERROR_.*")
    .allowlist_type(".*_ERROR_T")
    .allowlist_type("element_source_t")
    .allowlist_type("ast_node_type_T")
    .allowlist_type("error_type_T")
    .allowlist_type("hb_array_T")
    .allowlist_type("hb_string_T")
    .allowlist_type("token_T")
    .allowlist_type("position_T")
    .allowlist_type("location_T")
    .allowlist_type("herb_extract_language_T")
    .allowlist_var("AST_.*")
    .allowlist_var("ERROR_.*")
    .allowlist_var("ELEMENT_SOURCE_.*")
    .allowlist_var("HERB_EXTRACT_.*")
    .derive_debug(true)
    .derive_default(false)
    .prepend_enum_name(false)
    .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
    .generate()
    .expect("Unable to generate bindings");

  let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
  bindings
    .write_to_file(out_path.join("bindings.rs"))
    .expect("Couldn't write bindings!");

  for source in &c_sources {
    println!("cargo:rerun-if-changed={}", source.display());
  }

  println!("cargo:rerun-if-changed={}", include_dir.display());
  println!("cargo:rerun-if-changed=build.rs");
}

fn get_prism_path(root_dir: &Path) -> PathBuf {
  let output = Command::new("bundle")
    .args(["show", "prism"])
    .current_dir(root_dir)
    .output()
    .expect("Failed to run `bundle show prism`");

  let output_str = String::from_utf8(output.stdout).expect("Failed to parse bundle output");

  let path_str = output_str
    .lines()
    .last()
    .expect("No output from bundle show prism")
    .trim()
    .to_string();

  PathBuf::from(path_str)
}