13514
|
1 /*
|
|
2 * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
|
|
3 * code is released under a tri EPL/GPL/LGPL license. You can use it,
|
|
4 * redistribute it and/or modify it under the terms of the:
|
|
5 *
|
|
6 * Eclipse Public License version 1.0
|
|
7 * GNU General Public License version 2
|
|
8 * GNU Lesser General Public License version 2.1
|
|
9 */
|
|
10 package com.oracle.truffle.ruby.shell;
|
|
11
|
|
12 import java.io.*;
|
|
13 import java.util.*;
|
|
14
|
|
15 import com.oracle.truffle.ruby.runtime.configuration.*;
|
|
16
|
|
17 /**
|
|
18 * MRI-compatible command line parser, producing {@link CommandLineOptions}.
|
|
19 */
|
|
20 public abstract class CommandLineParser {
|
|
21
|
|
22 /**
|
|
23 * Parse an MRI-compatible command line.
|
|
24 */
|
|
25 public static CommandLineOptions parse(String[] args) {
|
|
26 assert args != null;
|
|
27
|
|
28 final List<String> preRequires = new ArrayList<>();
|
|
29 String preChangeDirectory = null;
|
|
30
|
|
31 final List<String> extraLoadPath = new ArrayList<>();
|
|
32
|
|
33 String programFile = null;
|
|
34 final List<String> commandLineScripts = new ArrayList<>();
|
|
35 final List<String> programArgs = new ArrayList<>();
|
|
36 final List<String> switchArgs = new ArrayList<>();
|
|
37
|
|
38 int recordSeparator = -1;
|
|
39 boolean autosplit = false;
|
|
40 String autosplitPattern = null;
|
|
41 boolean checkSyntaxOnly = false;
|
|
42 boolean lineEndingProcessing = false;
|
|
43 boolean inPlaceEdit = false;
|
|
44 String inPlaceBackupExtension = null;
|
|
45 boolean implicitLoop = false;
|
|
46 boolean implicitSedLoop = false;
|
|
47 boolean importFromPath = false;
|
|
48 boolean messageStrip = false;
|
|
49 boolean useJLine = true;
|
|
50
|
|
51 final ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
|
|
52
|
|
53 final List<String> normalizedArgs = normalizeArgs(args);
|
|
54
|
|
55 boolean stillRubyArgs = true;
|
|
56 boolean collectingSwitchArgs = false;
|
|
57 int n = 0;
|
|
58
|
|
59 while (n < normalizedArgs.size()) {
|
|
60 final String arg = normalizedArgs.get(n);
|
|
61
|
|
62 if (stillRubyArgs && arg.startsWith("-")) {
|
|
63 if (collectingSwitchArgs) {
|
|
64 switchArgs.add(arg);
|
|
65 } else if (arg.startsWith("-0")) {
|
|
66 if (arg.length() == 2) {
|
|
67 recordSeparator = 0;
|
|
68 } else {
|
|
69 recordSeparator = Integer.parseInt(arg.substring(2), 8);
|
|
70 }
|
|
71 } else if (arg.startsWith("-i")) {
|
|
72 inPlaceEdit = true;
|
|
73
|
|
74 if (arg.length() > 2) {
|
|
75 inPlaceBackupExtension = arg.substring(2);
|
|
76 }
|
|
77 } else if (arg.startsWith("-W")) {
|
|
78 if (arg.length() == 2) {
|
|
79 configurationBuilder.setWarningLevel(2);
|
|
80 } else if (arg.startsWith("-Wlevel=")) {
|
|
81 configurationBuilder.setWarningLevel(Integer.parseInt(arg.substring("-Wlevel=".length())));
|
|
82 } else {
|
|
83 throw new IllegalArgumentException("bad flag " + arg);
|
|
84 }
|
|
85 } else if (arg.startsWith("-T")) {
|
|
86 if (arg.length() == 2) {
|
|
87 configurationBuilder.setTaintCheckLevel(1);
|
|
88 } else if (arg.startsWith("-Tlevel=")) {
|
|
89 configurationBuilder.setTaintCheckLevel(Integer.parseInt(arg.substring("-Tlevel=".length())));
|
|
90 } else {
|
|
91 throw new IllegalArgumentException("bad flag " + arg);
|
|
92 }
|
|
93 } else if (arg.startsWith("-x")) {
|
|
94 messageStrip = true;
|
|
95
|
|
96 if (arg.length() > 2) {
|
|
97 preChangeDirectory = arg.substring(2);
|
|
98 }
|
|
99 } else {
|
|
100 switch (arg) {
|
|
101 case "-C":
|
|
102 case "-e":
|
|
103 case "-E":
|
|
104 case "-F":
|
|
105 case "-I":
|
|
106 case "-r":
|
|
107 case "--home":
|
|
108 if (n + 1 >= normalizedArgs.size()) {
|
|
109 throw new RuntimeException("Expecting value after " + arg);
|
|
110 }
|
|
111 }
|
|
112
|
|
113 switch (arg) {
|
|
114 case "--":
|
|
115 stillRubyArgs = false;
|
|
116 break;
|
|
117 case "-a":
|
|
118 autosplit = true;
|
|
119 break;
|
|
120 case "-c":
|
|
121 checkSyntaxOnly = true;
|
|
122 break;
|
|
123 case "-C":
|
|
124 if (n + 1 >= normalizedArgs.size()) {
|
|
125 throw new RuntimeException("Need a directory path after -C");
|
|
126 }
|
|
127 preChangeDirectory = normalizedArgs.get(n + 1);
|
|
128 n++;
|
|
129 break;
|
|
130 case "-d":
|
|
131 case "--debug":
|
|
132 configurationBuilder.setDebug(true);
|
|
133 break;
|
|
134 case "--no-debug":
|
|
135 configurationBuilder.setDebug(false);
|
|
136 break;
|
|
137 case "--trace":
|
|
138 configurationBuilder.setTrace(true);
|
|
139 break;
|
|
140 case "--no-trace":
|
|
141 configurationBuilder.setTrace(false);
|
|
142 break;
|
|
143 case "-e":
|
|
144 commandLineScripts.add(normalizedArgs.get(n + 1));
|
|
145 n++;
|
|
146 break;
|
|
147 case "-E":
|
|
148 final String[] encodings = normalizedArgs.get(n + 1).split(":");
|
|
149 configurationBuilder.setDefaultExternalEncoding(encodings[0]);
|
|
150
|
|
151 if (encodings.length == 2) {
|
|
152 configurationBuilder.setDefaultInternalEncoding(encodings[1]);
|
|
153 } else {
|
|
154 throw new IllegalArgumentException("bad flag " + arg);
|
|
155 }
|
|
156
|
|
157 n++;
|
|
158 break;
|
|
159 case "-F":
|
|
160 autosplitPattern = normalizedArgs.get(n + 1);
|
|
161 n++;
|
|
162 break;
|
|
163 case "-I":
|
|
164 extraLoadPath.add(normalizedArgs.get(n + 1));
|
|
165 n++;
|
|
166 break;
|
|
167 case "-l":
|
|
168 lineEndingProcessing = true;
|
|
169 break;
|
|
170 case "-n":
|
|
171 implicitLoop = true;
|
|
172 break;
|
|
173 case "-r":
|
|
174 preRequires.add(normalizedArgs.get(n + 1));
|
|
175 n++;
|
|
176 break;
|
|
177 case "-s":
|
|
178 collectingSwitchArgs = true;
|
|
179 break;
|
|
180 case "-p":
|
|
181 implicitSedLoop = true;
|
|
182 break;
|
|
183 case "-S":
|
|
184 importFromPath = true;
|
|
185 break;
|
|
186 case "-w":
|
|
187 configurationBuilder.setWarningLevel(1);
|
|
188 break;
|
|
189 case "-h":
|
|
190 case "--help":
|
|
191 help(System.out);
|
|
192 return null;
|
|
193 case "-v":
|
|
194 version(System.out);
|
|
195 configurationBuilder.setVerbose(true);
|
|
196 break;
|
|
197 case "--version":
|
|
198 version(System.out);
|
|
199 return null;
|
|
200 case "--copyright":
|
|
201 copyright(System.out);
|
|
202 return null;
|
|
203 case "--home":
|
|
204 configurationBuilder.setStandardLibrary(normalizedArgs.get(n + 1) + "/" + ConfigurationBuilder.JRUBY_STDLIB_JAR);
|
|
205 n++;
|
|
206 break;
|
|
207 case "--stdlib":
|
|
208 configurationBuilder.setStandardLibrary(normalizedArgs.get(n + 1));
|
|
209 n++;
|
|
210 break;
|
|
211 case "--1.8":
|
|
212 configurationBuilder.setRubyVersion(RubyVersion.RUBY_18);
|
|
213 break;
|
|
214 case "--1.9":
|
|
215 configurationBuilder.setRubyVersion(RubyVersion.RUBY_19);
|
|
216 break;
|
|
217 case "--2.0":
|
|
218 configurationBuilder.setRubyVersion(RubyVersion.RUBY_20);
|
|
219 break;
|
|
220 case "--full-object-space":
|
|
221 configurationBuilder.setFullObjectSpace(true);
|
|
222 break;
|
|
223 case "--no-jline":
|
|
224 useJLine = false;
|
|
225 break;
|
|
226 case "--print-parse-tree":
|
|
227 configurationBuilder.setPrintParseTree(true);
|
|
228 break;
|
|
229 case "--print-executed-files":
|
|
230 configurationBuilder.setPrintExecutedFiles(true);
|
|
231 break;
|
|
232 case "--print-spilt-instance-variables":
|
|
233 configurationBuilder.setPrintSpiltInstanceVariables(true);
|
|
234 break;
|
|
235 case "--print-uninitialized-calls":
|
|
236 configurationBuilder.setPrintUninitializedCalls(true);
|
|
237 break;
|
|
238 case "--print-java-exceptions":
|
|
239 configurationBuilder.setPrintJavaExceptions(true);
|
|
240 break;
|
|
241 case "--print-ruby-exceptions":
|
|
242 configurationBuilder.setPrintRubyExceptions(true);
|
|
243 break;
|
|
244 default:
|
|
245 throw new IllegalArgumentException("unknown flag " + arg);
|
|
246 }
|
|
247 }
|
|
248 } else if (programFile == null) {
|
|
249 programFile = arg;
|
|
250 stillRubyArgs = false;
|
|
251 } else {
|
|
252 programArgs.add(arg);
|
|
253 }
|
|
254
|
|
255 n++;
|
|
256 }
|
|
257
|
|
258 return new CommandLineOptions(preRequires, preChangeDirectory, extraLoadPath, programFile, commandLineScripts, programArgs, switchArgs, recordSeparator, autosplit, autosplitPattern,
|
|
259 checkSyntaxOnly, lineEndingProcessing, inPlaceEdit, inPlaceBackupExtension, implicitLoop, implicitSedLoop, importFromPath, messageStrip, useJLine, new Configuration(
|
|
260 configurationBuilder));
|
|
261 }
|
|
262
|
|
263 /**
|
|
264 * Produce a canonical set of arguments that includes {@code $RUBYOPT} and has contractions such
|
|
265 * as a {@code -rdir} replaced with separate arguments {@code -r} and {@code dir} for simpler
|
|
266 * processing.
|
|
267 */
|
|
268 private static List<String> normalizeArgs(String[] args) {
|
|
269 assert args != null;
|
|
270
|
|
271 // Arguments come from the main method arguments parameter and $RUBYOPT
|
|
272
|
|
273 final List<String> inputArgs = new ArrayList<>();
|
|
274
|
|
275 final String rubyoptVar = System.getenv("RUBYOPT");
|
|
276
|
|
277 if (rubyoptVar != null) {
|
|
278 /*
|
|
279 * TODO(cs): what we've got here is a string that we are supposed to treat as if it was
|
|
280 * an extra part of the command line, including more Ruby options. However, we just get
|
|
281 * the string and have to split it into arguments ourselves. Normally the shell does
|
|
282 * that, including lots of fancy quoting styles. Are we supposed to re-implement all of
|
|
283 * that? Otherwise arguments in RUBYOPT will be parsed differently to if they were
|
|
284 * actually on the command line. JRuby just splits like we do. I also think that's what
|
|
285 * MRI does, but is this correct?
|
|
286 */
|
|
287
|
|
288 inputArgs.addAll(Arrays.asList(rubyoptVar.split("\\s+")));
|
|
289 }
|
|
290
|
|
291 inputArgs.addAll(Arrays.asList(args));
|
|
292
|
|
293 // Replace some contractions such as -rdir with -r dir
|
|
294
|
|
295 final List<String> outputArgs = new ArrayList<>();
|
|
296
|
|
297 for (String arg : inputArgs) {
|
|
298 if (arg.startsWith("-C") || arg.startsWith("-E") || arg.startsWith("-F") || arg.startsWith("-I") || arg.startsWith("-r")) {
|
|
299 outputArgs.add(arg.substring(0, 2));
|
|
300 outputArgs.add(arg.substring(2));
|
|
301 } else {
|
|
302 outputArgs.add(arg);
|
|
303 }
|
|
304 }
|
|
305
|
|
306 return outputArgs;
|
|
307 }
|
|
308
|
|
309 /**
|
|
310 * Print help information.
|
|
311 */
|
|
312 private static void help(PrintStream out) {
|
|
313 out.println("Usage: ruby [switches] [--] [programfile] [arguments]");
|
|
314 out.println(" -0[octal] specify record separator (\0, if no argument)");
|
|
315 out.println(" -a autosplit mode with -n or -p (splits $_ into $F)");
|
|
316 out.println(" -c check syntax only");
|
|
317 out.println(" -Cdirectory cd to directory, before executing your script");
|
|
318 out.println(" -d, --debug set debugging flags (set $DEBUG to true) and enable Debug module");
|
|
319 out.println(" -e 'command' one line of script. Several -e's allowed. Omit [programfile]");
|
|
320 out.println(" -Eex[:in] specify the default external and internal character encodings");
|
|
321 out.println(" -Fpattern split() pattern for autosplit (-a)");
|
|
322 out.println(" -i[extension] edit ARGV files in place (make backup if extension supplied)");
|
|
323 out.println(" -Idirectory specify $LOAD_PATH directory (may be used more than once)");
|
|
324 out.println(" -l enable line ending processing");
|
|
325 out.println(" -n assume 'while gets(); ... end' loop around your script");
|
|
326 out.println(" -p assume loop like -n but print line also like sed");
|
|
327 out.println(" -rlibrary require the library, before executing your script");
|
|
328 out.println(" -s enable some switch parsing for switches after script name");
|
|
329 out.println(" -S look for the script using PATH environment variable");
|
|
330 out.println(" -T[level=1] turn on tainting checks");
|
|
331 out.println(" -v print version number, then turn on verbose mode");
|
|
332 out.println(" -w turn warnings on for your script");
|
|
333 out.println(" -W[level=2] set warning level; 0=silence, 1=medium, 2=verbose");
|
|
334 out.println(" -x[directory] strip off text before #!ruby line and perhaps cd to directory");
|
|
335 out.println(" --copyright print the copyright");
|
|
336 out.println(" --version print the version");
|
|
337 out.println("Extra rubytruffle switches:");
|
|
338 out.println(" --home dir set the location of the Ruby Truffle installation (default . or $RUBY_TRUFFLE_HOME)");
|
|
339 out.println(" --stdlib dir use a directory for the Ruby standard library");
|
|
340 out.println(" --1.8 1.8 compatibility mode");
|
|
341 out.println(" --1.9 1.9 compatibility mode (default)");
|
|
342 out.println(" --2.0 2.0 compatibility mode");
|
|
343 out.println(" --full-object-space enable full ObjectSpace#each_object and similar");
|
|
344 out.println(" --no-debug disable debugging");
|
|
345 out.println(" --no-trace disable tracing");
|
|
346 out.println("Debugging rubytruffle switches:");
|
|
347 out.println(" --no-cache-constant-lookup don't cache constant lookups");
|
|
348 out.println(" --no-cache-method-calls don't cache method lookups");
|
|
349 out.println(" --no-intrinsic-method-calls don't turn method calls into intrinsic nodes");
|
|
350 out.println(" --no-jline don't use JLine");
|
|
351 out.println(" --print-parse-tree print the result of parsing");
|
|
352 out.println(" --print-executed-files print the name of files as they are executed");
|
|
353 out.println(" --print-missing-intrinsics print method calls that don't have intrinsic nodes");
|
|
354 out.println(" --print-spilt-instance-variables print each time a native-typed instance variable is spilt to the boxed array");
|
|
355 out.println(" --print-uninitialized-calls print each time a method call is uninitialized");
|
|
356 out.println(" --print-java-exceptions print Java exception back traces at the point of translating them to Ruby exceptions");
|
|
357 out.println(" --print-ruby-exceptions print the Java exception back traces at the point of raising Ruby exceptions");
|
|
358 out.println("Relevant environment variables:");
|
|
359 out.println(" RUBYHOME location of the Ruby Truffle installation");
|
|
360 out.println(" RUBYOPT extra command line arguments");
|
|
361 out.println(" RUBYLIB list of colon separated paths to add to $LOAD_PATH");
|
|
362 out.println(" PATH as RUBYLIB, if -S is used");
|
|
363 }
|
|
364
|
|
365 /**
|
|
366 * Print version information.
|
|
367 */
|
|
368 private static void version(PrintStream out) {
|
|
369 out.printf("ruby (rubytruffle) dev [JVM %s]\n", System.getProperty("java.version"));
|
|
370 }
|
|
371
|
|
372 /**
|
|
373 * Print copyright information.
|
|
374 */
|
|
375 private static void copyright(PrintStream out) {
|
|
376 out.println("Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.");
|
|
377 out.println("ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.");
|
|
378 }
|
|
379
|
|
380 }
|