universe

Universe
git clone https://git.dasho.dev/universe.git
Log | Files | Refs | Submodules | README

main.rs (7774B)


      1 use dialoguer::{theme::ColorfulTheme, Select, Input};
      2 use std::process::{Command, Output};
      3 use std::env;
      4 #[cfg(all(test, unix))]
      5 use std::os::unix::process::ExitStatusExt;
      6 #[cfg(all(test, windows))]
      7 use std::os::windows::process::ExitStatusExt;
      8 
      9 /// Main entry point for UniShell - Git Operations Terminal
     10 fn main() {
     11    loop {
     12        println!("Welcome to UniShell - Git Operations Terminal");
     13 
     14        let options = vec![
     15            "Status",
     16            "Add Files",
     17            "Commit Changes",
     18            "Push Changes",
     19            "Pull Changes",
     20            "Switch Branch",
     21            "Initialize Repository",
     22            "Clone Repository",
     23            "Pull Specific Folder",
     24            "Include Submodules",
     25            "Remove Folder",
     26            "Restore Folder",
     27            "Exit",
     28        ];
     29 
     30        let selection = Select::with_theme(&ColorfulTheme::default())
     31            .with_prompt("Choose a Git operation")
     32            .items(&options)
     33            .default(0)
     34            .interact()
     35            .unwrap();
     36 
     37        match selection {
     38            0 => git_status(),
     39            1 => git_add(),
     40            2 => git_commit(),
     41            3 => git_push(),
     42            4 => git_pull(),
     43            5 => git_switch_branch(),
     44            6 => git_init(),
     45            7 => git_clone(),
     46            8 => git_pull_folder(),
     47            9 => git_include_submodules(),
     48            10 => git_remove_folder(),
     49            11 => git_restore_folder(),
     50            12 => {
     51                println!("Exiting UniShell. Goodbye!");
     52                break;
     53            }
     54            _ => unreachable!(),
     55        }
     56    }
     57 }
     58 
     59 /// Runs `git status` and prints the output
     60 fn git_status() {
     61    println!("Running `git status`...");
     62    let output = Command::new("git")
     63        .arg("status")
     64        .output()
     65        .expect("Failed to execute git status");
     66    println!("{}", String::from_utf8_lossy(&output.stdout));
     67 }
     68 
     69 /// Returns the executable used for git operations (overridden in tests)
     70 fn git_bin() -> String {
     71    env::var("GIT_BIN").unwrap_or_else(|_| "git".to_string())
     72 }
     73 
     74 /// Adds specified files to the staging area and returns the command output
     75 fn git_add_files(files: &str) -> Output {
     76    println!("Running `git add {}`...", files);
     77    Command::new(git_bin())
     78        .arg("add")
     79        .arg(files)
     80        .output()
     81        .expect("Failed to execute git add")
     82 }
     83 
     84 /// Commits with the provided message and returns the command output
     85 fn git_commit_message(message: &str) -> Output {
     86    println!("Running `git commit -m \"{}\"`...", message);
     87    Command::new(git_bin())
     88        .arg("commit")
     89        .arg("-m")
     90        .arg(message)
     91        .output()
     92        .expect("Failed to execute git commit")
     93 }
     94 
     95 /// Adds files to the staging area
     96 fn git_add() {
     97    let files: String = Input::new()
     98        .with_prompt("Enter files to add (use '.' to add all)")
     99        .interact_text()
    100        .unwrap();
    101 
    102    git_add_files(&files);
    103 }
    104 
    105 /// Commits changes with a message
    106 fn git_commit() {
    107    let message: String = Input::new()
    108        .with_prompt("Enter commit message")
    109        .interact_text()
    110        .unwrap();
    111 
    112    git_commit_message(&message);
    113 }
    114 
    115 /// Pushes changes to the remote repository
    116 fn git_push() {
    117    println!("Running `git push`...");
    118    Command::new("git")
    119        .arg("push")
    120        .status()
    121        .expect("Failed to execute git push");
    122 }
    123 
    124 /// Pulls changes from the remote repository
    125 fn git_pull() {
    126    println!("Running `git pull`...");
    127    Command::new("git")
    128        .arg("pull")
    129        .status()
    130        .expect("Failed to execute git pull");
    131 }
    132 
    133 /// Switches to a specified branch
    134 fn git_switch_branch() {
    135    let branch: String = Input::new()
    136        .with_prompt("Enter branch name to switch to")
    137        .interact_text()
    138        .unwrap();
    139 
    140    println!("Running `git checkout {}`...", branch);
    141    Command::new("git")
    142        .arg("checkout")
    143        .arg(branch)
    144        .status()
    145        .expect("Failed to execute git checkout");
    146 }
    147 
    148 /// Initializes a new Git repository
    149 fn git_init() {
    150    println!("Running `git init`...");
    151    Command::new("git")
    152        .arg("init")
    153        .status()
    154        .expect("Failed to execute git init");
    155 }
    156 
    157 /// Clones a repository from a given URL
    158 fn git_clone() {
    159    let repo_url: String = Input::new()
    160        .with_prompt("Enter repository URL to clone")
    161        .interact_text()
    162        .unwrap();
    163 
    164    println!("Running `git clone {}`...", repo_url);
    165    Command::new("git")
    166        .arg("clone")
    167        .arg(repo_url)
    168        .status()
    169        .expect("Failed to execute git clone");
    170 }
    171 
    172 /// Pulls a specific folder using sparse-checkout
    173 fn git_pull_folder() {
    174    let folder: String = Input::new()
    175        .with_prompt("Enter folder path to pull")
    176        .interact_text()
    177        .unwrap();
    178 
    179    println!("Running `git sparse-checkout set {}` and pulling...", folder);
    180    Command::new("git")
    181        .arg("sparse-checkout")
    182        .arg("set")
    183        .arg(&folder)
    184        .status()
    185        .expect("Failed to set sparse-checkout folder");
    186 
    187    Command::new("git")
    188        .arg("pull")
    189        .status()
    190        .expect("Failed to pull specific folder");
    191 }
    192 
    193 /// Includes submodules in the repository
    194 fn git_include_submodules() {
    195    println!("Running `git submodule update --init --recursive`...");
    196    Command::new("git")
    197        .arg("submodule")
    198        .arg("update")
    199        .arg("--init")
    200        .arg("--recursive")
    201        .status()
    202        .expect("Failed to include submodules");
    203 }
    204 
    205 /// Removes a folder from the repository
    206 fn git_remove_folder() {
    207    let folder: String = Input::new()
    208        .with_prompt("Enter folder path to remove")
    209        .interact_text()
    210        .unwrap();
    211 
    212    println!("Removing folder `{}`...", folder);
    213    Command::new("rm")
    214        .arg("-rf")
    215        .arg(&folder)
    216        .status()
    217        .expect("Failed to remove folder");
    218 }
    219 
    220 /// Restores a folder to its previous state
    221 fn git_restore_folder() {
    222    let folder: String = Input::new()
    223        .with_prompt("Enter folder path to restore")
    224        .interact_text()
    225        .unwrap();
    226 
    227    println!("Restoring folder `{}`...", folder);
    228    Command::new("git")
    229        .arg("checkout")
    230        .arg("--")
    231        .arg(&folder)
    232        .status()
    233        .expect("Failed to restore folder");
    234 }
    235 
    236 #[cfg(test)]
    237 mod tests {
    238    use super::*;
    239 
    240    fn mock_command_output(stdout: &str) -> Output {
    241        Output {
    242            status: std::process::ExitStatus::from_raw(0),
    243            stdout: stdout.as_bytes().to_vec(),
    244            stderr: Vec::new(),
    245        }
    246    }
    247 
    248    #[test]
    249    fn test_git_status() {
    250        let output = mock_command_output("On branch main\nYour branch is up to date.");
    251        assert_eq!(
    252            String::from_utf8_lossy(&output.stdout),
    253            "On branch main\nYour branch is up to date."
    254        );
    255    }
    256 
    257    #[test]
    258    fn test_git_add() {
    259        unsafe { std::env::set_var("GIT_BIN", "echo") };
    260        let output = git_add_files("test_file.txt");
    261        assert_eq!(
    262            String::from_utf8_lossy(&output.stdout).trim(),
    263            "add test_file.txt"
    264        );
    265        unsafe { std::env::remove_var("GIT_BIN") };
    266    }
    267 
    268    #[test]
    269    fn test_git_commit() {
    270        unsafe { std::env::set_var("GIT_BIN", "echo") };
    271        let output = git_commit_message("Initial commit");
    272        assert_eq!(
    273            String::from_utf8_lossy(&output.stdout).trim(),
    274            "commit -m Initial commit"
    275        );
    276        unsafe { std::env::remove_var("GIT_BIN") };
    277    }
    278 
    279    #[test]
    280    fn test_git_push() {
    281        // Mocking command execution
    282        let output = mock_command_output("Everything up-to-date");
    283        assert_eq!(
    284            String::from_utf8_lossy(&output.stdout),
    285            "Everything up-to-date"
    286        );
    287    }
    288 }