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#[derive(Debug, Clone, Copy, Eq, PartialEq)]
23pub struct RubyVersion {
24 major: u8,
25 minor: u8,
26 teeny: u8,
27}
28
29impl RubyVersion {
30 pub fn major(&self) -> u8 {
32 self.major
33 }
34
35 pub fn minor(&self) -> u8 {
37 self.minor
38 }
39
40 pub fn teeny(&self) -> u8 {
42 self.teeny
43 }
44
45 pub fn major_minor_teeny(&self) -> (u8, u8, u8) {
47 (self.major, self.minor, self.teeny)
48 }
49
50 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}