use super::*;
use crate::api::explore::Since;
use crate::api::packages::{PackageRef, PackagesOf};
use crate::api::project::ProjectRef;
pub fn base() -> String {
crate::host::current().web()
}
pub fn gists() -> String {
crate::host::current().gists()
}
pub fn raw(repo: &RepoId, rev: &str, path: &str) -> String {
format!("{}/{repo}/{rev}/{path}", crate::host::current().raw())
}
pub fn user(login: &str) -> String {
format!("{}/{login}", base())
}
pub fn repo(repo: &RepoId) -> String {
format!("{}/{repo}", base())
}
pub fn tree(repo: &RepoId, rev: &str, path: &str) -> String {
at_rev(repo, "tree", rev, &encode_path(path))
}
pub fn blob(repo: &RepoId, rev: &str, path: &str) -> String {
at_rev(repo, "blob", rev, &encode_path(path))
}
pub fn blame(repo: &RepoId, rev: &str, path: &str) -> String {
at_rev(repo, "blame", rev, &encode_path(path))
}
pub fn issues(repo: &RepoId) -> String {
format!("{}/{repo}/issues", base())
}
pub fn pulls(repo: &RepoId) -> String {
format!("{}/{repo}/pulls", base())
}
pub fn issue(repo: &RepoId, number: u64) -> String {
format!("{}/{repo}/issues/{number}", base())
}
pub fn pull(pr: &PrRef) -> String {
format!("{}/{}/pull/{}", base(), pr.repo, pr.number)
}
pub fn pull_tab(pr: &PrRef, tab: &str) -> String {
format!("{}/{tab}", pull(pr))
}
pub fn diff_line(pr: &PrRef, path: &str, line: Option<(char, u32)>) -> String {
let line = line.map(|(side, n)| format!("{side}{n}"));
diff_file(&pull_tab(pr, "files"), path, &line.unwrap_or_default())
}
pub fn diff_file(page: &str, path: &str, lines: &str) -> String {
format!("{page}#diff-{}{lines}", path_hash(path))
}
pub fn path_hash(path: &str) -> String {
use sha2::{Digest, Sha256};
Sha256::digest(path.as_bytes())
.iter()
.map(|b| format!("{b:02x}"))
.collect()
}
pub fn files(page: &str) -> String {
format!("{page}#files")
}
pub fn search(kind: SearchKind, query: &str) -> String {
let kind = match kind {
SearchKind::Repos => "repositories",
SearchKind::Issues => "issues",
SearchKind::Pulls => "pullrequests",
SearchKind::Users => "users",
SearchKind::Discussions => "discussions",
SearchKind::Commits => "commits",
SearchKind::Code => "code",
};
format!("{}/search?q={}&type={kind}", base(), encode(query))
}
pub fn commit(repo: &RepoId, oid: &str) -> String {
at_rev(repo, "commit", oid, "")
}
pub fn compare(repo: &RepoId, from: &str, to: &str) -> String {
format!("{}/compare/{from}..{to}", self::repo(repo))
}
pub fn label(repo: &RepoId, name: &str) -> String {
format!("{}/labels/{}", self::repo(repo), encode_path(name))
}
pub fn release(repo: &RepoId, tag: &str) -> String {
format!("{}/{repo}/releases/tag/{}", base(), encode_path(tag))
}
pub fn commits(repo: &RepoId, rev: &str, path: &str) -> String {
at_rev(repo, "commits", rev, &encode_path(path))
}
pub(crate) fn at_rev(repo: impl std::fmt::Display, view: &str, rev: &str, path: &str) -> String {
let rev = escape(rev, b"-_.~", false);
if path.is_empty() {
format!("{}/{repo}/{view}/{rev}", base())
} else {
format!("{}/{repo}/{view}/{rev}/{path}", base())
}
}
pub fn encode_path(s: &str) -> String {
escape(s, b"-_.~/", false)
}
pub fn encode(s: &str) -> String {
escape(s, b"-_.~:/@", true)
}
fn escape(s: &str, safe: &[u8], plus: bool) -> String {
let mut out = String::new();
for b in s.bytes() {
if b.is_ascii_alphanumeric() || safe.contains(&b) {
out.push(char::from(b));
} else if b == b' ' && plus {
out.push('+');
} else {
out.push_str(&format!("%{b:02X}"));
}
}
out
}
pub fn insights(repo: &RepoId, tab: InsightsTab) -> String {
format!("{}/{}", self::repo(repo), tab.path())
}
pub fn explore(language: Option<&str>, since: Since) -> String {
let mut url = format!("{}/trending", base());
if let Some(language) = language {
url.push('/');
url.push_str(&encode_path(
&language.trim().to_lowercase().replace(' ', "-"),
));
}
if since != Since::Daily {
url.push_str(&format!("?since={}", since.slug()));
}
url
}
pub fn topic(name: &str) -> String {
format!("{}/topics/{}", base(), encode_path(name))
}
pub fn star_list(login: &str, slug: &str) -> String {
format!("{}/stars/{login}/lists/{}", base(), encode_path(slug))
}
pub fn packages(of: &PackagesOf) -> String {
match of {
PackagesOf::Owner(login) => format!("{}?tab=packages", user(login)),
PackagesOf::Repo(repo) => format!("{}/packages", self::repo(repo)),
}
}
pub fn package(at: &PackageRef, version: Option<u64>) -> String {
let (owner, kind, name) = (&at.owner, at.kind.slug(), encode_path(&at.name));
match version {
None => format!("{}/users/{owner}/packages/{kind}/package/{name}", base()),
Some(id) => format!("{}/users/{owner}/packages/{kind}/{name}/{id}", base()),
}
}
pub fn project_view(project: &ProjectRef, view: Option<u64>) -> String {
match view {
Some(n) => format!("{}/views/{n}", project.url()),
None => project.url(),
}
}
pub fn project_item(project: &ProjectRef, view: Option<u64>, item: u64) -> String {
format!("{}?pane=issue&itemId={item}", project_view(project, view))
}