Skip to content

Commit 1b3b43e

Browse files
Leroy van Engelenlemmy
authored andcommitted
Introduce IOExecTemplate operator
This operator makes it a bit easier to invoke processes by using the `%s` format specifier as an argument placeholder. Note that this this does not allow for spaces in the process path (e.g. `C:\Program Files\...`) and only supports a single `%s` within a word (space delimited part of the template).
1 parent 6669389 commit 1b3b43e

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

modules/IOUtils.tla

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,13 @@ IOExec(command) ==
2121
(*******************************************************************************)
2222
CHOOSE r \in [exitValue : Int, stdout : STRING, stderr : STRING] : TRUE
2323

24+
IOExecTemplate(commandTemplate, parameters) ==
25+
(*************************************************************************)
26+
(* Spawns the given printf-style command as a sub-process of TLC. The *)
27+
(* n-th flag in `commandTemplate' is substituted with the n-th element *)
28+
(* of the sequence `parameters': IOExec("ls %s %s", <<"-lah", "/tmp">>) *)
29+
(* see http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html *)
30+
(*************************************************************************)
31+
CHOOSE r \in [exitValue : Int, stdout : STRING, stderr : STRING] : TRUE
32+
2433
============================================================================

modules/tlc2/overrides/IOUtils.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,47 @@ public static Value ioExec(final Value parameter) throws IOException, Interrupte
9393
return new RecordValue(EXEC_NAMES, new Value[] {exitCode, stdout, stderr}, false);
9494
}
9595

96+
@TLAPlusOperator(identifier = "IOExecTemplate", module = "IOUtils", minLevel = 1, warn = false)
97+
public static Value ioExecTemplate(final Value commandTemplate, final Value parameter) throws IOException, InterruptedException {
98+
// 1. Check parameters and covert.
99+
if (!(commandTemplate instanceof StringValue)) {
100+
throw new EvalException(EC.TLC_MODULE_ONE_ARGUMENT_ERROR,
101+
new String[] { "IOExec", "string", Values.ppr(commandTemplate.toString()) });
102+
}
103+
if (!(parameter instanceof TupleValue)) {
104+
throw new EvalException(EC.TLC_MODULE_ONE_ARGUMENT_ERROR,
105+
new String[] { "IOExec", "sequence", Values.ppr(parameter.toString()) });
106+
}
107+
final StringValue sv = (StringValue) commandTemplate;
108+
final TupleValue tv = (TupleValue) parameter;
109+
110+
// 2. Build actual command-line by merging command and parameter.
111+
// XXX does not support multiple %s inside a template part
112+
final String[] command = sv.val.toString().split("\\s+");
113+
int j = 0;
114+
for (int i = 0; i < command.length; ++i) {
115+
if (command[i].contains("%s")) {
116+
if (j < tv.elems.length) {
117+
command[i] = String.format(command[i], ((StringValue) tv.elems[j++]).val.toString());
118+
} else {
119+
// Too many %s
120+
// XXX throw proper exception
121+
throw new EvalException(EC.TLC_MODULE_ONE_ARGUMENT_ERROR,
122+
new String[] { "IOExec", "sequence", Values.ppr(parameter.toString()) });
123+
}
124+
}
125+
}
126+
127+
// 3. Run command-line and receive its output.
128+
final Process process = new ProcessBuilder(command)/*.inheritIO()*/.start();
129+
130+
final StringValue stdout = new StringValue(new String(process.getInputStream().readAllBytes()));
131+
final StringValue stderr = new StringValue(new String(process.getErrorStream().readAllBytes()));
132+
final IntValue exitCode = IntValue.gen(process.waitFor());
133+
134+
return new RecordValue(EXEC_NAMES, new Value[] {exitCode, stdout, stderr}, false);
135+
}
136+
96137
private static String convert(IValue v) {
97138
if (! (v instanceof StringValue)) {
98139
// XXX Proper exception

tests/IOUtilsTests.tla

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,14 @@ ASSUME(LET ret == IOExec(<<"cat", "/does/not/exist">>) IN /\ ret.exitValue = 1
1010
/\ ret.stdout = ""
1111
/\ ret.stderr = "cat: /does/not/exist: No such file or directory\n")
1212

13+
\* Spaces and quotes should be passed directly to the program.
14+
ASSUME(LET ret == IOExecTemplate("echo '%s' \"%s\"", <<" foo", "bar ">>) IN /\ ret.exitValue = 0
15+
/\ ret.stdout = "' foo' \"bar \"\n"
16+
/\ ret.stderr = "")
17+
18+
\* Exit values and standard error should be returned properly.
19+
ASSUME(LET ret == IOExecTemplate("cat /does/not/exist", <<>>) IN /\ ret.exitValue = 1
20+
/\ ret.stdout = ""
21+
/\ ret.stderr = "cat: /does/not/exist: No such file or directory\n")
22+
1323
=============================================================================

0 commit comments

Comments
 (0)