Skip to content

Commit dc4526d

Browse files
committed
refactor: reorganize github mod
1 parent 14e7c61 commit dc4526d

File tree

6 files changed

+676
-70
lines changed

6 files changed

+676
-70
lines changed

src/commands/remove.rs

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
//! Remove command implementation
22
33
use super::{Command, CommandContext};
4+
use crate::git;
45
use anyhow::Result;
56
use async_trait::async_trait;
67
use colored::*;
7-
use std::fs;
88

99
/// Remove command for deleting cloned repositories
1010
pub struct RemoveCommand;
@@ -46,13 +46,20 @@ impl Command for RemoveCommand {
4646
.map(|repo| {
4747
let repo_name = repo.name.clone();
4848
tokio::spawn(async move {
49-
let target_dir = repo.get_target_dir();
5049
let result = tokio::task::spawn_blocking(move || {
51-
if std::path::Path::new(&target_dir).exists() {
52-
fs::remove_dir_all(&target_dir).map_err(anyhow::Error::from)
53-
} else {
54-
println!("{} | Directory does not exist", repo.name.cyan().bold());
55-
Ok(())
50+
match git::remove_repository(&repo) {
51+
Ok(_) => Ok(()),
52+
Err(e)
53+
if e.to_string()
54+
.contains("Repository directory does not exist") =>
55+
{
56+
println!(
57+
"{} | Directory does not exist",
58+
repo.name.cyan().bold()
59+
);
60+
Ok(()) // Treat as success since desired state is achieved
61+
}
62+
Err(e) => Err(e),
5663
}
5764
})
5865
.await?;
@@ -76,25 +83,25 @@ impl Command for RemoveCommand {
7683
}
7784
} else {
7885
for repo in repositories {
79-
let target_dir = repo.get_target_dir();
80-
if std::path::Path::new(&target_dir).exists() {
81-
match fs::remove_dir_all(&target_dir) {
82-
Ok(_) => {
83-
println!("{} | {}", repo.name.cyan().bold(), "Removed".green());
84-
successful += 1;
85-
}
86-
Err(e) => {
87-
eprintln!(
88-
"{} | {}",
89-
repo.name.cyan().bold(),
90-
format!("Error: {e}").red()
91-
);
92-
errors.push((repo.name.clone(), e.into()));
93-
}
86+
match git::remove_repository(&repo) {
87+
Ok(_) => {
88+
successful += 1;
89+
}
90+
Err(e)
91+
if e.to_string()
92+
.contains("Repository directory does not exist") =>
93+
{
94+
println!("{} | Directory does not exist", repo.name.cyan().bold());
95+
successful += 1; // Count as success since the desired state is achieved
96+
}
97+
Err(e) => {
98+
eprintln!(
99+
"{} | {}",
100+
repo.name.cyan().bold(),
101+
format!("Error: {e}").red()
102+
);
103+
errors.push((repo.name.clone(), e));
94104
}
95-
} else {
96-
println!("{} | Directory does not exist", repo.name.cyan().bold());
97-
successful += 1; // Count as success since the desired state is achieved
98105
}
99106
}
100107
}

src/github/client.rs

Lines changed: 102 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,77 @@
11
//! GitHub API client implementation
2+
//!
3+
//! This module provides the main `GitHubClient` struct which serves as the entry point
4+
//! for all GitHub API operations. The client encapsulates authentication and HTTP client
5+
//! state, making it easy to perform various GitHub operations.
6+
//!
7+
//! ## Architecture
8+
//!
9+
//! The `GitHubClient` follows a modular design where different API endpoints are organized
10+
//! into separate modules:
11+
//! - `pull_requests.rs` - Pull request operations
12+
//! - `repositories.rs` - Repository information and releases
13+
//!
14+
//! Each module extends the `GitHubClient` with `impl` blocks containing related methods.
215
316
use super::auth::GitHubAuth;
4-
use super::types::{PullRequestParams, constants::*};
517
use anyhow::Result;
618
use reqwest::Client;
7-
use serde_json::{Value, json};
819

9-
/// GitHub API client
20+
/// GitHub API client for interacting with GitHub's REST API
21+
///
22+
/// This client provides a unified interface for GitHub API operations, managing
23+
/// authentication and HTTP client state. Different API endpoints are organized
24+
/// into separate modules that extend this client with specific functionality.
25+
///
26+
/// ## Features
27+
///
28+
/// - **Authentication Management**: Handles GitHub token authentication
29+
/// - **URL Parsing**: Supports both GitHub.com and GitHub Enterprise URLs
30+
/// - **Modular Design**: API operations are organized by functionality
31+
/// - **Error Handling**: Comprehensive error handling for API responses
32+
///
33+
/// ## Example
34+
///
35+
/// ```rust,no_run
36+
/// use repos::github::GitHubClient;
37+
///
38+
/// # async fn example() -> anyhow::Result<()> {
39+
/// // Create client with authentication
40+
/// let client = GitHubClient::new(Some("your_github_token".to_string()));
41+
///
42+
/// // Parse repository URL
43+
/// let (owner, repo) = client.parse_github_url("https://github.com/owner/repo")?;
44+
///
45+
/// // Use client for various operations (see specific modules for examples)
46+
/// // - Pull requests: client.create_pull_request()
47+
/// // - Repositories: client.get_repository()
48+
/// # Ok(())
49+
/// # }
50+
/// ```
1051
pub struct GitHubClient {
11-
client: Client,
12-
auth: Option<GitHubAuth>,
52+
pub(crate) client: Client,
53+
pub(crate) auth: Option<GitHubAuth>,
1354
}
1455

1556
impl GitHubClient {
1657
/// Create a new GitHub client
58+
///
59+
/// # Arguments
60+
/// * `token` - Optional GitHub personal access token for authentication
61+
///
62+
/// # Returns
63+
/// A new GitHubClient instance
64+
///
65+
/// # Example
66+
/// ```rust
67+
/// use repos::github::GitHubClient;
68+
///
69+
/// // Client without authentication (for public repositories)
70+
/// let public_client = GitHubClient::new(None);
71+
///
72+
/// // Client with authentication (for private repos and higher rate limits)
73+
/// let auth_client = GitHubClient::new(Some("your_token".to_string()));
74+
/// ```
1775
pub fn new(token: Option<String>) -> Self {
1876
let auth = token.map(GitHubAuth::new);
1977
Self {
@@ -23,7 +81,30 @@ impl GitHubClient {
2381
}
2482

2583
/// Parse GitHub URL to extract owner and repository name
26-
/// Supports both github.com and enterprise GitHub instances
84+
///
85+
/// Supports both github.com and enterprise GitHub instances with various URL formats:
86+
/// - SSH: `git@github.com:owner/repo` or `git@github-enterprise:owner/repo`
87+
/// - HTTPS: `https://github.com/owner/repo` or `https://github-enterprise/owner/repo`
88+
/// - Legacy: `github.com/owner/repo`
89+
///
90+
/// # Arguments
91+
/// * `url` - The GitHub repository URL to parse
92+
///
93+
/// # Returns
94+
/// A tuple containing (owner, repository_name)
95+
///
96+
/// # Errors
97+
/// Returns an error if the URL format is not recognized as a valid GitHub URL
98+
///
99+
/// # Example
100+
/// ```rust
101+
/// use repos::github::GitHubClient;
102+
///
103+
/// let client = GitHubClient::new(None);
104+
/// let (owner, repo) = client.parse_github_url("https://github.com/rust-lang/rust").unwrap();
105+
/// assert_eq!(owner, "rust-lang");
106+
/// assert_eq!(repo, "rust");
107+
/// ```
27108
pub fn parse_github_url(&self, url: &str) -> Result<(String, String)> {
28109
let url = url.trim_end_matches('/').trim_end_matches(".git");
29110

@@ -49,46 +130,23 @@ impl GitHubClient {
49130
return Ok((owner, repo));
50131
}
51132

52-
Err(anyhow::anyhow!("Invalid GitHub URL: {}", url))
133+
Err(anyhow::anyhow!("Invalid GitHub URL format: {}", url))
53134
}
54135

55-
/// Create a pull request
56-
pub async fn create_pull_request(&self, params: PullRequestParams<'_>) -> Result<Value> {
57-
let auth = self
58-
.auth
59-
.as_ref()
60-
.ok_or_else(|| anyhow::anyhow!("GitHub token is required"))?;
61-
62-
let url = format!(
63-
"{}/repos/{}/{}/pulls",
64-
GITHUB_API_BASE, params.owner, params.repo
65-
);
66-
67-
let payload = json!({
68-
"title": params.title,
69-
"body": params.body,
70-
"head": params.head,
71-
"base": params.base,
72-
"draft": params.draft
73-
});
74-
75-
let response = self
76-
.client
77-
.post(&url)
78-
.header("Authorization", format!("token {}", auth.token()))
79-
.header("User-Agent", DEFAULT_USER_AGENT)
80-
.header("Accept", "application/vnd.github.v3+json")
81-
.json(&payload)
82-
.send()
83-
.await?;
84-
85-
if response.status().is_success() {
86-
let result: Value = response.json().await?;
87-
Ok(result)
88-
} else {
89-
let error_text = response.text().await?;
90-
Err(anyhow::anyhow!("GitHub API error: {}", error_text))
91-
}
136+
/// Check if the client has authentication configured
137+
///
138+
/// # Returns
139+
/// `true` if the client has a GitHub token configured, `false` otherwise
140+
pub fn is_authenticated(&self) -> bool {
141+
self.auth.is_some()
142+
}
143+
144+
/// Get the authentication token (if available)
145+
///
146+
/// # Returns
147+
/// `Some(token)` if authenticated, `None` otherwise
148+
pub fn token(&self) -> Option<&str> {
149+
self.auth.as_ref().map(|auth| auth.token())
92150
}
93151
}
94152

src/github/mod.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,58 @@
11
//! GitHub API integration module
2+
//!
3+
//! This module provides a comprehensive interface for interacting with GitHub's REST API.
4+
//! It follows a modular design where different API endpoints are organized into separate
5+
//! sub-modules for better maintainability and organization.
6+
//!
7+
//! ## Architecture
8+
//!
9+
//! - [`client`]: Core GitHub client with authentication and URL parsing
10+
//! - [`auth`]: Authentication handling and token management
11+
//! - [`pull_requests`]: Pull request creation and management
12+
//! - [`repositories`]: Repository information and releases
13+
//! - [`types`]: Data structures and type definitions
14+
//! - [`api`]: High-level workflow functions
15+
//!
16+
//! ## Features
17+
//!
18+
//! - **Modular Design**: API operations grouped by functionality
19+
//! - **Authentication**: Secure token-based authentication
20+
//! - **Error Handling**: Comprehensive error types and handling
21+
//! - **Enterprise Support**: Works with both GitHub.com and GitHub Enterprise
22+
//! - **Async/Await**: Fully async API with tokio support
23+
//!
24+
//! ## Quick Start
25+
//!
26+
//! ```rust,no_run
27+
//! use repos::github::{GitHubClient, PrOptions};
28+
//! use repos::config::Repository;
29+
//!
30+
//! # async fn example() -> anyhow::Result<()> {
31+
//! // Create a client
32+
//! let client = GitHubClient::new(Some("your_token".to_string()));
33+
//!
34+
//! // Parse a GitHub URL
35+
//! let (owner, repo) = client.parse_github_url("https://github.com/rust-lang/rust")?;
36+
//!
37+
//! // Get repository information
38+
//! let repo_info = client.get_repository(&owner, &repo).await?;
39+
//! println!("Repository: {}", repo_info.full_name);
40+
//! # Ok(())
41+
//! # }
42+
//! ```
243
344
pub mod api;
445
pub mod auth;
546
pub mod client;
47+
pub mod pull_requests;
48+
pub mod repositories;
649
pub mod types;
750

851
// Re-export commonly used items for convenience
952
pub use api::create_pr_from_workspace;
1053
pub use auth::GitHubAuth;
1154
pub use client::GitHubClient;
12-
pub use types::{PrOptions, PullRequestParams};
55+
pub use types::{GitHubRepo, PrOptions, PullRequest, PullRequestParams, User};
1356

1457
// Re-export constants for easy access
1558
pub use crate::constants::github::{DEFAULT_BRANCH_PREFIX, DEFAULT_USER_AGENT};

0 commit comments

Comments
 (0)