shackle_shell/
lib.rs

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}