use crate::requirement::parser::parse_version_range;
use crate::version::RerVersion;
use core::fmt;
use lazy_static::lazy_static;
use regex::Regex;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use version_ranges::Ranges;
lazy_static! {
static ref SEP_REGEZ_STR: Regex =
Regex::new(r"[-@#]").expect("Can't compile SEP_REGEZ_STR regex");
static ref SEP_REGEX: Regex = Regex::new(r"[-@#=<>]").expect("Can't compile SEP_REGEX regex");
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Requirement {
pub name: String,
pub range: Option<Ranges<RerVersion>>,
weak_ref: bool,
conflict: bool,
sep: char,
original_name: String,
}
impl Requirement {
fn parse_from_string(input_str: &str) -> Self {
let original_name: String = input_str.to_string();
let mut range = None;
let mut weak_ref = false;
let mut conflict = input_str.starts_with('!');
let mut sep = '-';
let mut input_str = input_str.to_string();
if conflict {
input_str.remove(0);
} else if input_str.starts_with('~') {
input_str.remove(0);
conflict = true;
weak_ref = true;
}
let name: String = if let Some(m) = SEP_REGEX.find(&input_str) {
let mut req_str = input_str[m.start()..].to_string();
if ['-', '@', '#'].contains(&req_str.chars().next().unwrap()) {
sep = req_str.remove(0);
}
if req_str.contains('|') {
let reqs: Vec<Ranges<RerVersion>> =
req_str.split('|').map(parse_version_range).collect();
for req in reqs {
if range.is_none() {
range = Some(req);
} else {
range = Some(range.unwrap().union(&req));
}
}
} else {
range = Some(parse_version_range(&req_str));
}
if conflict {
range = Some(range.unwrap().complement());
}
input_str[..m.start()].to_string()
} else if conflict {
input_str
} else {
range = Some(Ranges::full());
input_str
};
Requirement {
name,
range,
weak_ref,
conflict,
sep,
original_name,
}
}
pub fn get_pubgrub(&self) -> (String, Ranges<RerVersion>) {
(self.name.clone(), self.range.clone().unwrap())
}
pub fn get_name(&self) -> &str {
&self.name
}
pub fn get_version_range(&self) -> Option<Ranges<RerVersion>> {
self.range.clone()
}
pub fn merge(&self, other: &Self) -> Option<Self> {
if self.name != other.name {
return None; }
let merged_range = match (&self.range, &other.range) {
(None, _) => other.range.clone(),
(_, None) => self.range.clone(),
(Some(a), Some(b)) => {
if self.conflict {
if other.conflict {
Some(a.union(b)) } else {
Some(Self::difference(b, a)) }
} else if other.conflict {
Some(Self::difference(a, b)) } else {
Some(a.intersection(b)) }
}
};
merged_range.map(|range| Requirement {
name: self.name.clone(),
range: Some(range),
weak_ref: self.weak_ref && other.weak_ref, conflict: self.conflict && other.conflict, sep: self.sep, original_name: self.original_name.clone(), })
}
fn difference(a: &Ranges<RerVersion>, b: &Ranges<RerVersion>) -> Ranges<RerVersion> {
a.intersection(&b.complement())
}
pub fn is_weak_ref(&self) -> bool {
self.weak_ref
}
fn from_pkg_and_range(name: String, range: Ranges<RerVersion>) -> Self {
let original_name = format!("{}-{}", name, range);
Requirement {
name,
range: Some(range),
weak_ref: false,
conflict: false,
sep: '-',
original_name,
}
}
}
#[test]
fn test_requierement() {
let a = Requirement::from("voodoo-1");
assert_eq!(a.name, "voodoo");
let v: RerVersion = "1".try_into().unwrap();
assert_eq!(a.range, Some(Ranges::between(v.clone(), v.bump())));
let a = Requirement::from("voodoo-1.13.0|2.1.0");
assert_eq!(a.name, "voodoo");
let v: RerVersion = "1.13.0".try_into().unwrap();
let v2: RerVersion = "2.1.0".try_into().unwrap();
assert_eq!(
a.range,
Some(Ranges::between(v.clone(), v.bump()).union(&Ranges::between(v2.clone(), v2.bump())))
);
}
impl fmt::Display for Requirement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.original_name)
}
}
impl Hash for Requirement {
fn hash<H: Hasher>(&self, state: &mut H) {
self.original_name.hash(state);
}
}
impl From<&str> for Requirement {
fn from(s: &str) -> Self {
Requirement::parse_from_string(s)
}
}
#[test]
fn test_to_string() {
let a = Requirement::from("maya-1");
assert_eq!(a.to_string(), "maya-1");
let a = Requirement::from("maya");
assert_eq!(a.to_string(), "maya");
let a = Requirement::from("maya-1.2.3+<2.0.0");
assert_eq!(a.to_string(), "maya-1.2.3+<2.0.0");
let a = Requirement::from("maya-1.2.3+");
assert_eq!(a.to_string(), "maya-1.2.3+");
}
#[test]
fn test_merge_requirement() {
let a = Requirement::from("foo-1.2");
let b = Requirement::from("~foo-1");
let c = a.merge(&b).unwrap();
assert_eq!(c.to_string(), "foo-1.2");
let a = Requirement::from("foo-1.2");
let b = Requirement::from("foo-1");
let c = a.merge(&b).unwrap();
assert_eq!(c.to_string(), "foo-1.2");
let a = Requirement::from("foo-1.2");
let b = Requirement::from("foo==1.2.2");
let c = a.merge(&b).unwrap();
let v_start: RerVersion = "1.2.2".try_into().unwrap();
assert_eq!(c.get_version_range(), Some(Ranges::singleton(v_start)));
let a = Requirement::from("foo-1.2");
let b = Requirement::from("~foo-1");
let c = a.merge(&b).unwrap();
let v_start: RerVersion = "1.2".try_into().unwrap();
let v_end: RerVersion = "1.2_".try_into().unwrap();
assert_eq!(c.get_version_range(), Some(Ranges::between(v_start, v_end)));
let a = Requirement::from("foo-1.2");
let b = Requirement::from("!foo-1");
let c = a.merge(&b).unwrap();
let v_start: RerVersion = "1.2".try_into().unwrap();
let v_end: RerVersion = "1.2_".try_into().unwrap();
println!("{:?}", c.get_version_range().unwrap().to_string());
assert_eq!(c.get_version_range(), Some(Ranges::between(v_start, v_end)));
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Requirements(Vec<Requirement>);
impl Requirements {
pub fn empty() -> Self {
Requirements(Vec::new())
}
pub fn add(&mut self, requirement: Requirement) {
self.0.push(requirement);
}
pub fn from(requirements: Vec<&str>) -> Self {
let requirements = requirements
.iter()
.map(|x| Requirement::parse_from_string(x))
.collect();
Requirements(requirements)
}
pub fn merge(&self) -> Self {
if self.0.len() <= 1 {
return self.clone();
}
let mut requirements = self.0.clone();
let mut i = 0;
while i < requirements.len() {
let mut j = i + 1;
while j < requirements.len() {
if let Some(req) = requirements[i].merge(&requirements[j]) {
requirements[i] = req;
requirements.remove(j);
} else {
j += 1;
}
}
i += 1;
}
Requirements(requirements)
}
pub fn extend(&mut self, other: &Self) {
self.0.extend(other.0.clone());
}
pub fn switch(&mut self, other: &Self) {
self.0.clone_from(&other.0)
}
pub fn to_pubgrub(&self) -> Vec<(String, Ranges<RerVersion>)> {
self.0.iter().map(|x| x.get_pubgrub()).collect()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn split_weak_ref(&self) -> (Self, Self) {
let mut weak_ref = Vec::new();
let mut strong_ref = Vec::new();
for req in &self.0 {
if req.is_weak_ref() {
weak_ref.push(req.clone());
} else {
strong_ref.push(req.clone());
}
}
(Requirements(weak_ref), Requirements(strong_ref))
}
pub fn split_conflict(&self) -> (Self, Self) {
let mut conflict = Vec::new();
let mut no_conflict = Vec::new();
for req in &self.0 {
if req.conflict {
conflict.push(req.clone());
} else {
no_conflict.push(req.clone());
}
}
(Requirements(no_conflict), Requirements(conflict))
}
pub fn reduced(
&mut self,
package_name: &String,
range: &Ranges<RerVersion>,
) -> (Ranges<RerVersion>, Self) {
let new_req = Requirement::from_pkg_and_range(package_name.clone(), range.clone());
let requierements: Requirements = Requirements(vec![new_req]);
self.extend(&requierements);
let n = self.merge();
let (no_conflict, _conflict) = n.split_conflict();
let range = no_conflict
.0
.iter()
.find(|x| x.name == *package_name)
.unwrap()
.range
.clone()
.unwrap();
(range, n)
}
}
impl fmt::Display for Requirements {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut first = true;
for req in &self.0 {
if first {
first = false;
} else {
write!(f, ", ")?;
}
write!(f, "{}", req)?;
}
Ok(())
}
}
impl Iterator for Requirements {
type Item = Requirement;
fn next(&mut self) -> Option<Self::Item> {
self.0.pop()
}
}
#[test]
fn test_merge() {
let requirements_str = vec!["foo-1.2", "bah-3", "~foo-1"];
let requirements = Requirements::from(requirements_str);
let requirements = requirements.merge();
assert_eq!(requirements.0.len(), 2);
assert_eq!(requirements.0[0].name, "foo");
}
#[test]
fn test_switch() {
let requirements_str = vec!["foo-1.2", "bah-3", "~foo-1"];
let mut requirements = Requirements::from(requirements_str);
let requirements_str2 = vec!["foo-1.5", "bah-4", "~foo-6", "~toto-6"];
let requirements2 = Requirements::from(requirements_str2);
requirements.switch(&requirements2);
assert_eq!(requirements, requirements2);
}
#[test]
fn test_reduce() {
let requirements_str = vec!["~foo-1.0.5"];
let mut requirements = Requirements::from(requirements_str);
let v: RerVersion = "1".try_into().unwrap();
let (range, _toto) =
requirements.reduced(&"foo".to_string(), &Ranges::between(v.clone(), v.bump()));
let v_req: RerVersion = "1.0.5".try_into().unwrap();
let req_range: Ranges<RerVersion> = Ranges::between(v_req.clone(), v_req.bump());
assert_eq!(range, req_range);
}