rb_sys_env/
ruby_version.rs

1use std::collections::HashMap;
2
3const COMPARABLE_RUBY_MAJORS: [u8; 4] = [1, 2, 3, 4];
4
5const COMPARABLE_RUBY_MINORS: [(u8, u8); 11] = [
6    (2, 2),
7    (2, 3),
8    (2, 4),
9    (2, 5),
10    (2, 6),
11    (2, 7),
12    (3, 0),
13    (3, 1),
14    (3, 2),
15    (3, 3),
16    (3, 4),
17];
18
19/// The current Ruby version.
20#[derive(Debug, Clone, Copy, Eq, PartialEq)]
21pub struct RubyVersion {
22    major: u8,
23    minor: u8,
24    teeny: u8,
25}
26
27impl RubyVersion {
28    /// The Ruby major version.
29    pub fn major(&self) -> u8 {
30        self.major
31    }
32
33    /// The Ruby minor version.
34    pub fn minor(&self) -> u8 {
35        self.minor
36    }
37
38    /// The Ruby teeny version.
39    pub fn teeny(&self) -> u8 {
40        self.teeny
41    }
42
43    /// The Ruby version as a u8 triple.
44    pub fn major_minor_teeny(&self) -> (u8, u8, u8) {
45        (self.major, self.minor, self.teeny)
46    }
47
48    /// The Ruby version as a u8 pair.
49    pub fn major_minor(&self) -> (u8, u8) {
50        (self.major, self.minor)
51    }
52
53    pub fn print_cargo_rustc_cfg(&self) {
54        rustc_cfg!(true, "ruby_{}", self.major);
55        rustc_cfg!(true, "ruby_{}_{}", self.major, self.minor);
56        rustc_cfg!(true, "ruby_{}_{}_{}", self.major, self.minor, self.teeny);
57
58        for v in &COMPARABLE_RUBY_MINORS {
59            rustc_cfg!(self.major_minor() < *v, r#"ruby_lt_{}_{}"#, v.0, v.1);
60            rustc_cfg!(self.major_minor() <= *v, r#"ruby_lte_{}_{}"#, v.0, v.1);
61            rustc_cfg!(self.major_minor() == *v, r#"ruby_{}_{}"#, v.0, v.1);
62            rustc_cfg!(self.major_minor() == *v, r#"ruby_eq_{}_{}"#, v.0, v.1);
63            rustc_cfg!(self.major_minor() >= *v, r#"ruby_gte_{}_{}"#, v.0, v.1);
64            rustc_cfg!(self.major_minor() > *v, r#"ruby_gt_{}_{}"#, v.0, v.1);
65        }
66
67        for v in &COMPARABLE_RUBY_MAJORS {
68            rustc_cfg!(self.major() < *v, r#"ruby_lt_{}"#, v);
69            rustc_cfg!(self.major() <= *v, r#"ruby_lte_{}"#, v);
70            rustc_cfg!(self.major() == *v, r#"ruby_{}"#, v);
71            rustc_cfg!(self.major() == *v, r#"ruby_eq_{}"#, v);
72            rustc_cfg!(self.major() >= *v, r#"ruby_gte_{}"#, v);
73            rustc_cfg!(self.major() > *v, r#"ruby_gt_{}"#, v);
74        }
75    }
76}
77
78impl From<u8> for RubyVersion {
79    fn from(major: u8) -> Self {
80        Self {
81            major,
82            minor: 0,
83            teeny: 0,
84        }
85    }
86}
87
88impl From<(u8, u8)> for RubyVersion {
89    fn from((major, minor): (u8, u8)) -> Self {
90        Self {
91            major,
92            minor,
93            teeny: 0,
94        }
95    }
96}
97
98impl From<(u8, u8, u8)> for RubyVersion {
99    fn from((major, minor, teeny): (u8, u8, u8)) -> Self {
100        Self {
101            major,
102            minor,
103            teeny,
104        }
105    }
106}
107
108impl RubyVersion {
109    pub(crate) fn from_raw_environment(env: &HashMap<String, String>) -> Self {
110        match (env.get("MAJOR"), env.get("MINOR"), env.get("TEENY")) {
111            (Some(major), Some(minor), Some(teeny)) => {
112                let major = major.parse().expect("MAJOR is not a number");
113                let minor = minor.parse().expect("MINOR is not a number");
114                let teeny = teeny.parse().expect("TEENY is not a number");
115
116                Self {
117                    major,
118                    minor,
119                    teeny,
120                }
121            }
122            _ => {
123                let env_ruby_version = env.get("ruby_version").cloned().unwrap_or_else(|| {
124                    std::env::var("RUBY_VERSION").expect("RUBY_VERSION is not set")
125                });
126
127                let mut ruby_version = env_ruby_version
128                    .split('.')
129                    .map(|s| s.parse().expect("version component is not a number"));
130
131                Self {
132                    major: ruby_version.next().expect("major"),
133                    minor: ruby_version.next().expect("minor"),
134                    teeny: ruby_version.next().expect("teeny"),
135                }
136            }
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_equality_from_tuple() {
147        assert_eq!(RubyVersion::from((3, 0, 0)), RubyVersion::from((3, 0)));
148        assert_ne!(RubyVersion::from((3, 0, 1)), RubyVersion::from((3, 0)));
149    }
150
151    #[test]
152    fn test_from_hashmap() {
153        let mut env = HashMap::new();
154        env.insert("MAJOR".to_string(), "3".to_string());
155        env.insert("MINOR".to_string(), "0".to_string());
156        env.insert("TEENY".to_string(), "0".to_string());
157
158        assert_eq!(
159            RubyVersion::from_raw_environment(&env),
160            RubyVersion::from((3, 0, 0))
161        );
162    }
163}