1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use crate::coauthor_repo::CoauthorRepo;
use crate::commands::{coauthor::Coauthor, mob::Mob};
use clap::{Parser, Subcommand};
use std::{io, str};

#[derive(Parser)]
#[command(
    author,
    version,
    about,
    long_about,
    bin_name = "git mob",
    override_usage = "git mob [COMMAND] [OPTIONS]"
)]
#[command(propagate_version = true)]
/// A CLI app which can help users automatically add co-author(s) to git commits
/// for pair/mob programming.
///
/// A user can attribute a git commit to more than one author by adding one or more
/// Co-authored-by trailers to the commit's message.
/// Co-authored commits are visible on GitHub.
/// This CLI app will helper users do the this automatically and also help them
/// store and manage co-authors for pair/mob programming sessions.
///
/// Usage example:
///
/// git mob co-author --add lm "Leo Messi" leo.messi@example.com
///
/// git pair with
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,
    #[command(flatten)]
    mob: Mob,
}

#[derive(Subcommand)]
enum Commands {
    /// Add/delete/list co-author(s) from co-author repository
    ///
    /// User must store co-author(s) to co-author repository by using keys
    /// (usually initials) before starting pair/mob programming session(s).
    Coauthor(Coauthor),
}

pub fn run(coauthor_repo: &impl CoauthorRepo, out: &mut impl io::Write, err: &mut impl io::Write) {
    let cli = Cli::parse();
    run_inner(&cli, coauthor_repo, out, err);
}

fn run_inner(
    cli: &Cli,
    coauthor_repo: &impl CoauthorRepo,
    out: &mut impl io::Write,
    err: &mut impl io::Write,
) {
    match &cli.command {
        None => cli.mob.handle(coauthor_repo, out, err),
        Some(Commands::Coauthor(coauthor)) => coauthor.handle(coauthor_repo, out, err),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::coauthor_repo::MockCoauthorRepo;
    use mockall::predicate;

    #[test]
    fn test_clear_mob_session() {
        let mut mock_coauthor_repo = MockCoauthorRepo::new();
        mock_coauthor_repo
            .expect_clear_mob()
            .once()
            .return_const(());

        let cli = Cli {
            command: None,
            mob: Mob {
                with: None,
                clear: true,
                list: false,
            },
        };

        let mut out = Vec::new();
        let mut err = Vec::new();
        run_inner(&cli, &mock_coauthor_repo, &mut out, &mut err);
    }

    #[test]
    fn test_delete_coauthor() {
        let key = "lm";
        let mut mock_coauthor_repo = MockCoauthorRepo::new();
        mock_coauthor_repo
            .expect_get()
            .with(predicate::eq(key))
            .once()
            .return_const("Leo Messi <leo.messi@example.com>".to_owned());
        mock_coauthor_repo
            .expect_remove()
            .with(predicate::eq(key))
            .once()
            .return_const(());

        let cli = Cli {
            command: Some(Commands::Coauthor(Coauthor {
                delete: Some(key.to_owned()),
                add: None,
                list: false,
            })),
            mob: Mob {
                with: None,
                clear: false,
                list: false,
            },
        };

        let mut out = Vec::new();
        let mut err = Vec::new();
        run_inner(&cli, &mock_coauthor_repo, &mut out, &mut err);
    }
}