semver_php/
interval.rs

1//! Single interval representation for numeric version ranges.
2
3use std::fmt::{self, Display};
4
5use crate::constraint::{Operator, SingleConstraint};
6
7/// Represents a numeric version interval with start and end bounds.
8#[derive(Debug, Clone)]
9pub struct Interval {
10	start: SingleConstraint,
11	end: SingleConstraint,
12}
13
14impl Interval {
15	/// Create a new interval from start and end constraints.
16	#[must_use]
17	pub const fn new(start: SingleConstraint, end: SingleConstraint) -> Self {
18		Self { start, end }
19	}
20
21	/// Get the start constraint.
22	#[must_use]
23	pub const fn start(&self) -> &SingleConstraint {
24		&self.start
25	}
26
27	/// Get the end constraint.
28	#[must_use]
29	pub const fn end(&self) -> &SingleConstraint {
30		&self.end
31	}
32
33	/// Create a constraint representing "from zero" (>= 0.0.0.0-dev).
34	#[must_use]
35	pub fn from_zero() -> SingleConstraint {
36		SingleConstraint::new(Operator::Ge, "0.0.0.0-dev")
37	}
38
39	/// Create a constraint representing "until positive infinity" (< MAX.0.0.0).
40	#[must_use]
41	pub fn until_positive_infinity() -> SingleConstraint {
42		// Use a very large number like PHP's PHP_INT_MAX
43		SingleConstraint::new(Operator::Lt, "9223372036854775807.0.0.0")
44	}
45
46	/// Create an interval covering everything (0 to +inf).
47	#[must_use]
48	pub fn any() -> Self {
49		Self::new(Self::from_zero(), Self::until_positive_infinity())
50	}
51}
52
53/// Branch constraints representation.
54/// - `names`: list of branch names
55/// - `exclude`: if true, all branches EXCEPT these are matched;
56///   if false, ONLY these branches are matched
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct BranchConstraint {
59	pub names: Vec<String>,
60	pub exclude: bool,
61}
62
63impl BranchConstraint {
64	/// Create a new branch constraint.
65	#[must_use]
66	pub const fn new(names: Vec<String>, exclude: bool) -> Self {
67		Self { names, exclude }
68	}
69
70	/// Match any dev branch (exclude nothing = match all).
71	#[must_use]
72	pub const fn any_dev() -> Self {
73		Self {
74			names: Vec::new(),
75			exclude: true,
76		}
77	}
78
79	/// Match no dev branches (include nothing).
80	#[must_use]
81	pub const fn no_dev() -> Self {
82		Self {
83			names: Vec::new(),
84			exclude: false,
85		}
86	}
87
88	/// Check if this matches any dev branch.
89	#[must_use]
90	pub const fn matches_any(&self) -> bool {
91		self.exclude && self.names.is_empty()
92	}
93
94	/// Check if this matches no dev branches.
95	#[must_use]
96	pub const fn matches_none(&self) -> bool {
97		!self.exclude && self.names.is_empty()
98	}
99}
100
101impl Default for BranchConstraint {
102	fn default() -> Self {
103		Self::no_dev()
104	}
105}
106
107/// Result of converting a constraint to intervals.
108#[derive(Debug, Clone)]
109pub struct IntervalResult {
110	pub numeric: Vec<Interval>,
111	pub branches: BranchConstraint,
112}
113
114impl IntervalResult {
115	#[must_use]
116	pub const fn new(numeric: Vec<Interval>, branches: BranchConstraint) -> Self {
117		Self { numeric, branches }
118	}
119
120	/// Create result matching nothing.
121	#[must_use]
122	pub const fn none() -> Self {
123		Self {
124			numeric: Vec::new(),
125			branches: BranchConstraint::no_dev(),
126		}
127	}
128
129	/// Create result matching everything.
130	#[must_use]
131	pub fn any() -> Self {
132		Self {
133			numeric: vec![Interval::any()],
134			branches: BranchConstraint::any_dev(),
135		}
136	}
137
138	/// Check if this result matches anything.
139	#[must_use]
140	pub const fn matches_something(&self) -> bool {
141		!self.numeric.is_empty() || self.branches.exclude || !self.branches.names.is_empty()
142	}
143}
144
145impl Default for IntervalResult {
146	fn default() -> Self {
147		Self::none()
148	}
149}
150
151impl Display for Interval {
152	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153		write!(f, "[{} - {}]", self.start, self.end)
154	}
155}
156
157#[cfg(test)]
158mod tests {
159	use super::*;
160
161	#[test]
162	fn test_interval_creation() {
163		let interval = Interval::new(
164			SingleConstraint::new(Operator::Ge, "1.0.0.0-dev"),
165			SingleConstraint::new(Operator::Lt, "2.0.0.0-dev"),
166		);
167		assert_eq!(interval.start().operator(), Operator::Ge);
168		assert_eq!(interval.end().operator(), Operator::Lt);
169	}
170
171	#[test]
172	fn test_branch_constraint() {
173		let any = BranchConstraint::any_dev();
174		assert!(any.matches_any());
175		assert!(!any.matches_none());
176
177		let none = BranchConstraint::no_dev();
178		assert!(none.matches_none());
179		assert!(!none.matches_any());
180	}
181}