Skip to main content

ranim_cli/
cli.rs

1pub mod preview;
2pub mod render;
3
4use anyhow::Result;
5use clap::{Args, Parser, Subcommand};
6
7#[derive(Args, Debug, Clone, Default)]
8#[group(multiple = false)]
9pub struct TargetArg {
10    #[arg(global = true, long, help_heading = "Cargo Target Options")]
11    pub lib: bool,
12    #[arg(global = true, long, help_heading = "Cargo Target Options")]
13    pub example: Option<String>,
14}
15
16#[derive(Parser, Debug, Clone, Default)]
17pub struct CliArgs {
18    #[arg(global = true, short, long, help_heading = "Cargo Options")]
19    pub package: Option<String>,
20
21    #[arg(global = true, long, help_heading = "Cargo Options")]
22    pub features: Vec<String>,
23
24    #[command(flatten)]
25    pub target: TargetArg,
26
27    #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
28    pub args: Vec<String>,
29}
30
31#[derive(Parser, Debug)]
32#[command(name = "ranim")]
33#[command(about = "A CLI tool for Ranim animation library")]
34#[command(version)]
35pub struct Cli {
36    #[command(subcommand)]
37    command: Commands,
38
39    #[command(flatten)]
40    pub args: CliArgs,
41}
42
43impl Cli {
44    pub fn run(self) -> Result<()> {
45        let args = self.args;
46
47        match self.command {
48            Commands::Preview { scene } => {
49                preview::preview_command(&args, &scene)?;
50            }
51            Commands::Render {
52                scenes,
53                buffer_count,
54            } => {
55                render::render_command(&args, &scenes, buffer_count)?;
56            }
57        }
58
59        Ok(())
60    }
61}
62
63#[derive(Subcommand, Debug)]
64pub enum Commands {
65    /// Launch a preview app, watch the lib crate and rebuild it to dylib when it is changed
66    Preview { scene: Option<String> },
67    /// Build the lib crate and load it, then render it to video
68    Render {
69        /// Optional scene names to render (if not provided, render all scenes)
70        #[arg(num_args = 0..)]
71        scenes: Vec<String>,
72
73        /// Number of GPU readback buffers (higher = more parallelism, more VRAM)
74        #[arg(long, default_value_t = 2)]
75        buffer_count: usize,
76    },
77}
78
79#[cfg(test)]
80mod test {
81    use crate::Target;
82
83    use super::*;
84
85    #[test]
86    fn test_cli() {
87        let parse_args = |args: &[&str]| {
88            println!("parsing args {:?}", args);
89            let cli = Cli::try_parse_from(args);
90            println!("result: {:?}", cli);
91            cli
92        };
93        let cli = parse_args(&["ranim", "render", "-p", "package"]).unwrap();
94        let Commands::Render { scenes, .. } = &cli.command else {
95            unreachable!()
96        };
97        assert!(scenes.is_empty());
98        assert_eq!(cli.args.package, Some("package".to_string()));
99        assert!(!cli.args.target.lib);
100        assert!(cli.args.target.example.is_none());
101
102        let cli = parse_args(&["ranim", "preview", "--lib"]).unwrap();
103        assert!(matches!(cli.command, Commands::Preview { scene: None }));
104        assert!(cli.args.package.is_none());
105        let TargetArg { lib, example } = cli.args.target.clone();
106        assert!(lib);
107        assert!(example.is_none());
108        assert_eq!(Target::from(cli.args.target.clone()), Target::Lib);
109
110        let cli = parse_args(&["ranim", "preview", "--example", "example"]).unwrap();
111        assert!(matches!(cli.command, Commands::Preview { scene: None }));
112        assert!(cli.args.package.is_none());
113        let TargetArg { lib, example } = cli.args.target.clone();
114        assert!(!lib);
115        assert_eq!(example, Some("example".to_string()));
116        assert_eq!(
117            Target::from(cli.args.target.clone()),
118            Target::Example("example".to_string())
119        );
120    }
121}