1use crate::config::E2eConfig;
19use crate::escape::{escape_shell, sanitize_filename, sanitize_ident};
20use crate::field_access::FieldResolver;
21use crate::fixture::{Assertion, Fixture, FixtureGroup};
22use alef_core::backend::GeneratedFile;
23use alef_core::config::AlefConfig;
24use anyhow::Result;
25use std::fmt::Write as FmtWrite;
26use std::path::PathBuf;
27
28use super::E2eCodegen;
29
30pub struct BrewCodegen;
32
33impl E2eCodegen for BrewCodegen {
34 fn generate(
35 &self,
36 groups: &[FixtureGroup],
37 e2e_config: &E2eConfig,
38 _alef_config: &AlefConfig,
39 ) -> Result<Vec<GeneratedFile>> {
40 let lang = self.language_name();
41 let output_base = PathBuf::from(e2e_config.effective_output()).join(lang);
42
43 let call = &e2e_config.call;
45 let overrides = call.overrides.get(lang);
46 let subcommand = overrides
47 .and_then(|o| o.function.as_ref())
48 .cloned()
49 .unwrap_or_else(|| call.function.clone());
50
51 let static_cli_args: Vec<String> = overrides.map(|o| o.cli_args.clone()).unwrap_or_default();
53
54 let cli_flags: std::collections::HashMap<String, String> =
56 overrides.map(|o| o.cli_flags.clone()).unwrap_or_default();
57
58 let binary_name = e2e_config
60 .registry
61 .packages
62 .get(lang)
63 .and_then(|p| p.name.as_ref())
64 .cloned()
65 .or_else(|| e2e_config.packages.get(lang).and_then(|p| p.name.as_ref()).cloned())
66 .unwrap_or_else(|| call.module.clone());
67
68 let active_groups: Vec<(&FixtureGroup, Vec<&Fixture>)> = groups
70 .iter()
71 .filter_map(|group| {
72 let active: Vec<&Fixture> = group
73 .fixtures
74 .iter()
75 .filter(|f| f.skip.as_ref().is_none_or(|s| !s.should_skip(lang)))
76 .collect();
77 if active.is_empty() { None } else { Some((group, active)) }
78 })
79 .collect();
80
81 let field_resolver = FieldResolver::new(
82 &e2e_config.fields,
83 &e2e_config.fields_optional,
84 &e2e_config.result_fields,
85 &e2e_config.fields_array,
86 );
87
88 let mut files = Vec::new();
89
90 let category_names: Vec<String> = active_groups
92 .iter()
93 .map(|(g, _)| sanitize_filename(&g.category))
94 .collect();
95 files.push(GeneratedFile {
96 path: output_base.join("run_tests.sh"),
97 content: render_run_tests(&category_names),
98 generated_header: true,
99 });
100
101 for (group, active) in &active_groups {
103 let safe_category = sanitize_filename(&group.category);
104 let filename = format!("test_{safe_category}.sh");
105 let content = render_category_file(
106 &group.category,
107 active,
108 &binary_name,
109 &subcommand,
110 &static_cli_args,
111 &cli_flags,
112 &e2e_config.call.args,
113 &field_resolver,
114 );
115 files.push(GeneratedFile {
116 path: output_base.join(filename),
117 content,
118 generated_header: true,
119 });
120 }
121
122 Ok(files)
123 }
124
125 fn language_name(&self) -> &'static str {
126 "brew"
127 }
128}
129
130fn render_run_tests(categories: &[String]) -> String {
132 let mut out = String::new();
133 let _ = writeln!(out, "#!/usr/bin/env bash");
134 let _ = writeln!(out, "# This file is auto-generated by alef. DO NOT EDIT.");
135 let _ = writeln!(out, "# shellcheck disable=SC1091");
136 let _ = writeln!(out, "set -euo pipefail");
137 let _ = writeln!(out);
138 let _ = writeln!(out, "# MOCK_SERVER_URL must be set to the base URL of the mock server.");
139 let _ = writeln!(out, ": \"${{MOCK_SERVER_URL:?MOCK_SERVER_URL is required}}\"");
140 let _ = writeln!(out);
141 let _ = writeln!(out, "# Verify that jq is available.");
142 let _ = writeln!(out, "if ! command -v jq &>/dev/null; then");
143 let _ = writeln!(out, " echo 'error: jq is required but not found in PATH' >&2");
144 let _ = writeln!(out, " exit 1");
145 let _ = writeln!(out, "fi");
146 let _ = writeln!(out);
147 let _ = writeln!(out, "PASS=0");
148 let _ = writeln!(out, "FAIL=0");
149 let _ = writeln!(out);
150
151 let _ = writeln!(out, "assert_equals() {{");
153 let _ = writeln!(out, " local actual=\"$1\" expected=\"$2\" label=\"$3\"");
154 let _ = writeln!(out, " if [ \"$actual\" != \"$expected\" ]; then");
155 let _ = writeln!(
156 out,
157 " echo \"FAIL [$label]: expected '$expected', got '$actual'\" >&2"
158 );
159 let _ = writeln!(out, " return 1");
160 let _ = writeln!(out, " fi");
161 let _ = writeln!(out, "}}");
162 let _ = writeln!(out);
163 let _ = writeln!(out, "assert_contains() {{");
164 let _ = writeln!(out, " local actual=\"$1\" expected=\"$2\" label=\"$3\"");
165 let _ = writeln!(out, " if [[ \"$actual\" != *\"$expected\"* ]]; then");
166 let _ = writeln!(
167 out,
168 " echo \"FAIL [$label]: expected to contain '$expected'\" >&2"
169 );
170 let _ = writeln!(out, " return 1");
171 let _ = writeln!(out, " fi");
172 let _ = writeln!(out, "}}");
173 let _ = writeln!(out);
174 let _ = writeln!(out, "assert_not_empty() {{");
175 let _ = writeln!(out, " local actual=\"$1\" label=\"$2\"");
176 let _ = writeln!(out, " if [ -z \"$actual\" ]; then");
177 let _ = writeln!(out, " echo \"FAIL [$label]: expected non-empty value\" >&2");
178 let _ = writeln!(out, " return 1");
179 let _ = writeln!(out, " fi");
180 let _ = writeln!(out, "}}");
181 let _ = writeln!(out);
182 let _ = writeln!(out, "assert_count_min() {{");
183 let _ = writeln!(out, " local count=\"$1\" min=\"$2\" label=\"$3\"");
184 let _ = writeln!(out, " if [ \"$count\" -lt \"$min\" ]; then");
185 let _ = writeln!(
186 out,
187 " echo \"FAIL [$label]: expected at least $min elements, got $count\" >&2"
188 );
189 let _ = writeln!(out, " return 1");
190 let _ = writeln!(out, " fi");
191 let _ = writeln!(out, "}}");
192 let _ = writeln!(out);
193 let _ = writeln!(out, "assert_greater_than() {{");
194 let _ = writeln!(out, " local val=\"$1\" threshold=\"$2\" label=\"$3\"");
195 let _ = writeln!(
196 out,
197 " if [ \"$(echo \"$val > $threshold\" | bc -l)\" != \"1\" ]; then"
198 );
199 let _ = writeln!(out, " echo \"FAIL [$label]: expected $val > $threshold\" >&2");
200 let _ = writeln!(out, " return 1");
201 let _ = writeln!(out, " fi");
202 let _ = writeln!(out, "}}");
203 let _ = writeln!(out);
204 let _ = writeln!(out, "assert_greater_than_or_equal() {{");
205 let _ = writeln!(out, " local actual=\"$1\" expected=\"$2\" label=\"$3\"");
206 let _ = writeln!(out, " if [ \"$actual\" -lt \"$expected\" ]; then");
207 let _ = writeln!(out, " echo \"FAIL [$label]: expected $actual >= $expected\" >&2");
208 let _ = writeln!(out, " return 1");
209 let _ = writeln!(out, " fi");
210 let _ = writeln!(out, "}}");
211 let _ = writeln!(out);
212 let _ = writeln!(out, "assert_is_empty() {{");
213 let _ = writeln!(out, " local actual=\"$1\" label=\"$2\"");
214 let _ = writeln!(out, " if [ -n \"$actual\" ]; then");
215 let _ = writeln!(
216 out,
217 " echo \"FAIL [$label]: expected empty value, got '$actual'\" >&2"
218 );
219 let _ = writeln!(out, " return 1");
220 let _ = writeln!(out, " fi");
221 let _ = writeln!(out, "}}");
222 let _ = writeln!(out);
223 let _ = writeln!(out, "assert_less_than() {{");
224 let _ = writeln!(out, " local actual=\"$1\" expected=\"$2\" label=\"$3\"");
225 let _ = writeln!(out, " if [ \"$actual\" -ge \"$expected\" ]; then");
226 let _ = writeln!(out, " echo \"FAIL [$label]: expected $actual < $expected\" >&2");
227 let _ = writeln!(out, " return 1");
228 let _ = writeln!(out, " fi");
229 let _ = writeln!(out, "}}");
230 let _ = writeln!(out);
231 let _ = writeln!(out, "assert_not_contains() {{");
232 let _ = writeln!(out, " local actual=\"$1\" expected=\"$2\" label=\"$3\"");
233 let _ = writeln!(out, " if [[ \"$actual\" == *\"$expected\"* ]]; then");
234 let _ = writeln!(
235 out,
236 " echo \"FAIL [$label]: expected not to contain '$expected'\" >&2"
237 );
238 let _ = writeln!(out, " return 1");
239 let _ = writeln!(out, " fi");
240 let _ = writeln!(out, "}}");
241 let _ = writeln!(out);
242
243 let script_dir = r#"SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)""#;
245 let _ = writeln!(out, "{script_dir}");
246 let _ = writeln!(out);
247 for category in categories {
248 let _ = writeln!(out, "# shellcheck source=test_{category}.sh");
249 let _ = writeln!(out, "source \"$SCRIPT_DIR/test_{category}.sh\"");
250 }
251 let _ = writeln!(out);
252
253 let _ = writeln!(out, "run_test() {{");
255 let _ = writeln!(out, " local name=\"$1\"");
256 let _ = writeln!(out, " if \"$name\"; then");
257 let _ = writeln!(out, " echo \"PASS: $name\"");
258 let _ = writeln!(out, " PASS=$((PASS + 1))");
259 let _ = writeln!(out, " else");
260 let _ = writeln!(out, " echo \"FAIL: $name\"");
261 let _ = writeln!(out, " FAIL=$((FAIL + 1))");
262 let _ = writeln!(out, " fi");
263 let _ = writeln!(out, "}}");
264 let _ = writeln!(out);
265
266 let _ = writeln!(out, "# Run all generated test functions.");
270 for category in categories {
271 let _ = writeln!(out, "# Category: {category}");
272 let _ = writeln!(out, "run_tests_{category}");
275 }
276 let _ = writeln!(out);
277 let _ = writeln!(out, "echo \"\"");
278 let _ = writeln!(out, "echo \"Results: $PASS passed, $FAIL failed\"");
279 let _ = writeln!(out, "[ \"$FAIL\" -eq 0 ]");
280 out
281}
282
283#[allow(clippy::too_many_arguments)]
285fn render_category_file(
286 category: &str,
287 fixtures: &[&Fixture],
288 binary_name: &str,
289 subcommand: &str,
290 static_cli_args: &[String],
291 cli_flags: &std::collections::HashMap<String, String>,
292 args: &[crate::config::ArgMapping],
293 field_resolver: &FieldResolver,
294) -> String {
295 let safe_category = sanitize_filename(category);
296 let mut out = String::new();
297 let _ = writeln!(out, "#!/usr/bin/env bash");
298 let _ = writeln!(out, "# This file is auto-generated by alef. DO NOT EDIT.");
299 let _ = writeln!(out, "# E2e tests for category: {category}");
300 let _ = writeln!(out, "set -euo pipefail");
301 let _ = writeln!(out);
302
303 for fixture in fixtures {
304 render_test_function(
305 &mut out,
306 fixture,
307 binary_name,
308 subcommand,
309 static_cli_args,
310 cli_flags,
311 args,
312 field_resolver,
313 );
314 let _ = writeln!(out);
315 }
316
317 let _ = writeln!(out, "run_tests_{safe_category}() {{");
319 for fixture in fixtures {
320 let fn_name = sanitize_ident(&fixture.id);
321 let _ = writeln!(out, " run_test test_{fn_name}");
322 }
323 let _ = writeln!(out, "}}");
324 out
325}
326
327#[allow(clippy::too_many_arguments)]
329fn render_test_function(
330 out: &mut String,
331 fixture: &Fixture,
332 binary_name: &str,
333 subcommand: &str,
334 static_cli_args: &[String],
335 cli_flags: &std::collections::HashMap<String, String>,
336 args: &[crate::config::ArgMapping],
337 field_resolver: &FieldResolver,
338) {
339 let fn_name = sanitize_ident(&fixture.id);
340 let description = &fixture.description;
341
342 let expects_error = fixture.assertions.iter().any(|a| a.assertion_type == "error");
343
344 let _ = writeln!(out, "test_{fn_name}() {{");
345 let _ = writeln!(out, " # {description}");
346
347 let cmd_parts = build_cli_command(fixture, binary_name, subcommand, static_cli_args, cli_flags, args);
349
350 if expects_error {
351 let cmd = cmd_parts.join(" ");
352 let _ = writeln!(out, " if {cmd} >/dev/null 2>&1; then");
353 let _ = writeln!(
354 out,
355 " echo 'FAIL [error]: expected command to fail but it succeeded' >&2"
356 );
357 let _ = writeln!(out, " return 1");
358 let _ = writeln!(out, " fi");
359 let _ = writeln!(out, "}}");
360 return;
361 }
362
363 let has_active_assertions = fixture.assertions.iter().any(|a| {
365 a.field
366 .as_ref()
367 .is_none_or(|f| f.is_empty() || field_resolver.is_valid_for_result(f))
368 });
369
370 let cmd = cmd_parts.join(" ");
372 if has_active_assertions {
373 let _ = writeln!(out, " local output");
374 let _ = writeln!(out, " output=$({cmd})");
375 } else {
376 let _ = writeln!(out, " {cmd} >/dev/null");
377 }
378 let _ = writeln!(out);
379
380 for assertion in &fixture.assertions {
382 render_assertion(out, assertion, field_resolver);
383 }
384
385 let _ = writeln!(out, "}}");
386}
387
388fn build_cli_command(
393 fixture: &Fixture,
394 binary_name: &str,
395 subcommand: &str,
396 static_cli_args: &[String],
397 cli_flags: &std::collections::HashMap<String, String>,
398 args: &[crate::config::ArgMapping],
399) -> Vec<String> {
400 let mut parts: Vec<String> = vec![binary_name.to_string(), subcommand.to_string()];
401
402 for arg in args {
403 match arg.arg_type.as_str() {
404 "mock_url" => {
405 parts.push(format!("\"${{MOCK_SERVER_URL}}/fixtures/{}\"", fixture.id));
407 }
408 "handle" => {
409 }
411 _ => {
412 if let Some(flag) = cli_flags.get(&arg.field) {
414 let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
415 if let Some(val) = fixture.input.get(field) {
416 if !val.is_null() {
417 let val_str = json_value_to_shell_arg(val);
418 parts.push(flag.clone());
419 parts.push(val_str);
420 }
421 }
422 }
423 }
424 }
425 }
426
427 for static_arg in static_cli_args {
429 parts.push(static_arg.clone());
430 }
431
432 parts
433}
434
435fn json_value_to_shell_arg(value: &serde_json::Value) -> String {
440 match value {
441 serde_json::Value::String(s) => format!("'{}'", escape_shell(s)),
442 serde_json::Value::Bool(b) => b.to_string(),
443 serde_json::Value::Number(n) => n.to_string(),
444 serde_json::Value::Null => "''".to_string(),
445 other => format!("'{}'", escape_shell(&other.to_string())),
446 }
447}
448
449fn field_to_jq_path(resolved: &str) -> String {
456 if let Some((prefix, suffix)) = resolved.rsplit_once('.') {
459 if suffix == "length" || suffix == "count" || suffix == "size" {
460 return format!(".{prefix} | length");
461 }
462 }
463 if resolved == "length" || resolved == "count" || resolved == "size" {
465 return ". | length".to_string();
466 }
467 format!(".{resolved}")
468}
469
470fn render_assertion(out: &mut String, assertion: &Assertion, field_resolver: &FieldResolver) {
472 if let Some(f) = &assertion.field {
474 if !f.is_empty() && !field_resolver.is_valid_for_result(f) {
475 let _ = writeln!(out, " # skipped: field '{f}' not available on result type");
476 return;
477 }
478 }
479
480 match assertion.assertion_type.as_str() {
481 "equals" => {
482 if let Some(field) = &assertion.field {
483 if let Some(expected) = &assertion.value {
484 let resolved = field_resolver.resolve(field);
485 let jq_path = field_to_jq_path(resolved);
486 let expected_str = json_value_to_shell_string(expected);
487 let safe_field = sanitize_ident(field);
488 let _ = writeln!(out, " local val_{safe_field}");
489 let _ = writeln!(out, " val_{safe_field}=$(echo \"$output\" | jq -r '{jq_path}')");
490 let _ = writeln!(
491 out,
492 " assert_equals \"$val_{safe_field}\" '{expected_str}' '{field}'"
493 );
494 }
495 }
496 }
497 "contains" => {
498 if let Some(field) = &assertion.field {
499 if let Some(expected) = &assertion.value {
500 let resolved = field_resolver.resolve(field);
501 let jq_path = field_to_jq_path(resolved);
502 let expected_str = json_value_to_shell_string(expected);
503 let safe_field = sanitize_ident(field);
504 let _ = writeln!(out, " local val_{safe_field}");
505 let _ = writeln!(out, " val_{safe_field}=$(echo \"$output\" | jq -r '{jq_path}')");
506 let _ = writeln!(
507 out,
508 " assert_contains \"$val_{safe_field}\" '{expected_str}' '{field}'"
509 );
510 }
511 }
512 }
513 "not_empty" | "tree_not_null" => {
514 if let Some(field) = &assertion.field {
515 let resolved = field_resolver.resolve(field);
516 let jq_path = field_to_jq_path(resolved);
517 let safe_field = sanitize_ident(field);
518 let _ = writeln!(out, " local val_{safe_field}");
519 let _ = writeln!(out, " val_{safe_field}=$(echo \"$output\" | jq -r '{jq_path}')");
520 let _ = writeln!(out, " assert_not_empty \"$val_{safe_field}\" '{field}'");
521 }
522 }
523 "count_min" | "root_child_count_min" => {
524 if let Some(field) = &assertion.field {
525 if let Some(val) = &assertion.value {
526 if let Some(min) = val.as_u64() {
527 let resolved = field_resolver.resolve(field);
528 let jq_path = field_to_jq_path(resolved);
529 let safe_field = sanitize_ident(field);
530 let _ = writeln!(out, " local count_{safe_field}");
531 let _ = writeln!(
532 out,
533 " count_{safe_field}=$(echo \"$output\" | jq '{jq_path} | length')"
534 );
535 let _ = writeln!(out, " assert_count_min \"$count_{safe_field}\" {min} '{field}'");
536 }
537 }
538 }
539 }
540 "greater_than" => {
541 if let Some(field) = &assertion.field {
542 if let Some(val) = &assertion.value {
543 let resolved = field_resolver.resolve(field);
544 let jq_path = field_to_jq_path(resolved);
545 let threshold = json_value_to_shell_string(val);
546 let safe_field = sanitize_ident(field);
547 let _ = writeln!(out, " local val_{safe_field}");
548 let _ = writeln!(out, " val_{safe_field}=$(echo \"$output\" | jq -r '{jq_path}')");
549 let _ = writeln!(
550 out,
551 " assert_greater_than \"$val_{safe_field}\" '{threshold}' '{field}'"
552 );
553 }
554 }
555 }
556 "greater_than_or_equal" => {
557 if let Some(field) = &assertion.field {
558 if let Some(val) = &assertion.value {
559 let resolved = field_resolver.resolve(field);
560 let jq_path = field_to_jq_path(resolved);
561 let threshold = json_value_to_shell_string(val);
562 let safe_field = sanitize_ident(field);
563 let _ = writeln!(out, " local val_{safe_field}");
564 let _ = writeln!(out, " val_{safe_field}=$(echo \"$output\" | jq -r '{jq_path}')");
565 let _ = writeln!(
566 out,
567 " assert_greater_than_or_equal \"$val_{safe_field}\" '{threshold}' '{field}'"
568 );
569 }
570 }
571 }
572 "contains_all" => {
573 if let Some(field) = &assertion.field {
574 if let Some(serde_json::Value::Array(items)) = &assertion.value {
575 let resolved = field_resolver.resolve(field);
576 let jq_path = field_to_jq_path(resolved);
577 let safe_field = sanitize_ident(field);
578 let _ = writeln!(out, " local val_{safe_field}");
579 let _ = writeln!(out, " val_{safe_field}=$(echo \"$output\" | jq -r '{jq_path}')");
580 for (index, item) in items.iter().enumerate() {
581 let item_str = json_value_to_shell_string(item);
582 let _ = writeln!(
583 out,
584 " assert_contains \"$val_{safe_field}\" '{item_str}' '{field}[{index}]'"
585 );
586 }
587 }
588 }
589 }
590 "is_empty" => {
591 if let Some(field) = &assertion.field {
592 let resolved = field_resolver.resolve(field);
593 let jq_path = field_to_jq_path(resolved);
594 let safe_field = sanitize_ident(field);
595 let _ = writeln!(out, " local val_{safe_field}");
596 let _ = writeln!(out, " val_{safe_field}=$(echo \"$output\" | jq -r '{jq_path}')");
597 let _ = writeln!(out, " assert_is_empty \"$val_{safe_field}\" '{field}'");
598 }
599 }
600 "less_than" => {
601 if let Some(field) = &assertion.field {
602 if let Some(val) = &assertion.value {
603 let resolved = field_resolver.resolve(field);
604 let jq_path = field_to_jq_path(resolved);
605 let threshold = json_value_to_shell_string(val);
606 let safe_field = sanitize_ident(field);
607 let _ = writeln!(out, " local val_{safe_field}");
608 let _ = writeln!(out, " val_{safe_field}=$(echo \"$output\" | jq -r '{jq_path}')");
609 let _ = writeln!(
610 out,
611 " assert_less_than \"$val_{safe_field}\" '{threshold}' '{field}'"
612 );
613 }
614 }
615 }
616 "not_contains" => {
617 if let Some(field) = &assertion.field {
618 if let Some(expected) = &assertion.value {
619 let resolved = field_resolver.resolve(field);
620 let jq_path = field_to_jq_path(resolved);
621 let expected_str = json_value_to_shell_string(expected);
622 let safe_field = sanitize_ident(field);
623 let _ = writeln!(out, " local val_{safe_field}");
624 let _ = writeln!(out, " val_{safe_field}=$(echo \"$output\" | jq -r '{jq_path}')");
625 let _ = writeln!(
626 out,
627 " assert_not_contains \"$val_{safe_field}\" '{expected_str}' '{field}'"
628 );
629 }
630 }
631 }
632 "count_equals" => {
633 if let Some(field) = &assertion.field {
634 if let Some(val) = &assertion.value {
635 if let Some(n) = val.as_u64() {
636 let resolved = field_resolver.resolve(field);
637 let jq_path = field_to_jq_path(resolved);
638 let safe_field = sanitize_ident(field);
639 let _ = writeln!(out, " local count_{safe_field}");
640 let _ = writeln!(
641 out,
642 " count_{safe_field}=$(echo \"$output\" | jq '{jq_path} | length')"
643 );
644 let _ = writeln!(out, " [ \"$count_{safe_field}\" -eq {n} ] || exit 1");
645 }
646 }
647 }
648 }
649 "is_true" => {
650 if let Some(field) = &assertion.field {
651 let resolved = field_resolver.resolve(field);
652 let jq_path = field_to_jq_path(resolved);
653 let safe_field = sanitize_ident(field);
654 let _ = writeln!(out, " local val_{safe_field}");
655 let _ = writeln!(out, " val_{safe_field}=$(echo \"$output\" | jq -r '{jq_path}')");
656 let _ = writeln!(out, " [ \"$val_{safe_field}\" = \"true\" ] || exit 1");
657 }
658 }
659 "not_error" => {
660 }
662 "error" => {
663 }
665 other => {
666 let _ = writeln!(out, " # TODO: unsupported assertion type: {other}");
667 }
668 }
669}
670
671fn json_value_to_shell_string(value: &serde_json::Value) -> String {
675 match value {
676 serde_json::Value::String(s) => escape_shell(s),
677 serde_json::Value::Bool(b) => b.to_string(),
678 serde_json::Value::Number(n) => n.to_string(),
679 serde_json::Value::Null => String::new(),
680 other => escape_shell(&other.to_string()),
681 }
682}