pji 0.2.0

A CLI for managing, finding, and opening Git repositories.
Documentation
use std::{fs::read_dir, io, path::PathBuf, process::Command};

use crate::{
    error::PjiError,
    repo::{GitProtocol, GitURI},
};

pub(crate) fn parse_git_url(url: &str) -> Option<GitURI> {
    parse_ssh_git_url(url).or_else(|| parse_http_git_url(url))
}

fn parse_ssh_git_url(url: &str) -> Option<GitURI> {
    let rest = url.strip_prefix("git@")?;
    let (hostname, path) = rest.split_once(':')?;
    let (user, repo) = split_repo_path(path)?;

    Some(GitURI {
        hostname: hostname.to_string(),
        user: user.to_string(),
        repo: repo.to_string(),
        protocol: GitProtocol::Ssh,
        uri: url.to_string(),
    })
}

fn parse_http_git_url(url: &str) -> Option<GitURI> {
    let rest = url
        .strip_prefix("https://")
        .or_else(|| url.strip_prefix("http://"))?;
    let (hostname, path) = rest.split_once('/')?;
    let (user, repo) = split_repo_path(path)?;

    Some(GitURI {
        hostname: hostname.to_string(),
        user: user.to_string(),
        repo: repo.to_string(),
        protocol: GitProtocol::Https,
        uri: url.to_string(),
    })
}

fn split_repo_path(path: &str) -> Option<(&str, &str)> {
    let (user, repo) = path.split_once('/')?;
    let repo = repo.strip_suffix(".git")?;
    if user.is_empty() || repo.is_empty() || repo.contains('/') {
        return None;
    }
    Some((user, repo))
}

pub(crate) fn try_get_repo_from_dir(dir: &PathBuf) -> Result<Option<String>, PjiError> {
    let command = format!("git -C {} config --get remote.origin.url", dir.display());
    let output = Command::new("git")
        .arg("-C")
        .arg(dir)
        .arg("config")
        .arg("--get")
        .arg("remote.origin.url")
        .output()?;

    if output.status.success() {
        let url = String::from_utf8_lossy(&output.stdout).trim().to_string();
        if url.is_empty() {
            return Err(PjiError::EmptyGitOutput { command });
        }
        return Ok(Some(url));
    }

    let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
    if stderr.is_empty() {
        return Ok(None);
    }

    Err(PjiError::GitCommand { command, stderr })
}

pub(crate) fn list_dir(dir: &PathBuf) -> io::Result<Vec<PathBuf>> {
    let mut dirs = vec![];
    for entry in read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();
        if path.is_dir() {
            dirs.push(path);
        }
    }
    Ok(dirs)
}