Skip to main content

bytes_radar/net/providers/
sourceforge.rs

1use crate::net::traits::{GitProvider, ParsedRepository, ProviderConfig};
2use async_trait::async_trait;
3use reqwest::Client;
4use std::collections::HashMap;
5
6pub struct SourceForgeProvider {
7    credentials: HashMap<String, String>,
8}
9
10impl SourceForgeProvider {
11    pub fn new() -> Self {
12        Self {
13            credentials: HashMap::new(),
14        }
15    }
16}
17
18#[async_trait]
19impl GitProvider for SourceForgeProvider {
20    fn name(&self) -> &'static str {
21        "sourceforge"
22    }
23
24    fn can_handle(&self, url: &str) -> bool {
25        url.contains("sourceforge.net") || url.contains("sf.net")
26    }
27
28    fn parse_url(&self, url: &str) -> Option<ParsedRepository> {
29        if !self.can_handle(url) {
30            return None;
31        }
32
33        let url = url.trim_end_matches('/');
34
35        if url.contains("/ci/") && url.contains("/tree/") {
36            return self.parse_tree_url(url);
37        }
38
39        if url.contains("/ci/") {
40            return self.parse_commit_url(url);
41        }
42
43        self.parse_basic_url(url)
44    }
45
46    fn build_download_urls(&self, parsed: &ParsedRepository) -> Vec<String> {
47        let mut urls = Vec::new();
48
49        if let Some(ref branch_or_commit) = parsed.branch_or_commit {
50            urls.push(format!(
51                "https://sourceforge.net/p/{}/code/ci/{}/tarball",
52                parsed.repo, branch_or_commit
53            ));
54        }
55
56        urls
57    }
58
59    async fn get_default_branch(
60        &self,
61        _client: &Client,
62        _parsed: &ParsedRepository,
63    ) -> Option<String> {
64        None
65    }
66
67    fn apply_config(&mut self, config: &ProviderConfig) {
68        self.credentials = config.credentials.clone();
69    }
70
71    fn get_project_name(&self, url: &str) -> String {
72        if let Some(parsed) = self.parse_url(url) {
73            return parsed.project_name;
74        }
75
76        if let Some(filename) = url.split('/').next_back() {
77            if filename.ends_with(".tar.gz") {
78                return filename.trim_end_matches(".tar.gz").to_string();
79            }
80            if filename.ends_with(".tgz") {
81                return filename.trim_end_matches(".tgz").to_string();
82            }
83            return filename.to_string();
84        }
85
86        "sourceforge-project".to_string()
87    }
88}
89
90impl SourceForgeProvider {
91    fn parse_commit_url(&self, url: &str) -> Option<ParsedRepository> {
92        let parts: Vec<&str> = url.split('/').collect();
93        if let Some(ci_pos) = parts.iter().position(|&x| x == "ci") {
94            if ci_pos + 1 < parts.len() && ci_pos >= 3 {
95                let project = parts[ci_pos - 1].to_string();
96                let commit = parts[ci_pos + 1].to_string();
97
98                return Some(
99                    ParsedRepository::new("sourceforge".to_string(), project)
100                        .with_commit(commit)
101                        .with_host("sourceforge.net".to_string()),
102                );
103            }
104        }
105        None
106    }
107
108    fn parse_tree_url(&self, url: &str) -> Option<ParsedRepository> {
109        let parts: Vec<&str> = url.split('/').collect();
110        if let Some(tree_pos) = parts.iter().position(|&x| x == "tree") {
111            if tree_pos + 1 < parts.len() {
112                if let Some(ci_pos) = parts.iter().position(|&x| x == "ci") {
113                    if ci_pos >= 3 {
114                        let project = parts[ci_pos - 1].to_string();
115                        let branch = parts[tree_pos + 1].to_string();
116
117                        return Some(
118                            ParsedRepository::new("sourceforge".to_string(), project)
119                                .with_branch(branch)
120                                .with_host("sourceforge.net".to_string()),
121                        );
122                    }
123                }
124            }
125        }
126        None
127    }
128
129    fn parse_basic_url(&self, url: &str) -> Option<ParsedRepository> {
130        if url.contains("/p/") && url.contains("/code") {
131            let parts: Vec<&str> = url.split('/').collect();
132            if let Some(p_pos) = parts.iter().position(|&x| x == "p") {
133                if p_pos + 1 < parts.len() {
134                    let project = parts[p_pos + 1].to_string();
135                    return Some(
136                        ParsedRepository::new("sourceforge".to_string(), project)
137                            .with_host("sourceforge.net".to_string()),
138                    );
139                }
140            }
141        }
142        None
143    }
144}
145
146impl Default for SourceForgeProvider {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_can_handle() {
158        let provider = SourceForgeProvider::new();
159        assert!(provider.can_handle("https://sourceforge.net/p/project/code/"));
160        assert!(provider.can_handle("https://sf.net/p/project/code/"));
161        assert!(!provider.can_handle("https://github.com/user/repo"));
162    }
163
164    #[test]
165    fn test_parse_basic_url() {
166        let provider = SourceForgeProvider::new();
167
168        let parsed = provider
169            .parse_url("https://sourceforge.net/p/myproject/code/")
170            .unwrap();
171        assert_eq!(parsed.owner, "sourceforge");
172        assert_eq!(parsed.repo, "myproject");
173        assert_eq!(parsed.project_name, "myproject@main");
174        assert_eq!(parsed.branch_or_commit, None);
175        assert!(!parsed.is_commit);
176        assert_eq!(parsed.host.as_ref().unwrap(), "sourceforge.net");
177    }
178
179    #[test]
180    fn test_build_download_urls() {
181        let provider = SourceForgeProvider::new();
182
183        let parsed = ParsedRepository::new("sourceforge".to_string(), "project".to_string())
184            .with_branch("master".to_string());
185
186        let urls = provider.build_download_urls(&parsed);
187        assert!(
188            urls.contains(&"https://sourceforge.net/p/project/code/ci/master/tarball".to_string())
189        );
190    }
191}