use argh::FromArgs;
use cargo_metadata::MetadataCommand;
use std::path::PathBuf;
use jetstream_codegen::parser::parse_file;
use jetstream_codegen::service_parser::parse_services_from_file;
use jetstream_codegen::swift_backend::{generate_swift_file, SwiftConfig};
use jetstream_codegen::swift_rpc_backend::generate_swift_rpc;
use jetstream_codegen::ts_backend::{generate_ts_file, TsConfig};
use jetstream_codegen::ts_rpc_backend::generate_ts_rpc;
#[derive(FromArgs)]
struct Args {
#[argh(option)]
input: PathBuf,
#[argh(option)]
ts_out: Option<PathBuf>,
#[argh(option)]
swift_out: Option<PathBuf>,
#[argh(option, default = "String::from(\"@sevki/jetstream-wireformat\")")]
ts_import_path: String,
#[argh(option, default = "String::from(\"@sevki/jetstream-rpc\")")]
ts_rpc_import_path: String,
#[argh(option)]
ts_extra_import: Vec<String>,
#[argh(option)]
ts_rpc_extra_import: Vec<String>,
#[argh(option, default = "String::from(\"JetStreamWireFormat\")")]
swift_module: String,
}
fn main() {
let args: Args = argh::from_env();
let source = match std::fs::read_to_string(&args.input) {
Ok(s) => s,
Err(e) => {
eprintln!("error reading {}: {e}", args.input.display());
std::process::exit(1);
}
};
let input_abs = std::fs::canonicalize(&args.input).unwrap_or_else(|e| {
eprintln!("error resolving {}: {e}", args.input.display());
std::process::exit(1);
});
let pkg_version = find_package_version(&input_abs);
let items = parse_file(&source);
let services = parse_services_from_file(&source, &pkg_version);
let stem = args
.input
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("output");
if let Some(ts_dir) = &args.ts_out {
std::fs::create_dir_all(ts_dir).unwrap();
let ts_config = TsConfig {
import_path: args.ts_import_path.clone(),
rpc_import_path: args.ts_rpc_import_path.clone(),
extra_imports: args.ts_extra_import.clone(),
rpc_extra_imports: args.ts_rpc_extra_import.clone(),
};
if !items.is_empty() {
let ts_types = generate_ts_file(&items, &ts_config);
let ts_path = ts_dir.join(format!("{stem}.ts"));
std::fs::write(&ts_path, ts_types).unwrap();
eprintln!("wrote {}", ts_path.display());
}
for service in &services {
let ts_rpc = generate_ts_rpc(service, &ts_config, stem);
let rpc_path =
ts_dir.join(format!("{}_rpc.ts", service.name.to_lowercase()));
std::fs::write(&rpc_path, ts_rpc).unwrap();
eprintln!("wrote {}", rpc_path.display());
}
}
if let Some(swift_dir) = &args.swift_out {
std::fs::create_dir_all(swift_dir).unwrap();
let swift_config = SwiftConfig {
wireformat_module: args.swift_module.clone(),
};
if !items.is_empty() {
let swift_types = generate_swift_file(&items, &swift_config);
let swift_path = swift_dir.join(format!("{stem}.swift"));
std::fs::write(&swift_path, swift_types).unwrap();
eprintln!("wrote {}", swift_path.display());
}
for service in &services {
let swift_rpc = generate_swift_rpc(service, &swift_config);
let rpc_path = swift_dir.join(format!("{}Rpc.swift", service.name));
std::fs::write(&rpc_path, swift_rpc).unwrap();
eprintln!("wrote {}", rpc_path.display());
}
}
if args.ts_out.is_none() && args.swift_out.is_none() {
eprintln!(
"no output directory specified; use --ts-out and/or --swift-out"
);
std::process::exit(1);
}
}
fn find_package_version(input_path: &std::path::Path) -> String {
let mut manifest_dir = input_path.parent();
let manifest_path = loop {
match manifest_dir {
Some(dir) => {
let candidate = dir.join("Cargo.toml");
if candidate.exists() {
break candidate;
}
manifest_dir = dir.parent();
}
None => {
eprintln!("no Cargo.toml found above {}", input_path.display());
std::process::exit(1);
}
}
};
let metadata = MetadataCommand::new()
.manifest_path(&manifest_path)
.no_deps()
.exec()
.unwrap_or_else(|e| {
eprintln!("cargo metadata failed: {e}");
std::process::exit(1);
});
for pkg in &metadata.packages {
if pkg.manifest_path == manifest_path {
return pkg.version.to_string();
}
}
if metadata.packages.len() == 1 {
return metadata.packages[0].version.to_string();
}
eprintln!(
"could not determine package version from {}",
manifest_path.display()
);
std::process::exit(1);
}