use crate::prelude::*;
use hash_map::Entry as E;
#[derive(Debug)]
pub struct GitRemoteUrlParsed<'s> {
pub scheme: &'s str, pub user: &'s str, pub host: &'s str,
pub path: &'s str,
}
#[throws(FE)]
pub fn parse_git_remote_url<'u>(url: &'u str, expect_host: Option<&str>)
-> GitRemoteUrlParsed<'u>
{
let p = (||{
let (scheme, rhs) = url.split_once(':').ok_or("no colon")?;
if scheme.contains('/') { throw!("slash before colon, local path") };
let (scheme, (userhost, path)) =
if let Some(rhs) = rhs.strip_prefix("//") {
if rhs.contains(':') { throw!("seems to contain port numbe (colon)"); }
(scheme,
rhs.split_once('/').ok_or("no slash after host")?)
} else {
let userhost = scheme;
let path = rhs.trim_start_matches('/');
("", (userhost, path))
};
let (user, host) = userhost.rsplit_once('@').unwrap_or(("", userhost));
Ok::<_,String>(GitRemoteUrlParsed { scheme, user, host, path })
})().map_err(|s| FE::UnsupportedRemoteUrlSyntax(
s,
format!("{:?}", url),
))?;
if let Some(e) = expect_host { if p.host != e { throw!(
FE::UrlHostMismatch{ url: p.host.to_owned(), forge: e.to_owned() }
) } }
p
}
#[derive(Clone,Debug)]
pub struct IdCacheData<F,Id>
where F: Forge + ?Sized,
Id: Clone + Debug + Hash + Eq + PartialEq
{
name2id: HashMap<Arc<String>, Id>,
id2name: HashMap<Id, Arc<String>>,
forge: PhantomData<F>,
}
impl<F,Id> Default for IdCacheData<F,Id>
where F: Forge + ?Sized,
Id: Clone + Debug + Hash + Eq + PartialEq
{
fn default() -> Self { Self::new() }
}
impl<F,Id> IdCacheData<F,Id>
where F: Forge + ?Sized,
Id: Clone + Debug + Hash + Eq + PartialEq
{
pub fn new() -> Self { IdCacheData {
name2id: default(),
id2name: default(),
forge: default(),
} }
pub fn clear(&mut self) {
self.name2id.clear();
self.id2name.clear();
}
}
pub trait IdCache<Id>: Forge
where Id: Clone + Debug + Hash + Eq + PartialEq + 'static
{
const UNKNOWN: RemoteObjectKind;
fn name2id_lookup(&mut self, name: &str) -> Result<Option<Id>, FE>;
fn id2name_lookup(&mut self, id: Id) -> Result<Option<String>, FE>;
fn id_cache(&mut self) -> &mut IdCacheData<Self, Id>;
#[throws(FE)]
fn name2id(&mut self, name: &str) -> Option<Id> {
let name = Arc::new(name.to_string()); if let Some(id) = self.id_cache().name2id.get(&name) {
return Some(id.clone());
}
let id = self.name2id_lookup(&name)?;
if let Some(id) = &id {
let cache = self.id_cache();
match cache.id2name.entry(id.clone()) {
E::Occupied(oe) => throw!(FE::AncillaryOperationFailed(anyhow!(
"inconsistent name->id->name mapping: {:?} -> {:?} -> {:?}",
name, &id, oe.get()
))),
E::Vacant(ve) => {
ve.insert(name.clone())
}
};
cache.name2id.insert(name, id.clone());
}
id
}
#[throws(FE)]
fn id2name(&mut self, id: Id) -> Option<&Arc<String>> {
if let Some(_name) = self.id_cache().id2name.get(&id) {
let name = self.id_cache().id2name.get(&id) .unwrap();
return Some(name);
}
let name = self.id2name_lookup(id.clone())?;
if let Some(name) = name {
let name: Arc<String> = name.into();
let cache = self.id_cache();
match cache.name2id.entry(name.clone()) {
E::Occupied(oe) => throw!(FE::AncillaryOperationFailed(anyhow!(
"inconsistent id->name->id mapping: {:?} -> {:?} -> {:?}",
&id, &name, oe.get()
))),
E::Vacant(ve) => {
ve.insert(id.clone())
}
};
let name: &Arc<String> = match cache.id2name.entry(id) {
E::Occupied(_oe) => unreachable!(),
E::Vacant(ve) => ve.insert(name),
};
Some(name)
} else {
None
}
}
#[throws(FE)]
fn name2id_required(&mut self, name: &str) -> Id {
self.name2id(name)?.ok_or_else(
|| FE::NameNotFound(Self::UNKNOWN, name.into())
)?
}
#[throws(FE)]
fn id2name_required(&mut self, id: Id) -> &Arc<String> {
self.id2name(id.clone())?.ok_or_else(
|| FE::IdNotFound(Self::UNKNOWN, format!("{:?}", id))
)?
}
}
macro_rules! filter_compare {
{ $q_field:expr, $mr_field:expr } => {
if let Some(specified) = &$q_field {
if specified != &$mr_field { return false }
}
}
}
pub fn filter_mergerequests<'q>(q: &'q Req_MergeRequests)
-> impl Fn(&Resp_MergeRequest) -> bool + 'q
{ move |mr| {
if q.target_repo != mr.target.repo { return false }
if let Some(statuses) = &q.statuses {
if ! statuses.contains(&mr.state.status) { return false }
}
filter_compare!{ q.number, mr.number }
filter_compare!{ q.author, mr.author }
filter_compare!{ q.source_repo, mr.source.repo }
filter_compare!{ q.source_branch, mr.source.branch }
filter_compare!{ q.target_branch, mr.target.branch }
true
}}