-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathJythonCli.java
More file actions
executable file
·302 lines (267 loc) · 9.26 KB
/
JythonCli.java
File metadata and controls
executable file
·302 lines (267 loc) · 9.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.tomlj:tomlj:1.1.1
import java.io.*;
import java.util.*;
import org.tomlj.Toml;
import org.tomlj.TomlParseResult;
import org.tomlj.TomlParseError;
public class JythonCli {
/**
* Default version of Jython to use.
*/
String jythonVersion = "2.7.4";
/**
* Default version of Java to use as determined by the JVM version running
* {@code jython-cli}. Only Java 8 or higher is supported.
*/
String javaVersion = getJvmMajorVersion();
/**
* List of Maven Central JAR dependencies
*/
List<String> deps = new ArrayList<>();
/**
* Java VM runtime options
*/
List<String> ropts = new ArrayList<>();
/**
* Jython arguments
*/
List<String> jythonArgs = new ArrayList<>();
/**
* Jython script filename (if specified) or null if not.
*/
String scriptFilename;
/**
* (optional) TOML text block extracted from the Jython script specified on
* the command-line
*/
StringBuilder tomlText = new StringBuilder();
/**
* (optional) TOML parsed result object from which runtime information is
* extracted
*/
TomlParseResult tpr = null;
/**
* Debug output can be specified by the {@code --cli-debug} command line
* option. If set, various critical state is displayed including the
* {@code jbang} block, its meaning as TOML and the arguments passed to
* ProcessBuilder.
*/
boolean debug = false;
/**
* Determine the major version number of the JVM {@code jython-cli} is
* running on.
*
* @return the major version number of the current JVM, that is "8", "9",
* "10", etc.
*/
static String getJvmMajorVersion() {
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
version = version.substring(2);
}
return version.replaceAll("(\\d+).*", "$1");
}
/**
* Process the command line arguments, giving special tratment to the
* {@code --cli-debug} option and the (optional) Jython script specified.
*
* @param args program arguments as specified on the command-line
* @throws IOException
*/
void initEnvironment(String[] args) throws IOException {
// Check that that Java 8 (1.8) or higher is used
if (Integer.parseInt(javaVersion) < 8) {
System.err.println("jython-cli: error, Java 8 or higher is required");
System.exit(1);
}
for (String arg : args) {
if (scriptFilename == null && arg.endsWith(".py")) {
scriptFilename = arg;
jythonArgs.add(arg);
} else if ("--cli-debug".equals(arg)) {
debug = true;
} else {
jythonArgs.add(arg);
}
}
}
/**
* Read a script and parse out a {@code jbang} block if possible,
* later to be interpreted as TOML data. Errors to do with framing
* the block are detected here, while errors in content must wait.
*
* @param script supplying text of the script
* @throws IOException
*/
void readJBangBlock(Reader script) throws IOException {
// Extract TOML data as a String
LineNumberReader lines = new LineNumberReader(script);
String line;
boolean found = false;
printIfDebug("");
printIfDebug("TOML data in Jython script:");
while ((line = lines.readLine())!=null) {
int lineno = lines.getLineNumber();
if (found && !line.startsWith("# ")) {
found = false;
tomlText = new StringBuilder();
}
if (!found && line.startsWith("# /// jbang")) {
printIfDebug(lineno, line);
found = true;
} else if (found && line.startsWith("# ///")) {
printIfDebug(lineno, line);
break;
} else if (found && (line.startsWith("# ") || line.equals("#"))) {
if (line.length() == 1) {
line += " ";
}
printIfDebug(lineno, line);
if (tomlText.length() > 0) {
tomlText.append("\n");
}
tomlText.append(line.substring(2));
}
}
}
/**
* Interpret the jbang block from the Jython script specified on the
* command-line as TOML data. The runtime options that are extracted from
* the TOML data will override default version specifications determined
* earlier.
*
* @throws IOException
*/
void interpretJBangBlock() throws IOException {
if (tomlText.length() > 0) {
int lineno = 0;
printIfDebug("");
printIfDebug("TOML data extracted from Jython script:");
for (String line: tomlText.toString().split("\\n", -1)) {
lineno += 1;
printIfDebug(lineno, line);
}
tpr = Toml.parse(tomlText.toString());
if (tpr.hasErrors()) {
for (TomlParseError err: tpr.errors()) {
System.err.println(err.toString());
}
if (debug) {
throw new IOException("Error interpreting JBang TOML data.");
} else {
throw new IOException("Error interpreting JBang TOML data. Re-run with '--cli-debug' for details.");
}
}
}
// Process the TOML data
if (tpr != null) {
// requires-jython
if (tpr.isString("requires-jython")) {
jythonVersion = tpr.getString("requires-jython");
}
// requires-java
if (tpr.isString("requires-java")) {
javaVersion = tpr.getString("requires-java");
}
// dependencies
for (Object e : tpr.getArrayOrEmpty("dependencies").toList()) {
String dep = (String) e;
deps.add(dep);
}
// runtime-options
for (Object e : tpr.getArrayOrEmpty("runtime-options").toList()) {
String ropt = (String) e;
ropts.add(ropt);
}
}
}
/**
* Run the Jython jar using JBang passing along the required Maven
* dependencies and JVM runtime options.
*
* @param args program arguments as specified on the command-line
* @throws IOException
* @throws InterruptedException
*/
void runProcess() throws IOException, InterruptedException {
// Compose the launch command here
List<String> cmd = new LinkedList<>();
boolean windows = System.getProperty("os.name").toLowerCase().startsWith("win");
cmd.add("jbang" + (windows ? ".cmd" : ""));
cmd.add("run");
cmd.add("--java");
cmd.add(javaVersion);
for (String ropt : ropts) {
cmd.add("--runtime-option");
cmd.add(ropt);
}
for (String dep : deps) {
cmd.add("--deps");
cmd.add(dep);
}
cmd.add("--main");
cmd.add("org.python.util.jython");
cmd.add("org.python:jython-slim:" + jythonVersion);
cmd.addAll(jythonArgs);
printIfDebug(cmd.toString());
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.inheritIO();
pb.start().waitFor();
}
/**
* Shorthand to print a line of the source jbang block if {@link #debug} is
* set.
*
* @param lineno source line number
* @param line text captured to {@link #tomlText}
*/
void printIfDebug(int lineno, String line) {
if (debug) {
System.err.printf("%6d :%s\n", lineno, line);
}
}
/**
* Shorthand to print something if {@link #debug} is set.
*
* @param text to print
*/
void printIfDebug(String text) {
if (debug) {
System.err.println(text);
}
}
/**
* Main {@code jython-cli} (JythonCli.java) program. The arguments are
* exactly the same command-line arguments Jython itself supports as
* documented in
* <a href="https://www.jython.org/jython-old-sites/docs/using/cmdline.html">Jython
* Command Line</a>
*
* @param args arguments to the program.
* @throws IOException
* @throws InterruptedException
*/
public static void main(String[] args) {
// Create an instance of the class in which to compose argument list
JythonCli jythonCli = new JythonCli();
try {
jythonCli.initEnvironment(args);
// Normally we have a script file (but it's optional)
if (jythonCli.scriptFilename != null) {
Reader script = new BufferedReader(
new InputStreamReader(
new FileInputStream(jythonCli.scriptFilename)));
jythonCli.readJBangBlock(script);
jythonCli.interpretJBangBlock();
}
// Finally launch Jython via JBang
jythonCli.runProcess();
} catch (IOException ioe) {
System.err.println(ioe.toString());
System.exit(1);
} catch (InterruptedException ie) {
System.exit(3);
}
}
}