Compare commits

...

3 Commits

Author SHA1 Message Date
Martin Frost 6ad4b14b37 WIP: where was I? 2025-10-09 21:49:16 +02:00
Martin Frost b239eacfa3 chore: Juggle dependencies around a bit
This will make manpage generation a bit easier, hopefully.
2025-07-06 12:42:05 +02:00
Martin Frost 20c68a3b14 feat: Merge all functionality into one binary
This groups all entrypoints into one `git-mob` binary.

One of the main questions I've gotten about this library is why the
`add-coauthor` (and similar) aren't an option or a subcommand to
`git-mob`. I've also come to the conclusion that it's a bit confusing to
have to memorize five different binaries rather than one, since they're
all related anyway.
2025-07-06 11:56:28 +02:00
8 changed files with 262 additions and 201 deletions

View File

@ -1,4 +1,3 @@
use clap::CommandFactory;
use clap_mangen::Man;
use std::env;
use std::path::Path;
@ -21,11 +20,21 @@ macro_rules! generate_manpage {
}
fn main() -> std::io::Result<()> {
generate_manpage!(GitMob);
generate_manpage!(GitSolo);
generate_manpage!(GitAddCoauthor);
generate_manpage!(GitEditCoauthor);
generate_manpage!(GitDeleteCoauthor);
let target_dir = env::var("CARGO_TARGET_DIR").unwrap_or("target".to_string());
let output_dir = Path::new(&target_dir).join(env::var("PROFILE").unwrap());
let cmd = cli::git_mob_cmd();
let man = Man::new(cmd.clone());
let mut buffer: Vec<u8> = Default::default();
man.render(&mut buffer)?;
std::fs::write(output_dir.join("git-mob.1"), buffer)?;
for subcommand in cmd.get_subcommands() {
let man = Man::new(subcommand.clone());
let mut buffer: Vec<u8> = Default::default();
man.render(&mut buffer)?;
std::fs::write(output_dir.join(format!("git-mob-{}.1", subcommand.get_name())), buffer)?;
}
Ok(())
}

View File

@ -1,15 +0,0 @@
use clap::Parser;
use git_mob::{cli, parse_coauthors_file, write_coauthors_file, Author};
fn main() {
let opt = cli::GitAddCoauthor::parse();
let mut authors = parse_coauthors_file().unwrap_or_default();
let new_author = Author {
name: opt.name,
email: opt.email,
};
authors.insert(opt.initials, new_author);
write_coauthors_file(authors);
}

View File

@ -1,9 +0,0 @@
use clap::Parser;
use git_mob::{cli, get_available_coauthors, write_coauthors_file};
fn main() {
let opt = cli::GitDeleteCoauthor::parse();
let mut authors = get_available_coauthors();
authors.remove(&opt.initials);
write_coauthors_file(authors);
}

View File

@ -1,22 +0,0 @@
use clap::Parser;
use git_mob::{cli, get_available_coauthors, write_coauthors_file, Author};
use std::process;
fn main() {
let opt = cli::GitDeleteCoauthor::parse();
let mut authors = get_available_coauthors();
if let Some(author) = authors.get(&opt.initials) {
let mut updated_author: Author = author.clone();
updated_author.name = opt.name;
updated_author.email = opt.email;
authors.insert(opt.initials, updated_author);
write_coauthors_file(authors);
} else {
eprintln!("No author found with initials {}", &opt.initials);
process::exit(1);
};
}

View File

@ -1,79 +0,0 @@
use clap::Parser;
use git_mob::{
cli, ensure_commit_template_is_set, get_available_coauthors, get_main_author, set_main_author,
with_gitmessage_template_path_or_exit, Author,
};
use std::fmt::Write;
use std::fs;
use std::process;
fn main() {
let args = cli::GitMob::parse();
if args.list {
list_coauthors();
process::exit(0);
}
if let Some(initials) = args.overwrite {
override_main_author(&initials);
}
write_coauthors_to_gitmessage_file(&args.coauthors);
ensure_commit_template_is_set();
}
fn list_coauthors() {
for (abbrev, author) in &get_available_coauthors() {
println!("{abbrev}\t{author}");
}
}
fn override_main_author(initials: &str) {
let all_authors = get_available_coauthors();
match all_authors.get(initials) {
Some(new_main_author) => set_main_author(new_main_author),
None => {
eprintln!("Error: author with initials {initials} not found");
process::exit(1);
}
}
}
fn write_coauthors_to_gitmessage_file(coauthor_initials: &[String]) {
let coauthors = select_coauthors(coauthor_initials);
let mut content = String::from("\n\n");
for author in &coauthors {
_ = writeln!(content, "Co-authored-by: {}", &author.to_string());
}
with_gitmessage_template_path_or_exit(|path| match fs::write(path, content) {
Ok(_) => {
println!("{}", get_main_author());
for author in &coauthors {
println!("{author}");
}
}
Err(e) => {
eprintln!("Error writing to .gitmessage template: {e}");
process::exit(1);
}
});
}
fn select_coauthors(coauthor_initials: &[String]) -> Vec<Author> {
let all_coauthors = get_available_coauthors();
let mut coauthors: Vec<Author> = Vec::new();
for initial in coauthor_initials {
match all_coauthors.get(initial) {
Some(coauthor) => coauthors.push(coauthor.clone()),
None => {
eprintln!("Error: author with initials {initial} not found");
process::exit(1);
}
}
}
coauthors
}

View File

@ -1,16 +0,0 @@
use clap::Parser;
use git_mob::{
cli, ensure_commit_template_is_set, get_main_author, with_gitmessage_template_path_or_exit,
};
use std::fs::File;
fn main() {
let _opt = cli::GitSolo::parse();
let main_author = get_main_author();
println!("{main_author}");
with_gitmessage_template_path_or_exit(|path| {
let _template = File::create(path);
});
ensure_commit_template_is_set();
}

View File

@ -1,56 +1,69 @@
use clap::Parser;
use clap::{Arg, ArgAction};
#[derive(Parser, Debug)]
#[clap(version, name = "git-mob")]
/// Assemble a group of co-authors to help you on your coding quest
pub struct GitMob {
/// Prints list of available co-authors
#[clap(short, long)]
pub list: bool,
/// Overwrite the main author
#[clap(short, long)]
pub overwrite: Option<String>,
/// A list of co-author initials
pub coauthors: Vec<String>,
pub fn git_mob_cmd() -> clap::Command {
clap::Command::new("git-mob")
.bin_name("git-mob")
.author("Martin Frost, martin@frost.codes")
.version("0.0.0")
.about("A command-line tool for social coding")
.arg(Arg::new("add")
.display_order(3)
.exclusive(true)
.help("Add a coauthor to the database")
.long("add-coauthor")
.num_args(3)
.short('a')
.value_names(["handle", "name", "email"])
)
.arg(Arg::new("edit")
.display_order(3)
.exclusive(true)
.help("Edit a coauthor in the database")
.long("edit-coauthor")
.num_args(3)
.short('e')
.value_names(["handle", "name", "email"])
)
.arg(Arg::new("delete")
.display_order(3)
.exclusive(true)
.help("Delete a coauthor from the database")
.long("delete-coauthor")
.short('d')
.value_name("handle")
)
.arg(Arg::new("solo")
.action(ArgAction::SetTrue)
.display_order(1)
.exclusive(true)
.help("Continue on your coding quest by yourself")
.long("solo")
.short('s')
)
.arg(Arg::new("list")
.action(ArgAction::SetTrue)
.display_order(4)
.exclusive(true)
.help("List available coauthors in the database")
.long("list-coauthors")
.short('l')
)
.arg(Arg::new("with")
.conflicts_with_all(["add", "edit", "delete", "solo", "list"])
.exclusive(true)
.help("Choose coauthors to help you on your coding quest")
.long("with")
.num_args(1..)
.short('w')
.display_order(0)
.value_name("handle")
)
.arg(Arg::new("current")
.action(ArgAction::SetTrue)
.display_order(2)
.exclusive(true)
.help("Show current mob (including main author)")
.long("current-mob")
.short('c')
)
}
#[derive(Parser, Debug)]
#[clap(name = "git-add-coauthor", version)]
/// Add a co-author to your list of available co-authors
pub struct GitAddCoauthor {
/// Co-author initials
pub initials: String,
/// The name of the co-author, in quotes, e.g. "Foo Bar"
pub name: String,
/// The email of the co-author
pub email: String,
}
#[derive(Parser, Debug)]
#[clap(name = "git-edit-coauthor", version)]
/// Edit a co-author in your list of available co-authors
pub struct GitEditCoauthor {
/// Co-author initials
pub initials: String,
/// The name of the co-author, in quotes, e.g. "Foo Bar"
pub name: String,
/// The email of the co-author
pub email: String,
}
#[derive(Parser, Debug)]
#[clap(name = "git-delete-coauthor", version)]
/// Delete a co-author from your list of available co-authors
pub struct GitDeleteCoauthor {
/// Co-author initials
pub initials: String,
/// The name of the co-author, in quotes, e.g. "Foo Bar"
pub name: String,
/// The email of the co-author
pub email: String,
}
#[derive(Parser, Debug)]
#[clap(name = "git-solo", version)]
/// Disband the mob and continue working solo.
pub struct GitSolo {}

180
src/main.rs Normal file
View File

@ -0,0 +1,180 @@
use git_mob::cli;
use std::fmt::Write;
use std::fs;
use std::fs::File;
use std::process;
use git_mob::{
ensure_commit_template_is_set, get_available_coauthors,
get_main_author, set_main_author, with_gitmessage_template_path_or_exit,
Author, parse_coauthors_file, write_coauthors_file
};
fn main() {
let matches = cli::git_mob_cmd().get_matches();
println!("matches: {:?}", matches);
if let Some(handles) = matches.get_raw("with") {
println!("with: {:?}", handles);
}
if matches.get_one("solo") == Some(&true) {
solo();
}
if matches.get_one("current") == Some(&true) {
println!("current.");
}
if let Some(add_args) = matches.get_raw("add") {
println!("add: {:?}", add_args);
}
if let Some(edit_args) = matches.get_raw("edit") {
println!("edit: {:?}", edit_args);
}
if let Some(delete_handle) = matches.get_one::<String>("delete") {
println!("delete: {:?}", delete_handle);
}
if matches.get_one("list") == Some(&true) {
list_coauthors();
}
// match matches.subcommand_name() {
// Some("add") => {
// let args = matches.subcommand_matches("add").unwrap();
// let handle = args.get_one("handle").unwrap();
// let name = args.get_one("name").unwrap();
// let email = args.get_one("email").unwrap();
// add_coauthor(handle, name, email);
// },
// Some("edit") => {
// let args = matches.subcommand_matches("edit").unwrap();
// let handle = args.get_one("handle").unwrap();
// let name = args.get_one("name").unwrap();
// let email = args.get_one("email").unwrap();
// edit_coauthor(handle, name, email);
// },
// Some("remove") => {
// let handle = matches.subcommand_matches("remove").unwrap()
// .get_one("handle").unwrap();
// remove_coauthor(handle);
// },
// Some("list") => list_coauthors(),
// Some("solo") => solo(),
// Some("with") => {
// let matches = matches.subcommand_matches("with").unwrap();
// let handles: Vec<String> =
// matches.get_many::<String>("handles").unwrap().map(|s| s.clone()).collect();
// if let Some(handle) = matches.get_one::<String>("override") {
// override_main_author(handle);
// }
// write_coauthors_to_gitmessage_file(&handles);
// },
// _ => println!("Something else"),
// }
}
fn list_coauthors() {
for (abbrev, author) in &get_available_coauthors() {
println!("{abbrev}\t{author}");
}
}
fn add_coauthor(handle: &String, name: &String, email: &String) {
let mut authors = parse_coauthors_file().unwrap_or_default();
let new_author = Author {
name: name.to_string(),
email: email.to_string(),
};
authors.insert(handle.to_string(), new_author);
write_coauthors_file(authors);
}
fn edit_coauthor(handle: &String, name: &String, email: &String) {
let mut authors = get_available_coauthors();
if let Some(author) = authors.get(handle) {
let mut updated_author: Author = author.clone();
updated_author.name = name.to_string();
updated_author.email = email.to_string();
authors.insert(handle.to_string(), updated_author);
write_coauthors_file(authors);
} else {
eprintln!("No author found with initials {}", handle);
process::exit(1);
};
}
fn remove_coauthor(handle: &String) {
let mut authors = get_available_coauthors();
authors.remove(handle);
write_coauthors_file(authors);
}
fn solo() {
let main_author = get_main_author();
println!("{main_author}");
with_gitmessage_template_path_or_exit(|path| {
let _template = File::create(path);
});
ensure_commit_template_is_set();
}
fn override_main_author(initials: &str) {
let all_authors = get_available_coauthors();
match all_authors.get(initials) {
Some(new_main_author) => set_main_author(new_main_author),
None => {
eprintln!("Error: author with initials {initials} not found");
process::exit(1);
}
}
}
fn write_coauthors_to_gitmessage_file(coauthor_initials: &[String]) {
let coauthors = select_coauthors(coauthor_initials);
let mut content = String::from("\n\n");
for author in &coauthors {
_ = writeln!(content, "Co-authored-by: {}", &author.to_string());
}
with_gitmessage_template_path_or_exit(|path| match fs::write(path, content) {
Ok(_) => {
println!("{}", get_main_author());
for author in &coauthors {
println!("{author}");
}
}
Err(e) => {
eprintln!("Error writing to .gitmessage template: {e}");
process::exit(1);
}
});
}
fn select_coauthors(coauthor_initials: &[String]) -> Vec<Author> {
let all_coauthors = get_available_coauthors();
let mut coauthors: Vec<Author> = Vec::new();
for initial in coauthor_initials {
match all_coauthors.get(initial) {
Some(coauthor) => coauthors.push(coauthor.clone()),
None => {
eprintln!("Error: author with initials {initial} not found");
process::exit(1);
}
}
}
coauthors
}