extern crate bindgen;
use std::error::Error;
use yap::{IntoTokens, Tokens};
const SEPS: [char; 5] = [' ', '\t', '\r', '\n', '['];
fn format_ref(str: String) -> String {
if str.contains("://") {
format!("[{str}]({str})")
} else {
format!("[`{str}`]")
}
}
fn take_word(toks: &mut impl Tokens<Item = char>) -> String {
toks
.take_while(|&c| !SEPS.into_iter().any(|s| c == s))
.collect::<String>()
}
fn skip_whitespace(toks: &mut impl Tokens<Item = char>) {
toks.skip_while(|c| c.is_ascii_whitespace());
}
fn emit_section_header(output: &mut Vec<String>, header: &str) {
if !output.iter().any(|line| line.trim() == header) {
output.push(header.to_owned());
output.push("\n\n".to_owned());
}
}
fn transform(str: &str) -> Result<String, Box<dyn Error>> {
let mut res: Vec<String> = vec![];
let mut toks = str.into_tokens();
skip_whitespace(&mut toks);
while let Some(tok) = toks.next() {
if "@\\".chars().any(|c| c == tok) {
let tag = take_word(&mut toks);
skip_whitespace(&mut toks);
match tag.as_str() {
"param" => {
emit_section_header(&mut res, "# Arguments");
let (mut argument, mut attributes) = (take_word(&mut toks), "".to_owned());
if argument.is_empty() {
if toks.next() != Some('[') {
return Err("Expected opening '[' inside attribute list".into());
}
attributes = toks.take_while(|&c| c != ']').collect::<String>();
if toks.next() != Some(']') {
return Err("Expected closing ']' inside attribute list".into());
}
attributes = format!(" \\[{}\\] ", attributes);
skip_whitespace(&mut toks);
argument = take_word(&mut toks);
}
res.push(format!("* `{}`{} -", argument, attributes));
}
"retval" => res.push(format!("* `{}`", take_word(&mut toks))),
"c" | "p" => res.push(format!("`{}`", take_word(&mut toks))),
"ref" => res.push(format_ref(take_word(&mut toks))),
"see" | "sa" => {
emit_section_header(&mut res, "# See also");
res.push(format!("> {}", format_ref(take_word(&mut toks))));
}
"a" | "e" | "em" => res.push(format!("_{}_", take_word(&mut toks))),
"b" => res.push(format!("**{}**", take_word(&mut toks))),
"note" => res.push("> **Note** ".to_owned()),
"since" => res.push("> **Since** ".to_owned()),
"deprecated" => res.push("> **Deprecated** ".to_owned()),
"remark" | "remarks" => res.push("> ".to_owned()),
"li" => res.push("- ".to_owned()),
"par" => res.push("# ".to_owned()),
"returns" | "return" | "result" => emit_section_header(&mut res, "# Returns"),
"{" => { }
"}" => { }
"brief" | "short" => {}
_ => res.push(format!("{tok}{tag} ")),
}
} else if tok == '\n' {
skip_whitespace(&mut toks);
res.push(format!("{tok}"));
} else {
res.push(format!("{tok}"));
}
}
Ok(res.join(""))
}
#[derive(Debug)]
struct ProcessComments;
impl bindgen::callbacks::ParseCallbacks for ProcessComments {
fn process_comment(&self, comment: &str) -> Option<String> {
match transform(comment) {
Ok(res) => Some(res),
Err(err) => {
println!("cargo:warning=Problem processing doxygen comment: {comment}\n{err}");
None
}
}
}
}
fn main() {
let dest = cmake::Config::new("opus")
.profile("Release")
.define("OPUS_BUILD_TESTING", "OFF")
.define("OPUS_BUILD_SHARED_LIBRARY", "OFF")
.define("OPUS_BUILD_PROGRAMS", "OFF")
.define("OPUS_ENABLE_FLOAT_API", "ON")
.define("OPUS_INSTALL_PKG_CONFIG_MODULE", "ON")
.define("OPUS_INSTALL_CMAKE_CONFIG_MODULE", "ON")
.define("CMAKE_INTERPROCEDURAL_OPTIMIZATION", "ON")
.build();
println!("cargo:root={}", dest.display());
println!("cargo:include={}/include", dest.display());
println!("cargo:rustc-link-search=native={}/lib", dest.display());
println!("cargo:rustc-link-lib=static=opus");
let bindings = bindgen::Builder::default()
.use_core()
.header(dest.join("include/opus/opus.h").display().to_string())
.allowlist_file(dest.join("include/opus/opus.h").display().to_string())
.allowlist_file(dest.join("include/opus/opus_types.h").display().to_string())
.allowlist_file(dest.join("include/opus/opus_defines.h").display().to_string())
.allowlist_file(dest.join("include/opus/opus_multistream.h").display().to_string())
.allowlist_file(dest.join("include/opus/opus_projection.h").display().to_string())
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.parse_callbacks(Box::new(ProcessComments))
.generate()
.expect("Unable to generate bindings");
bindings
.write_to_file(dest.join("bindings.rs"))
.expect("Couldn't write bindings.rs");
}