git_mob_tool/
cli.rs

1use crate::coauthor_repo::CoauthorRepo;
2use crate::commands::{coauthor::Coauthor, mob::Mob, setup::Setup};
3use clap::{Parser, Subcommand};
4use std::error::Error;
5use std::io::Write;
6use std::str;
7
8#[derive(Parser)]
9#[command(
10    author,
11    version,
12    about,
13    long_about,
14    bin_name = "git mob",
15    override_usage = "git mob [COMMAND] [OPTIONS]"
16)]
17#[command(propagate_version = true)]
18/// A CLI app which can help users automatically add co-author(s) to git commits
19/// for pair/mob programming.
20///
21/// A user can attribute a git commit to more than one author by adding one or more
22/// Co-authored-by trailers to the commit's message.
23/// Co-authored commits are visible on GitHub.
24/// This CLI app will helper users do the this automatically and also help them
25/// store and manage co-authors for pair/mob programming sessions.
26///
27/// Usage example:
28///
29/// git mob setup --global
30///
31/// git mob coauthor --add lm "Leo Messi" leo.messi@example.com
32///
33/// git mob --with lm
34struct Cli {
35    #[command(subcommand)]
36    command: Option<Commands>,
37    #[command(flatten)]
38    mob: Mob,
39}
40
41#[derive(Subcommand)]
42enum Commands {
43    /// Create prepare-commit-msg githook which append Co-authored-by trailers to commit message
44    Setup(Setup),
45    /// Add/delete/list co-author(s) from co-author repository
46    ///
47    /// User must store co-author(s) to co-author repository by using keys
48    /// before starting pair/mob programming session(s).
49    Coauthor(Coauthor),
50}
51
52pub fn run(coauthor_repo: &impl CoauthorRepo, out: &mut impl Write) -> Result<(), Box<dyn Error>> {
53    let cli = Cli::parse();
54    run_inner(&cli, coauthor_repo, out)
55}
56
57fn run_inner(
58    cli: &Cli,
59    coauthor_repo: &impl CoauthorRepo,
60    out: &mut impl Write,
61) -> Result<(), Box<dyn Error>> {
62    match &cli.command {
63        None => cli.mob.handle(coauthor_repo, out)?,
64        Some(Commands::Setup(setup)) => setup.handle(out)?,
65        Some(Commands::Coauthor(coauthor)) => coauthor.handle(coauthor_repo, out)?,
66    }
67    Ok(())
68}
69
70#[cfg(test)]
71mod tests {
72    use std::error::Error;
73
74    use super::*;
75    use crate::coauthor_repo::MockCoauthorRepo;
76    use mockall::predicate;
77
78    #[test]
79    fn test_clear_mob_session() -> Result<(), Box<dyn Error>> {
80        let mut mock_coauthor_repo = MockCoauthorRepo::new();
81        mock_coauthor_repo
82            .expect_clear_mob()
83            .once()
84            .returning(|| Ok(()));
85
86        let cli = Cli {
87            command: None,
88            mob: Mob {
89                with: None,
90                clear: true,
91                list: false,
92                trailers: false,
93            },
94        };
95
96        let mut out = Vec::new();
97        run_inner(&cli, &mock_coauthor_repo, &mut out)?;
98
99        Ok(())
100    }
101
102    #[test]
103    fn test_delete_coauthor() -> Result<(), Box<dyn Error>> {
104        let key = "lm";
105        let mut mock_coauthor_repo = MockCoauthorRepo::new();
106        mock_coauthor_repo
107            .expect_get()
108            .with(predicate::eq(key))
109            .once()
110            .returning(|_| Ok(Some("Leo Messi <leo.messi@example.com>".to_owned())));
111        mock_coauthor_repo
112            .expect_remove()
113            .with(predicate::eq(key))
114            .once()
115            .returning(|_| Ok(()));
116
117        let cli = Cli {
118            command: Some(Commands::Coauthor(Coauthor {
119                delete: Some(key.to_owned()),
120                add: None,
121                list: false,
122            })),
123            mob: Mob {
124                with: None,
125                clear: false,
126                list: false,
127                trailers: false,
128            },
129        };
130
131        let mut out = Vec::new();
132        run_inner(&cli, &mock_coauthor_repo, &mut out)?;
133
134        Ok(())
135    }
136}