Index: RubyKernel.java
===================================================================
RCS file: /cvsroot/jruby/jruby/src/org/jruby/RubyKernel.java,v
retrieving revision 1.45
diff -u -r1.45 RubyKernel.java
--- RubyKernel.java	28 Mar 2006 03:06:16 -0000	1.45
+++ RubyKernel.java	6 Apr 2006 02:04:51 -0000
@@ -35,11 +35,14 @@
  ***** END LICENSE BLOCK *****/
 package org.jruby;
 
-import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStreamReader;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
 import java.util.Iterator;
+import java.util.List;
 import java.util.StringTokenizer;
 import java.util.regex.Pattern;
 
@@ -55,6 +58,7 @@
 import org.jruby.runtime.load.IAutoloadMethod;
 import org.jruby.runtime.load.LoadService;
 import org.jruby.util.PrintfFormat;
+import org.jruby.util.UnsynchronizedStack;
 
 /**
  * Note: For CVS history, see KernelModule.java.
@@ -683,10 +687,12 @@
     }
 
     public static IRubyObject backquote(IRubyObject recv, IRubyObject aString) {
-        StringBuffer output = new StringBuffer();
         IRuby runtime = recv.getRuntime();
-        runtime.getGlobalVariables().set("$?", runtime.newFixnum(
-            runInShell(runtime, aString.toString(), output)));
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        
+        int resultCode = runInShell(runtime, new IRubyObject[] {aString}, output);
+        
+        recv.getRuntime().getGlobalVariables().set("$?", runtime.newFixnum(resultCode));
         
         return recv.getRuntime().newString(output.toString());
     }
@@ -702,93 +708,178 @@
      * @return The "fixed" full command line
      */
     private static String repairDirSeps(String command) {
-        // TODO: This could be improved and optimized
-        StringTokenizer toker = new StringTokenizer(command, " ");
-        StringBuffer executable = new StringBuffer();
-        
-        boolean insideQuotes = false;
-        char quoteChar = 0;
-        loop: while (true) {
-            String token = toker.nextToken();
+        String executable = "", remainder = "";
+        command = command.trim();
+        if (command.startsWith("'")) {
+            String [] tokens = command.split("'", 3);
+            executable = "'"+tokens[1]+"'";
+            if (tokens.length > 2)
+                remainder = tokens[2];
+        } else if (command.startsWith("\"")) {
+            String [] tokens = command.split("\"", 3);
+            executable = "\""+tokens[1]+"\"";
+            if (tokens.length > 2)
+                remainder = tokens[2];
+        } else {
+            String [] tokens = command.split(" ", 2);
+            executable = tokens[0];
+            if (tokens.length > 1)
+                remainder = " "+tokens[1];
+        }
+        
+        // Matcher.replaceAll treats backslashes in the replacement string as escaped characters
+        String replacement = File.separator;
+        if (File.separatorChar == '\\')
+            replacement = "\\\\";
             
-            if (!insideQuotes) {
-                char first = token.charAt(0);
-                switch (first) {
-                    case '"':
-                    case '\'':
-                        insideQuotes = true;
-                        quoteChar = first;
-                        executable.append(token + " ");
-                        break;
-                    default:
-                        executable.append(token);
-                        break loop;
+        return PATH_SEPARATORS.matcher(executable).replaceAll(replacement) + remainder;
+                }
+
+    private static List parseCommandLine(IRubyObject[] rawArgs) {
+        // first parse the first element of rawArgs since this may contain
+        // the whole command line
+        String command = rawArgs[0].toString();
+        UnsynchronizedStack args = new UnsynchronizedStack();
+        StringTokenizer st = new StringTokenizer(command, " ");
+        String quoteChar = null;
+
+        while (st.hasMoreTokens()) {
+            String token = st.nextToken();
+            if (quoteChar == null) {
+                // not currently in the middle of a quoted token
+                if (token.startsWith("'") || token.startsWith("\"")) {
+                    // note quote char and remove from beginning of token
+                    quoteChar = token.substring(0, 1);
+                    token = token.substring(1);
                 }
+                if (quoteChar!=null && token.endsWith(quoteChar)) {
+                    // quoted token self contained, remove from end of token
+                    token = token.substring(0, token.length()-1);
+                    quoteChar = null;
+                }
+                // add new token to list
+                args.push(token);
             } else {
-                char last = token.charAt(token.length() - 1);
-                executable.append(token);
-                if (last == quoteChar) {
-                    insideQuotes = false;
-                    break loop;
-                } else {
-                    executable.append(" ");
+                // in the middle of quoted token
+                if (token.endsWith(quoteChar)) {
+                    // end of quoted token
+                    token = token.substring(0, token.length()-1);
+                    quoteChar = null;
                 }
+                // update token at end of list
+                token = args.pop() + " " + token;
+                args.push(token);
             }
         }
         
-        String remainder = command.substring(executable.length());
+        // now append the remaining raw args to the cooked arg list
+        for (int i=1;i<rawArgs.length;i++) {
+            args.push(rawArgs[i].toString());
+        }
         
-        // Matcher.replaceAll treats backslashes in the replacement string as escaped characters
-        String replacement = File.separator;
-        if (File.separatorChar == '\\') replacement = "\\\\";
+        return args;
+    }
         
-        return PATH_SEPARATORS.matcher(executable).replaceAll(replacement) + remainder;
+    private static boolean isRubyCommand(String command) {
+        command = command.trim();
+        String [] spaceDelimitedTokens = command.split(" ", 2);
+        String [] slashDelimitedTokens = spaceDelimitedTokens[0].split("/");
+        String finalToken = slashDelimitedTokens[slashDelimitedTokens.length-1];
+        if (finalToken.contains("ruby") || finalToken.endsWith(".rb"))
+            return true;
+        else
+            return false;
     }
 
-    private static int runInShell(IRuby runtime, String command, StringBuffer output) {
+    private static int runInShell(IRuby runtime, IRubyObject[] rawArgs, OutputStream output) {
         try {
+            // startup scripts set jruby.shell to /bin/sh for Unix, cmd.exe for Windows
             String shell = System.getProperty("jruby.shell");
-            Process aProcess;
-            String shellSwitch = "-c";
+            rawArgs[0] = runtime.newString(repairDirSeps(rawArgs[0].toString()));
+            Process aProcess = null;
             
-            command = repairDirSeps(command);
+            if (isRubyCommand(rawArgs[0].toString())) {
+                final List args = parseCommandLine(rawArgs);
+                final String command = (String)args.get(0);
+                Thread t = new Thread() {
+                    public void run() {
+                        try {
+                            String[] argArray = new String[args.size()-1];
+                            // snip off ruby or jruby command from list of arguments
+                            // leave alone if the command is the name of a script
+                            int startIndex = command.endsWith(".rb") ? 0 : 1;
+                            args.subList(startIndex,args.size()).toArray(argArray);
+                            org.jruby.Main.main(argArray);
+                        } catch (Throwable t) {
+                        }
+                    }
+                };
+                
+                // FIXME: Must find a better way to capture stdout, probably by passing in a stream to Main
+                // redirect stdout to output
+                PrintStream out = System.out;
+                PrintStream redirect = new PrintStream(output);
+                System.setOut(redirect);
+                
+                // execute ruby command in new thread
+                t.start();
+                t.join();
+                
+                // reset stdout and stderr back to their orignal values
+                System.setOut(out);
+            } else if (shell != null && rawArgs.length == 1) {
+                // execute command with sh -c or cmd.exe /c
+                // this does shell expansion of wildcards
+                String shellSwitch = shell.endsWith("sh") ? "-c" : "/c";
+                String[] argArray = new String[3];
+                argArray[0] = shell;
+                argArray[1] = shellSwitch;
+                argArray[2] = rawArgs[0].toString();
+                aProcess = Runtime.getRuntime().exec(argArray);
+            } else {
+                // execute command directly, no wildcard expansion
+                if (rawArgs.length > 1) {
+                    String[] argArray = new String[rawArgs.length];
+                    for (int i=0;i<rawArgs.length;i++) {
+                        argArray[i] = rawArgs[i].toString();
+                    }
+                    aProcess = Runtime.getRuntime().exec(argArray);
+                } else {
+                    aProcess = Runtime.getRuntime().exec(rawArgs[0].toString());
+                }
+            }
             
-            if (shell != null) {
-                if (!shell.endsWith("sh")) {
-                    shellSwitch = "/c";
+            if (aProcess != null) {
+                InputStream processOutput = aProcess.getInputStream();
+                
+                // Fairly innefficient impl, but readLine is unable to tell
+                // whether the last line in a process ended with a newline or not.
+                int b;
+                boolean crSeen = false;
+                while ((b = processOutput.read()) != -1) {
+                    if (b == '\r') {
+                        crSeen = true;
+                    } else {
+                        if (crSeen) {
+                            if (b != '\n') {
+                                output.write('\r');
+                            }
+                            crSeen = false;
+                        }
+                        output.write(b);
+                    }
+                }
+                if (crSeen) {
+                    output.write('\r');
                 }
-                aProcess = Runtime.getRuntime().exec(new String[] { shell, shellSwitch, command });
+                aProcess.getErrorStream().close();
+                aProcess.getOutputStream().close();
+                processOutput.close();
+                
+                return aProcess.waitFor();
             } else {
-                aProcess = Runtime.getRuntime().exec(command);
+                return 0;
             }
-
-            final BufferedReader reader = new BufferedReader(new InputStreamReader(aProcess.getInputStream()));
-
-            // Fairly innefficient impl, but readLine is unable to tell 
-            // whether the last line in a process ended with a newline or not.
-            int c;
-            boolean crSeen = false;
-            while ((c = reader.read()) != -1) {
-            	if (c == '\r') {
-            		crSeen = true;
-            	} else {
-            		if (crSeen) {
-            			if (c != '\n') {
-            				output.append('\r');
-            			}
-            			crSeen = false;
-            		}
-            		output.append((char)c);
-            	}
-            }
-            if (crSeen) {
-            	output.append('\r');
-            }
-            aProcess.getErrorStream().close();
-            aProcess.getOutputStream().close();
-            reader.close();
-            
-            return aProcess.waitFor();
         } catch (IOException e) {
             throw runtime.newIOErrorFromException(e);
         } catch (InterruptedException e) {
@@ -839,11 +930,8 @@
 
     public static RubyBoolean system(IRubyObject recv, IRubyObject[] args) {
         IRuby runtime = recv.getRuntime();
-        if (args.length > 1) {
-            throw runtime.newArgumentError("more arguments not yet supported");
-        }
-        StringBuffer output = new StringBuffer();
-        int resultCode = runInShell(runtime, args[0].toString(), output);
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        int resultCode = runInShell(runtime, args, output);
         recv.getRuntime().getGlobalVariables().set("$?", runtime.newFixnum(resultCode));
         return runtime.newBoolean(resultCode == 0);
     }
