@@ -31,8 +31,8 @@ public class ForeignExeMock {
31
31
enum TargetOs {
32
32
WINDOWS {
33
33
@ Override
34
- protected ForeignExeMockWriter mockWriter (PrintWriter output ) {
35
- return new WindowsForeignExeMockWriter (output );
34
+ protected ForeignExeMockWriter mockWriter (PrintWriter output , String name ) {
35
+ return new WindowsForeignExeMockWriter (output , "output written by " + name );
36
36
}
37
37
38
38
@ Override
@@ -42,8 +42,8 @@ protected String asFileName(String name) {
42
42
},
43
43
UNIX {
44
44
@ Override
45
- protected ForeignExeMockWriter mockWriter (PrintWriter output ) {
46
- return new UnixForeignExeMockWriter (output );
45
+ protected ForeignExeMockWriter mockWriter (PrintWriter output , String name ) {
46
+ return new UnixForeignExeMockWriter (output , "output written by " + name );
47
47
}
48
48
49
49
@ Override
@@ -60,7 +60,7 @@ static TargetOs current() {
60
60
}
61
61
}
62
62
63
- protected abstract ForeignExeMockWriter mockWriter (PrintWriter output );
63
+ protected abstract ForeignExeMockWriter mockWriter (PrintWriter output , String name );
64
64
65
65
protected abstract String asFileName (String name );
66
66
}
@@ -149,10 +149,10 @@ private Map<String, String> optionDefaults() {
149
149
return defaults ;
150
150
}
151
151
152
- private String content (TargetOs os ) {
152
+ private String content (TargetOs os , String name ) {
153
153
try (StringWriter stringWriter = new StringWriter ();
154
154
PrintWriter printWriter = new PrintWriter (stringWriter )) {
155
- ForeignExeMockWriter writer = os .mockWriter (printWriter );
155
+ ForeignExeMockWriter writer = os .mockWriter (printWriter , name );
156
156
writer = writer .writeIntro (optionDefaults ())
157
157
.writeOptionParserIntro ()
158
158
.writeStringReturningOption ("--version" , "version " + version );
@@ -188,7 +188,7 @@ public ForeignExeMock build() {
188
188
}
189
189
190
190
ForeignExeMock build (TargetOs os ) {
191
- return new ForeignExeMock (name , content (os ), os );
191
+ return new ForeignExeMock (name , content (os , name ), os );
192
192
}
193
193
}
194
194
@@ -215,23 +215,26 @@ private interface ForeignExeMockWriter {
215
215
private static class UnixForeignExeMockWriter implements ForeignExeMockWriter {
216
216
217
217
private final PrintWriter output ;
218
+ private final String constantOut ; // text we always emit to stdout
218
219
219
- UnixForeignExeMockWriter (@ NotNull PrintWriter output ) {
220
+ UnixForeignExeMockWriter (@ NotNull PrintWriter output , @ NotNull String constantOut ) {
220
221
this .output = Objects .requireNonNull (output );
222
+ this .constantOut = Objects .requireNonNull (constantOut );
221
223
}
222
224
223
225
private String asVarName (String optionName ) {
224
226
return optionName .replaceAll ("[^a-zA-Z0-9]" , "_" );
225
227
}
226
228
229
+ /* ───────────────────────── intro ───────────────────────── */
227
230
@ Override
228
231
public ForeignExeMockWriter writeIntro (@ NotNull Map <String , String > optionDefaults ) {
229
232
Objects .requireNonNull (optionDefaults );
230
- output .println ("#!/bin/bash" );
233
+ output .println ("#!/usr/bin/env bash" );
234
+ output .println ("set -euo pipefail" );
235
+ output .println ();
236
+ optionDefaults .forEach ((k , v ) -> output .printf ("%s=\" %s\" %n" , asVarName (k ), v ));
231
237
output .println ();
232
- optionDefaults .forEach ((k , v ) -> output .printf ("%s=\" %s\" \n " , asVarName (k ), v ));
233
- // collect value from stdin
234
- output .println ("stdin_value=\" \" " );
235
238
return this ;
236
239
}
237
240
@@ -305,38 +308,44 @@ public ForeignExeMockWriter writeOptionParserOutro() {
305
308
return this ;
306
309
}
307
310
311
+ /* ─────────────────── stdin → /dev/null (drain) ─────────────────── */
308
312
@ Override
309
313
public ForeignExeMockWriter writeReadFromStdin () {
310
- // collect the stdin to stdin_value
311
- output .println ("while IFS= read -r line; do" );
312
- output .println (" stdin_value+=\" ${line}\" $'\\ n'" );
313
- output .println ("done" );
314
+ output .println ("# consume everything the caller pipes into us -------------" );
315
+ output .println ("cat > /dev/null" );
314
316
output .println ();
315
317
return this ;
316
318
}
317
319
320
+ /* ─────────────── constant text → stdout ─────────────── */
321
+ /** Quote a literal for POSIX shells using single‑quotes. */
322
+ private static String shQuote (String s ) {
323
+ // close quote, insert escaped single‑quote, reopen quote: ' --> '\''
324
+ return "'" + s .replace ("'" , "'\" '\" '" ) + "'" ;
325
+ }
326
+
318
327
@ Override
319
328
public ForeignExeMockWriter writeWriteToStdout () {
320
- // write the stdin_value to stdout but add 4 spaces at the end of each line that does not already end with 4
321
- // spaces
322
- // output.println("echo \"${stdin_value}\" | sed '/ $/! s/$/ /'");
323
- output .println ("printf \" %s\" \" $stdin_value\" | sed '/ $/! s/$/ /'" );
329
+ output .printf ("echo %s%n" , shQuote (constantOut ));
330
+ output .println ();
324
331
return this ;
325
332
}
326
333
334
+ /* ─────────────────────── exit ─────────────────────── */
327
335
@ Override
328
336
public ForeignExeMockWriter writeExitCode (int exitCode ) {
329
- output .printf ("exit %d\ n " , exitCode );
337
+ output .printf ("exit %d% n" , exitCode );
330
338
return this ;
331
339
}
332
340
}
333
341
334
342
private static class WindowsForeignExeMockWriter implements ForeignExeMockWriter {
335
-
336
343
private final PrintWriter output ;
344
+ private final String constantOut ; // what we will echo to stdout
337
345
338
- WindowsForeignExeMockWriter (@ NotNull PrintWriter output ) {
346
+ WindowsForeignExeMockWriter (@ NotNull PrintWriter output , @ NotNull String constantOut ) {
339
347
this .output = Objects .requireNonNull (output );
348
+ this .constantOut = Objects .requireNonNull (constantOut );
340
349
}
341
350
342
351
private String asVarName (String optionName ) {
@@ -352,9 +361,7 @@ public ForeignExeMockWriter writeIntro(@NotNull Map<String, String> optionDefaul
352
361
output .println ();
353
362
354
363
optionDefaults .forEach ((k , v ) -> output .printf ("set \" %s=%s\" %n" , asVarName (k ), v ));
355
-
356
- output .println ("set \" stdin_file=%TEMP%\\ foreign_mock_%RANDOM%%RANDOM%.tmp\" " );
357
- output .println ();
364
+ output .println (); // nothing else needed here
358
365
return this ;
359
366
}
360
367
@@ -441,75 +448,21 @@ public ForeignExeMockWriter writeOptionParserOutro() {
441
448
// ───────────────────── stdin → file ─────────────────────
442
449
@ Override
443
450
public ForeignExeMockWriter writeReadFromStdin () {
444
- output .println ("rem -- read everything the caller pipes into us ------- -----" );
445
- output .println ("more > \" !stdin_file! \" " );
451
+ output .println ("rem -- consume everything the caller pipes into us -----" );
452
+ output .println ("more > nul" ); // reads stdin until EOF, discards it
446
453
output .println ();
447
454
return this ;
448
455
}
449
456
450
457
// ─────────────── file → stdout (pad lines) ──────────────
451
458
@ Override
452
459
public ForeignExeMockWriter writeWriteToStdout () {
453
-
454
- // rolling buffer
455
- output .println ("set \" prev_line=\" " );
456
- output .println ("set \" have_prev=0\" " );
457
-
458
- /*
459
- read each physical record (trailing blanks intact)
460
- findstr /n /R ".*" prefixes lines with N: (even empty ones)
461
- */
462
- output .println ("for /f \" usebackq delims=\" %%L in (`findstr /n /R \" .*\" \" !stdin_file!\" `) do (" );
463
- output .println (" set \" raw=%%L\" " );
464
- output .println (" set \" curr_line=!raw:*:=!\" " ); // strip N:
465
- output .println (" if \" !have_prev!\" ==\" 1\" (" );
466
- output .println (" call :emit_with_nl" ); // print previous line + NL
467
- output .println (" )" );
468
- output .println (" set \" prev_line=!curr_line!\" " );
469
- output .println (" set \" have_prev=1\" " );
470
- output .println (")" );
471
-
472
- /*
473
- After the loop prev_line holds LAST record.
474
- If that record is empty, the input *did* end with an EOL, so nothing more
475
- to print. If it is non‑empty, the input lacked a final EOL — print the
476
- line *without* adding a new one.
477
- */
478
- output .println ("if \" !prev_line!\" ==\" \" (" );
479
- output .println (" rem input ended with newline – nothing left to write" );
480
- output .println (") else (" );
481
- output .println (" call :emit_no_nl" ); // print last line, no NL
482
- output .println (")" );
483
-
484
- output .println ("del \" !stdin_file!\" >nul 2>&1" );
485
- output .println ("goto :finish" );
486
- output .println ();
487
-
488
- /* ---------- helpers --------------------------------------- */
489
-
490
- // prev_line → stdout, padding rule, WITH trailing newline
491
- output .println (":emit_with_nl" );
492
- output .println ("if \" !prev_line:~-4!\" ==\" \" (" );
493
- output .println (" <nul set /p \" =!prev_line!\" " );
494
- output .println (") else (" );
495
- output .println (" <nul set /p \" =!prev_line! \" " );
496
- output .println (")" );
497
- output .println ("echo(" ); // newline
498
- output .println ("exit /b" );
460
+ output .printf ("echo %s%n" , constantOut .replace ("\" " , "\" \" " ));
461
+ output .println ("goto :finish" ); // skip anything that might follow
499
462
output .println ();
500
-
501
- // prev_line → stdout, padding rule, NO trailing newline
502
- output .println (":emit_no_nl" );
503
- output .println ("if \" !prev_line:~-4!\" ==\" \" (" );
504
- output .println (" <nul set /p \" =!prev_line!\" " );
505
- output .println (") else (" );
506
- output .println (" <nul set /p \" =!prev_line! \" " );
507
- output .println (")" );
508
- output .println ("exit /b" );
509
- output .println ();
510
-
511
463
return this ;
512
464
}
465
+
513
466
// ───────────────────────── exit ─────────────────────────
514
467
/* writeExitCode – central, always‑reached exit point */
515
468
@ Override
0 commit comments