version_spec/
lib.rs

1mod resolved_spec;
2mod spec_error;
3mod unresolved_parser;
4mod unresolved_spec;
5mod version_types;
6
7pub use resolved_spec::*;
8pub use spec_error::*;
9pub use unresolved_parser::*;
10pub use unresolved_spec::*;
11pub use version_types::*;
12
13use regex::Regex;
14use std::sync::OnceLock;
15
16/// Returns true if the provided value is an alias. An alias is a word that
17/// maps to version, for example, "latest" -> "1.2.3".
18///
19/// Is considered an alias if the string is alpha-numeric, starts with a
20/// character, and supports `-`, `_`, `/`, `.`, and `*` characters.
21pub fn is_alias_name<T: AsRef<str>>(value: T) -> bool {
22    let value = value.as_ref();
23
24    value.chars().enumerate().all(|(i, c)| {
25        if i == 0 {
26            char::is_ascii_alphabetic(&c)
27        } else {
28            char::is_ascii_alphanumeric(&c)
29                || c == '-'
30                || c == '_'
31                || c == '/'
32                || c == '.'
33                || c == '*'
34        }
35    })
36}
37
38/// Returns true if the provided value is a calendar version string.
39pub fn is_calver<T: AsRef<str>>(value: T) -> bool {
40    get_calver_regex().is_match(value.as_ref())
41}
42
43/// Returns true if the provided value is a semantic version string.
44pub fn is_semver<T: AsRef<str>>(value: T) -> bool {
45    get_semver_regex().is_match(value.as_ref())
46}
47
48/// Cleans a potential version string by removing a leading `v` or `V`.
49pub fn clean_version_string<T: AsRef<str>>(value: T) -> String {
50    let mut version = value.as_ref().trim();
51
52    // Remove a leading "v" or "V" from a version string
53    #[allow(clippy::assigning_clones)]
54    if (version.starts_with('v') || version.starts_with('V'))
55        && version.as_bytes()[1].is_ascii_digit()
56    {
57        version = &version[1..];
58    }
59
60    version.to_owned()
61}
62
63/// Cleans a version requirement string by removing * version parts,
64/// and correcting AND operators.
65pub fn clean_version_req_string<T: AsRef<str>>(value: T) -> String {
66    value
67        .as_ref()
68        .trim()
69        .replace(".*", "")
70        .replace(".x", "")
71        .replace(".X", "")
72        .replace("-*", "")
73        .replace("&&", ",")
74}
75
76static CALVER_REGEX: OnceLock<Regex> = OnceLock::new();
77
78/// Get a regex pattern that matches calendar versions (calver).
79/// For example: 2024-02-26, 2024-12, 2024-01-alpha, etc.
80pub fn get_calver_regex() -> &'static Regex {
81    CALVER_REGEX.get_or_init(|| {
82        Regex::new(r"^(?<year>[0-9]{1,4})-(?<month>((0?[1-9]{1})|10|11|12))(-(?<day>(0?[1-9]{1}|[1-3]{1}[0-9]{1})))?((_|\.)(?<micro>[0-9]+))?(?<pre>-[a-zA-Z]{1}[-0-9a-zA-Z.]+)?$").unwrap()
83    })
84}
85
86static SEMVER_REGEX: OnceLock<Regex> = OnceLock::new();
87
88/// Get a regex pattern that matches semantic versions (semver).
89/// For example: 1.2.3, 6.5.4, 7.8.9-alpha, etc.
90pub fn get_semver_regex() -> &'static Regex {
91    // https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions
92    SEMVER_REGEX.get_or_init(|| {
93        Regex::new(r"^(?<major>[0-9]+).(?<minor>[0-9]+).(?<patch>[0-9]+)(?<pre>-[-0-9a-zA-Z.]+)?(?<build>\+[-0-9a-zA-Z.]+)?$")
94        .unwrap()
95    })
96}