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<String>,} #[derive(Parser, Debug, Clone, Default)]pub struct CliArgs { #[arg(global = true, short, long, help_heading = "Cargo Options")] pub package: Option<String>, #[arg(global = true, long, help_heading = "Cargo Options")] pub features: Vec<String>, #[command(flatten)] pub target: TargetArg, #[arg(trailing_var_arg = true, allow_hyphen_values = true)] pub args: Vec<String>,} #[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<String> }, /// 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<String>, /// 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()) ); }}
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<String>,
}
#[derive(Parser, Debug, Clone, Default)]
pub struct CliArgs {
#[arg(global = true, short, long, help_heading = "Cargo Options")]
pub package: Option<String>,
#[arg(global = true, long, help_heading = "Cargo Options")]
pub features: Vec<String>,
#[command(flatten)]
pub target: TargetArg,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub args: Vec<String>,
}
#[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<String> },
/// 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<String>,
/// 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())
);
}
}