changepacks_cli/
lib.rs

1use anyhow::Result;
2
3use changepacks_core::UpdateType;
4use clap::{Parser, Subcommand, ValueEnum};
5
6use crate::{
7    commands::{
8        ChangepackArgs, CheckArgs, ConfigArgs, InitArgs, PublishArgs, UpdateArgs,
9        handle_changepack, handle_check, handle_config, handle_init, handle_publish, handle_update,
10    },
11    options::FilterOptions,
12};
13pub mod commands;
14mod finders;
15pub mod options;
16pub mod prompter;
17
18pub use prompter::UserCancelled;
19
20#[derive(ValueEnum, Debug, Clone)]
21enum CliUpdateType {
22    Major,
23    Minor,
24    Patch,
25}
26
27impl From<CliUpdateType> for UpdateType {
28    fn from(value: CliUpdateType) -> Self {
29        match value {
30            CliUpdateType::Major => UpdateType::Major,
31            CliUpdateType::Minor => UpdateType::Minor,
32            CliUpdateType::Patch => UpdateType::Patch,
33        }
34    }
35}
36
37#[derive(Parser, Debug)]
38#[command(
39    name = "changepacks",
40    author,
41    version,
42    about = "A unified version management and changelog tool for multi-language projects",
43    help_template = "{name} {version}\n{about}\n\n{usage-heading} {usage}\n\n{all-args}"
44)]
45struct Cli {
46    #[command(subcommand)]
47    command: Option<Commands>,
48
49    #[arg(short, long)]
50    filter: Option<FilterOptions>,
51
52    #[arg(short, long, default_value = "false")]
53    remote: bool,
54
55    #[arg(short, long, default_value = "false")]
56    yes: bool,
57
58    #[arg(short, long)]
59    message: Option<String>,
60
61    #[arg(short, long)]
62    update_type: Option<CliUpdateType>,
63}
64
65#[derive(Subcommand, Debug)]
66enum Commands {
67    Init(InitArgs),
68    Check(CheckArgs),
69    Update(UpdateArgs),
70    Config(ConfigArgs),
71    Publish(PublishArgs),
72}
73
74pub async fn main(args: &[String]) -> Result<()> {
75    let cli = Cli::parse_from(args);
76    if let Some(command) = cli.command {
77        match command {
78            Commands::Init(args) => handle_init(&args).await?,
79            Commands::Check(args) => handle_check(&args).await?,
80            Commands::Update(args) => handle_update(&args).await?,
81            Commands::Config(args) => handle_config(&args).await?,
82            Commands::Publish(args) => handle_publish(&args).await?,
83        }
84    } else {
85        handle_changepack(&ChangepackArgs {
86            filter: cli.filter,
87            remote: cli.remote,
88            yes: cli.yes,
89            message: cli.message,
90            update_type: cli.update_type.map(Into::into),
91        })
92        .await?;
93    }
94    Ok(())
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use rstest::rstest;
101
102    #[rstest]
103    #[case(CliUpdateType::Major, UpdateType::Major)]
104    #[case(CliUpdateType::Minor, UpdateType::Minor)]
105    #[case(CliUpdateType::Patch, UpdateType::Patch)]
106    fn test_cli_update_type_to_update_type(
107        #[case] cli_type: CliUpdateType,
108        #[case] expected: UpdateType,
109    ) {
110        let result: UpdateType = cli_type.into();
111        assert_eq!(result, expected);
112    }
113
114    // Test that Cli struct parses correctly
115    #[test]
116    fn test_cli_parsing_init() {
117        use clap::Parser;
118        let cli = Cli::parse_from(["changepacks", "init"]);
119        assert!(matches!(cli.command, Some(Commands::Init(_))));
120    }
121
122    #[test]
123    fn test_cli_parsing_check() {
124        use clap::Parser;
125        let cli = Cli::parse_from(["changepacks", "check"]);
126        assert!(matches!(cli.command, Some(Commands::Check(_))));
127    }
128
129    #[test]
130    fn test_cli_parsing_update() {
131        use clap::Parser;
132        let cli = Cli::parse_from(["changepacks", "update", "--dry-run"]);
133        assert!(matches!(cli.command, Some(Commands::Update(_))));
134    }
135
136    #[test]
137    fn test_cli_parsing_config() {
138        use clap::Parser;
139        let cli = Cli::parse_from(["changepacks", "config"]);
140        assert!(matches!(cli.command, Some(Commands::Config(_))));
141    }
142
143    #[test]
144    fn test_cli_parsing_publish() {
145        use clap::Parser;
146        let cli = Cli::parse_from(["changepacks", "publish", "--dry-run"]);
147        assert!(matches!(cli.command, Some(Commands::Publish(_))));
148    }
149
150    #[test]
151    fn test_cli_parsing_default_with_options() {
152        use clap::Parser;
153        let cli = Cli::parse_from([
154            "changepacks",
155            "--yes",
156            "--message",
157            "test",
158            "--update-type",
159            "patch",
160        ]);
161        assert!(cli.command.is_none());
162        assert!(cli.yes);
163        assert_eq!(cli.message, Some("test".to_string()));
164        assert!(matches!(cli.update_type, Some(CliUpdateType::Patch)));
165    }
166
167    #[test]
168    fn test_cli_parsing_with_filter() {
169        use clap::Parser;
170        let cli = Cli::parse_from(["changepacks", "--filter", "package"]);
171        assert!(cli.command.is_none());
172        assert!(matches!(cli.filter, Some(FilterOptions::Package)));
173    }
174
175    #[test]
176    fn test_cli_parsing_with_remote() {
177        use clap::Parser;
178        let cli = Cli::parse_from(["changepacks", "--remote"]);
179        assert!(cli.remote);
180    }
181}