thesa 4.1.36

Archive GitHub repositories, ML models, datasets, and websites with Scrin/Aisling workflows
use std::path::PathBuf;

use crate::error::{Result, ThesaError};

#[derive(Debug, Clone)]
pub(crate) enum Target {
    Owner(String),
    Repo { owner: String, repo: String },
}

impl Target {
    pub(crate) fn archive_target_id(&self) -> String {
        match self {
            Target::Owner(owner) => owner.clone(),
            Target::Repo { owner, repo } => format!("{owner}/{repo}"),
        }
    }
}

pub(crate) fn parse_target(input: &str) -> Result<Target> {
    let cleaned = input.trim().trim_end_matches(".git");
    if cleaned.is_empty() {
        return Err(ThesaError::InvalidTarget(input.to_string()));
    }

    if let Some(path) = cleaned.strip_prefix("https://github.com/") {
        return parse_owner_or_repo_path(path);
    }
    if let Some(path) = cleaned.strip_prefix("http://github.com/") {
        return parse_owner_or_repo_path(path);
    }
    if let Some(path) = cleaned.strip_prefix("git@github.com:") {
        return parse_owner_or_repo_path(path);
    }

    parse_owner_or_repo_path(cleaned)
}

fn parse_owner_or_repo_path(input: &str) -> Result<Target> {
    let parts: Vec<&str> = input
        .trim_matches('/')
        .split('/')
        .filter(|part| !part.is_empty())
        .collect();

    if parts.is_empty() {
        return Err(ThesaError::InvalidTarget(input.to_string()));
    }

    if parts.len() == 1 {
        return Ok(Target::Owner(parts[0].to_string()));
    }

    Ok(Target::Repo {
        owner: parts[0].to_string(),
        repo: parts[1].to_string(),
    })
}

pub(crate) fn default_output_path(target: &Target) -> PathBuf {
    let slug = match target {
        Target::Owner(owner) => owner.clone(),
        Target::Repo { repo, .. } => repo.clone(),
    };

    PathBuf::from("./archives").join(slug)
}