1use crate::CompatibilityMode;
10use std::ffi::OsStr;
11use std::path::Path;
12
13pub fn resolve(
15 strict_flag: Option<bool>,
16 env_strict: Option<&OsStr>,
17 argv0: Option<&OsStr>,
18) -> CompatibilityMode {
19 if let Some(flag) = strict_flag {
20 return if flag {
21 CompatibilityMode::Strict
22 } else {
23 CompatibilityMode::Default
24 };
25 }
26 if let Some(value) = env_strict {
27 if env_var_is_truthy(value) {
28 return CompatibilityMode::Strict;
29 }
30 }
31 if let Some(arg0) = argv0 {
32 if argv0_implies_strict(arg0) {
33 return CompatibilityMode::Strict;
34 }
35 }
36 CompatibilityMode::Default
37}
38
39fn env_var_is_truthy(value: &OsStr) -> bool {
40 let Some(s) = value.to_str() else {
41 return false;
42 };
43 matches!(
44 s.trim().to_ascii_lowercase().as_str(),
45 "1" | "true" | "yes" | "on"
46 )
47}
48
49fn argv0_implies_strict(arg0: &OsStr) -> bool {
50 let Some(stem) = Path::new(arg0).file_stem() else {
51 return false;
52 };
53 stem == OsStr::new("vipe")
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn explicit_strict_flag_wins() {
62 assert_eq!(resolve(Some(true), None, None), CompatibilityMode::Strict);
63 assert_eq!(
64 resolve(Some(false), Some(OsStr::new("1")), Some(OsStr::new("vipe"))),
65 CompatibilityMode::Default,
66 "explicit --no-strict beats env and argv[0]"
67 );
68 }
69
70 #[test]
71 fn env_var_truthy_implies_strict() {
72 for v in ["1", "true", "yes", "on", "TRUE", " 1 ", "On"] {
73 assert_eq!(
74 resolve(None, Some(OsStr::new(v)), None),
75 CompatibilityMode::Strict,
76 "env value {v:?} should imply strict"
77 );
78 }
79 }
80
81 #[test]
82 fn env_var_falsy_does_not_imply_strict() {
83 for v in ["0", "false", "no", "off", ""] {
84 assert_eq!(
85 resolve(None, Some(OsStr::new(v)), None),
86 CompatibilityMode::Default,
87 "env value {v:?} should NOT imply strict"
88 );
89 }
90 }
91
92 #[test]
93 fn argv0_vipe_implies_strict() {
94 assert_eq!(
95 resolve(None, None, Some(OsStr::new("vipe"))),
96 CompatibilityMode::Strict
97 );
98 assert_eq!(
99 resolve(None, None, Some(OsStr::new("/usr/local/bin/vipe"))),
100 CompatibilityMode::Strict
101 );
102 assert_eq!(
103 resolve(None, None, Some(OsStr::new("vipe.exe"))),
104 CompatibilityMode::Strict,
105 "argv[0] = vipe.exe must imply strict (file_stem strips .exe)"
106 );
107 }
108
109 #[cfg(windows)]
110 #[test]
111 fn argv0_windows_backslash_path() {
112 assert_eq!(
113 resolve(None, None, Some(OsStr::new("C:\\bin\\vipe.exe"))),
114 CompatibilityMode::Strict
115 );
116 }
117
118 #[test]
119 fn argv0_rusty_vipe_does_not_imply_strict() {
120 assert_eq!(
121 resolve(None, None, Some(OsStr::new("rusty-vipe"))),
122 CompatibilityMode::Default
123 );
124 assert_eq!(
125 resolve(None, None, Some(OsStr::new("rusty-vipe.exe"))),
126 CompatibilityMode::Default
127 );
128 }
129
130 #[test]
131 fn default_when_nothing_set() {
132 assert_eq!(resolve(None, None, None), CompatibilityMode::Default);
133 }
134
135 #[test]
143 fn ladder_strict_flag_beats_env_var() {
144 assert_eq!(
146 resolve(Some(false), Some(OsStr::new("1")), None),
147 CompatibilityMode::Default,
148 "ladder rung 1 (--no-strict) must beat rung 2 (env=1)"
149 );
150 assert_eq!(
152 resolve(Some(true), Some(OsStr::new("0")), None),
153 CompatibilityMode::Strict,
154 "ladder rung 1 (--strict) must beat rung 2 (env=0)"
155 );
156 }
157
158 #[test]
159 fn ladder_env_var_beats_argv0() {
160 assert_eq!(
162 resolve(None, Some(OsStr::new("1")), Some(OsStr::new("rusty-vipe"))),
163 CompatibilityMode::Strict,
164 "ladder rung 2 (env=1) must beat rung 3 (argv0=rusty-vipe → Default)"
165 );
166 assert_eq!(
169 resolve(None, Some(OsStr::new("0")), Some(OsStr::new("vipe"))),
170 CompatibilityMode::Strict,
171 "rung 2 falsy is no-op; rung 3 (argv0=vipe) still engages Strict"
172 );
173 }
174
175 #[test]
176 fn ladder_argv0_beats_default() {
177 assert_eq!(
179 resolve(None, None, Some(OsStr::new("vipe"))),
180 CompatibilityMode::Strict,
181 "ladder rung 3 (argv0=vipe) beats rung 4 (Default)"
182 );
183 assert_eq!(
185 resolve(None, None, Some(OsStr::new("rusty-vipe"))),
186 CompatibilityMode::Default,
187 "rung 3 only fires for argv0=vipe; rusty-vipe falls to rung 4"
188 );
189 }
190}