zoi/cmd/
repo.rs

1use crate::pkg::config;
2use anyhow::Result;
3use clap::{Parser, Subcommand};
4use colored::*;
5use comfy_table::{Table, presets::UTF8_FULL};
6use std::collections::HashSet;
7
8#[derive(Parser)]
9pub struct RepoCommand {
10    #[arg(
11        short = 'y',
12        long,
13        help = "Automatically answer yes to all prompts",
14        global = true
15    )]
16    yes: bool,
17    #[command(subcommand)]
18    command: Commands,
19}
20
21#[derive(Subcommand)]
22enum Commands {
23    /// Add a repository to the configuration or clone from a git URL
24    #[command(alias = "a")]
25    Add {
26        /// The name of the repository to add or a git URL to clone
27        repo_or_url: Option<String>,
28    },
29    /// Remove a repository from the active configuration
30    #[command(alias = "rm")]
31    Remove { repo_name: String },
32    /// List repositories (active by default); use `list all` to show all
33    #[command(alias = "ls")]
34    List {
35        #[command(subcommand)]
36        which: Option<ListSub>,
37    },
38    /// Manage cloned git repositories
39    #[command(subcommand)]
40    Git(GitCommand),
41}
42
43pub fn run(args: RepoCommand) {
44    let yes = args.yes;
45    match args.command {
46        Commands::Add { repo_or_url } => {
47            if let Some(val) = repo_or_url {
48                if val.starts_with("http://")
49                    || val.starts_with("https://")
50                    || val.ends_with(".git")
51                {
52                    if let Err(e) = config::clone_git_repo(&val) {
53                        eprintln!("{}: {}", "Error".red().bold(), e);
54                    }
55                } else if let Err(e) = config::add_repo(&val) {
56                    eprintln!("{}: {}", "Error".red().bold(), e);
57                } else {
58                    println!("Repository '{}' added successfully.", val.green());
59                }
60            } else if !yes {
61                if let Err(e) = config::interactive_add_repo() {
62                    eprintln!("{}: {}", "Error".red().bold(), e);
63                }
64            } else if let Err(e) = config::interactive_add_repo() {
65                eprintln!("{}: {}", "Error".red().bold(), e);
66            }
67        }
68        Commands::Remove { repo_name } => {
69            if let Err(e) = config::remove_repo(&repo_name) {
70                eprintln!("{}: {}", "Error".red().bold(), e);
71            } else {
72                println!("Repository '{}' removed successfully.", repo_name.green());
73            }
74        }
75        Commands::List { which } => match which {
76            None => {
77                if let Err(e) = run_list_active() {
78                    eprintln!("{}: {}", "Error".red().bold(), e);
79                }
80            }
81            Some(ListSub::All) => {
82                if let Err(e) = run_list_all() {
83                    eprintln!("{}: {}", "Error".red().bold(), e);
84                }
85            }
86        },
87        Commands::Git(cmd) => match cmd {
88            GitCommand::List => {
89                if let Err(e) = run_list_git_only() {
90                    eprintln!("{}: {}", "Error".red().bold(), e);
91                }
92            }
93            GitCommand::Rm { repo_name } => {
94                if let Err(e) = config::remove_git_repo(&repo_name) {
95                    eprintln!("{}: {}", "Error".red().bold(), e);
96                }
97            }
98        },
99    }
100}
101
102fn run_list_active() -> Result<()> {
103    let config = config::read_config()?;
104    if config.repos.is_empty() {
105        println!("No active repositories.");
106        return Ok(());
107    }
108
109    let mut table = Table::new();
110    table
111        .load_preset(UTF8_FULL)
112        .set_header(vec!["Active Repositories"]);
113    for repo in config.repos {
114        table.add_row(vec![repo]);
115    }
116    println!("{table}");
117    Ok(())
118}
119
120fn run_list_all() -> Result<()> {
121    let active_repos = config::read_config()?
122        .repos
123        .into_iter()
124        .collect::<HashSet<_>>();
125    let all_repos = config::get_all_repos()?;
126
127    let mut table = Table::new();
128    table
129        .load_preset(UTF8_FULL)
130        .set_header(vec!["Status", "Repository"]);
131
132    for repo in all_repos {
133        let status = if active_repos.contains(&repo.to_lowercase()) {
134            "Added"
135        } else {
136            ""
137        };
138        table.add_row(vec![status.to_string(), repo]);
139    }
140    println!("{table}");
141    Ok(())
142}
143
144#[derive(Subcommand)]
145enum ListSub {
146    /// Show all available repositories (active + discovered)
147    All,
148}
149
150#[derive(Subcommand)]
151enum GitCommand {
152    /// Show only cloned git repositories (~/.zoi/pkgs/git)
153    #[command(alias = "ls")]
154    List,
155    /// Remove a cloned git repository directory (~/.zoi/pkgs/git/<repo-name>)
156    Rm { repo_name: String },
157}
158
159fn run_list_git_only() -> Result<()> {
160    let repos = config::list_git_repos()?;
161    if repos.is_empty() {
162        println!("No cloned git repositories.");
163        return Ok(());
164    }
165
166    let mut table = Table::new();
167    table
168        .load_preset(UTF8_FULL)
169        .set_header(vec!["Cloned Git Repositories (~/.zoi/pkgs/git)"]);
170    for repo in repos {
171        table.add_row(vec![repo]);
172    }
173    println!("{table}");
174    Ok(())
175}