use std::env;
use std::error::Error as StdError;
use std::fmt;
use std::path::PathBuf;
pub const USAGE: &str = "\
rudzio-migrate \u{2014} best-effort converter of Rust tests into rudzio tests.
USAGE:
rudzio-migrate [OPTIONS]
OPTIONS:
--path <DIR> Repo root (default: current working directory;
must be inside a git repo).
--runtime <NAME> Default runtime for generated suites. One of:
tokio-mt (default), tokio-ct, compio,
futures-mt, futures-ct. Explicit per-test
flavors in #[tokio::test(...)] override this.
--dry-run Parse and report planned changes; do not
write any files, do not create backups.
--no-shared-runner Skip the Cargo.toml + tests/main.rs
scaffolding prompt.
--no-preserve-originals Do not emit a pre-migration block comment
above each converted fn.
--only-package <NAME> Restrict the run to a single workspace member
(matched against the `cargo metadata` package
name). Other packages are left alone.
--tests-only Skip src/**/*.rs during conversion \u{2014} only
tests/ files are migrated. Use when src/
is dense with macros (`ambassador`,
delegation) that syn parses but
prettyplease can't round-trip.
--help, -h Print this message.
NOTE: The tool refuses to run on a dirty git tree and requires the user
to type an acknowledgement phrase. Both gates are load-bearing safety
features \u{2014} there is no bypass flag.
";
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Cli {
pub generation: GenerationFlags,
pub only_package: Option<String>,
pub path: PathBuf,
pub run: RunFlags,
pub runtime: RuntimeChoice,
}
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct GenerationFlags {
pub no_preserve_originals: bool,
pub no_shared_runner: bool,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ParseError {
HelpRequested,
MissingValue(String),
UnknownFlag(String),
UnknownRuntime(String),
}
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct RunFlags {
pub dry_run: bool,
pub tests_only: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum RuntimeChoice {
Compio,
FuturesCt,
FuturesMt,
TokioCt,
TokioMt,
}
impl fmt::Display for ParseError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::HelpRequested => write!(f, "help"),
Self::MissingValue(flag) => write!(f, "missing value for {flag}"),
Self::UnknownFlag(flag) => write!(f, "unknown flag: {flag}"),
Self::UnknownRuntime(name) => write!(
f,
"unknown runtime `{name}` — pick one of: tokio-mt, tokio-ct, compio, futures-mt, futures-ct",
),
}
}
}
impl StdError for ParseError {}
impl RuntimeChoice {
#[inline]
#[must_use]
pub const fn cargo_feature(self) -> &'static str {
match self {
Self::Compio => "runtime-compio",
Self::FuturesCt | Self::FuturesMt => "runtime-futures",
Self::TokioCt => "runtime-tokio-current-thread",
Self::TokioMt => "runtime-tokio-multi-thread",
}
}
#[inline]
#[must_use]
pub const fn suite_path(self) -> &'static str {
match self {
Self::Compio => "::rudzio::runtime::compio::Compio::new",
Self::FuturesCt => "::rudzio::runtime::futures::CurrentThread::new",
Self::FuturesMt => "::rudzio::runtime::futures::Multithread::new",
Self::TokioCt => "::rudzio::runtime::tokio::CurrentThread::new",
Self::TokioMt => "::rudzio::runtime::tokio::Multithread::new",
}
}
}
#[inline]
pub fn parse<I: IntoIterator<Item = String>>(args: I) -> Result<Cli, ParseError> {
let mut path: Option<PathBuf> = None;
let mut runtime = RuntimeChoice::TokioMt;
let mut run = RunFlags::default();
let mut generation = GenerationFlags::default();
let mut only_package: Option<String> = None;
let mut iter = args.into_iter();
let _program = iter.next();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--help" | "-h" => return Err(ParseError::HelpRequested),
"--dry-run" => run.dry_run = true,
"--no-shared-runner" => generation.no_shared_runner = true,
"--no-preserve-originals" => generation.no_preserve_originals = true,
"--tests-only" => run.tests_only = true,
"--path" => {
let value = iter
.next()
.ok_or_else(|| ParseError::MissingValue("--path".to_owned()))?;
path = Some(PathBuf::from(value));
}
"--runtime" => {
let value = iter
.next()
.ok_or_else(|| ParseError::MissingValue("--runtime".to_owned()))?;
runtime = parse_runtime(&value)?;
}
"--only-package" => {
let value = iter
.next()
.ok_or_else(|| ParseError::MissingValue("--only-package".to_owned()))?;
only_package = Some(value);
}
flag if flag.starts_with("--path=") => {
path = Some(PathBuf::from(flag.get("--path=".len()..).unwrap_or("")));
}
flag if flag.starts_with("--runtime=") => {
runtime = parse_runtime(flag.get("--runtime=".len()..).unwrap_or(""))?;
}
flag if flag.starts_with("--only-package=") => {
only_package = Some(flag.get("--only-package=".len()..).unwrap_or("").to_owned());
}
other => return Err(ParseError::UnknownFlag(other.to_owned())),
}
}
let resolved_path =
path.unwrap_or_else(|| env::current_dir().unwrap_or_else(|_err| PathBuf::from(".")));
Ok(Cli {
generation,
only_package,
path: resolved_path,
run,
runtime,
})
}
fn parse_runtime(name: &str) -> Result<RuntimeChoice, ParseError> {
match name {
"tokio-mt" => Ok(RuntimeChoice::TokioMt),
"tokio-ct" => Ok(RuntimeChoice::TokioCt),
"compio" => Ok(RuntimeChoice::Compio),
"futures-mt" => Ok(RuntimeChoice::FuturesMt),
"futures-ct" => Ok(RuntimeChoice::FuturesCt),
other => Err(ParseError::UnknownRuntime(other.to_owned())),
}
}