mod build;
mod check;
mod ci;
mod dev;
mod format;
mod init;
mod ota;
mod preview;
mod submit;
mod version;
mod watch;
use clap::{Parser, Subcommand};
use std::io::{self, IsTerminal, Write};
#[derive(Parser)]
#[command(
name = "nativ",
about = "Compile-time native UI compiler: .nativ -> SwiftUI + Jetpack Compose",
version = env!("CARGO_PKG_VERSION"),
after_help = "Learn more: https://nativ.dev"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Command>,
#[arg(long, global = true)]
pub verbose: bool,
#[arg(long, global = true)]
pub quiet: bool,
}
#[derive(Subcommand)]
pub enum Command {
Init(init::InitArgs),
Build(build::BuildArgs),
Check(check::CheckArgs),
Ci(ci::CiArgs),
Watch(watch::WatchArgs),
Dev(dev::DevArgs),
Format(format::FormatArgs),
Preview(preview::PreviewArgs),
Submit(submit::SubmitArgs),
Ota(ota::OtaArgs),
Version(version::VersionArgs),
}
pub fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
let command = match cli.command {
Some(command) => command,
None if !cli.quiet && io::stdin().is_terminal() && io::stdout().is_terminal() => {
prompt_root_command()?
}
None => return Err("No command selected. Run `nativ --help`.".into()),
};
match command {
Command::Init(args) => init::run(args, cli.verbose),
Command::Build(args) => build::run(args, cli.verbose, cli.quiet),
Command::Check(args) => check::run(args, cli.verbose),
Command::Ci(args) => ci::run(args),
Command::Watch(args) => watch::run(args, cli.verbose),
Command::Dev(args) => dev::run(args, cli.verbose, cli.quiet),
Command::Format(args) => format::run(args, cli.verbose),
Command::Preview(args) => preview::run(args),
Command::Submit(args) => submit::run(args),
Command::Ota(args) => ota::run(args),
Command::Version(args) => version::run(args),
}
}
fn prompt_root_command() -> Result<Command, Box<dyn std::error::Error>> {
println!("Nativ command:");
println!(" 1) Init project");
println!(" 2) Build");
println!(" 3) Native app preview");
println!(" 4) Browser preview");
println!(" 5) Check");
println!(" 6) Format");
println!(" 7) Version");
print!("Select: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
parse_root_menu_choice(input.trim()).ok_or_else(|| "Invalid command selection".into())
}
fn parse_root_menu_choice(choice: &str) -> Option<Command> {
Some(match choice {
"1" => Command::Init(init::InitArgs { name: None }),
"2" => Command::Build(build::BuildArgs {
ios: false,
android: false,
web: false,
dev: false,
dir: ".".to_string(),
}),
"3" => Command::Dev(dev::DevArgs {
dir: ".".to_string(),
ios: false,
android: false,
dev_shell: false,
dev_shell_port: 0,
dev_shell_compile: false,
}),
"4" => Command::Preview(preview::PreviewArgs {
path: ".".to_string(),
out: None,
open: true,
watch: true,
serve: true,
stdin: false,
json: false,
port: 4173,
}),
"5" => Command::Check(check::CheckArgs {
dir: ".".to_string(),
}),
"6" => Command::Format(format::FormatArgs {
check: false,
dir: ".".to_string(),
}),
"7" => Command::Version(version::VersionArgs { command: None }),
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn root_menu_maps_build_and_native_preview() {
assert!(matches!(
parse_root_menu_choice("2"),
Some(Command::Build(build::BuildArgs { .. }))
));
assert!(matches!(
parse_root_menu_choice("3"),
Some(Command::Dev(dev::DevArgs { .. }))
));
}
#[test]
fn root_menu_rejects_unknown_choice() {
assert!(parse_root_menu_choice("").is_none());
assert!(parse_root_menu_choice("x").is_none());
}
}