Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / hexpm / src / version.rs
9.2 kB 358 lines
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}