Fork of daniellemaywood.uk/gleam — Wasm codegen work
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2026 The Gleam contributors
3
4//! Functions for parsing and matching versions against requirements, based off
5//! and compatible with the Elixir Version module, which is used by Hex
6//! internally as well as be the Elixir build tool Hex client.
7
8use std::{cmp::Ordering, convert::TryFrom, fmt};
9
10use self::parser::Parser;
11use serde::{
12 Deserialize, Serialize,
13 de::{self, Deserializer, Visitor},
14};
15
16mod lexer;
17mod parser;
18#[cfg(test)]
19mod tests;
20
21/// In a nutshell, a version is represented by three numbers:
22///
23/// MAJOR.MINOR.PATCH
24///
25/// Pre-releases are supported by optionally appending a hyphen and a series of
26/// period-separated identifiers immediately following the patch version.
27/// Identifiers consist of only ASCII alphanumeric characters and hyphens (`[0-9A-Za-z-]`):
28///
29/// "1.0.0-alpha.3"
30///
31/// Build information can be added by appending a plus sign and a series of
32/// dot-separated identifiers immediately following the patch or pre-release version.
33/// Identifiers consist of only ASCII alphanumeric characters and hyphens (`[0-9A-Za-z-]`):
34///
35/// "1.0.0-alpha.3+20130417140000.amd64"
36///
37#[derive(Clone, Debug, PartialEq, Eq, Hash)]
38pub struct Version {
39 pub major: u32,
40 pub minor: u32,
41 pub patch: u32,
42 pub pre: Vec<Identifier>,
43 pub build: Option<String>,
44}
45
46impl Version {
47 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
48 Self {
49 major,
50 minor,
51 patch,
52 pre: vec![],
53 build: None,
54 }
55 }
56
57 fn bump_major(&self) -> Self {
58 Self {
59 major: self.major + 1,
60 minor: 0,
61 patch: 0,
62 pre: vec![],
63 build: None,
64 }
65 }
66
67 fn bump_minor(&self) -> Self {
68 Self {
69 major: self.major,
70 minor: self.minor + 1,
71 patch: 0,
72 pre: vec![],
73 build: None,
74 }
75 }
76
77 fn bump_patch(&self) -> Self {
78 Self {
79 major: self.major,
80 minor: self.minor,
81 patch: self.patch + 1,
82 pre: vec![],
83 build: None,
84 }
85 }
86
87 /// Parse a version.
88 pub fn parse(input: &str) -> Result<Self, parser::Error> {
89 let mut parser = Parser::new(input)?;
90 let version = parser.version()?;
91 if !parser.is_eof() {
92 return Err(parser::Error::MoreInput(
93 parser
94 .tail()?
95 .into_iter()
96 .map(|t| t.to_string())
97 .collect::<Vec<_>>()
98 .join(""),
99 ));
100 }
101 Ok(version)
102 }
103
104 /// Parse a Hex compatible version range. i.e. `> 1 and < 2 or == 4.5.2`.
105 fn parse_range(input: &str) -> Result<pubgrub::Range<Version>, parser::Error> {
106 let mut parser = Parser::new(input)?;
107 let version = parser.range()?;
108 if !parser.is_eof() {
109 return Err(parser::Error::MoreInput(
110 parser
111 .tail()?
112 .into_iter()
113 .map(|t| t.to_string())
114 .collect::<Vec<_>>()
115 .join(""),
116 ));
117 }
118 Ok(version)
119 }
120
121 pub fn lowest() -> Self {
122 Self::new(0, 0, 0)
123 }
124
125 fn tuple(&self) -> (u32, u32, u32, PreOrder<'_>) {
126 (
127 self.major,
128 self.minor,
129 self.patch,
130 PreOrder(self.pre.as_slice()),
131 )
132 }
133
134 pub fn is_pre(&self) -> bool {
135 !self.pre.is_empty()
136 }
137}
138
139pub trait LowestVersion {
140 fn lowest_version(&self) -> Option<Version>;
141}
142impl LowestVersion for pubgrub::Range<Version> {
143 fn lowest_version(&self) -> Option<Version> {
144 self.iter()
145 .flat_map(|(lower, _higher)| match lower {
146 std::ops::Bound::Included(v) => Some(v.clone()),
147 std::ops::Bound::Excluded(_) => None,
148 std::ops::Bound::Unbounded => Some(Version::lowest()),
149 })
150 .min()
151 }
152}
153
154impl<'de> Deserialize<'de> for Version {
155 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
156 where
157 D: Deserializer<'de>,
158 {
159 deserializer.deserialize_str(VersionVisitor)
160 }
161}
162
163struct VersionVisitor;
164
165impl<'de> Visitor<'de> for VersionVisitor {
166 type Value = Version;
167
168 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
169 formatter.write_str("a Hex version string")
170 }
171
172 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
173 where
174 E: de::Error,
175 {
176 Version::try_from(value).map_err(de::Error::custom)
177 }
178}
179
180impl Serialize for Version {
181 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
182 where
183 S: serde::Serializer,
184 {
185 serializer.serialize_str(&self.to_string())
186 }
187}
188
189impl std::cmp::PartialOrd for Version {
190 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
191 Some(self.cmp(other))
192 }
193}
194
195impl std::cmp::Ord for Version {
196 fn cmp(&self, other: &Self) -> Ordering {
197 self.tuple().cmp(&other.tuple())
198 }
199}
200
201impl<'a> TryFrom<&'a str> for Version {
202 type Error = parser::Error;
203
204 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
205 Self::parse(value)
206 }
207}
208
209impl fmt::Display for Version {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
212 if !self.pre.is_empty() {
213 write!(f, "-")?;
214 for (i, identifier) in self.pre.iter().enumerate() {
215 if i != 0 {
216 write!(f, ".")?;
217 }
218 identifier.fmt(f)?;
219 }
220 }
221 if let Some(build) = self.build.as_ref() {
222 write!(f, "+{build}")?;
223 }
224 Ok(())
225 }
226}
227
228#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
229pub enum Identifier {
230 Numeric(u32),
231 AlphaNumeric(String),
232}
233
234impl fmt::Display for Identifier {
235 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
236 match *self {
237 Identifier::Numeric(ref id) => id.fmt(f),
238 Identifier::AlphaNumeric(ref id) => id.fmt(f),
239 }
240 }
241}
242
243impl Identifier {
244 pub fn concat(self, add_str: &str) -> Identifier {
245 match self {
246 Identifier::Numeric(n) => Identifier::AlphaNumeric(format!("{n}{add_str}")),
247 Identifier::AlphaNumeric(mut s) => {
248 s.push_str(add_str);
249 Identifier::AlphaNumeric(s)
250 }
251 }
252 }
253}
254
255#[derive(Clone, PartialEq, Eq)]
256pub struct Range {
257 spec: String,
258 range: pubgrub::Range<Version>,
259}
260
261impl Range {
262 pub fn new(spec: String) -> Result<Self, parser::Error> {
263 let range = Version::parse_range(&spec)?;
264 Ok(Self { spec, range })
265 }
266}
267
268impl Range {
269 pub fn to_pubgrub(&self) -> &pubgrub::Range<Version> {
270 &self.range
271 }
272
273 pub fn as_str(&self) -> &str {
274 &self.spec
275 }
276}
277
278impl From<pubgrub::Range<Version>> for Range {
279 fn from(range: pubgrub::Range<Version>) -> Self {
280 let spec = range.to_string();
281 Self { spec, range }
282 }
283}
284
285impl From<Version> for Range {
286 fn from(version: Version) -> Self {
287 pubgrub::Range::singleton(version).into()
288 }
289}
290
291impl From<Range> for pubgrub::Range<Version> {
292 fn from(range: Range) -> Self {
293 range.range
294 }
295}
296
297impl fmt::Debug for Range {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 f.debug_tuple("Range").field(&self.spec).finish()
300 }
301}
302
303impl<'de> Deserialize<'de> for Range {
304 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
305 where
306 D: Deserializer<'de>,
307 {
308 let s: &str = Deserialize::deserialize(deserializer)?;
309 Range::new(s.to_string()).map_err(serde::de::Error::custom)
310 }
311}
312
313impl Serialize for Range {
314 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
315 where
316 S: serde::Serializer,
317 {
318 serializer.serialize_str(&self.to_string())
319 }
320}
321
322impl fmt::Display for Range {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 f.write_str(&self.spec)
325 }
326}
327
328// A wrapper around Vec where an empty vector is greater than a non-empty one.
329// This is desires as if there is a pre-segment in a version (1.0.0-rc1) it is
330// lower than the same version with no pre-segments (1.0.0).
331#[derive(PartialEq, Eq)]
332pub struct PreOrder<'a>(&'a [Identifier]);
333
334impl PreOrder<'_> {
335 fn is_empty(&self) -> bool {
336 self.0.is_empty()
337 }
338}
339
340impl std::cmp::PartialOrd for PreOrder<'_> {
341 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
342 Some(self.cmp(other))
343 }
344}
345
346impl std::cmp::Ord for PreOrder<'_> {
347 fn cmp(&self, other: &Self) -> Ordering {
348 if self.is_empty() && other.is_empty() {
349 Ordering::Equal
350 } else if self.is_empty() {
351 Ordering::Greater
352 } else if other.is_empty() {
353 Ordering::Less
354 } else {
355 self.0.cmp(other.0)
356 }
357 }
358}