1mod parser;
2pub mod user_info;
3pub mod vcs;
4
5use crate::vcs::git;
6use comfy_table::Table;
7use humansize::{format_size, BINARY};
8use parser::*;
9use rustyline::{error::ReadlineError, DefaultEditor};
10use std::{io, ops::ControlFlow};
11use thiserror::Error;
12
13pub fn run_command(user_input: &str) -> Result<ControlFlow<(), ()>, ShackleError> {
14 match user_input.parse::<ShackleCommand>() {
15 Err(parse_error) => {
16 println!("{}", parse_error);
17 }
18 Ok(ShackleCommand::Exit) => {
19 return Ok(ControlFlow::Break(()));
20 }
21 Ok(ShackleCommand::List(ListArgs { verbose })) => {
22 let mut table = Table::new();
23 if !verbose {
24 table.set_header(vec!["path", "description"]);
25 let listing = git::list()?;
26 for meta in listing {
27 table.add_row(vec![meta.path.display().to_string(), meta.description]);
28 }
29 } else {
30 table.set_header(vec!["path", "description", "size"]);
31 let listing = git::list_verbose()?;
32 for meta in listing {
33 table.add_row(vec![
34 meta.path.display().to_string(),
35 meta.description,
36 format_size(meta.size, BINARY),
37 ]);
38 }
39 }
40
41 println!("{table}");
42 }
43 Ok(ShackleCommand::SetDescription(SetDescriptionArgs {
44 directory,
45 description,
46 })) => {
47 git::set_description(&directory, &description)?;
48 println!("Successfully updated description");
49 }
50 Ok(ShackleCommand::SetBranch(SetBranchArgs { directory, branch })) => {
51 git::set_branch(&directory, &branch)?;
52 println!("Successfully updated branch");
53 }
54 Ok(ShackleCommand::Init(InitArgs {
55 repo_name,
56 group,
57 description,
58 branch,
59 mirror,
60 })) => {
61 let init_result = git::init(&repo_name, &group, &description, &branch, &mirror)?;
62 println!("Successfully created \"{}\"", init_result.path.display());
63 }
64 Ok(ShackleCommand::Delete(DeleteArgs { directory })) => {
65 if confirm_risky_action(format!(
66 "Are you sure you want to delete \"{}\"?",
67 directory.display()
68 ))? {
69 git::delete(&directory)?;
70 println!("Successfully deleted \"{}\"", directory.display());
71 } else {
72 println!("Action cancelled");
73 }
74 }
75 Ok(ShackleCommand::Housekeeping(HousekeepingArgs { directory })) => match directory {
76 Some(directory) => {
77 git::housekeeping(&directory)?;
78 println!(
79 "Successfully did housekeeping on \"{}\"",
80 directory.display()
81 );
82 }
83 None => {
84 let list = git::list()?;
85 for repo in list {
86 git::housekeeping(&repo.path)?;
87 println!(
88 "Successfully did housekeeping on \"{}\"",
89 repo.path.display()
90 );
91 }
92 }
93 },
94 Ok(ShackleCommand::GitUploadPack(upload_pack_args)) => {
95 git::upload_pack(&upload_pack_args)?;
96 }
97 Ok(ShackleCommand::GitReceivePack(receive_pack_args)) => {
98 git::receive_pack(&receive_pack_args)?;
99 }
100 }
101 Ok(ControlFlow::Continue(()))
102}
103
104fn confirm_risky_action(prompt: String) -> Result<bool, ShackleError> {
105 let mut rl = DefaultEditor::new()?;
106 loop {
107 let user_input = rl
108 .readline(&format!("{} (yes/no) ", prompt))
109 .map(|user_input| user_input.to_lowercase())?;
110
111 match user_input.trim() {
112 "" => {}
113 "yes" => {
114 return Ok(true);
115 }
116 "no" => {
117 return Ok(false);
118 }
119 _ => {
120 println!("Please answer 'yes' or 'no'.");
121 }
122 }
123 }
124}
125
126#[derive(Error, Debug)]
127pub enum ShackleError {
128 #[error(transparent)]
129 IoError(#[from] io::Error),
130 #[error(transparent)]
131 NixError(#[from] nix::errno::Errno),
132 #[error(transparent)]
133 GitError(#[from] git2::Error),
134 #[error(transparent)]
135 ReadlineError(#[from] ReadlineError),
136 #[error("Could not get the current user name")]
137 UserReadError,
138 #[error("Path is not accessible")]
139 InvalidDirectory,
140 #[error("Unknown group")]
141 InvalidGroup,
142}