use anyhow::{Result, bail};
use clap::{Args, Parser, Subcommand};
use crate::ir::Version;
#[derive(Parser, Debug)]
#[command(
name = "gloam",
version,
about = "Vulkan/OpenGL/GLES/EGL/GLX/WGL loader generator"
)]
pub struct Cli {
#[arg(long)]
pub promoted: bool,
#[arg(long)]
pub predecessors: bool,
#[arg(long, required = true)]
pub api: String,
#[arg(long)]
pub extensions: Option<String>,
#[arg(long)]
pub merge: bool,
#[arg(long, default_value = ".")]
pub out_path: String,
#[arg(long)]
pub quiet: bool,
#[arg(long)]
pub fetch: bool,
#[command(subcommand)]
pub generator: Generator,
}
#[derive(Subcommand, Debug)]
pub enum Generator {
C(CArgs),
Rust(RustArgs),
}
#[derive(Args, Debug)]
pub struct CArgs {
#[arg(long)]
pub alias: bool,
#[arg(long)]
pub loader: bool,
}
#[derive(Args, Debug)]
pub struct RustArgs {
#[arg(long)]
pub alias: bool,
}
impl Cli {
pub fn api_requests(&self) -> Result<Vec<ApiRequest>> {
self.api
.split(',')
.map(|s| ApiRequest::parse(s.trim()))
.collect()
}
pub fn extension_filter(&self) -> Result<Option<Vec<String>>> {
let Some(ref spec) = self.extensions else {
return Ok(None);
};
if std::path::Path::new(spec).exists() {
let text = std::fs::read_to_string(spec)?;
let list = text
.lines()
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.map(str::to_string)
.collect();
return Ok(Some(list));
}
let list = spec
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
Ok(Some(list))
}
}
#[derive(Debug, Clone)]
pub struct ApiRequest {
pub name: String,
pub profile: Option<String>,
pub version: Option<Version>,
}
impl ApiRequest {
pub fn parse(s: &str) -> Result<Self> {
let (name_profile, ver_str) = match s.find('=') {
Some(i) => (&s[..i], Some(&s[i + 1..])),
None => (s, None),
};
let (name, profile) = match name_profile.find(':') {
Some(i) => (&name_profile[..i], Some(&name_profile[i + 1..])),
None => (name_profile, None),
};
if name.is_empty() {
bail!("empty API name in --api argument");
}
let version = ver_str
.map(|v| {
let (maj, min) = v.split_once('.').ok_or_else(|| {
anyhow::anyhow!("invalid version '{}', expected major.minor", v)
})?;
Ok::<_, anyhow::Error>(Version::new(maj.parse()?, min.parse()?))
})
.transpose()?;
Ok(Self {
name: name.to_string(),
profile: profile.map(str::to_string),
version,
})
}
pub fn spec_name(&self) -> &str {
match self.name.as_str() {
"gl" | "gles1" | "gles2" | "glcore" => "gl",
"egl" => "egl",
"glx" => "glx",
"wgl" => "wgl",
"vk" | "vulkan" => "vk",
other => other,
}
}
#[allow(dead_code)]
pub fn is_gl_family(&self) -> bool {
matches!(self.name.as_str(), "gl" | "gles1" | "gles2" | "glcore")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_gl_core_versioned() {
let r = ApiRequest::parse("gl:core=3.3").unwrap();
assert_eq!(r.name, "gl");
assert_eq!(r.profile.as_deref(), Some("core"));
assert_eq!(r.version, Some(Version::new(3, 3)));
}
#[test]
fn parse_gl_compat_no_version() {
let r = ApiRequest::parse("gl:compat").unwrap();
assert_eq!(r.name, "gl");
assert_eq!(r.profile.as_deref(), Some("compat"));
assert!(r.version.is_none());
}
#[test]
fn parse_gles2_versioned() {
let r = ApiRequest::parse("gles2=3.0").unwrap();
assert_eq!(r.name, "gles2");
assert!(r.profile.is_none());
assert_eq!(r.version, Some(Version::new(3, 0)));
}
#[test]
fn parse_vk_versioned() {
let r = ApiRequest::parse("vk=1.3").unwrap();
assert_eq!(r.name, "vk");
assert_eq!(r.version, Some(Version::new(1, 3)));
}
#[test]
fn parse_bare_name_no_version() {
let r = ApiRequest::parse("egl").unwrap();
assert_eq!(r.name, "egl");
assert!(r.profile.is_none());
assert!(r.version.is_none());
}
#[test]
fn parse_empty_name_errors() {
assert!(ApiRequest::parse("=1.0").is_err());
}
#[test]
fn parse_version_missing_minor_errors() {
assert!(ApiRequest::parse("gl:core=3").is_err());
}
#[test]
fn parse_version_non_numeric_errors() {
assert!(ApiRequest::parse("gl:core=three.three").is_err());
}
#[test]
fn spec_name_gl_family_maps_to_gl() {
for name in &["gl", "gles1", "gles2", "glcore"] {
let r = ApiRequest::parse(name).unwrap();
assert_eq!(r.spec_name(), "gl", "failed for api name '{name}'");
}
}
#[test]
fn spec_name_passthrough() {
for name in &["egl", "glx", "wgl"] {
let r = ApiRequest::parse(name).unwrap();
assert_eq!(r.spec_name(), *name);
}
}
#[test]
fn spec_name_vulkan_alias() {
assert_eq!(ApiRequest::parse("vk").unwrap().spec_name(), "vk");
assert_eq!(ApiRequest::parse("vulkan").unwrap().spec_name(), "vk");
}
}