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