Make sure we capture the output of evaluation if there’s no explicit result

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736025 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/src/test/java/org/apache/felix/gogo/jline/AbstractParserTest.java b/gogo/jline/src/test/java/org/apache/felix/gogo/jline/AbstractParserTest.java
new file mode 100644
index 0000000..0b04ab3
--- /dev/null
+++ b/gogo/jline/src/test/java/org/apache/felix/gogo/jline/AbstractParserTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+import junit.framework.TestCase;
+import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
+
+public abstract class AbstractParserTest extends TestCase {
+
+    private ThreadIOImpl threadIO;
+    private InputStream sin;
+    private PrintStream sout;
+    private PrintStream serr;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        sin = new NoCloseInputStream(System.in);
+        sout = new NoClosePrintStream(System.out);
+        serr = new NoClosePrintStream(System.err);
+        threadIO = new ThreadIOImpl();
+        threadIO.start();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        threadIO.stop();
+        super.tearDown();
+    }
+
+    public class Context extends org.apache.felix.gogo.jline.Context {
+        public Context() {
+            super(AbstractParserTest.this.threadIO, sin, sout, serr);
+        }
+    }
+
+    private static class NoCloseInputStream extends FilterInputStream {
+        public NoCloseInputStream(InputStream in) {
+            super(in);
+        }
+        @Override
+        public void close() throws IOException {
+        }
+    }
+
+    private static class NoClosePrintStream extends PrintStream {
+        public NoClosePrintStream(OutputStream out) {
+            super(out);
+        }
+        @Override
+        public void close() {
+        }
+    }
+
+}
diff --git a/gogo/jline/src/test/java/org/apache/felix/gogo/jline/Context.java b/gogo/jline/src/test/java/org/apache/felix/gogo/jline/Context.java
new file mode 100644
index 0000000..5e7a859
--- /dev/null
+++ b/gogo/jline/src/test/java/org/apache/felix/gogo/jline/Context.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.file.Path;
+
+import org.apache.felix.gogo.runtime.CommandProcessorImpl;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.threadio.ThreadIO;
+
+public class Context extends CommandProcessorImpl
+{
+    public static final String EMPTY = "";
+    
+    private final CommandSession session;
+
+    public Context(ThreadIO threadio, InputStream in, PrintStream out, PrintStream err)
+    {
+        super(threadio);
+        Shell shell = new Shell(new MyContext(), this);
+        addCommand("gogo", this, "addCommand");
+        addCommand("gogo", this, "removeCommand");
+        addCommand("gogo", this, "eval");
+        register(this, new Builtin(), Builtin.functions);
+        register(this, new Procedural(), Procedural.functions);
+        register(this, new Posix(this), Posix.functions);
+        register(this, shell, Shell.functions);
+        session = createSession(in, out, err);
+    }
+
+    static void register(CommandProcessorImpl processor, Object target, String[] functions) {
+        for (String function : functions) {
+            processor.addCommand("gogo", target, function);
+        }
+    }
+
+    private static class MyContext implements Shell.Context {
+
+        public String getProperty(String name) {
+            return System.getProperty(name);
+        }
+
+        public void exit() throws Exception {
+            System.exit(0);
+        }
+    }
+
+    public Object execute(CharSequence source) throws Exception
+    {
+        Object result = new Exception();
+        try
+        {
+            return result = session.execute(source);
+        }
+        finally
+        {
+            System.err.println("execute<" + source + "> = ("
+                + (null == result ? "Null" : result.getClass().getSimpleName()) + ")("
+                + result + ")\n");
+        }
+    }
+
+    public void addCommand(String function, Object target)
+    {
+        addCommand("test", target, function);
+    }
+
+    public Object set(String name, Object value)
+    {
+        return session.put(name, value);
+    }
+
+    public Object get(String name)
+    {
+        return session.get(name);
+    }
+
+    public void currentDir(Path path) {
+        session.currentDir(path);
+    }
+
+    public Path currentDir() {
+        return session.currentDir();
+    }
+}
diff --git a/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ShellTest.java b/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ShellTest.java
new file mode 100644
index 0000000..b3e7738
--- /dev/null
+++ b/gogo/jline/src/test/java/org/apache/felix/gogo/jline/ShellTest.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import org.junit.Test;
+
+public class ShellTest extends AbstractParserTest {
+
+
+    @Test
+    public void test() throws Exception {
+        Context context = new Context();
+        context.execute("a = \"foo\"");
+        assertEquals("foo", context.get("a"));
+        context.execute("a = $(echo bar)");
+        assertEquals("bar", context.get("a"));
+    }
+
+}
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
index 0025f11..304485c 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
@@ -18,6 +18,7 @@
  */
 package org.apache.felix.gogo.runtime;
 
+import java.io.ByteArrayOutputStream;
 import java.io.EOFException;
 import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
@@ -144,11 +145,16 @@
     // implements Function interface
     public Object execute(CommandSession x, List<Object> values) throws Exception
     {
+        return execute(x, values, null);
+    }
+
+    public Object execute(CommandSession x, List<Object> values, Channel capturingOutput) throws Exception
+    {
         try
         {
             location.remove();
             session.put(LOCATION, null);
-            return execute(values);
+            return execute(values, capturingOutput);
         }
         catch (Exception e)
         {
@@ -157,7 +163,7 @@
     }
 
     @SuppressWarnings("unchecked")
-    private Object execute(List<Object> values) throws Exception
+    private Object execute(List<Object> values, Channel capturingOutput) throws Exception
     {
         if (null != values)
         {
@@ -213,6 +219,9 @@
                 streams = new Channel[10];
                 System.arraycopy(session.channels, 0, streams, 0, 3);
             }
+            if (capturingOutput != null) {
+                streams[1] = capturingOutput;
+            }
 
             List<Pipe> pipes = new ArrayList<>();
             if (executable instanceof Pipeline) {
@@ -317,8 +326,19 @@
         }
         else if (t instanceof Sequence)
         {
-            return new Closure(session, this, ((Sequence) t).program())
-                    .execute(session, parms);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            Channel out = Channels.newChannel(baos);
+            Object result = new Closure(session, this, ((Sequence) t).program())
+                    .execute(session, parms, out);
+            if (result != null) {
+                return result;
+            } else {
+                String s = baos.toString();
+                while (s.charAt(s.length() - 1) == '\n') {
+                    s = s.substring(0, s.length() - 1);
+                }
+                return s;
+            }
         }
         else if (t instanceof Array)
         {
@@ -342,7 +362,7 @@
         }
         else if (executable instanceof Sequence)
         {
-            return new Closure(session, this, ((Sequence) executable).program()).execute(new ArrayList<>());
+            return new Closure(session, this, ((Sequence) executable).program()).execute(new ArrayList<>(), null);
         }
         else
         {