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    #[command(flatten)]
22    pub target: TargetArg,
23
24    #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
25    pub args: Vec<String>,
26}
27
28#[derive(Parser, Debug)]
29#[command(name = "ranim")]
30#[command(about = "A CLI tool for Ranim animation library")]
31#[command(version)]
32pub struct Cli {
33    #[command(subcommand)]
34    command: Commands,
35
36    #[command(flatten)]
37    pub args: CliArgs,
38}
39
40impl Cli {
41    pub fn run(self) -> Result<()> {
42        let args = self.args;
43
44        match self.command {
45            Commands::Preview { scene } => {
46                preview::preview_command(&args, &scene)?;
47            }
48            Commands::Render { scenes } => {
49                render::render_command(&args, &scenes)?;
50            }
51        }
52
53        Ok(())
54    }
55}
56
57#[derive(Subcommand, Debug)]
58pub enum Commands {
59    /// Launch a preview app, watch the lib crate and rebuild it to dylib when it is changed
60    Preview { scene: Option<String> },
61    /// Build the lib crate and load it, then render it to video
62    Render {
63        /// Optional scene names to render (if not provided, render all scenes)
64        #[arg(num_args = 0..)]
65        scenes: Vec<String>,
66    },
67}
68
69#[cfg(test)]
70mod test {
71    use crate::Target;
72
73    use super::*;
74
75    #[test]
76    fn test_cli() {
77        let parse_args = |args: &[&str]| {
78            println!("parsing args {:?}", args);
79            let cli = Cli::try_parse_from(args);
80            println!("result: {:?}", cli);
81            cli
82        };
83        let cli = parse_args(&["ranim", "render", "-p", "package"]).unwrap();
84        let Commands::Render { scenes } = &cli.command else {
85            unreachable!()
86        };
87        assert!(scenes.is_empty());
88        assert_eq!(cli.args.package, Some("package".to_string()));
89        assert!(!cli.args.target.lib);
90        assert!(cli.args.target.example.is_none());
91
92        let cli = parse_args(&["ranim", "preview", "--lib"]).unwrap();
93        assert!(matches!(cli.command, Commands::Preview { scene: None }));
94        assert!(cli.args.package.is_none());
95        let TargetArg { lib, example } = cli.args.target.clone();
96        assert!(lib);
97        assert!(example.is_none());
98        assert_eq!(Target::from(cli.args.target.clone()), Target::Lib);
99
100        let cli = parse_args(&["ranim", "preview", "--example", "example"]).unwrap();
101        assert!(matches!(cli.command, Commands::Preview { scene: None }));
102        assert!(cli.args.package.is_none());
103        let TargetArg { lib, example } = cli.args.target.clone();
104        assert!(!lib);
105        assert_eq!(example, Some("example".to_string()));
106        assert_eq!(
107            Target::from(cli.args.target.clone()),
108            Target::Example("example".to_string())
109        );
110    }
111}