use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
#[doc(hidden)]
pub const GITHUB_LINK_FORMAT: &str = "{repo}/blob/{commit}/{path}{file}#L{line}";
#[doc(hidden)]
pub const GITLAB_LINK_FORMAT: &str = "{repo}/-/blob/{commit}/{path}{file}#L{line}";
#[doc(hidden)]
pub const GITEA_LINK_FORMAT: &str = "{repo}/src/commit/{commit}/{path}{file}#L{line}";
#[doc(hidden)]
pub const BITBUCKET_LINK_FORMAT: &str = "{repo}/src/{commit}/{path}{file}#lines-{line}";
#[derive(Debug, Clone, Copy)]
pub struct AtCrateInfo {
name: &'static str,
repo: Option<&'static str>,
commit: Option<&'static str>,
crate_path: Option<&'static str>,
module: &'static str,
meta: &'static [(&'static str, &'static str)],
link_format: &'static str,
}
impl AtCrateInfo {
pub const fn builder() -> AtCrateInfoBuilder {
AtCrateInfoBuilder::new()
}
pub const fn name(&self) -> &'static str {
self.name
}
pub const fn repo(&self) -> Option<&'static str> {
self.repo
}
pub const fn commit(&self) -> Option<&'static str> {
self.commit
}
pub const fn crate_path(&self) -> Option<&'static str> {
self.crate_path
}
pub const fn module(&self) -> &'static str {
self.module
}
pub const fn meta(&self) -> &'static [(&'static str, &'static str)] {
self.meta
}
pub const fn link_format(&self) -> &'static str {
self.link_format
}
pub const fn get_meta(&self, key: &str) -> Option<&'static str> {
let mut i = 0;
while i < self.meta.len() {
let (k, v) = self.meta[i];
if const_str_eq(k, key) {
return Some(v);
}
i += 1;
}
None
}
}
const fn const_str_eq(a: &str, b: &str) -> bool {
let a = a.as_bytes();
let b = b.as_bytes();
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
#[derive(Debug, Clone, Copy)]
pub struct AtCrateInfoBuilder {
name: &'static str,
repo: Option<&'static str>,
commit: Option<&'static str>,
crate_path: Option<&'static str>,
module: &'static str,
meta: &'static [(&'static str, &'static str)],
link_format: &'static str,
link_format_explicit: bool,
}
impl AtCrateInfoBuilder {
pub const fn new() -> Self {
Self {
name: "",
repo: None,
commit: None,
crate_path: None,
module: "",
meta: &[],
link_format: GITHUB_LINK_FORMAT,
link_format_explicit: false,
}
}
pub const fn name(mut self, name: &'static str) -> Self {
self.name = name;
self
}
pub const fn repo(mut self, repo: Option<&'static str>) -> Self {
self.repo = repo;
self
}
pub const fn commit(mut self, commit: Option<&'static str>) -> Self {
self.commit = commit;
self
}
pub const fn path(mut self, path: Option<&'static str>) -> Self {
self.crate_path = path;
self
}
pub const fn module(mut self, module: &'static str) -> Self {
self.module = module;
self
}
pub const fn meta(mut self, meta: &'static [(&'static str, &'static str)]) -> Self {
self.meta = meta;
self
}
pub const fn link_format(mut self, format: &'static str) -> Self {
self.link_format = format;
self.link_format_explicit = true;
self
}
pub const fn build(self) -> AtCrateInfo {
let link_format = if self.link_format_explicit {
self.link_format
} else {
match self.repo {
Some(url) => detect_link_format(url),
None => self.link_format,
}
};
AtCrateInfo {
name: self.name,
repo: self.repo,
commit: self.commit,
crate_path: self.crate_path,
module: self.module,
meta: self.meta,
link_format,
}
}
#[inline]
pub fn name_owned(mut self, name: String) -> Self {
self.name = Box::leak(name.into_boxed_str());
self
}
#[inline]
pub fn repo_owned(mut self, repo: Option<String>) -> Self {
self.repo = repo.map(|s| {
let leaked: &'static str = Box::leak(s.into_boxed_str());
leaked
});
self
}
#[inline]
pub fn commit_owned(mut self, commit: Option<String>) -> Self {
self.commit = commit.map(|s| {
let leaked: &'static str = Box::leak(s.into_boxed_str());
leaked
});
self
}
#[inline]
pub fn path_owned(mut self, path: Option<String>) -> Self {
self.crate_path = path.map(|s| {
let leaked: &'static str = Box::leak(s.into_boxed_str());
leaked
});
self
}
#[inline]
pub fn module_owned(mut self, module: String) -> Self {
self.module = Box::leak(module.into_boxed_str());
self
}
#[inline]
pub fn meta_owned(mut self, entries: Vec<(String, String)>) -> Self {
let leaked: &'static [(&'static str, &'static str)] = Box::leak(
entries
.into_iter()
.map(|(k, v)| {
let k: &'static str = Box::leak(k.into_boxed_str());
let v: &'static str = Box::leak(v.into_boxed_str());
(k, v)
})
.collect::<Vec<_>>()
.into_boxed_slice(),
);
self.meta = leaked;
self
}
#[inline]
pub fn link_format_owned(mut self, format: String) -> Self {
self.link_format = Box::leak(format.into_boxed_str());
self.link_format_explicit = true;
self
}
#[inline]
pub const fn link_format_auto(mut self) -> Self {
self.link_format = match self.repo {
Some(url) => detect_link_format(url),
None => GITHUB_LINK_FORMAT,
};
self.link_format_explicit = true;
self
}
}
const fn contains_ci(haystack: &[u8], needle: &[u8]) -> bool {
if needle.len() > haystack.len() {
return false;
}
let mut i = 0;
while i <= haystack.len() - needle.len() {
let mut j = 0;
while j < needle.len() {
let h = if haystack[i + j] >= b'A' && haystack[i + j] <= b'Z' {
haystack[i + j] + 32
} else {
haystack[i + j]
};
if h != needle[j] {
break;
}
j += 1;
}
if j == needle.len() {
return true;
}
i += 1;
}
false
}
const fn detect_link_format(repo_url: &str) -> &'static str {
let url = repo_url.as_bytes();
if contains_ci(url, b"github.com") || contains_ci(url, b"github.") {
GITHUB_LINK_FORMAT
} else if contains_ci(url, b"gitlab.com") || contains_ci(url, b"gitlab.") {
GITLAB_LINK_FORMAT
} else if contains_ci(url, b"gitea.")
|| contains_ci(url, b"forgejo.")
|| contains_ci(url, b"codeberg.org")
{
GITEA_LINK_FORMAT
} else if contains_ci(url, b"bitbucket.org") || contains_ci(url, b"bitbucket.") {
BITBUCKET_LINK_FORMAT
} else {
GITHUB_LINK_FORMAT
}
}
impl Default for AtCrateInfoBuilder {
fn default() -> Self {
Self::new()
}
}