Repository: zeppelin
Updated Branches:
  refs/heads/master 2b0e2a41c -> 034fdc673


[ZEPPELIN-1917] Improve python.conda interpreter

### What is this PR for?

Add missing commands to the `python.conda` interpreter

- `conda info`
- `conda list`
- `conda create`
- `conda install`
- `conda uninstall (alias of remove)`
- `conda env *`

#### Implementation Detail

The reason I modified `PythonProcess` is due to NPE

```java
// 
https://github.com/apache/zeppelin/blob/master/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java#L107-L118

  public String sendAndGetResult(String cmd) throws IOException {
    writer.println(cmd);
    writer.println();
    writer.println("\"" + STATEMENT_END + "\"");
    StringBuilder output = new StringBuilder();
    String line = null;

    // NPE when line is null
    while (!(line = reader.readLine()).contains(STATEMENT_END)) {
      logger.debug("Read line from python shell : " + line);
      output.append(line + "\n");
    }
    return output.toString();
  }
```

```
java.lang.NullPointerException
at 
org.apache.zeppelin.python.PythonProcess.sendAndGetResult(PythonProcess.java:113)
at 
org.apache.zeppelin.python.PythonInterpreter.sendCommandToPython(PythonInterpreter.java:250)
at 
org.apache.zeppelin.python.PythonInterpreter.bootStrapInterpreter(PythonInterpreter.java:272)
at org.apache.zeppelin.python.PythonInterpreter.open(PythonInterpreter.java:100)
at 
org.apache.zeppelin.python.PythonCondaInterpreter.restartPythonProcess(PythonCondaInterpreter.java:139)
at 
org.apache.zeppelin.python.PythonCondaInterpreter.interpret(PythonCondaInterpreter.java:88)
at 
org.apache.zeppelin.interpreter.LazyOpenInterpreter.interpret(LazyOpenInterpreter.java:94)
at 
org.apache.zeppelin.interpreter.remote.RemoteInterpreterServer$InterpretJob.jobRun(RemoteInterpreterServer.java:494)
at org.apache.zeppelin.scheduler.Job.run(Job.java:175)
at org.apache.zeppelin.scheduler.FIFOScheduler$1.run(FIFOScheduler.java:139)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at 
java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at 
java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at 
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at 
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
```

### What type of PR is it?
[Improvement | Refactoring]

### Todos
* [x] - info
* [x] - list
* [x] - create
* [x] - install
* [x] - uninstall (= remove)
* [x] - env *

### What is the Jira issue?

[ZEPPELIN-1917](https://issues.apache.org/jira/browse/ZEPPELIN-1917)

### How should this be tested?

1. Install [miniconda](http://conda.pydata.org/miniconda.html)
2. Make sure that your python interpreter can use `conda` (check the 
Interpreter Binding page)
3. Remove `test` conda env since we will create in the following section

```sh
$ conda env remove --yes --name test
```

4. Run these commands with `%python.conda`

```
%python.conda info
%python.conda env list
%python.conda create --name test

# you should be able to see `test` in the list
%python.conda env list
%python.conda activate pymysql
%python.conda install pymysql

# you should be able to import
%python
import pymysql.cursors

%python.conda uninstall pymysql
%python.conda deactivate pymysql

# you should be able to see `No module named pymysql.cursor` since we 
deactivated
%python
import pymysql.cursors
```

### Screenshots (if appropriate)

![conda-screenshot](https://cloud.githubusercontent.com/assets/4968473/21747565/98c0e366-d5ad-11e6-8000-e293996089fa.gif)

### Questions:
* Does the licenses files need update? - NO
* Is there breaking changes for older versions? - NO
* Does this needs documentation? - NO

Author: 1ambda <1am...@gmail.com>

Closes #1868 from 1ambda/ZEPPELIN-1917/improve-conda-interpreter and squashes 
the following commits:

3ba171a [1ambda] fix: Wrap output style
292ed6d [1ambda] refactor: Throw exception in runCommand
2d4aa7d [1ambda] test: Add some tests
49a4a11 [1ambda] feat: Supports other env commands
6eb7e92 [1ambda] fix: NPE in PythonProcess
9c5dd86 [1ambda] refactor: Activate, Deactivate
f955889 [1ambda] fix: minor
935cb89 [1ambda] refactor: Abstract commands
b1c4c9f [1ambda] feat: Add conda remove (uninstall)
e539c42 [1ambda] feat: Add conda install
4f58fa2 [1ambda] feat: Add conda create
7da132d [1ambda] docs: Add missing conda list description
929ca8a [1ambda] feat: Make conda output beautiful
0c6ebb4 [1ambda] feat: Add list conda command
017c76f [1ambda] refactor: Import InterpreterResult.{Code, Type} to short codes
b8a5154 [1ambda] refactor: Simplify exception flow so private funcs don't need 
care exceptions
64d4bef [1ambda] style: Rename some funcs
afc456d [1ambda] refactor: Add private to member vars
f36fc74 [1ambda] feat: Add info command
2eb9bf5 [1ambda] style: Remove useless newlines
bd2564e [1ambda] refactor: PythonCondaInterpreter.interpret
f0d69bc [1ambda] fix: Use specific command for env list in conda


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/034fdc67
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/034fdc67
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/034fdc67

Branch: refs/heads/master
Commit: 034fdc6735e075c89f727bb6bc6fddbc89b639c4
Parents: 2b0e2a4
Author: 1ambda <1am...@gmail.com>
Authored: Wed Jan 11 07:56:35 2017 +0900
Committer: Lee moon soo <m...@apache.org>
Committed: Fri Jan 13 13:36:37 2017 -0800

----------------------------------------------------------------------
 .../zeppelin/python/PythonCondaInterpreter.java | 319 ++++++++++++++-----
 .../apache/zeppelin/python/PythonProcess.java   |   5 +-
 .../resources/output_templates/conda_usage.html |  25 +-
 .../python/PythonCondaInterpreterTest.java      |  63 ++--
 4 files changed, 307 insertions(+), 105 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/034fdc67/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java
----------------------------------------------------------------------
diff --git 
a/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java 
b/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java
index 304e1f0..455d786 100644
--- 
a/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java
+++ 
b/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java
@@ -16,14 +16,16 @@
 */
 package org.apache.zeppelin.python;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.zeppelin.interpreter.*;
+import org.apache.zeppelin.interpreter.InterpreterResult.Code;
+import org.apache.zeppelin.interpreter.InterpreterResult.Type;
 import org.apache.zeppelin.scheduler.Scheduler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.*;
-import java.util.HashMap;
-import java.util.Properties;
+import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -36,11 +38,17 @@ public class PythonCondaInterpreter extends Interpreter {
   public static final String CONDA_PYTHON_PATH = "/bin/python";
   public static final String DEFAULT_ZEPPELIN_PYTHON = "python";
 
-  Pattern condaEnvListPattern = Pattern.compile("([^\\s]*)[\\s*]*\\s(.*)");
-  Pattern listPattern = Pattern.compile("env\\s*list\\s?");
-  Pattern activatePattern = Pattern.compile("activate\\s*(.*)");
-  Pattern deactivatePattern = Pattern.compile("deactivate");
-  Pattern helpPattern = Pattern.compile("help");
+  public static final Pattern PATTERN_OUTPUT_ENV_LIST = 
Pattern.compile("([^\\s]*)[\\s*]*\\s(.*)");
+  public static final Pattern PATTERN_COMMAND_ENV_LIST = 
Pattern.compile("env\\s*list\\s?");
+  public static final Pattern PATTERN_COMMAND_ENV = 
Pattern.compile("env\\s*(.*)");
+  public static final Pattern PATTERN_COMMAND_LIST = Pattern.compile("list");
+  public static final Pattern PATTERN_COMMAND_CREATE = 
Pattern.compile("create\\s*(.*)");
+  public static final Pattern PATTERN_COMMAND_ACTIVATE = 
Pattern.compile("activate\\s*(.*)");
+  public static final Pattern PATTERN_COMMAND_DEACTIVATE = 
Pattern.compile("deactivate");
+  public static final Pattern PATTERN_COMMAND_INSTALL = 
Pattern.compile("install\\s*(.*)");
+  public static final Pattern PATTERN_COMMAND_UNINSTALL = 
Pattern.compile("uninstall\\s*(.*)");
+  public static final Pattern PATTERN_COMMAND_HELP = Pattern.compile("help");
+  public static final Pattern PATTERN_COMMAND_INFO = Pattern.compile("info");
 
   public PythonCondaInterpreter(Properties property) {
     super(property);
@@ -59,33 +67,53 @@ public class PythonCondaInterpreter extends Interpreter {
   @Override
   public InterpreterResult interpret(String st, InterpreterContext context) {
     InterpreterOutput out = context.out;
+    Matcher activateMatcher = PATTERN_COMMAND_ACTIVATE.matcher(st);
+    Matcher createMatcher = PATTERN_COMMAND_CREATE.matcher(st);
+    Matcher installMatcher = PATTERN_COMMAND_INSTALL.matcher(st);
+    Matcher uninstallMatcher = PATTERN_COMMAND_UNINSTALL.matcher(st);
+    Matcher envMatcher = PATTERN_COMMAND_ENV.matcher(st);
 
-    Matcher listMatcher = listPattern.matcher(st);
-    Matcher activateMatcher = activatePattern.matcher(st);
-    Matcher deactivateMatcher = deactivatePattern.matcher(st);
-    Matcher helpMatcher = helpPattern.matcher(st);
-
-    if (st == null || st.isEmpty() || listMatcher.matches()) {
-      listEnv(out, getCondaEnvs());
-      return new InterpreterResult(InterpreterResult.Code.SUCCESS);
-    } else if (activateMatcher.matches()) {
-      String envName = activateMatcher.group(1);
-      changePythonEnvironment(envName);
-      restartPythonProcess();
-      return new InterpreterResult(InterpreterResult.Code.SUCCESS, "\"" + 
envName + "\" activated");
-    } else if (deactivateMatcher.matches()) {
-      changePythonEnvironment(null);
-      restartPythonProcess();
-      return new InterpreterResult(InterpreterResult.Code.SUCCESS, 
"Deactivated");
-    } else if (helpMatcher.matches()) {
-      printUsage(out);
-      return new InterpreterResult(InterpreterResult.Code.SUCCESS);
-    } else {
-      return new InterpreterResult(InterpreterResult.Code.ERROR, "Not 
supported command: " + st);
+    try {
+      if (PATTERN_COMMAND_ENV_LIST.matcher(st).matches()) {
+        String result = runCondaEnvList();
+        return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
+      } else if (envMatcher.matches()) {
+        // `envMatcher` should be used after `listEnvMatcher`
+        String result = runCondaEnv(getRestArgsFromMatcher(envMatcher));
+        return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
+      } else if (PATTERN_COMMAND_LIST.matcher(st).matches()) {
+        String result = runCondaList();
+        return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
+      } else if (createMatcher.matches()) {
+        String result = runCondaCreate(getRestArgsFromMatcher(createMatcher));
+        return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
+      } else if (activateMatcher.matches()) {
+        String envName = activateMatcher.group(1).trim();
+        return runCondaActivate(envName);
+      } else if (PATTERN_COMMAND_DEACTIVATE.matcher(st).matches()) {
+        return runCondaDeactivate();
+      } else if (installMatcher.matches()) {
+        String result = 
runCondaInstall(getRestArgsFromMatcher(installMatcher));
+        return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
+      } else if (uninstallMatcher.matches()) {
+        String result = 
runCondaUninstall(getRestArgsFromMatcher(uninstallMatcher));
+        return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
+      } else if (st == null || PATTERN_COMMAND_HELP.matcher(st).matches()) {
+        runCondaHelp(out);
+        return new InterpreterResult(Code.SUCCESS);
+      } else if (PATTERN_COMMAND_INFO.matcher(st).matches()) {
+        String result = runCondaInfo();
+        return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
+      } else {
+        return new InterpreterResult(Code.ERROR, "Not supported command: " + 
st);
+      }
+    } catch (RuntimeException | IOException | InterruptedException e) {
+      throw new InterpreterException(e);
     }
   }
 
-  private void changePythonEnvironment(String envName) {
+  private void changePythonEnvironment(String envName)
+      throws IOException, InterruptedException {
     PythonInterpreter python = getPythonInterpreter();
     String binPath = null;
     if (envName == null) {
@@ -94,7 +122,7 @@ public class PythonCondaInterpreter extends Interpreter {
         binPath = DEFAULT_ZEPPELIN_PYTHON;
       }
     } else {
-      HashMap<String, String> envList = getCondaEnvs();
+      Map<String, String> envList = getCondaEnvs();
       for (String name : envList.keySet()) {
         if (envName.equals(name)) {
           binPath = envList.get(name) + CONDA_PYTHON_PATH;
@@ -114,7 +142,8 @@ public class PythonCondaInterpreter extends Interpreter {
   protected PythonInterpreter getPythonInterpreter() {
     LazyOpenInterpreter lazy = null;
     PythonInterpreter python = null;
-    Interpreter p = 
getInterpreterInTheSameSessionByClassName(PythonInterpreter.class.getName());
+    Interpreter p =
+        
getInterpreterInTheSameSessionByClassName(PythonInterpreter.class.getName());
 
     while (p instanceof WrappedInterpreter) {
       if (p instanceof LazyOpenInterpreter) {
@@ -130,59 +159,75 @@ public class PythonCondaInterpreter extends Interpreter {
     return python;
   }
 
-  private HashMap getCondaEnvs() {
-    HashMap envList = null;
+  public static String runCondaCommandForTextOutput(String title, List<String> 
commands)
+      throws IOException, InterruptedException {
 
-    StringBuilder sb = createStringBuilder();
-    try {
-      int exit = runCommand(sb, "conda", "env", "list");
-      if (exit == 0) {
-        envList = new HashMap();
-        String[] lines = sb.toString().split("\n");
-        for (String s : lines) {
-          if (s == null || s.isEmpty() || s.startsWith("#")) {
-            continue;
-          }
-          Matcher match = condaEnvListPattern.matcher(s);
-
-          if (!match.matches()) {
-            continue;
-          }
-          envList.put(match.group(1), match.group(2));
-        }
-      }
-    } catch (IOException | InterruptedException e) {
-      throw new InterpreterException(e);
-    }
+    String result = runCommand(commands);
+    return wrapCondaBasicOutputStyle(title, result);
+  }
+
+  private String runCondaCommandForTableOutput(String title, List<String> 
commands)
+      throws IOException, InterruptedException {
+
+    StringBuilder sb = new StringBuilder();
+    String result = runCommand(commands);
+
+    // use table output for pretty output
+    Map<String, String> envPerName = parseCondaCommonStdout(result);
+    return wrapCondaTableOutputStyle(title, envPerName);
+  }
+
+  protected Map<String, String> getCondaEnvs()
+      throws IOException, InterruptedException {
+    String result = runCommand("conda", "env", "list");
+    Map<String, String> envList = parseCondaCommonStdout(result);
     return envList;
   }
 
-  private void listEnv(InterpreterOutput out, HashMap<String, String> envList) 
{
-    try {
-      out.setType(InterpreterResult.Type.HTML);
-      out.write("<h4>Conda environments</h4>\n");
-      // start table
-      out.write("<div style=\"display:table\">\n");
+  private String runCondaEnvList() throws IOException, InterruptedException {
+    return wrapCondaTableOutputStyle("Environment List", getCondaEnvs());
+  }
 
-      for (String name : envList.keySet()) {
-        String path = envList.get(name);
+  private String runCondaEnv(List<String> restArgs)
+      throws IOException, InterruptedException {
 
-        out.write(String.format("<div style=\"display:table-row\">" +
-            "<div style=\"display:table-cell;width:150px\">%s</div>" +
-            "<div style=\"display:table-cell;\">%s</div>" +
-            "</div>\n",
-          name, path));
-      }
-      // end table
-      out.write("</div><br />\n");
-      out.write("<small><code>%python.conda help</code> for the 
usage</small>\n");
-    } catch  (IOException e) {
-      throw new InterpreterException(e);
+    restArgs.add(0, "conda");
+    restArgs.add(1, "env");
+    restArgs.add(3, "--yes"); // --yes should be inserted after command
+
+    return runCondaCommandForTextOutput(null, restArgs);
+  }
+
+  private InterpreterResult runCondaActivate(String envName)
+      throws IOException, InterruptedException {
+
+    if (null == envName || envName.isEmpty()) {
+      return new InterpreterResult(Code.ERROR, "Env name should be specified");
     }
+
+    changePythonEnvironment(envName);
+    restartPythonProcess();
+
+    return new InterpreterResult(Code.SUCCESS, "'" + envName + "' is 
activated");
   }
 
+  private InterpreterResult runCondaDeactivate()
+      throws IOException, InterruptedException {
 
-  private void printUsage(InterpreterOutput out) {
+    changePythonEnvironment(null);
+    restartPythonProcess();
+    return new InterpreterResult(Code.SUCCESS, "Deactivated");
+  }
+
+  private String runCondaList() throws IOException, InterruptedException {
+    List<String> commands = new ArrayList<String>();
+    commands.add("conda");
+    commands.add("list");
+
+    return runCondaCommandForTableOutput("Installed Package List", commands);
+  }
+
+  private void runCondaHelp(InterpreterOutput out) {
     try {
       out.setType(InterpreterResult.Type.HTML);
       out.writeResource("output_templates/conda_usage.html");
@@ -191,6 +236,98 @@ public class PythonCondaInterpreter extends Interpreter {
     }
   }
 
+  private String runCondaInfo() throws IOException, InterruptedException {
+    List<String> commands = new ArrayList<String>();
+    commands.add("conda");
+    commands.add("info");
+
+    return runCondaCommandForTextOutput("Conda Information", commands);
+  }
+
+  private String runCondaCreate(List<String> restArgs)
+      throws IOException, InterruptedException {
+    restArgs.add(0, "conda");
+    restArgs.add(1, "create");
+    restArgs.add(2, "--yes");
+
+    return runCondaCommandForTextOutput("Environment Creation", restArgs);
+  }
+
+  private String runCondaInstall(List<String> restArgs)
+      throws IOException, InterruptedException {
+
+    restArgs.add(0, "conda");
+    restArgs.add(1, "install");
+    restArgs.add(2, "--yes");
+
+    return runCondaCommandForTextOutput("Package Installation", restArgs);
+  }
+
+  private String runCondaUninstall(List<String> restArgs)
+      throws IOException, InterruptedException {
+
+    restArgs.add(0, "conda");
+    restArgs.add(1, "uninstall");
+    restArgs.add(2, "--yes");
+
+    return runCondaCommandForTextOutput("Package Uninstallation", restArgs);
+  }
+
+  public static String wrapCondaBasicOutputStyle(String title, String content) 
{
+    StringBuilder sb = new StringBuilder();
+    if (null != title && !title.isEmpty()) {
+      sb.append("<h4>").append(title).append("</h4>\n")
+          .append("</div><br />\n");
+    }
+    sb.append("<div style=\"white-space:pre-wrap;\">\n")
+        .append(content)
+        .append("</div>");
+
+    return sb.toString();
+  }
+
+  public static String wrapCondaTableOutputStyle(String title, Map<String, 
String> kv) {
+    StringBuilder sb = new StringBuilder();
+
+    if (null != title && !title.isEmpty()) {
+      sb.append("<h4>").append(title).append("</h4>\n");
+    }
+
+    sb.append("<div style=\"display:table;white-space:pre-wrap;\">\n");
+    for (String name : kv.keySet()) {
+      String path = kv.get(name);
+
+      sb.append(String.format("<div style=\"display:table-row\">" +
+              "<div style=\"display:table-cell;width:150px\">%s</div>" +
+              "<div style=\"display:table-cell;\">%s</div>" +
+              "</div>\n",
+          name, path));
+    }
+    sb.append("</div>\n");
+
+    return sb.toString();
+  }
+
+  public static Map<String, String> parseCondaCommonStdout(String out)
+      throws IOException, InterruptedException {
+
+    Map<String, String> kv = new LinkedHashMap<String, String>();
+    String[] lines = out.split("\n");
+    for (String s : lines) {
+      if (s == null || s.isEmpty() || s.startsWith("#")) {
+        continue;
+      }
+      Matcher match = PATTERN_OUTPUT_ENV_LIST.matcher(s);
+
+      if (!match.matches()) {
+        continue;
+      }
+      kv.put(match.group(1), match.group(2));
+    }
+
+    return kv;
+  }
+
   @Override
   public void cancel(InterpreterContext context) {
 
@@ -206,7 +343,6 @@ public class PythonCondaInterpreter extends Interpreter {
     return 0;
   }
 
-
   /**
    * Use python interpreter's scheduler.
    * To make sure %python.conda paragraph and %python paragraph runs 
sequentially
@@ -221,9 +357,12 @@ public class PythonCondaInterpreter extends Interpreter {
     }
   }
 
-  protected int runCommand(StringBuilder sb, String ... command)
+  public static String runCommand(List<String> commands)
       throws IOException, InterruptedException {
-    ProcessBuilder builder = new ProcessBuilder(command);
+
+    StringBuilder sb = new StringBuilder();
+
+    ProcessBuilder builder = new ProcessBuilder(commands);
     builder.redirectErrorStream(true);
     Process process = builder.start();
     InputStream stdout = process.getInputStream();
@@ -234,10 +373,28 @@ public class PythonCondaInterpreter extends Interpreter {
       sb.append("\n");
     }
     int r = process.waitFor(); // Let the process finish.
-    return r;
+
+    if (r != 0) {
+      throw new RuntimeException("Failed to execute `" +
+          StringUtils.join(commands, " ") + "` exited with " + r);
+    }
+
+    return sb.toString();
+  }
+
+  public static String runCommand(String ... command)
+      throws IOException, InterruptedException {
+
+    List<String> list = new ArrayList<>(command.length);
+    for (String arg : command) {
+      list.add(arg);
+    }
+
+    return runCommand(list);
   }
 
-  protected StringBuilder createStringBuilder() {
-    return new StringBuilder();
+  public static List<String> getRestArgsFromMatcher(Matcher m) {
+    // Arrays.asList just returns fixed-size, so we should use ctor instead of
+    return new ArrayList<>(Arrays.asList(m.group(1).split(" ")));
   }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/034fdc67/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java
----------------------------------------------------------------------
diff --git a/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java 
b/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java
index ef9bd2d..578ffeb 100644
--- a/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java
+++ b/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java
@@ -110,10 +110,13 @@ public class PythonProcess {
     writer.println("\"" + STATEMENT_END + "\"");
     StringBuilder output = new StringBuilder();
     String line = null;
-    while (!(line = reader.readLine()).contains(STATEMENT_END)) {
+
+    while ((line = reader.readLine()) != null &&
+        !line.contains(STATEMENT_END)) {
       logger.debug("Read line from python shell : " + line);
       output.append(line + "\n");
     }
+
     return output.toString();
   }
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/034fdc67/python/src/main/resources/output_templates/conda_usage.html
----------------------------------------------------------------------
diff --git a/python/src/main/resources/output_templates/conda_usage.html 
b/python/src/main/resources/output_templates/conda_usage.html
index 79191b8..e1146fc 100644
--- a/python/src/main/resources/output_templates/conda_usage.html
+++ b/python/src/main/resources/output_templates/conda_usage.html
@@ -13,6 +13,18 @@ limitations under the License.
 -->
 <h4>Usage</h4>
 <div>
+  Get the Conda Infomation
+  <pre>%python.conda info</pre>
+</div>
+<div>
+  List the Conda environments
+  <pre>%python.conda env list</pre>
+</div>
+<div>
+  Create a conda enviornment
+  <pre>%python.conda create --name [ENV NAME]</pre>
+</div>
+<div>
   Activate an environment (python interpreter will be restarted)
   <pre>%python.conda activate [ENV NAME]</pre>
 </div>
@@ -21,7 +33,14 @@ limitations under the License.
   <pre>%python.conda deactivate</pre>
 </div>
 <div>
-  List the Conda environments
-  <pre>%python.conda</pre>
+  Get installed package list inside the current environment
+  <pre>%python.conda list</pre>
+</div>
+<div>
+  Install Package
+  <pre>%python.conda install [PACKAGE NAME]</pre>
+</div>
+<div>
+  Uninstall Package
+  <pre>%python.conda uninstall [PACKAGE NAME]</pre>
 </div>
-

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/034fdc67/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java
----------------------------------------------------------------------
diff --git 
a/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java
 
b/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java
index b654d2e..c6d2a84 100644
--- 
a/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java
+++ 
b/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java
@@ -23,13 +23,11 @@ import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Properties;
+import java.util.*;
+import java.util.regex.Matcher;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.*;
 import static org.mockito.Mockito.*;
 
 public class PythonCondaInterpreterTest {
@@ -49,35 +47,32 @@ public class PythonCondaInterpreterTest {
     doReturn(python).when(conda).getPythonInterpreter();
   }
 
-  private void setCondaEnvs() throws IOException, InterruptedException {
-    StringBuilder sb = new StringBuilder();
-    sb.append("#comment\n\nenv1   *  /path1\nenv2\t/path2\n");
-
-    doReturn(sb).when(conda).createStringBuilder();
-    doReturn(0).when(conda)
-      .runCommand(any(StringBuilder.class), anyString(), anyString(), 
anyString());
+  private void setMockCondaEnvList() throws IOException, InterruptedException {
+    Map<String, String> envList = new LinkedHashMap<String, String>();
+    envList.put("env1", "/path1");
+    envList.put("env2", "/path2");
+    doReturn(envList).when(conda).getCondaEnvs();
   }
 
   @Test
   public void testListEnv() throws IOException, InterruptedException {
-    setCondaEnvs();
+    setMockCondaEnvList();
 
     // list available env
     InterpreterContext context = getInterpreterContext();
-    InterpreterResult result = conda.interpret("", context);
+    InterpreterResult result = conda.interpret("env list", context);
     assertEquals(InterpreterResult.Code.SUCCESS, result.code());
 
-    context.out.flush();
-    String out = new String(context.out.toByteArray());
-    assertTrue(out.contains(">env1<"));
-    assertTrue(out.contains(">/path1<"));
-    assertTrue(out.contains(">env2<"));
-    assertTrue(out.contains(">/path2<"));
+    assertTrue(result.toString().contains(">env1<"));
+    assertTrue(result.toString().contains("/path1<"));
+    assertTrue(result.toString().contains(">env2<"));
+    assertTrue(result.toString().contains("/path2<"));
   }
 
   @Test
   public void testActivateEnv() throws IOException, InterruptedException {
-    setCondaEnvs();
+    setMockCondaEnvList();
+
     InterpreterContext context = getInterpreterContext();
     conda.interpret("activate env1", context);
     verify(python, times(1)).open();
@@ -94,6 +89,34 @@ public class PythonCondaInterpreterTest {
     verify(python).setPythonCommand("python");
   }
 
+  @Test
+  public void testParseCondaCommonStdout()
+      throws IOException, InterruptedException {
+
+    StringBuilder sb = new StringBuilder()
+        .append("# comment1\n")
+        .append("# comment2\n")
+        .append("env1     /location1\n")
+        .append("env2     /location2\n");
+
+    Map<String, String> locationPerEnv =
+        PythonCondaInterpreter.parseCondaCommonStdout(sb.toString());
+
+    assertEquals("/location1", locationPerEnv.get("env1"));
+    assertEquals("/location2", locationPerEnv.get("env2"));
+  }
+
+  @Test
+  public void testGetRestArgsFromMatcher() {
+    Matcher m =
+        PythonCondaInterpreter.PATTERN_COMMAND_ENV.matcher("env remove --name 
test --yes");
+    m.matches();
+
+    List<String> restArgs = PythonCondaInterpreter.getRestArgsFromMatcher(m);
+    List<String> expected = Arrays.asList(new String[]{"remove", "--name", 
"test", "--yes"});
+    assertEquals(expected, restArgs);
+  }
+
   private InterpreterContext getInterpreterContext() {
     return new InterpreterContext(
         "noteId",

Reply via email to