ArgumentProcessor.java

  1. /***** BEGIN LICENSE BLOCK *****
  2.  * Version: EPL 1.0/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Eclipse Public
  5.  * License Version 1.0 (the "License"); you may not use this file
  6.  * except in compliance with the License. You may obtain a copy of
  7.  * the License at http://www.eclipse.org/legal/epl-v10.html
  8.  *
  9.  * Software distributed under the License is distributed on an "AS
  10.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11.  * implied. See the License for the specific language governing
  12.  * rights and limitations under the License.
  13.  *
  14.  * Copyright (C) 2007-2011 Nick Sieger <nicksieger@gmail.com>
  15.  * Copyright (C) 2009 Joseph LaFata <joe@quibb.org>
  16.  *
  17.  * Alternatively, the contents of this file may be used under the terms of
  18.  * either of the GNU General Public License Version 2 or later (the "GPL"),
  19.  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  20.  * in which case the provisions of the GPL or the LGPL are applicable instead
  21.  * of those above. If you wish to allow use of your version of this file only
  22.  * under the terms of either the GPL or the LGPL, and not to allow others to
  23.  * use your version of this file under the terms of the EPL, indicate your
  24.  * decision by deleting the provisions above and replace them with the notice
  25.  * and other provisions required by the GPL or the LGPL. If you do not delete
  26.  * the provisions above, a recipient may use your version of this file under
  27.  * the terms of any one of the EPL, the GPL or the LGPL.
  28.  ***** END LICENSE BLOCK *****/
  29. package org.jruby.util.cli;

  30. import org.jruby.Ruby;
  31. import org.jruby.RubyInstanceConfig;
  32. import org.jruby.exceptions.MainExitException;
  33. import org.jruby.runtime.profile.builtin.ProfileOutput;
  34. import org.jruby.util.JRubyFile;
  35. import org.jruby.util.KCode;
  36. import org.jruby.util.SafePropertyAccessor;

  37. import java.io.File;
  38. import java.io.FileNotFoundException;
  39. import java.io.IOException;
  40. import java.util.ArrayList;
  41. import java.util.Arrays;
  42. import java.util.List;
  43. import java.util.Set;
  44. import java.util.HashSet;

  45. /**
  46.  * Encapsulated logic for processing JRuby's command-line arguments.
  47.  *
  48.  * This class holds the processing logic for JRuby's non-JVM command-line arguments.
  49.  * All standard Ruby options are processed here, as well as nonstandard JRuby-
  50.  * specific options.
  51.  *
  52.  * Options passed directly to the JVM are processed separately, by either a launch
  53.  * script or by a native executable.
  54.  */
  55. public class ArgumentProcessor {
  56.     private final class Argument {
  57.         public final String originalValue;
  58.         public final String dashedValue;
  59.         public Argument(String value, boolean dashed) {
  60.             this.originalValue = value;
  61.             this.dashedValue = dashed && !value.startsWith("-") ? "-" + value : value;
  62.         }

  63.         public String toString() {
  64.             return dashedValue;
  65.         }
  66.     }

  67.     private List<Argument> arguments;
  68.     private int argumentIndex = 0;
  69.     private boolean processArgv;
  70.     private final boolean rubyOpts;
  71.     RubyInstanceConfig config;
  72.     private boolean endOfArguments = false;
  73.     private int characterIndex = 0;

  74.     public ArgumentProcessor(String[] arguments, RubyInstanceConfig config) {
  75.         this(arguments, true, false, false, config);
  76.     }

  77.     public ArgumentProcessor(String[] arguments, boolean processArgv, boolean dashed, boolean rubyOpts, RubyInstanceConfig config) {
  78.         this.config = config;
  79.         this.arguments = new ArrayList<Argument>();
  80.         if (arguments != null && arguments.length > 0) {
  81.             for (String argument : arguments) {
  82.                 this.arguments.add(new Argument(argument, dashed));
  83.             }
  84.         }
  85.         this.processArgv = processArgv;
  86.         this.rubyOpts = rubyOpts;
  87.     }

  88.     public void processArguments() {
  89.         processArguments(true);
  90.     }

  91.     public void processArguments(boolean inline) {
  92.         checkProperties();

  93.         while (argumentIndex < arguments.size() && isInterpreterArgument(arguments.get(argumentIndex).originalValue)) {
  94.             processArgument();
  95.             argumentIndex++;
  96.         }
  97.         if (inline && !config.isInlineScript() && config.getScriptFileName() == null && !config.isForceStdin()) {
  98.             if (argumentIndex < arguments.size()) {
  99.                 config.setScriptFileName(arguments.get(argumentIndex).originalValue); //consume the file name
  100.                 argumentIndex++;
  101.             }
  102.         }
  103.         if (processArgv) {
  104.             processArgv();
  105.         }
  106.     }

  107.     private void processArgv() {
  108.         List<String> arglist = new ArrayList<String>();
  109.         for (; argumentIndex < arguments.size(); argumentIndex++) {
  110.             String arg = arguments.get(argumentIndex).originalValue;
  111.             if (config.isArgvGlobalsOn() && arg.startsWith("-")) {
  112.                 arg = arg.substring(1);
  113.                 if (arg.indexOf('=') > 0) {
  114.                     String[] keyvalue = arg.split("=", 2);

  115.                     // argv globals getService their dashes replaced with underscores
  116.                     String globalName = keyvalue[0].replaceAll("-", "_");
  117.                     config.getOptionGlobals().put(globalName, keyvalue[1]);
  118.                 } else {
  119.                     config.getOptionGlobals().put(arg, null);
  120.                 }
  121.             } else {
  122.                 config.setArgvGlobalsOn(false);
  123.                 arglist.add(arg);
  124.             }
  125.         }
  126.         // Remaining arguments are for the script itself
  127.         arglist.addAll(Arrays.asList(config.getArgv()));
  128.         config.setArgv(arglist.toArray(new String[arglist.size()]));
  129.     }

  130.     private boolean isInterpreterArgument(String argument) {
  131.         return argument.length() > 0 && (argument.charAt(0) == '-' || argument.charAt(0) == '+') && !endOfArguments;
  132.     }

  133.     private String getArgumentError(String additionalError) {
  134.         return "jruby: invalid argument\n" + additionalError + "\n";
  135.     }

  136.     private void processArgument() {
  137.         String argument = arguments.get(argumentIndex).dashedValue;

  138.         if (argument.length() == 1) {
  139.             // sole "-" means read from stdin and pass remaining args as ARGV
  140.             endOfArguments = true;
  141.             config.setForceStdin(true);
  142.             return;
  143.         }

  144.         FOR:
  145.         for (characterIndex = 1; characterIndex < argument.length(); characterIndex++) {
  146.             switch (argument.charAt(characterIndex)) {
  147.                 case '0':
  148.                     {
  149.                         disallowedInRubyOpts(argument);
  150.                         String temp = grabOptionalValue();
  151.                         if (null == temp) {
  152.                             config.setRecordSeparator("\u0000");
  153.                         } else if (temp.equals("0")) {
  154.                             config.setRecordSeparator("\n\n");
  155.                         } else if (temp.equals("777")) {
  156.                             config.setRecordSeparator("\uffff"); // Specify something that can't separate
  157.                         } else {
  158.                             try {
  159.                                 int val = Integer.parseInt(temp, 8);
  160.                                 config.setRecordSeparator("" + (char) val);
  161.                             } catch (Exception e) {
  162.                                 MainExitException mee = new MainExitException(1, getArgumentError(" -0 must be followed by either 0, 777, or a valid octal value"));
  163.                                 mee.setUsageError(true);
  164.                                 throw mee;
  165.                             }
  166.                         }
  167.                         break FOR;
  168.                     }
  169.                 case 'a':
  170.                     disallowedInRubyOpts(argument);
  171.                     config.setSplit(true);
  172.                     break;
  173.                 case 'c':
  174.                     disallowedInRubyOpts(argument);
  175.                     config.setShouldCheckSyntax(true);
  176.                     break;
  177.                 case 'C':
  178.                     disallowedInRubyOpts(argument);
  179.                     try {
  180.                         String saved = grabValue(getArgumentError(" -C must be followed by a directory expression"));
  181.                         File base = new File(config.getCurrentDirectory());
  182.                         File newDir = new File(saved);
  183.                         if (newDir.isAbsolute()) {
  184.                             config.setCurrentDirectory(newDir.getCanonicalPath());
  185.                         } else {
  186.                             config.setCurrentDirectory(new File(base, newDir.getPath()).getCanonicalPath());
  187.                         }
  188.                         if (!(new File(config.getCurrentDirectory()).isDirectory())) {
  189.                             MainExitException mee = new MainExitException(1, "jruby: Can't chdir to " + saved + " (fatal)");
  190.                             throw mee;
  191.                         }
  192.                     } catch (IOException e) {
  193.                         MainExitException mee = new MainExitException(1, getArgumentError(" -C must be followed by a valid directory"));
  194.                         throw mee;
  195.                     }
  196.                     break FOR;
  197.                 case 'd':
  198.                     config.setDebug(true);
  199.                     config.setVerbosity(RubyInstanceConfig.Verbosity.TRUE);
  200.                     break;
  201.                 case 'e':
  202.                     disallowedInRubyOpts(argument);
  203.                     config.getInlineScript().append(grabValue(getArgumentError(" -e must be followed by an expression to report")));
  204.                     config.getInlineScript().append('\n');
  205.                     config.setHasInlineScript(true);
  206.                     break FOR;
  207.                 case 'E':
  208.                     processEncodingOption(grabValue(getArgumentError("unknown encoding name")));
  209.                     break FOR;
  210.                 case 'F':
  211.                     disallowedInRubyOpts(argument);
  212.                     config.setInputFieldSeparator(grabValue(getArgumentError(" -F must be followed by a pattern for input field separation")));
  213.                     break FOR;
  214.                 case 'h':
  215.                     disallowedInRubyOpts(argument);
  216.                     config.setShouldPrintUsage(true);
  217.                     config.setShouldRunInterpreter(false);
  218.                     break;
  219.                 case 'i':
  220.                     disallowedInRubyOpts(argument);
  221.                     config.setInPlaceBackupExtension(grabOptionalValue());
  222.                     if (config.getInPlaceBackupExtension() == null) {
  223.                         config.setInPlaceBackupExtension("");
  224.                     }
  225.                     break FOR;
  226.                 case 'I':
  227.                     String s = grabValue(getArgumentError("-I must be followed by a directory name to add to lib path"));
  228.                     String[] ls = s.split(java.io.File.pathSeparator);
  229.                     config.getLoadPaths().addAll(Arrays.asList(ls));
  230.                     break FOR;
  231.                 case 'J':
  232.                     grabOptionalValue();
  233.                     config.getError().println("warning: " + argument + " argument ignored (launched in same VM?)");
  234.                     break FOR;
  235.                 case 'K':
  236.                     // FIXME: No argument seems to work for -K in MRI plus this should not
  237.                     // siphon off additional args 'jruby -K ~/scripts/foo'.  Also better error
  238.                     // processing.
  239.                     String eArg = grabValue(getArgumentError("provide a value for -K"));

  240.                     config.setKCode(KCode.create(null, eArg));

  241.                     // source encoding
  242.                     config.setSourceEncoding(config.getKCode().getEncoding().toString());

  243.                     // set external encoding if not already specified
  244.                     if (config.getExternalEncoding() == null) {
  245.                         config.setExternalEncoding(config.getKCode().getEncoding().toString());
  246.                     }

  247.                     break;
  248.                 case 'l':
  249.                     disallowedInRubyOpts(argument);
  250.                     config.setProcessLineEnds(true);
  251.                     break;
  252.                 case 'n':
  253.                     disallowedInRubyOpts(argument);
  254.                     config.setAssumeLoop(true);
  255.                     config.setKernelGsubDefined(true);
  256.                     break;
  257.                 case 'p':
  258.                     disallowedInRubyOpts(argument);
  259.                     config.setAssumePrinting(true);
  260.                     config.setAssumeLoop(true);
  261.                     config.setKernelGsubDefined(true);
  262.                     break;
  263.                 case 'r':
  264.                     config.getRequiredLibraries().add(grabValue(getArgumentError("-r must be followed by a package to require")));
  265.                     break FOR;
  266.                 case 's':
  267.                     disallowedInRubyOpts(argument);
  268.                     config.setArgvGlobalsOn(true);
  269.                     break;
  270.                 case 'G':
  271.                     config.setLoadGemfile(true);
  272.                     break;
  273.                 case 'S':
  274.                     disallowedInRubyOpts(argument);
  275.                     runBinScript();
  276.                     break FOR;
  277.                 case 'T':
  278.                     {
  279.                         String temp = grabOptionalValue();
  280.                         break FOR;
  281.                     }
  282.                 case 'U':
  283.                     config.setInternalEncoding("UTF-8");
  284.                     break;
  285.                 case 'v':
  286.                     config.setVerbosity(RubyInstanceConfig.Verbosity.TRUE);
  287.                     config.setShowVersion(true);
  288.                     break;
  289.                 case 'w':
  290.                     config.setVerbosity(RubyInstanceConfig.Verbosity.TRUE);
  291.                     break;
  292.                 case 'W':
  293.                     {
  294.                         String temp = grabOptionalValue();
  295.                         if (temp == null) {
  296.                             config.setVerbosity(RubyInstanceConfig.Verbosity.TRUE);
  297.                         } else {
  298.                             if (temp.equals("0")) {
  299.                                 config.setVerbosity(RubyInstanceConfig.Verbosity.NIL);
  300.                             } else if (temp.equals("1")) {
  301.                                 config.setVerbosity(RubyInstanceConfig.Verbosity.FALSE);
  302.                             } else if (temp.equals("2")) {
  303.                                 config.setVerbosity(RubyInstanceConfig.Verbosity.TRUE);
  304.                             } else {
  305.                                 MainExitException mee = new MainExitException(1, getArgumentError(" -W must be followed by either 0, 1, 2 or nothing"));
  306.                                 mee.setUsageError(true);
  307.                                 throw mee;
  308.                             }
  309.                         }
  310.                         break FOR;
  311.                     }
  312.                 case 'x':
  313.                     disallowedInRubyOpts(argument);
  314.                     try {
  315.                         String saved = grabOptionalValue();
  316.                         if (saved != null) {
  317.                             File base = new File(config.getCurrentDirectory());
  318.                             File newDir = new File(saved);
  319.                             if (newDir.isAbsolute()) {
  320.                                 config.setCurrentDirectory(newDir.getCanonicalPath());
  321.                             } else {
  322.                                 config.setCurrentDirectory(new File(base, newDir.getPath()).getCanonicalPath());
  323.                             }
  324.                             if (!(new File(config.getCurrentDirectory()).isDirectory())) {
  325.                                 MainExitException mee = new MainExitException(1, "jruby: Can't chdir to " + saved + " (fatal)");
  326.                                 throw mee;
  327.                             }
  328.                         }
  329.                         config.setXFlag(true);
  330.                     } catch (IOException e) {
  331.                         MainExitException mee = new MainExitException(1, getArgumentError(" -x must be followed by a valid directory"));
  332.                         throw mee;
  333.                     }
  334.                     break FOR;
  335.                 case 'X':
  336.                     disallowedInRubyOpts(argument);
  337.                     String extendedOption = grabOptionalValue();
  338.                     if (extendedOption == null) {
  339.                         if (SafePropertyAccessor.getBoolean("jruby.launcher.nopreamble", false)) {
  340.                             throw new MainExitException(0, OutputStrings.getExtendedHelp());
  341.                         } else {
  342.                             throw new MainExitException(0, "jruby: missing argument\n" + OutputStrings.getExtendedHelp());
  343.                         }
  344.                     } else if (extendedOption.equals("-O")) {
  345.                         config.setObjectSpaceEnabled(false);
  346.                     } else if (extendedOption.equals("+O")) {
  347.                         config.setObjectSpaceEnabled(true);
  348.                     } else if (extendedOption.equals("-C") || extendedOption.equals("-CIR")) {
  349.                         config.setCompileMode(RubyInstanceConfig.CompileMode.OFF);
  350.                     } else if (extendedOption.equals("+C") || extendedOption.equals("+CIR")) {
  351.                         config.setCompileMode(RubyInstanceConfig.CompileMode.FORCE);
  352.                     } else if (extendedOption.equals("+T")) {
  353.                         checkGraalVersion();
  354.                         config.setCompileMode(RubyInstanceConfig.CompileMode.TRUFFLE);
  355.                         config.setDisableGems(true);
  356.                         Options.PARSER_DETAILED_SOURCE_POSITIONS.force(Boolean.toString(true));
  357.                     } else if (extendedOption.endsWith("...")) {
  358.                         Options.listPrefix(extendedOption.substring(0, extendedOption.length() - "...".length()));
  359.                         config.setShouldRunInterpreter(false);
  360.                     } else if (extendedOption.endsWith("?")) {
  361.                         Options.listContains(extendedOption.substring(0, extendedOption.length() - 1));
  362.                         config.setShouldRunInterpreter(false);
  363.                     } else {
  364.                         MainExitException mee = new MainExitException(1, "jruby: invalid extended option " + extendedOption + " (-X will list valid options)\n");
  365.                         mee.setUsageError(true);
  366.                         throw mee;
  367.                     }
  368.                     break FOR;
  369.                 case 'y':
  370.                     disallowedInRubyOpts(argument);
  371.                     config.setParserDebug(true);
  372.                     break FOR;
  373.                 case '-':
  374.                     if (argument.equals("--command") || argument.equals("--bin")) {
  375.                         characterIndex = argument.length();
  376.                         runBinScript();
  377.                         break;
  378.                     } else if (argument.equals("--compat")) {
  379.                         characterIndex = argument.length();
  380.                         grabValue(getArgumentError("--compat takes an argument, but will be ignored"));
  381.                         config.getError().println("warning: " + argument + " ignored");
  382.                         break FOR;
  383.                     } else if (argument.equals("--copyright")) {
  384.                         disallowedInRubyOpts(argument);
  385.                         config.setShowCopyright(true);
  386.                         config.setShouldRunInterpreter(false);
  387.                         break FOR;
  388.                     } else if (argument.equals("--debug")) {
  389.                         disallowedInRubyOpts(argument);
  390.                         RubyInstanceConfig.FULL_TRACE_ENABLED = true;
  391.                         config.setCompileMode(RubyInstanceConfig.CompileMode.OFF);
  392.                         break FOR;
  393.                     } else if (argument.equals("--jdb")) {
  394.                         config.setDebug(true);
  395.                         config.setVerbosity(RubyInstanceConfig.Verbosity.TRUE);
  396.                         break;
  397.                     } else if (argument.equals("--help")) {
  398.                         disallowedInRubyOpts(argument);
  399.                         config.setShouldPrintUsage(true);
  400.                         config.setShouldRunInterpreter(false);
  401.                         break;
  402.                     } else if (argument.equals("--properties")) {
  403.                         config.setShouldPrintProperties(true);
  404.                         config.setShouldRunInterpreter(false);
  405.                         break;
  406.                     } else if (argument.equals("--version")) {
  407.                         disallowedInRubyOpts(argument);
  408.                         config.setShowVersion(true);
  409.                         config.setShouldRunInterpreter(false);
  410.                         break FOR;
  411.                     } else if (argument.equals("--bytecode")) {
  412.                         config.setShowBytecode(true);
  413.                         break FOR;
  414.                     } else if (argument.equals("--fast")) {
  415.                         config.setCompileMode(RubyInstanceConfig.CompileMode.FORCE);
  416.                         break FOR;
  417.                     } else if (argument.startsWith("--profile")) {
  418.                         characterIndex = argument.length();
  419.                         int dotIndex = argument.indexOf(".");
  420.                        
  421.                         if (dotIndex == -1) {
  422.                             config.setProfilingMode(RubyInstanceConfig.ProfilingMode.FLAT);
  423.                            
  424.                         } else {
  425.                             String profilingMode = argument.substring(dotIndex + 1, argument.length());
  426.                            
  427.                             if (profilingMode.equals("out")) {
  428.                                 // output file for profiling results
  429.                                 String outputFile = grabValue(getArgumentError("--profile.out requires an output file argument"));
  430.                                
  431.                                 try {
  432.                                     config.setProfileOutput(new ProfileOutput(new File(outputFile)));
  433.                                 } catch (FileNotFoundException e) {
  434.                                     throw new MainExitException(1, String.format("jruby: %s", e.getMessage()));
  435.                                 }

  436.                             } else if (profilingMode.equals("service")) {
  437.                                 // service class name
  438.                                 String service = grabValue(getArgumentError("--profile.service requires an class name argument"));

  439.                                 config.setProfilingMode( RubyInstanceConfig.ProfilingMode.SERVICE);
  440.                                 config.setProfilingService(service);

  441.                             } else {
  442.                                 try {
  443.                                     config.setProfilingMode(RubyInstanceConfig.ProfilingMode.valueOf(profilingMode.toUpperCase()));
  444.                                 } catch (IllegalArgumentException e) {
  445.                                     throw new MainExitException(1, String.format("jruby: unknown profiler mode \"%s\"", profilingMode));
  446.                                 }
  447.                             }
  448.                         }
  449.                        
  450.                         break FOR;
  451.                     } else if (argument.equals("--1.8")) {
  452.                         config.getError().println("warning: " + argument + " ignored");
  453.                         break FOR;
  454.                     } else if (argument.equals("--1.9")) {
  455.                         config.getError().println("warning: " + argument + " ignored");
  456.                         break FOR;
  457.                     } else if (argument.equals("--2.0")) {
  458.                         config.getError().println("warning: " + argument + " ignored");
  459.                         break FOR;
  460.                     } else if (argument.equals("--2.1")) {
  461.                         // keep the switch for consistency
  462.                         break FOR;
  463.                     } else if (argument.equals("--disable-gems")) {
  464.                         config.setDisableGems(true);
  465.                         break FOR;
  466.                     } else if (argument.equals("--disable")) {
  467.                         errorMissingDisable();
  468.                     } else if (argument.startsWith("--disable=")) {
  469.                         String disablesStr = argument.substring("--disable=".length());
  470.                         String[] disables = disablesStr.split(",");

  471.                         if (disables.length == 0) errorMissingDisable();

  472.                         for (String disable : disables) {
  473.                             boolean all = disable.equals("all");
  474.                             if (disable.equals("gems") || all) {
  475.                                 config.setDisableGems(true);
  476.                                 continue;
  477.                             }
  478.                             if (disable.equals("rubyopt") || all) {
  479.                                 config.setDisableRUBYOPT(true);
  480.                                 continue;
  481.                             }

  482.                             config.getError().println("warning: unknown argument for --disable: `" + disable + "'");
  483.                         }
  484.                         break FOR;
  485.                     } else if (argument.equals("--gemfile")) {
  486.                         config.setLoadGemfile(true);
  487.                         break FOR;
  488.                     } else if (argument.equals("--dump")) {
  489.                         characterIndex = argument.length();
  490.                         String error = "--dump only supports [version, copyright, usage, yydebug, syntax, insns] on JRuby";
  491.                         String dumpArg = grabValue(getArgumentError(error));
  492.                         if (dumpArg.equals("version")) {
  493.                             config.setShowVersion(true);
  494.                             config.setShouldRunInterpreter(false);
  495.                             break FOR;
  496.                         } else if (dumpArg.equals("copyright")) {
  497.                             config.setShowCopyright(true);
  498.                             config.setShouldRunInterpreter(false);
  499.                             break FOR;
  500.                         } else if (dumpArg.equals("usage")) {
  501.                             config.setShouldPrintUsage(true);
  502.                             config.setShouldRunInterpreter(false);
  503.                             break FOR;
  504.                         } else if (dumpArg.equals("yydebug")) {
  505.                             config.setParserDebug(true);
  506.                             break FOR;
  507.                         } else if (dumpArg.equals("syntax")) {
  508.                             config.setShouldCheckSyntax(true);
  509.                         } else if (dumpArg.equals("insns")) {
  510.                             config.setShowBytecode(true);
  511.                         } else {
  512.                             MainExitException mee = new MainExitException(1, error);
  513.                             mee.setUsageError(true);
  514.                             throw mee;
  515.                         }
  516.                         break;
  517.                     } else if (argument.equals("--dev")) {
  518.                         // most we can do after JVM boot
  519.                         Options.COMPILE_INVOKEDYNAMIC.force("false");
  520.                         config.setCompileMode(RubyInstanceConfig.CompileMode.OFF);
  521.                         break FOR;
  522.                     } else if (argument.equals("--server")) {
  523.                         // ignore this...can't do anything with it after boot
  524.                         break FOR;
  525.                     } else if (argument.equals("--client")) {
  526.                         // ignore this...can't do anything with it after boot
  527.                         break FOR;
  528.                     } else if (argument.equals("--yydebug")) {
  529.                         disallowedInRubyOpts(argument);
  530.                         config.setParserDebug(true);
  531.                     } else {
  532.                         if (argument.equals("--")) {
  533.                             // ruby interpreter compatibilty
  534.                             // Usage: ruby [switches] [--] [programfile] [arguments])
  535.                             endOfArguments = true;
  536.                             break;
  537.                         }
  538.                     }
  539.                 default:
  540.                     throw new MainExitException(1, "jruby: unknown option " + argument);
  541.             }
  542.         }
  543.     }

  544.     private void disallowedInRubyOpts(String option) {
  545.         if (rubyOpts) {
  546.             throw new MainExitException(1, "jruby: invalid switch in RUBYOPT: " + option + " (RuntimeError)");
  547.         }
  548.     }

  549.     private void errorMissingDisable() {
  550.         MainExitException mee;
  551.         mee = new MainExitException(1, "missing argument for --disable\n");
  552.         mee.setUsageError(true);
  553.         throw mee;
  554.     }

  555.     private void processEncodingOption(String value) {
  556.         String[] encodings = value.split(":", 3);
  557.         switch (encodings.length) {
  558.             case 3:
  559.                 throw new MainExitException(1, "extra argument for -E: " + encodings[2]);
  560.             case 2:
  561.                 config.setInternalEncoding(encodings[1]);
  562.             case 1:
  563.                 config.setExternalEncoding(encodings[0]);
  564.                 // Zero is impossible
  565.         }
  566.     }

  567.     private void runBinScript() {
  568.         String scriptName = grabValue("jruby: provide a bin script to execute");
  569.         if (scriptName.equals("irb")) {
  570.             scriptName = "jirb";
  571.         }
  572.         config.setScriptFileName(resolveScript(scriptName));
  573.         // run as a command if we couldn't find a script
  574.         if (config.getScriptFileName() == null) {
  575.             config.setScriptFileName(scriptName);
  576.             config.getRequiredLibraries().add("jruby/commands");
  577.             config.getInlineScript().append("JRuby::Commands.").append(scriptName);
  578.             config.getInlineScript().append("\n");
  579.             config.setHasInlineScript(true);
  580.         }
  581.         endOfArguments = true;
  582.     }

  583.     private String resolveScript(String scriptName) {
  584.         // These try/catches are to allow failing over to the "commands" logic
  585.         // when running from within a jruby-complete jar file, which has
  586.         // jruby.home = a jar file URL that does not resolve correctly with
  587.         // JRubyFile.create.
  588.         File fullName = null;
  589.         try {
  590.             // try cwd first
  591.             fullName = JRubyFile.create(config.getCurrentDirectory(), scriptName);
  592.             if (fullName.exists() && fullName.isFile()) {
  593.                 logScriptResolutionSuccess(fullName.getAbsolutePath());
  594.                 return scriptName;
  595.             } else {
  596.                 logScriptResolutionFailure(config.getCurrentDirectory());
  597.             }
  598.         } catch (Exception e) {
  599.             // keep going, try bin/#{scriptName}
  600.         }
  601.         try {
  602.             fullName = JRubyFile.create(config.getJRubyHome(), "bin/" + scriptName);
  603.             if (fullName.exists() && fullName.isFile()) {
  604.                 logScriptResolutionSuccess(fullName.getAbsolutePath());
  605.                 return fullName.getAbsolutePath();
  606.             } else {
  607.                 logScriptResolutionFailure(config.getJRubyHome() + "/bin");
  608.             }
  609.         } catch (Exception e) {
  610.             // keep going, try PATH
  611.         }
  612.         if(Ruby.getClassLoader().getResourceAsStream("bin/" + scriptName) != null){
  613.             return "classpath:bin/" + scriptName;
  614.         }
  615.         try {
  616.             Object pathObj = config.getEnvironment().get("PATH");
  617.             String path = pathObj.toString();
  618.             if (path != null) {
  619.                 String[] paths = path.split(System.getProperty("path.separator"));
  620.                 for (int i = 0; i < paths.length; i++) {
  621.                     fullName = JRubyFile.create(new File(paths[i]).getAbsolutePath(), scriptName);
  622.                     if (fullName.exists() && fullName.isFile()) {
  623.                         logScriptResolutionSuccess(fullName.getAbsolutePath());
  624.                         return fullName.getAbsolutePath();
  625.                     }
  626.                 }
  627.                 logScriptResolutionFailure("PATH=" + path);
  628.             }
  629.         } catch (Exception e) {
  630.             // will fall back to JRuby::Commands
  631.         }
  632.         if (config.isDebug() || RubyInstanceConfig.DEBUG_SCRIPT_RESOLUTION) {
  633.             config.getError().println("warning: could not resolve -S script on filesystem: " + scriptName);
  634.         }
  635.         return null;
  636.     }

  637.     private String grabValue(String errorMessage) {
  638.         String optValue = grabOptionalValue();
  639.         if (optValue != null) {
  640.             return optValue;
  641.         }
  642.         argumentIndex++;
  643.         if (argumentIndex < arguments.size()) {
  644.             return arguments.get(argumentIndex).originalValue;
  645.         }
  646.         MainExitException mee = new MainExitException(1, errorMessage);
  647.         mee.setUsageError(true);
  648.         throw mee;
  649.     }

  650.     private String grabOptionalValue() {
  651.         characterIndex++;
  652.         String argValue = arguments.get(argumentIndex).originalValue;
  653.         if (characterIndex < argValue.length()) {
  654.             return argValue.substring(characterIndex);
  655.         }
  656.         return null;
  657.     }

  658.     private void logScriptResolutionSuccess(String path) {
  659.         if (RubyInstanceConfig.DEBUG_SCRIPT_RESOLUTION) {
  660.             config.getError().println("Found: " + path);
  661.         }
  662.     }

  663.     private void logScriptResolutionFailure(String path) {
  664.         if (RubyInstanceConfig.DEBUG_SCRIPT_RESOLUTION) {
  665.             config.getError().println("Searched: " + path);
  666.         }
  667.     }

  668.     public static void checkGraalVersion() {
  669.         if (Options.TRUFFLE_RUNTIME_VERSION_CHECK.load()) {
  670.             final String graalVersion = System.getProperty("graal.version", "unknown");
  671.             final String expectedGraalVersion = "0.6";

  672.             if (graalVersion.equals("unknown")) {
  673.                 return;
  674.             } else if (!graalVersion.equals(expectedGraalVersion)) {
  675.                 throw new RuntimeException("This version of JRuby is built against Graal " + expectedGraalVersion + " but you are using it with version " + graalVersion + " - either update Graal or use with (-J)-original to disable Graal and ignore this error");
  676.             }
  677.         }
  678.     }

  679.     private void checkProperties() {
  680.         final Set<String> propertyNames = new HashSet<>();
  681.         propertyNames.addAll(Options.getPropertyNames());
  682.         propertyNames.add("jruby.home");
  683.         propertyNames.add("jruby.script");
  684.         propertyNames.add("jruby.shell");
  685.         propertyNames.add("jruby.lib");
  686.         propertyNames.add("jruby.bindir");
  687.         propertyNames.add("jruby.jar");
  688.         propertyNames.add("jruby.compat.version");
  689.         propertyNames.add("jruby.reflection");
  690.         propertyNames.add("jruby.thread.pool.enabled");

  691.         for (String propertyName : System.getProperties().stringPropertyNames()) {
  692.             if (propertyName.startsWith("jruby.")) {
  693.                 if (!propertyNames.contains(propertyName)) {
  694.                     System.err.println("jruby: warning: unknown property " + propertyName);
  695.                 }
  696.             }
  697.         }
  698.     }

  699. }