1use core::fmt;
2use std::{ops::Range, sync::Arc};
3
4use anyhow::Result;
5use url::Url;
6use util::{github, http::HttpClient};
7
8use crate::Oid;
9
10#[derive(Clone, Debug, Hash)]
11pub enum HostingProvider {
12 Github,
13 Gitlab,
14 Gitee,
15 Bitbucket,
16 Sourcehut,
17 Codeberg,
18}
19
20impl HostingProvider {
21 pub(crate) fn base_url(&self) -> Url {
22 let base_url = match self {
23 Self::Github => "https://github.com",
24 Self::Gitlab => "https://gitlab.com",
25 Self::Gitee => "https://gitee.com",
26 Self::Bitbucket => "https://bitbucket.org",
27 Self::Sourcehut => "https://git.sr.ht",
28 Self::Codeberg => "https://codeberg.org",
29 };
30
31 Url::parse(&base_url).unwrap()
32 }
33
34 /// Returns the fragment portion of the URL for the selected lines in
35 /// the representation the [`GitHostingProvider`] expects.
36 pub(crate) fn line_fragment(&self, selection: &Range<u32>) -> String {
37 if selection.start == selection.end {
38 let line = selection.start + 1;
39
40 match self {
41 Self::Github | Self::Gitlab | Self::Gitee | Self::Sourcehut | Self::Codeberg => {
42 format!("L{}", line)
43 }
44 Self::Bitbucket => format!("lines-{}", line),
45 }
46 } else {
47 let start_line = selection.start + 1;
48 let end_line = selection.end + 1;
49
50 match self {
51 Self::Github | Self::Codeberg => format!("L{}-L{}", start_line, end_line),
52 Self::Gitlab | Self::Gitee | Self::Sourcehut => {
53 format!("L{}-{}", start_line, end_line)
54 }
55 Self::Bitbucket => format!("lines-{}:{}", start_line, end_line),
56 }
57 }
58 }
59
60 pub fn supports_avatars(&self) -> bool {
61 match self {
62 HostingProvider::Github => true,
63 _ => false,
64 }
65 }
66
67 pub async fn commit_author_avatar_url(
68 &self,
69 repo_owner: &str,
70 repo: &str,
71 commit: Oid,
72 client: Arc<dyn HttpClient>,
73 ) -> Result<Option<Url>> {
74 match self {
75 HostingProvider::Github => {
76 let commit = commit.to_string();
77
78 let author =
79 github::fetch_github_commit_author(repo_owner, repo, &commit, &client).await?;
80
81 let url = if let Some(author) = author {
82 let mut url = Url::parse(&author.avatar_url)?;
83 url.set_query(Some("size=128"));
84 Some(url)
85 } else {
86 None
87 };
88 Ok(url)
89 }
90 _ => Ok(None),
91 }
92 }
93}
94
95impl fmt::Display for HostingProvider {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 let name = match self {
98 HostingProvider::Github => "GitHub",
99 HostingProvider::Gitlab => "GitLab",
100 HostingProvider::Gitee => "Gitee",
101 HostingProvider::Bitbucket => "Bitbucket",
102 HostingProvider::Sourcehut => "Sourcehut",
103 HostingProvider::Codeberg => "Codeberg",
104 };
105 write!(f, "{}", name)
106 }
107}