1use super::*;
2
3pub const DEFAULT_SHELL: &str = "sh";
4pub const DEFAULT_SHELL_ARGS: &[&str] = &["-cu"];
5pub const WINDOWS_POWERSHELL_SHELL: &str = "powershell.exe";
6pub const WINDOWS_POWERSHELL_ARGS: &[&str] = &["-NoLogo", "-Command"];
7
8#[derive(Debug, PartialEq, Serialize, Default)]
9pub struct Settings<'src> {
10 pub allow_duplicate_recipes: bool,
11 pub allow_duplicate_variables: bool,
12 pub dotenv_filename: Option<String>,
13 pub dotenv_load: bool,
14 pub dotenv_path: Option<PathBuf>,
15 pub dotenv_required: bool,
16 pub export: bool,
17 pub fallback: bool,
18 pub ignore_comments: bool,
19 pub positional_arguments: bool,
20 pub quiet: bool,
21 #[serde(skip)]
22 pub script_interpreter: Option<Interpreter<'src>>,
23 pub shell: Option<Interpreter<'src>>,
24 pub tempdir: Option<String>,
25 pub unstable: bool,
26 pub windows_powershell: bool,
27 pub windows_shell: Option<Interpreter<'src>>,
28 pub working_directory: Option<PathBuf>,
29}
30
31impl<'src> Settings<'src> {
32 pub fn from_table(sets: Table<'src, Set<'src>>) -> Self {
33 let mut settings = Self::default();
34
35 for (_name, set) in sets {
36 match set.value {
37 Setting::AllowDuplicateRecipes(allow_duplicate_recipes) => {
38 settings.allow_duplicate_recipes = allow_duplicate_recipes;
39 }
40 Setting::AllowDuplicateVariables(allow_duplicate_variables) => {
41 settings.allow_duplicate_variables = allow_duplicate_variables;
42 }
43 Setting::DotenvFilename(filename) => {
44 settings.dotenv_filename = Some(filename.cooked);
45 }
46 Setting::DotenvLoad(dotenv_load) => {
47 settings.dotenv_load = dotenv_load;
48 }
49 Setting::DotenvPath(path) => {
50 settings.dotenv_path = Some(PathBuf::from(path.cooked));
51 }
52 Setting::DotenvRequired(dotenv_required) => {
53 settings.dotenv_required = dotenv_required;
54 }
55 Setting::Export(export) => {
56 settings.export = export;
57 }
58 Setting::Fallback(fallback) => {
59 settings.fallback = fallback;
60 }
61 Setting::IgnoreComments(ignore_comments) => {
62 settings.ignore_comments = ignore_comments;
63 }
64 Setting::PositionalArguments(positional_arguments) => {
65 settings.positional_arguments = positional_arguments;
66 }
67 Setting::Quiet(quiet) => {
68 settings.quiet = quiet;
69 }
70 Setting::ScriptInterpreter(script_interpreter) => {
71 settings.script_interpreter = Some(script_interpreter);
72 }
73 Setting::Shell(shell) => {
74 settings.shell = Some(shell);
75 }
76 Setting::Unstable(unstable) => {
77 settings.unstable = unstable;
78 }
79 Setting::WindowsPowerShell(windows_powershell) => {
80 settings.windows_powershell = windows_powershell;
81 }
82 Setting::WindowsShell(windows_shell) => {
83 settings.windows_shell = Some(windows_shell);
84 }
85 Setting::Tempdir(tempdir) => {
86 settings.tempdir = Some(tempdir.cooked);
87 }
88 Setting::WorkingDirectory(working_directory) => {
89 settings.working_directory = Some(working_directory.cooked.into());
90 }
91 }
92 }
93
94 settings
95 }
96
97 pub fn shell_command(&self, config: &Config) -> Command {
98 let (command, args) = self.shell(config);
99
100 let mut cmd = Command::new(command);
101
102 cmd.args(args);
103
104 cmd
105 }
106
107 pub fn shell<'a>(&'a self, config: &'a Config) -> (&'a str, Vec<&'a str>) {
108 match (&config.shell, &config.shell_args) {
109 (Some(shell), Some(shell_args)) => (shell, shell_args.iter().map(String::as_ref).collect()),
110 (Some(shell), None) => (shell, DEFAULT_SHELL_ARGS.to_vec()),
111 (None, Some(shell_args)) => (
112 DEFAULT_SHELL,
113 shell_args.iter().map(String::as_ref).collect(),
114 ),
115 (None, None) => {
116 if let (true, Some(shell)) = (cfg!(windows), &self.windows_shell) {
117 (
118 shell.command.cooked.as_ref(),
119 shell
120 .arguments
121 .iter()
122 .map(|argument| argument.cooked.as_ref())
123 .collect(),
124 )
125 } else if cfg!(windows) && self.windows_powershell {
126 (WINDOWS_POWERSHELL_SHELL, WINDOWS_POWERSHELL_ARGS.to_vec())
127 } else if let Some(shell) = &self.shell {
128 (
129 shell.command.cooked.as_ref(),
130 shell
131 .arguments
132 .iter()
133 .map(|argument| argument.cooked.as_ref())
134 .collect(),
135 )
136 } else {
137 (DEFAULT_SHELL, DEFAULT_SHELL_ARGS.to_vec())
138 }
139 }
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn default_shell() {
150 let settings = Settings::default();
151
152 let config = Config {
153 shell_command: false,
154 ..testing::config(&[])
155 };
156
157 assert_eq!(settings.shell(&config), ("sh", vec!["-cu"]));
158 }
159
160 #[test]
161 fn default_shell_powershell() {
162 let settings = Settings {
163 windows_powershell: true,
164 ..Default::default()
165 };
166
167 let config = Config {
168 shell_command: false,
169 ..testing::config(&[])
170 };
171
172 if cfg!(windows) {
173 assert_eq!(
174 settings.shell(&config),
175 ("powershell.exe", vec!["-NoLogo", "-Command"])
176 );
177 } else {
178 assert_eq!(settings.shell(&config), ("sh", vec!["-cu"]));
179 }
180 }
181
182 #[test]
183 fn overwrite_shell() {
184 let settings = Settings::default();
185
186 let config = Config {
187 shell_command: true,
188 shell: Some("lol".to_string()),
189 shell_args: Some(vec!["-nice".to_string()]),
190 ..testing::config(&[])
191 };
192
193 assert_eq!(settings.shell(&config), ("lol", vec!["-nice"]));
194 }
195
196 #[test]
197 fn overwrite_shell_powershell() {
198 let settings = Settings {
199 windows_powershell: true,
200 ..Default::default()
201 };
202
203 let config = Config {
204 shell_command: true,
205 shell: Some("lol".to_string()),
206 shell_args: Some(vec!["-nice".to_string()]),
207 ..testing::config(&[])
208 };
209
210 assert_eq!(settings.shell(&config), ("lol", vec!["-nice"]));
211 }
212
213 #[test]
214 fn shell_cooked() {
215 let settings = Settings {
216 shell: Some(Interpreter {
217 command: StringLiteral {
218 kind: StringKind::from_token_start("\"").unwrap(),
219 raw: "asdf.exe",
220 cooked: "asdf.exe".to_string(),
221 expand: false,
222 },
223 arguments: vec![StringLiteral {
224 kind: StringKind::from_token_start("\"").unwrap(),
225 raw: "-nope",
226 cooked: "-nope".to_string(),
227 expand: false,
228 }],
229 }),
230 ..Default::default()
231 };
232
233 let config = Config {
234 shell_command: false,
235 ..testing::config(&[])
236 };
237
238 assert_eq!(settings.shell(&config), ("asdf.exe", vec!["-nope"]));
239 }
240
241 #[test]
242 fn shell_present_but_not_shell_args() {
243 let settings = Settings {
244 windows_powershell: true,
245 ..Default::default()
246 };
247
248 let config = Config {
249 shell: Some("lol".to_string()),
250 ..testing::config(&[])
251 };
252
253 assert_eq!(settings.shell(&config).0, "lol");
254 }
255
256 #[test]
257 fn shell_args_present_but_not_shell() {
258 let settings = Settings {
259 windows_powershell: true,
260 ..Default::default()
261 };
262
263 let config = Config {
264 shell_command: false,
265 shell_args: Some(vec!["-nice".to_string()]),
266 ..testing::config(&[])
267 };
268
269 assert_eq!(settings.shell(&config), ("sh", vec!["-nice"]));
270 }
271}