git_mob_tool/
cli.rs

1use crate::commands::{Mob, Setup, TeamMember};
2use crate::repositories::{MobSessionRepo, TeamMemberRepo};
3use crate::Result;
4use clap::{Parser, Subcommand};
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 tool 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 tool 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
30///
31/// git mob team-member --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 global prepare-commit-msg githook which appends Co-authored-by trailers to commit message
44    Setup(Setup),
45    /// Add/delete/list team member(s) from team member repository
46    ///
47    /// User must store team member(s) to team member repository by using keys
48    /// before starting pair/mob programming session(s).
49    #[clap(alias = "coauthor")] // alias for backward compatibility
50    TeamMember(TeamMember),
51}
52
53pub fn run(
54    team_member_repo: &impl TeamMemberRepo,
55    mob_repo: &impl MobSessionRepo,
56    out: &mut impl Write,
57) -> Result<()> {
58    let cli = Cli::parse();
59    run_inner(&cli, team_member_repo, mob_repo, out)
60}
61
62fn run_inner(
63    cli: &Cli,
64    team_member_repo: &impl TeamMemberRepo,
65    mob_repo: &impl MobSessionRepo,
66    out: &mut impl Write,
67) -> Result<()> {
68    match &cli.command {
69        None => cli.mob.handle(team_member_repo, mob_repo, out)?,
70        Some(Commands::Setup(setup)) => setup.handle(out)?,
71        Some(Commands::TeamMember(team_member)) => team_member.handle(team_member_repo, out)?,
72    }
73    Ok(())
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::repositories::{MockMobSessionRepo, MockTeamMemberRepo};
80    use mockall::predicate;
81
82    #[test]
83    fn test_clear_mob_session() -> Result<()> {
84        let mock_team_member_repo = MockTeamMemberRepo::new();
85        let mut mock_mob_repo = MockMobSessionRepo::new();
86        mock_mob_repo.expect_clear().once().returning(|| Ok(()));
87
88        let cli = Cli {
89            command: None,
90            mob: Mob {
91                with: None,
92                clear: true,
93                list: false,
94                trailers: false,
95                add: None,
96            },
97        };
98
99        let mut out = Vec::new();
100        run_inner(&cli, &mock_team_member_repo, &mock_mob_repo, &mut out)?;
101
102        Ok(())
103    }
104
105    #[test]
106    fn test_delete_team_member() -> Result<()> {
107        let key = "lm";
108        let mut mock_team_member_repo = MockTeamMemberRepo::new();
109        let mock_mob_repo = MockMobSessionRepo::new();
110        mock_team_member_repo
111            .expect_get()
112            .with(predicate::eq(key))
113            .once()
114            .returning(|_| Ok(Some("Leo Messi <leo.messi@example.com>".to_owned())));
115        mock_team_member_repo
116            .expect_remove()
117            .with(predicate::eq(key))
118            .once()
119            .returning(|_| Ok(()));
120
121        let cli = Cli {
122            command: Some(Commands::TeamMember(TeamMember {
123                delete: Some(key.to_owned()),
124                add: None,
125                list: false,
126            })),
127            mob: Mob {
128                with: None,
129                clear: false,
130                list: false,
131                trailers: false,
132                add: None,
133            },
134        };
135
136        let mut out = Vec::new();
137        run_inner(&cli, &mock_team_member_repo, &mock_mob_repo, &mut out)?;
138
139        Ok(())
140    }
141}