pub mod inspect; pub mod preview; pub mod render; use anyhow::Result; use clap::{Args, Parser, Subcommand}; use crate::cli::inspect::InspectCommand; #[derive(Args, Debug, Clone, Default)] #[group(multiple = false)] pub struct TargetArg { #[arg(global = true, long, help_heading = "Cargo Target Options")] pub lib: bool, #[arg(global = true, long, help_heading = "Cargo Target Options")] pub example: Option, } #[derive(Parser, Debug, Clone, Default)] pub struct CliArgs { #[arg(global = true, short, long, help_heading = "Cargo Options")] pub package: Option, #[arg(global = true, long, help_heading = "Cargo Options")] pub features: Vec, #[command(flatten)] pub target: TargetArg, #[arg(trailing_var_arg = true, allow_hyphen_values = true)] pub args: Vec, } #[derive(Parser, Debug)] #[command(name = "ranim")] #[command(about = "A CLI tool for Ranim animation library")] #[command(version)] pub struct Cli { #[command(subcommand)] command: Commands, #[command(flatten)] pub args: CliArgs, } impl Cli { pub fn run(self) -> Result<()> { let args = self.args; match self.command { Commands::Preview { scene } => { preview::preview_command(&args, &scene)?; } Commands::Output { scenes, buffer_count, } => { render::output_command(&args, &scenes, buffer_count)?; } Commands::Render { scene, buffer_count, } => { render::render_command(&args, &scene, buffer_count)?; } Commands::Inspect { command } => { inspect::inspect_command(&args, command)?; } } Ok(()) } } #[derive(Subcommand, Debug)] pub enum Commands { /// Launch a preview app, watch the lib crate and rebuild it to dylib when it is changed Preview { scene: Option }, /// Render every `#[output(...)]` declared by the selected scenes Output { /// Optional scene names to render (if not provided, render all scenes) #[arg(num_args = 0..)] scenes: Vec, /// Number of GPU readback buffers (higher = more parallelism, more VRAM) #[arg(long, default_value_t = 2)] buffer_count: usize, }, /// Render a single scene once with default output settings (`1920x1080`, 60 fps, mp4) Render { /// Scene name to render scene: String, /// Number of GPU readback buffers (higher = more parallelism, more VRAM) #[arg(long, default_value_t = 2)] buffer_count: usize, }, /// Inspect scenes, animation trees, or evaluated frames without rendering Inspect { #[command(subcommand)] command: InspectCommand, }, } #[cfg(test)] mod test { use crate::{ Target, cli::inspect::{FormatArg, InspectCommand}, }; use super::*; #[test] fn test_cli() { let parse_args = |args: &[&str]| Cli::try_parse_from(args); let cli = parse_args(&["ranim", "output", "-p", "package"]).unwrap(); let Commands::Output { scenes, .. } = &cli.command else { unreachable!() }; assert!(scenes.is_empty()); assert_eq!(cli.args.package, Some("package".to_string())); assert!(!cli.args.target.lib); assert!(cli.args.target.example.is_none()); let cli = parse_args(&["ranim", "render", "scene_name"]).unwrap(); let Commands::Render { scene, .. } = &cli.command else { unreachable!() }; assert_eq!(scene, "scene_name"); let cli = parse_args(&["ranim", "preview", "--lib"]).unwrap(); assert!(matches!(cli.command, Commands::Preview { scene: None })); assert!(cli.args.package.is_none()); let TargetArg { lib, example } = cli.args.target.clone(); assert!(lib); assert!(example.is_none()); assert_eq!(Target::from(cli.args.target.clone()), Target::Lib); let cli = parse_args(&["ranim", "preview", "--example", "example"]).unwrap(); assert!(matches!(cli.command, Commands::Preview { scene: None })); assert!(cli.args.package.is_none()); let TargetArg { lib, example } = cli.args.target.clone(); assert!(!lib); assert_eq!(example, Some("example".to_string())); assert_eq!( Target::from(cli.args.target.clone()), Target::Example("example".to_string()) ); let cli = parse_args(&["ranim", "inspect", "scenes", "--format", "json"]).unwrap(); assert!(matches!( &cli.command, Commands::Inspect { command: InspectCommand::Scenes { format: FormatArg::Json } } )); let cli = parse_args(&[ "ranim", "inspect", "frame", "basic", "--at", "2.0", "--verbose", "--example", "example", ]) .unwrap(); assert!(matches!( &cli.command, Commands::Inspect { command: InspectCommand::Frame { scene, at: 2.0, format: FormatArg::Text, verbose: true, } } if scene == "basic" )); assert_eq!( Target::from(cli.args.target.clone()), Target::Example("example".to_string()) ); } }