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