http://git-wip-us.apache.org/repos/asf/metron/blob/2dd01b17/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarExecutor.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarExecutor.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarExecutor.java
deleted file mode 100644
index 1be38c3..0000000
--- 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarExecutor.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- *
- *  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.metron.stellar.common.shell;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
-import java.io.ByteArrayInputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Properties;
-import java.util.SortedMap;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import org.apache.commons.collections4.trie.PatriciaTrie;
-import org.apache.commons.lang.StringUtils;
-import org.apache.curator.framework.CuratorFramework;
-import org.apache.metron.stellar.common.StellarProcessor;
-import org.apache.metron.stellar.common.configuration.ConfigurationsUtils;
-import org.apache.metron.stellar.common.utils.JSONUtils;
-import org.apache.metron.stellar.dsl.Context;
-import org.apache.metron.stellar.dsl.MapVariableResolver;
-import org.apache.metron.stellar.dsl.StellarFunctionInfo;
-import org.apache.metron.stellar.dsl.StellarFunctions;
-import org.apache.metron.stellar.dsl.VariableResolver;
-import org.apache.metron.stellar.dsl.functions.resolver.FunctionResolver;
-import org.jboss.aesh.console.Console;
-
-import static 
org.apache.metron.stellar.common.configuration.ConfigurationsUtils.readGlobalConfigBytesFromZookeeper;
-import static 
org.apache.metron.stellar.common.shell.StellarExecutor.OperationType.DOC;
-import static 
org.apache.metron.stellar.common.shell.StellarExecutor.OperationType.NORMAL;
-import static org.apache.metron.stellar.dsl.Context.Capabilities.GLOBAL_CONFIG;
-import static 
org.apache.metron.stellar.dsl.Context.Capabilities.STELLAR_CONFIG;
-import static 
org.apache.metron.stellar.dsl.Context.Capabilities.ZOOKEEPER_CLIENT;
-
-/**
- * Executes Stellar expressions and maintains state across multiple 
invocations.
- */
-public class StellarExecutor {
-
-  public static String SHELL_VARIABLES = "shellVariables";
-  public static String CONSOLE = "console";
-
-  private ReadWriteLock indexLock = new ReentrantReadWriteLock();
-
-  public static class VariableResult {
-    private String expression;
-    private Object result;
-
-    public VariableResult(String expression, Object result) {
-      this.expression = expression;
-      this.result = result;
-    }
-
-    public String getExpression() {
-      return expression;
-    }
-
-    public Object getResult() {
-      return result;
-    }
-
-    @Override
-    public String toString() {
-      String ret = "" + result;
-      if(expression != null) {
-        ret += " via " + expression;
-      }
-      return ret;
-    }
-  }
-
-  /**
-   * Prefix tree index of auto-completes.
-   */
-  private PatriciaTrie<AutoCompleteType> autocompleteIndex;
-
-  /**
-   * The variables known by Stellar.
-   */
-  private Map<String, VariableResult> variables;
-
-  /**
-   * The function resolver.
-   */
-  private FunctionResolver functionResolver;
-
-  /**
-   * A Zookeeper client. Only defined if given a valid Zookeeper URL.
-   */
-  private Optional<CuratorFramework> client;
-
-  /**
-   * The Stellar execution context.
-   */
-  private Context context;
-
-  private Console console;
-
-  public enum OperationType {
-    DOC
-    , MAGIC
-    , NORMAL
-  }
-
-  public interface AutoCompleteTransformation {
-    String transform(OperationType type, String key);
-  }
-
-  public enum AutoCompleteType implements AutoCompleteTransformation{
-      FUNCTION((type, key) -> {
-        if(type == DOC) {
-          return StellarShell.DOC_PREFIX + key;
-        }
-        else if(type == NORMAL) {
-          return key + "(";
-        }
-        return key;
-      })
-    , VARIABLE((type, key) -> key )
-    , TOKEN((type, key) -> key)
-    ;
-
-    AutoCompleteTransformation transform;
-    AutoCompleteType(AutoCompleteTransformation transform) {
-      this.transform = transform;
-    }
-
-    @Override
-    public String transform(OperationType type, String key) {
-      return transform.transform(type, key);
-    }
-  }
-
-  /**
-   * @param console The console used to drive the REPL.
-   * @param properties The Stellar properties.
-   * @throws Exception
-   */
-  public StellarExecutor(Console console, Properties properties) throws 
Exception {
-    this(null, console, properties);
-  }
-
-  /**
-   * @param console The console used to drive the REPL.
-   * @param properties The Stellar properties.
-   * @throws Exception
-   */
-  public StellarExecutor(String zookeeperUrl, Console console, Properties 
properties) throws Exception {
-    this.variables = new HashMap<>();
-    this.client = createClient(zookeeperUrl);
-    this.context = createContext(properties);
-
-    // initialize the default function resolver
-    StellarFunctions.initialize(this.context);
-    this.functionResolver = StellarFunctions.FUNCTION_RESOLVER();
-
-    this.autocompleteIndex = initializeIndex();
-    this.console = console;
-
-    // asynchronously update the index with function names found from a 
classpath scan.
-    new Thread( () -> {
-      Iterable<StellarFunctionInfo> functions = 
functionResolver.getFunctionInfo();
-      indexLock.writeLock().lock();
-      try {
-        for(StellarFunctionInfo info: functions) {
-          String functionName = info.getName();
-          autocompleteIndex.put(functionName, AutoCompleteType.FUNCTION);
-        }
-      } finally {
-        System.out.println("Functions loaded, you may refer to functions 
now...");
-        indexLock.writeLock().unlock();
-      }
-    }).start();
-  }
-
-  private PatriciaTrie<AutoCompleteType> initializeIndex() {
-    Map<String, AutoCompleteType> index = new HashMap<>();
-
-    index.put("==", AutoCompleteType.TOKEN);
-    index.put(">=", AutoCompleteType.TOKEN);
-    index.put("<=", AutoCompleteType.TOKEN);
-    index.put(":=", AutoCompleteType.TOKEN);
-    index.put("quit", AutoCompleteType.TOKEN);
-    index.put(StellarShell.MAGIC_FUNCTIONS, AutoCompleteType.FUNCTION);
-    index.put(StellarShell.MAGIC_VARS, AutoCompleteType.FUNCTION);
-    index.put(StellarShell.MAGIC_GLOBALS, AutoCompleteType.FUNCTION);
-    index.put(StellarShell.MAGIC_DEFINE, AutoCompleteType.FUNCTION);
-    index.put(StellarShell.MAGIC_UNDEFINE, AutoCompleteType.FUNCTION);
-    return new PatriciaTrie<>(index);
-  }
-
-  public Iterable<String> autoComplete(String buffer, final OperationType 
opType) {
-    indexLock.readLock().lock();
-    try {
-      SortedMap<String, AutoCompleteType> ret = 
autocompleteIndex.prefixMap(buffer);
-      if (ret.isEmpty()) {
-        return new ArrayList<>();
-      }
-      return Iterables.transform(ret.entrySet(), kv -> 
kv.getValue().transform(opType, kv.getKey()));
-    }
-    finally {
-      indexLock.readLock().unlock();
-    }
-  }
-
-  /**
-   * Creates a Zookeeper client.
-   * @param zookeeperUrl The Zookeeper URL.
-   */
-  private Optional<CuratorFramework> createClient(String zookeeperUrl) {
-
-    // can only create client, if have valid zookeeper URL
-    if(StringUtils.isNotBlank(zookeeperUrl)) {
-      CuratorFramework client = ConfigurationsUtils.getClient(zookeeperUrl);
-      client.start();
-      return Optional.of(client);
-
-    } else {
-      return Optional.empty();
-    }
-  }
-
-  /**
-   * Creates a Context initialized with configuration stored in Zookeeper.
-   */
-  private Context createContext(Properties properties) throws Exception {
-
-    Context.Builder contextBuilder = new Context.Builder()
-            .with(SHELL_VARIABLES, () -> variables)
-            .with(CONSOLE, () -> console)
-            .with(STELLAR_CONFIG, () -> properties);
-
-    // load global configuration from zookeeper
-    if (client.isPresent()) {
-
-      // fetch the global configuration
-      Map<String, Object> global = JSONUtils.INSTANCE.load(
-              new 
ByteArrayInputStream(readGlobalConfigBytesFromZookeeper(client.get())),
-              new TypeReference<Map<String, Object>>() {});
-
-      contextBuilder
-              .with(GLOBAL_CONFIG, () -> global)
-              .with(ZOOKEEPER_CLIENT, () -> client.get())
-              .with(STELLAR_CONFIG, () -> getStellarConfig(global, 
properties));
-    }
-
-    return contextBuilder.build();
-  }
-
-  private Map<String, Object> getStellarConfig(Map<String, Object> 
globalConfig, Properties props) {
-    Map<String, Object> ret = new HashMap<>();
-    ret.putAll(globalConfig);
-    if(props != null) {
-      for (Map.Entry<Object, Object> kv : props.entrySet()) {
-        ret.put(kv.getKey().toString(), kv.getValue());
-      }
-    }
-    return ret;
-  }
-
-  /**
-   * Executes the Stellar expression and returns the result.
-   * @param expression The Stellar expression to execute.
-   * @return The result of the expression.
-   */
-  public Object execute(String expression) {
-    VariableResolver variableResolver = new 
MapVariableResolver(Maps.transformValues(variables, result -> 
result.getResult())
-                                                               , 
Collections.emptyMap());
-    StellarProcessor processor = new StellarProcessor();
-    return processor.parse(expression, variableResolver, functionResolver, 
context);
-  }
-
-  /**
-   * Assigns a value to a variable.
-   * @param variable The name of the variable.
-   * @param value The value of the variable
-   */
-  public void assign(String variable, String expression, Object value) {
-    this.variables.put(variable, new VariableResult(expression, value));
-    indexLock.writeLock().lock();
-    try {
-      if (value != null) {
-        this.autocompleteIndex.put(variable, AutoCompleteType.VARIABLE);
-      } else {
-        this.autocompleteIndex.remove(variable);
-      }
-    }
-    finally {
-      indexLock.writeLock().unlock();
-    }
-  }
-
-  public Map<String, VariableResult> getVariables() {
-    return this.variables;
-  }
-
-  public FunctionResolver getFunctionResolver() {
-    return functionResolver;
-  }
-
-  public Context getContext() {
-    return context;
-  }
-}
-

http://git-wip-us.apache.org/repos/asf/metron/blob/2dd01b17/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarResult.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarResult.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarResult.java
new file mode 100644
index 0000000..4e5c81e
--- /dev/null
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarResult.java
@@ -0,0 +1,195 @@
+/*
+ *
+ *  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.metron.stellar.common.shell;
+
+import java.util.Optional;
+
+/**
+ * The result of executing a Stellar expression within a StellarShellExecutor.
+ */
+public class StellarResult {
+
+  /**
+   * Indicates that a Stellar expression resulted in either
+   * success or an error.
+   */
+  enum Status {
+    SUCCESS,
+    ERROR,
+    TERMINATE
+  }
+
+  /**
+   * Indicates either success or failure of executing the expression.
+   */
+  private Status status;
+
+  /**
+   * The result of executing the expression.  Only valid when execution is 
successful.
+   */
+  private Optional<Object> value;
+
+  /**
+   * The error that occurred when executing the expression.  Only valid when 
execution results in an error.
+   */
+  private Optional<Throwable> exception;
+
+  /**
+   * Indicates if the value is null;
+   *
+   * A null is a valid result, but cannot be unwrapped from an Optional.  
Because of this
+   * a boolean is used to indicate if the result is a success and the value is 
null.
+   */
+  private boolean isValueNull;
+
+  /**
+   * Private constructor to construct a result indicate success. Use the 
static methods; success.
+   *
+   * @param status Indicates success or failure.
+   * @param value The value of executing the expression.
+   */
+  private StellarResult(Status status, Object value) {
+    this.status = status;
+    this.value = Optional.ofNullable(value);
+    this.exception = Optional.empty();
+    this.isValueNull = (value == null) && (status == Status.SUCCESS);
+  }
+
+  /**
+   * Private constructor to construct a result indicating an error occurred. 
Use the static method; error.
+   *
+   * @param status Indicates success or failure.
+   * @param exception The exception that occurred when executing the 
expression.
+   */
+  private StellarResult(Status status, Throwable exception) {
+    this.status = status;
+    this.value = Optional.empty();
+    this.exception = Optional.of(exception);
+    this.isValueNull = false;
+  }
+
+  /**
+   * Create a result indicating the execution of an expression was successful.
+   *
+   * @param value The result of executing the expression.
+   * @return A Result indicating success.
+   */
+  public static StellarResult success(Object value) {
+    return new StellarResult(Status.SUCCESS, value);
+  }
+
+  /**
+   * Create a result indicating that the execution of an expression was not 
successful.
+   *
+   * @param exception The exception that occurred while executing the 
expression.
+   * @return A Result indicating that an error occurred.
+   */
+  public static StellarResult error(Throwable exception) {
+    return new StellarResult(Status.ERROR, exception);
+  }
+
+  /**
+   * Create a result indicating that the execution of an expression was not 
successful.
+   *
+   * @param errorMessage An error message.
+   * @return A Result indicating that an error occurred.
+   */
+  public static StellarResult error(String errorMessage) {
+    return new StellarResult(Status.ERROR, new 
IllegalArgumentException(errorMessage));
+  }
+
+  /**
+   * Indicates an empty result; one that is successful yet has no result.  For 
example,
+   * executing a comment.
+   *
+   * @return An empty result.
+   */
+  public static StellarResult noop() {
+    return new StellarResult(Status.SUCCESS, "");
+  }
+
+  /**
+   * Indicates that the user would like to terminate the session.
+   *
+   * @return A result indicating that the session should be terminated.
+   */
+  public static StellarResult terminate() {
+    return new StellarResult(Status.TERMINATE, "");
+  }
+
+  /**
+   * @return True, if the result indicates success.  Otherwise, false.
+   */
+  public boolean isSuccess() {
+    return status == Status.SUCCESS;
+  }
+
+  /**
+   * @return True, if the result indicates an error.  Otherwise, false.
+   */
+  public boolean isError() {
+    return status == Status.ERROR;
+  }
+
+  /**
+   * @return True, if status indicates terminate was requested.  Otherwise, 
false.
+   */
+  public boolean isTerminate() {
+    return status == Status.TERMINATE;
+  }
+
+  /**
+   * @return True, if the value is null.  Otherwise, false.
+   */
+  public boolean isValueNull() {
+    return isValueNull;
+  }
+
+  /**
+   * @return The status which indicates success or failure.
+   */
+  public Status getStatus() {
+    return status;
+  }
+
+  /**
+   * @return An optional value that only applies when status is success.
+   */
+  public Optional<Object> getValue() {
+    return value;
+  }
+
+  /**
+   * @return An optional exception that only applies when status is error.
+   */
+  public Optional<Throwable> getException() {
+    return exception;
+  }
+
+  @Override
+  public String toString() {
+    return "StellarResult{" +
+            "status=" + status +
+            ", value=" + value +
+            ", exception=" + exception +
+            ", isValueNull=" + isValueNull +
+            '}';
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/2dd01b17/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShell.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShell.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShell.java
deleted file mode 100644
index 6f23a5e..0000000
--- 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShell.java
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- *
- *  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.metron.stellar.common.shell;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import org.apache.commons.cli.*;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.log4j.PropertyConfigurator;
-import org.apache.metron.stellar.common.StellarAssignment;
-import org.apache.metron.stellar.common.utils.JSONUtils;
-import org.apache.metron.stellar.dsl.StellarFunctionInfo;
-import org.jboss.aesh.complete.CompleteOperation;
-import org.jboss.aesh.complete.Completion;
-import org.jboss.aesh.console.AeshConsoleCallback;
-import org.jboss.aesh.console.Console;
-import org.jboss.aesh.console.ConsoleOperation;
-import org.jboss.aesh.console.Prompt;
-import org.jboss.aesh.console.settings.SettingsBuilder;
-import org.jboss.aesh.terminal.CharacterType;
-import org.jboss.aesh.terminal.Color;
-import org.jboss.aesh.terminal.TerminalCharacter;
-import org.jboss.aesh.terminal.TerminalColor;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Properties;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-import static org.apache.metron.stellar.dsl.Context.Capabilities.GLOBAL_CONFIG;
-
-/**
- * A REPL environment for Stellar.
- *
- * Useful for debugging Stellar expressions.
- */
-public class StellarShell extends AeshConsoleCallback implements Completion {
-
-  private static final String WELCOME = "Stellar, Go!\n" +
-          "Please note that functions are loading lazily in the background and 
will be unavailable until loaded fully.";
-  private List<TerminalCharacter> EXPRESSION_PROMPT = new 
ArrayList<TerminalCharacter>()
-  {{
-    add(new TerminalCharacter('[', new TerminalColor(Color.RED, 
Color.DEFAULT)));
-    add(new TerminalCharacter('S', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
-    add(new TerminalCharacter('t', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
-    add(new TerminalCharacter('e', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
-    add(new TerminalCharacter('l', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
-    add(new TerminalCharacter('l', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
-    add(new TerminalCharacter('a', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
-    add(new TerminalCharacter('r', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
-    add(new TerminalCharacter(']', new TerminalColor(Color.RED, 
Color.DEFAULT)));
-    add(new TerminalCharacter('>', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.UNDERLINE));
-    add(new TerminalCharacter('>', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.UNDERLINE));
-    add(new TerminalCharacter('>', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.UNDERLINE));
-    add(new TerminalCharacter(' ', new TerminalColor(Color.DEFAULT, 
Color.DEFAULT)));
-  }};
-
-  public static final String ERROR_PROMPT = "[!] ";
-  public static final String MAGIC_PREFIX = "%";
-  public static final String MAGIC_FUNCTIONS = MAGIC_PREFIX + "functions";
-  public static final String MAGIC_VARS = MAGIC_PREFIX + "vars";
-  public static final String DOC_PREFIX = "?";
-  public static final String STELLAR_PROPERTIES_FILENAME = 
"stellar.properties";
-  public static final String MAGIC_GLOBALS = MAGIC_PREFIX + "globals";
-  public static final String MAGIC_DEFINE = MAGIC_PREFIX + "define";
-  public static final String MAGIC_UNDEFINE = MAGIC_PREFIX + "undefine";
-
-  private StellarExecutor executor;
-
-  private Console console;
-
-  /**
-   * Execute the Stellar REPL.
-   */
-  public static void main(String[] args) throws Exception {
-    StellarShell shell = new StellarShell(args);
-    shell.execute();
-  }
-
-  /**
-   * Create a Stellar REPL.
-   * @param args The commmand-line arguments.
-   */
-  public StellarShell(String[] args) throws Exception {
-
-    // define valid command-line options
-    Options options = new Options();
-    options.addOption("z", "zookeeper", true, "Zookeeper URL fragment in the 
form [HOSTNAME|IPADDRESS]:PORT");
-    options.addOption("v", "variables", true, "File containing a JSON Map of 
variables");
-    options.addOption("irc", "inputrc", true, "File containing the inputrc if 
not the default ~/.inputrc");
-    options.addOption("na", "no_ansi", false, "Make the input prompt not use 
ANSI colors.");
-    options.addOption("h", "help", false, "Print help");
-    options.addOption("p", "properties", true, "File containing Stellar 
properties");
-    {
-      Option o = new Option("l", "log4j", true, "The log4j properties file to 
load");
-      o.setArgName("FILE");
-      o.setRequired(false);
-      options.addOption(o);
-    }
-    CommandLineParser parser = new PosixParser();
-    CommandLine commandLine = parser.parse(options, args);
-
-    // print help
-    if(commandLine.hasOption("h")) {
-      HelpFormatter formatter = new HelpFormatter();
-      formatter.printHelp("stellar", options);
-      System.exit(0);
-    }
-
-    try {
-      StellarShellOptionsValidator.validateOptions(commandLine);
-    }catch(IllegalArgumentException e){
-      System.out.println(e.getMessage());
-      System.exit(1);
-    }
-    //setting up logging if specified
-    if(commandLine.hasOption("l")) {
-      PropertyConfigurator.configure(commandLine.getOptionValue("l"));
-    }
-    console = createConsole(commandLine);
-    executor = createExecutor(commandLine, console, 
getStellarProperties(commandLine));
-    loadVariables(commandLine, executor);
-    console.setPrompt(new Prompt(EXPRESSION_PROMPT));
-    console.addCompletion(this);
-    console.setConsoleCallback(this);
-  }
-
-  /**
-   * Loads any variables defined in an external file.
-   * @param commandLine The command line arguments.
-   * @param executor The stellar executor.
-   * @throws IOException
-   */
-  private static void loadVariables(CommandLine commandLine, StellarExecutor 
executor) throws IOException {
-    if(commandLine.hasOption("v")) {
-
-      Map<String, Object> variables = JSONUtils.INSTANCE.load(
-              new File(commandLine.getOptionValue("v")),
-              new TypeReference<Map<String, Object>>() {});
-
-      for(Map.Entry<String, Object> kv : variables.entrySet()) {
-        executor.assign(kv.getKey(), null, kv.getValue());
-      }
-    }
-  }
-
-  /**
-   * Creates the Stellar execution environment.
-   * @param commandLine The command line arguments.
-   * @param console The console which drives the REPL.
-   * @param properties Stellar properties.
-   */
-  private static StellarExecutor createExecutor(CommandLine commandLine, 
Console console, Properties properties) throws Exception {
-    StellarExecutor executor;
-
-    // create the executor
-    if(commandLine.hasOption("z")) {
-      String zookeeperUrl = commandLine.getOptionValue("z");
-      executor = new StellarExecutor(zookeeperUrl, console, properties);
-
-    } else {
-      executor = new StellarExecutor(console, properties);
-    }
-
-    return executor;
-  }
-
-  /**
-   * Creates the REPL's console.
-   * @param commandLine The command line options.
-   */
-  private Console createConsole(CommandLine commandLine) {
-
-    // console settings
-    boolean useAnsi = !commandLine.hasOption("na");
-    SettingsBuilder settings = new SettingsBuilder().enableAlias(true)
-                                                    .enableMan(true)
-                                                    .ansi(useAnsi)
-                                                    .parseOperators(false)
-                                                    
.inputStream(PausableInput.INSTANCE);
-
-    if(commandLine.hasOption("irc")) {
-      settings = settings.inputrc(new File(commandLine.getOptionValue("irc")));
-    }
-
-    return new Console(settings.create());
-  }
-
-  /**
-   * Retrieves the Stellar properties. The properties are either loaded from a 
file in
-   * the classpath or a set of defaults are used.
-   */
-  private Properties getStellarProperties(CommandLine commandLine) throws 
IOException {
-    Properties properties = new Properties();
-
-    if (commandLine.hasOption("p")) {
-
-      // first attempt to load properties from a file specified on the 
command-line
-      try (InputStream in = new 
FileInputStream(commandLine.getOptionValue("p"))) {
-        if(in != null) {
-          properties.load(in);
-        }
-      }
-
-    } else {
-
-      // otherwise attempt to load properties from the classpath
-      try (InputStream in = 
getClass().getClassLoader().getResourceAsStream(STELLAR_PROPERTIES_FILENAME)) {
-        if(in != null) {
-          properties.load(in);
-        }
-      }
-    }
-
-    return properties;
-  }
-
-  /**
-   * Handles the main loop for the REPL.
-   */
-  public void execute() {
-
-    // welcome message and print globals
-    writeLine(WELCOME);
-    executor.getContext()
-            .getCapability(GLOBAL_CONFIG, false)
-            .ifPresent(conf -> writeLine(conf.toString()));
-
-    console.start();
-  }
-
-  /**
-   * Handles user interaction when executing a Stellar expression.
-   * @param expression The expression to execute.
-   */
-  private void handleStellar(String expression) {
-
-    String stellarExpression = expression;
-    String variable = null;
-    if(StellarAssignment.isAssignment(expression)) {
-      StellarAssignment expr = StellarAssignment.from(expression);
-      variable = expr.getVariable();
-      stellarExpression = expr.getStatement();
-    }
-    else {
-      if (!stellarExpression.isEmpty()) {
-        stellarExpression = stellarExpression.trim();
-      }
-    }
-
-    try {
-      Object result = executor.execute(stellarExpression);
-      if (result != null && variable == null) {
-        writeLine(result.toString());
-      }
-      if (variable != null) {
-        executor.assign(variable, stellarExpression, result);
-      }
-    } catch (Throwable t) {
-      if(variable != null) {
-        writeLine(String.format("%s ERROR: Variable %s not assigned", 
ERROR_PROMPT, variable));
-      }
-      writeLine(ERROR_PROMPT + t.getMessage());
-      t.printStackTrace();
-    }
-  }
-
-  /**
-   * Executes a magic expression.
-   * @param rawExpression The expression to execute.
-   */
-  private void handleMagic(String rawExpression) {
-
-    String[] expression = rawExpression.trim().split("\\s+");
-    String command = expression[0];
-
-    if (MAGIC_FUNCTIONS.equals(command)) {
-      handleMagicFunctions(expression);
-
-    } else if (MAGIC_VARS.equals(command)) {
-      handleMagicVars();
-
-    } else if (MAGIC_GLOBALS.equals(command)) {
-      handleMagicGlobals();
-
-    } else if (MAGIC_DEFINE.equals(command)) {
-      handleMagicDefine(rawExpression);
-
-    } else if(MAGIC_UNDEFINE.equals(command)) {
-      handleMagicUndefine(expression);
-
-    } else {
-      writeLine(ERROR_PROMPT + "undefined magic command: " + rawExpression);
-    }
-  }
-
-  /**
-   * Handle a magic '%functions'.  Lists all of the variables in-scope.
-   * @param expression
-   */
-  private void handleMagicFunctions(String[] expression) {
-
-    // if '%functions FOO' then show only functions that contain 'FOO'
-    Predicate<String> nameFilter = (name -> true);
-    if (expression.length > 1) {
-      nameFilter = (name -> name.contains(expression[1]));
-    }
-
-    // '%functions' -> list all functions in scope
-    String functions = StreamSupport
-            
.stream(executor.getFunctionResolver().getFunctionInfo().spliterator(), false)
-            .map(info -> String.format("%s", info.getName()))
-            .filter(nameFilter)
-            .sorted()
-            .collect(Collectors.joining(", "));
-    writeLine(functions);
-  }
-
-  /**
-   * Handle a magic '%vars'.  Lists all of the variables in-scope.
-   */
-  private void handleMagicVars() {
-    executor.getVariables()
-            .forEach((k, v) -> writeLine(String.format("%s = %s", k, v)));
-  }
-
-  /**
-   * Handle a magic '%globals'.  List all of the global configuration values.
-   */
-  private void handleMagicGlobals() {
-    Map<String, Object> globals = getOrCreateGlobalConfig(executor);
-    writeLine(globals.toString());
-  }
-
-  /**
-   * Handle a magic '%define var=value'.  Alter the global configuration.
-   * @param expression The expression passed to %define
-   */
-  public void handleMagicDefine(String expression) {
-
-    // grab the expression in '%define <assign-expression>'
-    String assignExpr = 
StringUtils.trimToEmpty(expression.substring(MAGIC_DEFINE.length()));
-    if (assignExpr.length() > 0) {
-
-      // the expression must be an assignment
-      if(StellarAssignment.isAssignment(assignExpr)) {
-        StellarAssignment expr = StellarAssignment.from(assignExpr);
-
-        // execute the expression
-        Object result = executor.execute(expr.getStatement());
-        if (result != null) {
-          writeLine(result.toString());
-
-          // alter the global configuration
-          getOrCreateGlobalConfig(executor).put(expr.getVariable(), result);
-        }
-
-      } else {
-        // the expression is not an assignment.  boo!
-        writeLine(ERROR_PROMPT + MAGIC_DEFINE + " expected assignment 
expression");
-      }
-    }
-  }
-
-  /**
-   * Handle a magic '%undefine var'.  Removes a variable from the global 
configuration.
-   * @param expression
-   */
-  private void handleMagicUndefine(String[] expression) {
-    if(expression.length > 1) {
-      Map<String, Object> globals = getOrCreateGlobalConfig(executor);
-      globals.remove(expression[1]);
-    }
-  }
-
-  /**
-   * Retrieves the GLOBAL_CONFIG, if it exists.  If it does not, it creates 
the GLOBAL_CONFIG
-   * and adds it to the Stellar execution context.
-   * @param executor The Stellar executor.
-   * @return The global configuration.
-   */
-  private Map<String, Object> getOrCreateGlobalConfig(StellarExecutor 
executor) {
-    Map<String, Object> globals;
-    Optional<Object> capability = 
executor.getContext().getCapability(GLOBAL_CONFIG, false);
-    if (capability.isPresent()) {
-      globals = (Map<String, Object>) capability.get();
-
-    } else {
-      // if it does not exist, create it.  this creates the global config for 
the current stellar executor
-      // session only.  this does not change the global config maintained 
externally in zookeeper
-      globals = new HashMap<>();
-      executor.getContext().addCapability(GLOBAL_CONFIG, () -> globals);
-    }
-    return globals;
-  }
-
-  /**
-   * Executes a doc expression.
-   * @param expression The doc expression to execute.
-   */
-  private void handleDoc(String expression) {
-
-    String functionName = StringUtils.substring(expression, 1);
-    StreamSupport
-            
.stream(executor.getFunctionResolver().getFunctionInfo().spliterator(), false)
-            .filter(info -> StringUtils.equals(functionName, info.getName()))
-            .map(info -> format(info))
-            .forEach(doc -> write(doc));
-  }
-
-  /**
-   * Executes a quit.
-   */
-  private void handleQuit() {
-    try {
-      console.stop();
-    } catch (Throwable e) {
-      e.printStackTrace();
-    }
-  }
-
-  /**
-   * Formats the Stellar function info object into a readable string.
-   * @param info The stellar function info object.
-   * @return A readable string.
-   */
-  private String format(StellarFunctionInfo info) {
-    StringBuffer ret = new StringBuffer();
-    ret.append(info.getName() + "\n");
-    ret.append(String.format("Description: %-60s\n\n", info.getDescription()));
-    if(info.getParams().length > 0) {
-      ret.append("Arguments:\n");
-      for(String param : info.getParams()) {
-        ret.append(String.format("\t%-60s\n", param));
-      }
-      ret.append("\n");
-    }
-    ret.append(String.format("Returns: %-60s\n", info.getReturns()));
-
-    return ret.toString();
-  }
-
-  /**
-   * Is a given expression a built-in magic?
-   * @param expression The expression.
-   */
-  private boolean isMagic(String expression) {
-    return StringUtils.startsWith(expression, MAGIC_PREFIX);
-  }
-
-  /**
-   * Is a given expression asking for function documentation?
-   * @param expression The expression.
-   */
-  private boolean isDoc(String expression) {
-    return StringUtils.startsWith(expression, DOC_PREFIX);
-  }
-
-  private void write(String out) {
-    System.out.print(out);
-  }
-
-  private void writeLine(String out) {
-    console.getShell().out().println(out);
-  }
-
-  @Override
-  public int execute(ConsoleOperation output) throws InterruptedException {
-    String expression = output.getBuffer().trim();
-    if(StringUtils.isNotBlank(expression) ) {
-      if(isMagic(expression)) {
-        handleMagic( expression);
-
-      } else if(isDoc(expression)) {
-        handleDoc(expression);
-
-      } else if (expression.equals("quit")) {
-        handleQuit();
-
-      } else if(expression.charAt(0) == '#') {
-        return 0; // comment, do nothing
-
-      } else {
-        handleStellar(expression);
-      }
-    }
-
-    return 0;
-  }
-
-  @Override
-  public void complete(CompleteOperation completeOperation) {
-    if(!completeOperation.getBuffer().isEmpty()) {
-      String lastToken = Iterables.getLast(Splitter.on(" 
").split(completeOperation.getBuffer()), null);
-      if(lastToken != null && !lastToken.isEmpty()) {
-        lastToken = lastToken.trim();
-        final String lastBit = lastToken;
-        final boolean isDocRequest = isDoc(lastToken);
-        if(isDocRequest) {
-          lastToken = lastToken.substring(1);
-        }
-        StellarExecutor.OperationType opType = 
StellarExecutor.OperationType.NORMAL;
-        if(isDocRequest) {
-          opType = StellarExecutor.OperationType.DOC;
-        }
-        else if(isMagic(lastToken)) {
-          opType = StellarExecutor.OperationType.MAGIC;
-        }
-        Iterable<String> candidates = executor.autoComplete(lastToken, opType);
-        if(candidates != null && !Iterables.isEmpty(candidates)) {
-          completeOperation.setCompletionCandidates( Lists.newArrayList(
-                  Iterables.transform(candidates, s -> 
stripOff(completeOperation.getBuffer(), lastBit) + s )
-                  )
-          );
-        }
-      }
-    }
-  }
-
-  private static String stripOff(String baseString, String lastBit) {
-    int index = baseString.lastIndexOf(lastBit);
-    if(index < 0) {
-      return baseString;
-    }
-    return baseString.substring(0, index);
-  }
-}

http://git-wip-us.apache.org/repos/asf/metron/blob/2dd01b17/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShellExecutor.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShellExecutor.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShellExecutor.java
new file mode 100644
index 0000000..7e2d4fc
--- /dev/null
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShellExecutor.java
@@ -0,0 +1,79 @@
+/*
+ *
+ *  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.metron.stellar.common.shell;
+
+import org.apache.metron.stellar.dsl.Context;
+import org.apache.metron.stellar.dsl.functions.resolver.FunctionResolver;
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Responsible for executing Stellar in a shell-like environment.
+ *
+ * Provides the additional capabilities expected of executing Stellar
+ * in a shell-like environment including maintaining state, variable 
assignment,
+ * magic commands, doc strings, and comments.
+ */
+public interface StellarShellExecutor extends StellarExecutionNotifier {
+
+  /**
+   * Initialize the Stellar executor.
+   */
+  void init();
+
+  /**
+   * Execute the Stellar expression.
+   * @param expression The Stellar expression to execute.
+   * @return The result of executing the Stellar expression.
+   */
+  StellarResult execute(String expression);
+
+  /**
+   * Update the state of the executor by assign a value to a variable.
+   * @param variable The name of the variable.
+   * @param value The value to assign.
+   * @param expression The expression that resulted in the given value.  
Optional.
+   */
+  void assign(String variable, Object value, Optional<String> expression);
+
+  /**
+   * The current state of the Stellar execution environment.
+   */
+  Map<String, VariableResult> getState();
+
+  /**
+   * Returns the Context for the Stellar execution environment.
+   * @return The execution context.
+   */
+  Context getContext();
+
+  /**
+   * Returns the global configuration of the Stellar execution environment.
+   * @return A map of values defined in the global configuration.
+   */
+  Map<String, Object> getGlobalConfig();
+
+  /**
+   * Returns the function resolver of the Stellar execution environment.
+   * @return The function resolver.
+   */
+  FunctionResolver getFunctionResolver();
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/2dd01b17/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShellOptionsValidator.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShellOptionsValidator.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShellOptionsValidator.java
deleted file mode 100644
index ab92401..0000000
--- 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/StellarShellOptionsValidator.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- *
- *  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.metron.stellar.common.shell;
-
-import java.io.File;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import com.google.common.base.Splitter;
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.validator.routines.InetAddressValidator;
-
-public class StellarShellOptionsValidator {
-
-  private static final Pattern validPortPattern = 
Pattern.compile("(^.*)[:](\\d+)$");
-  private static final Predicate<String> hostnameValidator = hostname -> {
-    if(StringUtils.isEmpty(hostname)) {
-      return false;
-    }
-    try {
-      InetAddress add = InetAddress.getByName(hostname);
-      return true;
-    } catch (UnknownHostException e) {
-      return false;
-    }
-  };
-
-
-
-  private static final InetAddressValidator inetAddressValidator = 
InetAddressValidator
-      .getInstance();
-
-  /**
-   * Validates Stellar CLI Options.
-   */
-  public static void validateOptions(CommandLine commandLine) throws 
IllegalArgumentException {
-    if (commandLine.hasOption('z')) {
-      validateZookeeperOption(commandLine.getOptionValue('z'));
-    }
-    // v, irc, p are files
-    if (commandLine.hasOption('v')) {
-      validateFileOption("v", commandLine.getOptionValue('v'));
-    }
-    if (commandLine.hasOption("irc")) {
-      validateFileOption("irc", commandLine.getOptionValue("irc"));
-    }
-    if (commandLine.hasOption('p')) {
-      validateFileOption("p", commandLine.getOptionValue('p'));
-    }
-
-  }
-
-  /**
-   * Zookeeper argument should be in the form [HOST|IP]:PORT.
-   *
-   * @param zMulti the zookeeper url fragment
-   */
-  private static void validateZookeeperOption(String zMulti) throws 
IllegalArgumentException {
-    for(String z : Splitter.on(",").split(zMulti)) {
-      Matcher matcher = validPortPattern.matcher(z);
-      boolean hasPort = z.contains(":");
-      if (hasPort && !matcher.matches()) {
-        throw new IllegalArgumentException(String.format("Zookeeper option 
must have valid port: %s", z));
-      }
-
-      if (hasPort && matcher.groupCount() != 2) {
-        throw new IllegalArgumentException(
-                String.format("Zookeeper Option must be in the form of 
[HOST|IP]:PORT  %s", z));
-      }
-      String name = hasPort?matcher.group(1):z;
-      Integer port = hasPort?Integer.parseInt(matcher.group(2)):null;
-
-      if (!hostnameValidator.test(name) && 
!inetAddressValidator.isValid(name)) {
-        throw new IllegalArgumentException(
-                String.format("Zookeeper Option %s is not a valid host name or 
ip address  %s", name, z));
-      }
-
-      if (hasPort && (port == 0 || port > 65535)) {
-        throw new IllegalArgumentException(
-                String.format("Zookeeper Option %s port is not valid", z));
-      }
-    }
-  }
-
-  /**
-   * File options must exist and be readable.
-   *
-   * @param option name of the option
-   * @param fileName the file name
-   */
-  private static void validateFileOption(String option, String fileName)
-      throws IllegalArgumentException {
-    File file = new File(fileName);
-    if (!file.exists()) {
-      throw new IllegalArgumentException(
-          String.format("%s: File %s doesn't exist", option, fileName));
-    }
-    if (!file.canRead()) {
-      throw new IllegalArgumentException(
-          String.format("%s: File %s is not readable", option, fileName));
-    }
-  }
-}
-
-

http://git-wip-us.apache.org/repos/asf/metron/blob/2dd01b17/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/VariableResult.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/VariableResult.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/VariableResult.java
new file mode 100644
index 0000000..6cb1173
--- /dev/null
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/VariableResult.java
@@ -0,0 +1,101 @@
+/*
+ *
+ *  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.metron.stellar.common.shell;
+
+import java.util.Optional;
+
+/**
+ * The value assigned to a variable.
+ *
+ * Allows us to maintain not only the resulting value, but the
+ * expression that resulted in that value.
+ */
+public class VariableResult {
+
+  /**
+   * The expression that resulted in the value.  Not always available.
+   */
+  private Optional<String> expression;
+
+  /**
+   * The value of the variable.
+   */
+  private Object result;
+
+  /**
+   * Create a new VariableResult when the expression that resulted in a value 
is known.
+   *
+   * @param value The value.
+   * @param expression The expression that resulted in the given value.
+   * @return A VariableResult.
+   */
+  public static VariableResult withExpression(Object value, String expression) 
{
+    return new VariableResult(Optional.of(expression), value);
+  }
+
+  /**
+   * Create a new VariableResult when the expression that resulted in a value 
is known.
+   *
+   * @param value The value.
+   * @param expression The expression that resulted in the given value.
+   * @return A VariableResult.
+   */
+  public static VariableResult withExpression(Object value, Optional<String> 
expression) {
+    return new VariableResult(expression, value);
+  }
+
+  /**
+   * Create a new VariableResult when only the value is known.
+   *
+   * @param value The value.
+   * @return A VariableResult.
+   */
+  public static VariableResult withValue(Object value) {
+    return new VariableResult(Optional.empty(), value);
+  }
+
+  /**
+   * Private constructor.  Use the static method 'withExpression' and 
'withValue'.
+   *
+   * @param expression The expression that resulted in the given value.
+   * @param result The value assigned to the variable.
+   */
+  private VariableResult(Optional<String> expression, Object result) {
+    this.expression = expression;
+    this.result = result;
+  }
+
+  public Optional<String> getExpression() {
+    return expression;
+  }
+
+  public Object getResult() {
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    String ret = "" + result;
+    if(getExpression().isPresent()) {
+      ret += " via " + expression.get();
+    }
+    return ret;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metron/blob/2dd01b17/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/PausableInput.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/PausableInput.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/PausableInput.java
new file mode 100644
index 0000000..fad0115
--- /dev/null
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/PausableInput.java
@@ -0,0 +1,372 @@
+/*
+ *
+ *  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.metron.stellar.common.shell.cli;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An input stream which mirrors System.in, but allows you to 'pause' and 
'unpause' it.
+ * The Aeshell has an external thread which is constantly polling System.in.  
If you
+ * need to spawn a program externally (i.e. an editor) which shares stdin, 
this thread
+ * and the spawned program both share a buffer.  This causes contention and 
unpredictable
+ * results (e.g. an input may be consumed by either the aeshell thread or the 
spawned program)
+ *
+ * Because you can inject an input stream into the console, we create this 
which can act as a
+ * facade to System.in under normal 'unpaused' circumstances, and when paused, 
turn off the
+ * access to System.in.  This allows us to turn off access to aeshell while 
maintaining access
+ * to the external program.
+ *
+ */
+public class PausableInput extends InputStream {
+  InputStream in = System.in;
+  boolean paused = false;
+  private PausableInput() {
+    super();
+  }
+
+  /**
+   * Stop mirroring stdin
+   */
+  public void pause() {
+    paused = true;
+  }
+
+  /**
+   * Resume mirroring stdin.
+   * @throws IOException
+   */
+  public void unpause() throws IOException {
+    in.read(new byte[in.available()]);
+    paused = false;
+  }
+
+  public final static PausableInput INSTANCE = new PausableInput();
+
+  /**
+   * Reads the next byte of data from the input stream. The value byte is
+   * returned as an <code>int</code> in the range <code>0</code> to
+   * <code>255</code>. If no byte is available because the end of the stream
+   * has been reached, the value <code>-1</code> is returned. This method
+   * blocks until input data is available, the end of the stream is detected,
+   * or an exception is thrown.
+   * <p>
+   * <p> A subclass must provide an implementation of this method.
+   *
+   * @return the next byte of data, or <code>-1</code> if the end of the
+   * stream is reached.
+   * @throws IOException if an I/O error occurs.
+   */
+  @Override
+  public int read() throws IOException {
+
+    return in.read();
+  }
+
+  /**
+   * Reads some number of bytes from the input stream and stores them into
+   * the buffer array <code>b</code>. The number of bytes actually read is
+   * returned as an integer.  This method blocks until input data is
+   * available, end of file is detected, or an exception is thrown.
+   * <p>
+   * <p> If the length of <code>b</code> is zero, then no bytes are read and
+   * <code>0</code> is returned; otherwise, there is an attempt to read at
+   * least one byte. If no byte is available because the stream is at the
+   * end of the file, the value <code>-1</code> is returned; otherwise, at
+   * least one byte is read and stored into <code>b</code>.
+   * <p>
+   * <p> The first byte read is stored into element <code>b[0]</code>, the
+   * next one into <code>b[1]</code>, and so on. The number of bytes read is,
+   * at most, equal to the length of <code>b</code>. Let <i>k</i> be the
+   * number of bytes actually read; these bytes will be stored in elements
+   * <code>b[0]</code> through <code>b[</code><i>k</i><code>-1]</code>,
+   * leaving elements <code>b[</code><i>k</i><code>]</code> through
+   * <code>b[b.length-1]</code> unaffected.
+   * <p>
+   * <p> The <code>read(b)</code> method for class <code>InputStream</code>
+   * has the same effect as: <pre><code> read(b, 0, b.length) </code></pre>
+   *
+   * @param b the buffer into which the data is read.
+   * @return the total number of bytes read into the buffer, or
+   * <code>-1</code> if there is no more data because the end of
+   * the stream has been reached.
+   * @throws IOException          If the first byte cannot be read for any 
reason
+   *                              other than the end of the file, if the input 
stream has been closed, or
+   *                              if some other I/O error occurs.
+   * @throws NullPointerException if <code>b</code> is <code>null</code>.
+   * @see InputStream#read(byte[], int, int)
+   */
+  @Override
+  public int read(byte[] b) throws IOException {
+
+    if(paused) {
+      try {
+        Thread.sleep(1000);
+      } catch (InterruptedException e) {
+        e.printStackTrace();
+      }
+      return 0;
+    }
+    int ret = in.read(b);
+    return ret;
+  }
+
+  /**
+   * Reads up to <code>len</code> bytes of data from the input stream into
+   * an array of bytes.  An attempt is made to read as many as
+   * <code>len</code> bytes, but a smaller number may be read.
+   * The number of bytes actually read is returned as an integer.
+   * <p>
+   * <p> This method blocks until input data is available, end of file is
+   * detected, or an exception is thrown.
+   * <p>
+   * <p> If <code>len</code> is zero, then no bytes are read and
+   * <code>0</code> is returned; otherwise, there is an attempt to read at
+   * least one byte. If no byte is available because the stream is at end of
+   * file, the value <code>-1</code> is returned; otherwise, at least one
+   * byte is read and stored into <code>b</code>.
+   * <p>
+   * <p> The first byte read is stored into element <code>b[off]</code>, the
+   * next one into <code>b[off+1]</code>, and so on. The number of bytes read
+   * is, at most, equal to <code>len</code>. Let <i>k</i> be the number of
+   * bytes actually read; these bytes will be stored in elements
+   * <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>,
+   * leaving elements <code>b[off+</code><i>k</i><code>]</code> through
+   * <code>b[off+len-1]</code> unaffected.
+   * <p>
+   * <p> In every case, elements <code>b[0]</code> through
+   * <code>b[off]</code> and elements <code>b[off+len]</code> through
+   * <code>b[b.length-1]</code> are unaffected.
+   * <p>
+   * <p> The <code>read(b,</code> <code>off,</code> <code>len)</code> method
+   * for class <code>InputStream</code> simply calls the method
+   * <code>read()</code> repeatedly. If the first such call results in an
+   * <code>IOException</code>, that exception is returned from the call to
+   * the <code>read(b,</code> <code>off,</code> <code>len)</code> method.  If
+   * any subsequent call to <code>read()</code> results in a
+   * <code>IOException</code>, the exception is caught and treated as if it
+   * were end of file; the bytes read up to that point are stored into
+   * <code>b</code> and the number of bytes read before the exception
+   * occurred is returned. The default implementation of this method blocks
+   * until the requested amount of input data <code>len</code> has been read,
+   * end of file is detected, or an exception is thrown. Subclasses are 
encouraged
+   * to provide a more efficient implementation of this method.
+   *
+   * @param b   the buffer into which the data is read.
+   * @param off the start offset in array <code>b</code>
+   *            at which the data is written.
+   * @param len the maximum number of bytes to read.
+   * @return the total number of bytes read into the buffer, or
+   * <code>-1</code> if there is no more data because the end of
+   * the stream has been reached.
+   * @throws IOException               If the first byte cannot be read for 
any reason
+   *                                   other than end of file, or if the input 
stream has been closed, or if
+   *                                   some other I/O error occurs.
+   * @throws NullPointerException      If <code>b</code> is <code>null</code>.
+   * @throws IndexOutOfBoundsException If <code>off</code> is negative,
+   *                                   <code>len</code> is negative, or 
<code>len</code> is greater than
+   *                                   <code>b.length - off</code>
+   * @see InputStream#read()
+   */
+  @Override
+  public int read(byte[] b, int off, int len) throws IOException {
+    if(paused) {
+      try {
+        Thread.sleep(1000);
+      } catch (InterruptedException e) {
+        e.printStackTrace();
+      }
+      return 0;
+    }
+    int ret = in.read(b, off, len);
+    return ret;
+  }
+
+  /**
+   * Skips over and discards <code>n</code> bytes of data from this input
+   * stream. The <code>skip</code> method may, for a variety of reasons, end
+   * up skipping over some smaller number of bytes, possibly <code>0</code>.
+   * This may result from any of a number of conditions; reaching end of file
+   * before <code>n</code> bytes have been skipped is only one possibility.
+   * The actual number of bytes skipped is returned. If {@code n} is
+   * negative, the {@code skip} method for class {@code InputStream} always
+   * returns 0, and no bytes are skipped. Subclasses may handle the negative
+   * value differently.
+   * <p>
+   * <p> The <code>skip</code> method of this class creates a
+   * byte array and then repeatedly reads into it until <code>n</code> bytes
+   * have been read or the end of the stream has been reached. Subclasses are
+   * encouraged to provide a more efficient implementation of this method.
+   * For instance, the implementation may depend on the ability to seek.
+   *
+   * @param n the number of bytes to be skipped.
+   * @return the actual number of bytes skipped.
+   * @throws IOException if the stream does not support seek,
+   *                     or if some other I/O error occurs.
+   */
+  @Override
+  public long skip(long n) throws IOException {
+
+    return in.skip(n);
+  }
+
+  /**
+   * Returns an estimate of the number of bytes that can be read (or
+   * skipped over) from this input stream without blocking by the next
+   * invocation of a method for this input stream. The next invocation
+   * might be the same thread or another thread.  A single read or skip of this
+   * many bytes will not block, but may read or skip fewer bytes.
+   * <p>
+   * <p> Note that while some implementations of {@code InputStream} will 
return
+   * the total number of bytes in the stream, many will not.  It is
+   * never correct to use the return value of this method to allocate
+   * a buffer intended to hold all data in this stream.
+   * <p>
+   * <p> A subclass' implementation of this method may choose to throw an
+   * {@link IOException} if this input stream has been closed by
+   * invoking the {@link #close()} method.
+   * <p>
+   * <p> The {@code available} method for class {@code InputStream} always
+   * returns {@code 0}.
+   * <p>
+   * <p> This method should be overridden by subclasses.
+   *
+   * @return an estimate of the number of bytes that can be read (or skipped
+   * over) from this input stream without blocking or {@code 0} when
+   * it reaches the end of the input stream.
+   * @throws IOException if an I/O error occurs.
+   */
+  @Override
+  public int available() throws IOException {
+
+    return in.available();
+  }
+
+  /**
+   * Closes this input stream and releases any system resources associated
+   * with the stream.
+   * <p>
+   * <p> The <code>close</code> method of <code>InputStream</code> does
+   * nothing.
+   *
+   * @throws IOException if an I/O error occurs.
+   */
+  @Override
+  public void close() throws IOException {
+    in.close();
+  }
+
+  /**
+   * Marks the current position in this input stream. A subsequent call to
+   * the <code>reset</code> method repositions this stream at the last marked
+   * position so that subsequent reads re-read the same bytes.
+   * <p>
+   * <p> The <code>readlimit</code> arguments tells this input stream to
+   * allow that many bytes to be read before the mark position gets
+   * invalidated.
+   * <p>
+   * <p> The general contract of <code>mark</code> is that, if the method
+   * <code>markSupported</code> returns <code>true</code>, the stream somehow
+   * remembers all the bytes read after the call to <code>mark</code> and
+   * stands ready to supply those same bytes again if and whenever the method
+   * <code>reset</code> is called.  However, the stream is not required to
+   * remember any data at all if more than <code>readlimit</code> bytes are
+   * read from the stream before <code>reset</code> is called.
+   * <p>
+   * <p> Marking a closed stream should not have any effect on the stream.
+   * <p>
+   * <p> The <code>mark</code> method of <code>InputStream</code> does
+   * nothing.
+   *
+   * @param readlimit the maximum limit of bytes that can be read before
+   *                  the mark position becomes invalid.
+   * @see InputStream#reset()
+   */
+  @Override
+  public synchronized void mark(int readlimit) {
+    in.mark(readlimit);
+  }
+
+  /**
+   * Repositions this stream to the position at the time the
+   * <code>mark</code> method was last called on this input stream.
+   * <p>
+   * <p> The general contract of <code>reset</code> is:
+   * <p>
+   * <ul>
+   * <li> If the method <code>markSupported</code> returns
+   * <code>true</code>, then:
+   * <p>
+   * <ul><li> If the method <code>mark</code> has not been called since
+   * the stream was created, or the number of bytes read from the stream
+   * since <code>mark</code> was last called is larger than the argument
+   * to <code>mark</code> at that last call, then an
+   * <code>IOException</code> might be thrown.
+   * <p>
+   * <li> If such an <code>IOException</code> is not thrown, then the
+   * stream is reset to a state such that all the bytes read since the
+   * most recent call to <code>mark</code> (or since the start of the
+   * file, if <code>mark</code> has not been called) will be resupplied
+   * to subsequent callers of the <code>read</code> method, followed by
+   * any bytes that otherwise would have been the next input data as of
+   * the time of the call to <code>reset</code>. </ul>
+   * <p>
+   * <li> If the method <code>markSupported</code> returns
+   * <code>false</code>, then:
+   * <p>
+   * <ul><li> The call to <code>reset</code> may throw an
+   * <code>IOException</code>.
+   * <p>
+   * <li> If an <code>IOException</code> is not thrown, then the stream
+   * is reset to a fixed state that depends on the particular type of the
+   * input stream and how it was created. The bytes that will be supplied
+   * to subsequent callers of the <code>read</code> method depend on the
+   * particular type of the input stream. </ul></ul>
+   * <p>
+   * <p>The method <code>reset</code> for class <code>InputStream</code>
+   * does nothing except throw an <code>IOException</code>.
+   *
+   * @throws IOException if this stream has not been marked or if the
+   *                     mark has been invalidated.
+   * @see InputStream#mark(int)
+   * @see IOException
+   */
+  @Override
+  public synchronized void reset() throws IOException {
+    in.reset();
+  }
+
+  /**
+   * Tests if this input stream supports the <code>mark</code> and
+   * <code>reset</code> methods. Whether or not <code>mark</code> and
+   * <code>reset</code> are supported is an invariant property of a
+   * particular input stream instance. The <code>markSupported</code> method
+   * of <code>InputStream</code> returns <code>false</code>.
+   *
+   * @return <code>true</code> if this stream instance supports the mark
+   * and reset methods; <code>false</code> otherwise.
+   * @see InputStream#mark(int)
+   * @see InputStream#reset()
+   */
+  @Override
+  public boolean markSupported() {
+    return in.markSupported();
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/2dd01b17/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/StellarShell.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/StellarShell.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/StellarShell.java
new file mode 100644
index 0000000..44ad28c
--- /dev/null
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/StellarShell.java
@@ -0,0 +1,427 @@
+/*
+ *
+ *  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.metron.stellar.common.shell.cli;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.PosixParser;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.metron.stellar.common.shell.DefaultStellarAutoCompleter;
+import org.apache.metron.stellar.common.shell.DefaultStellarShellExecutor;
+import org.apache.metron.stellar.common.shell.StellarAutoCompleter;
+import org.apache.metron.stellar.common.shell.StellarResult;
+import org.apache.metron.stellar.common.shell.StellarShellExecutor;
+import org.apache.metron.stellar.common.utils.JSONUtils;
+import org.jboss.aesh.complete.CompleteOperation;
+import org.jboss.aesh.complete.Completion;
+import org.jboss.aesh.console.AeshConsoleCallback;
+import org.jboss.aesh.console.Console;
+import org.jboss.aesh.console.ConsoleOperation;
+import org.jboss.aesh.console.Prompt;
+import org.jboss.aesh.console.settings.SettingsBuilder;
+import org.jboss.aesh.terminal.CharacterType;
+import org.jboss.aesh.terminal.Color;
+import org.jboss.aesh.terminal.TerminalCharacter;
+import org.jboss.aesh.terminal.TerminalColor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+
+import static org.apache.metron.stellar.dsl.Context.Capabilities.CONSOLE;
+import static org.apache.metron.stellar.dsl.Context.Capabilities.GLOBAL_CONFIG;
+import static 
org.apache.metron.stellar.dsl.Context.Capabilities.SHELL_VARIABLES;
+
+/**
+ * A REPL environment for Stellar.
+ *
+ * Useful for debugging Stellar expressions.
+ */
+public class StellarShell extends AeshConsoleCallback implements Completion {
+
+  static final String WELCOME = "Stellar, Go!\n" +
+          "Functions are loading lazily in the background and will be 
unavailable until loaded fully.";
+
+  private List<TerminalCharacter> EXPRESSION_PROMPT = new 
ArrayList<TerminalCharacter>()
+  {{
+    add(new TerminalCharacter('[', new TerminalColor(Color.RED, 
Color.DEFAULT)));
+    add(new TerminalCharacter('S', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
+    add(new TerminalCharacter('t', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
+    add(new TerminalCharacter('e', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
+    add(new TerminalCharacter('l', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
+    add(new TerminalCharacter('l', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
+    add(new TerminalCharacter('a', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
+    add(new TerminalCharacter('r', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.BOLD));
+    add(new TerminalCharacter(']', new TerminalColor(Color.RED, 
Color.DEFAULT)));
+    add(new TerminalCharacter('>', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.UNDERLINE));
+    add(new TerminalCharacter('>', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.UNDERLINE));
+    add(new TerminalCharacter('>', new TerminalColor(Color.GREEN, 
Color.DEFAULT), CharacterType.UNDERLINE));
+    add(new TerminalCharacter(' ', new TerminalColor(Color.DEFAULT, 
Color.DEFAULT)));
+  }};
+
+  public static final String ERROR_PROMPT = "[!] ";
+  public static final String STELLAR_PROPERTIES_FILENAME = 
"stellar.properties";
+
+  /**
+   * Executes Stellar expressions for the shell environment.
+   */
+  private StellarShellExecutor executor;
+
+  /**
+   * The Aesh shell console.
+   */
+  private Console console;
+
+  /**
+   * Provides auto-complete functionality.
+   */
+  private StellarAutoCompleter autoCompleter;
+
+  /**
+   * Execute the Stellar REPL.
+   */
+  public static void main(String[] args) throws Exception {
+    StellarShell shell = new StellarShell(args);
+    shell.run();
+  }
+
+  /**
+   * Create a Stellar REPL.
+   * @param args The commmand-line arguments.
+   */
+  public StellarShell(String[] args) throws Exception {
+
+    // define valid command-line options
+    CommandLineParser parser = new PosixParser();
+    Options options = defineCommandLineOptions();
+    CommandLine commandLine = parser.parse(options, args);
+
+    // print help
+    if(commandLine.hasOption("h")) {
+      HelpFormatter formatter = new HelpFormatter();
+      formatter.printHelp("stellar", options);
+      System.exit(0);
+    }
+
+    // validate the command line options
+    try {
+      StellarShellOptionsValidator.validateOptions(commandLine);
+
+    } catch(IllegalArgumentException e){
+      System.err.println(e.getMessage());
+      System.exit(1);
+    }
+
+    // setup logging, if specified
+    if(commandLine.hasOption("l")) {
+      PropertyConfigurator.configure(commandLine.getOptionValue("l"));
+    }
+
+    console = createConsole(commandLine);
+    autoCompleter = new DefaultStellarAutoCompleter();
+    Properties props = getStellarProperties(commandLine);
+    executor = createExecutor(commandLine, console, props, autoCompleter);
+    loadVariables(commandLine, executor);
+    console.setPrompt(new Prompt(EXPRESSION_PROMPT));
+    console.addCompletion(this);
+    console.setConsoleCallback(this);
+  }
+
+  /**
+   * @return The valid command line options.
+   */
+  private Options defineCommandLineOptions() {
+    Options options = new Options();
+    options.addOption(
+            "z",
+            "zookeeper",
+            true,
+            "Zookeeper URL fragment in the form [HOSTNAME|IPADDRESS]:PORT");
+    options.addOption(
+            "v",
+            "variables",
+            true,
+            "File containing a JSON Map of variables");
+    options.addOption(
+            "irc",
+            "inputrc",
+            true,
+            "File containing the inputrc if not the default ~/.inputrc");
+    options.addOption(
+            "na",
+            "no_ansi",
+            false,
+            "Make the input prompt not use ANSI colors.");
+    options.addOption(
+            "h",
+            "help",
+            false,
+            "Print help");
+    options.addOption(
+            "p",
+            "properties",
+            true,
+            "File containing Stellar properties");
+    Option log4j = new Option(
+            "l",
+            "log4j",
+            true,
+            "The log4j properties file to load");
+    log4j.setArgName("FILE");
+    log4j.setRequired(false);
+    options.addOption(log4j);
+
+    return options;
+  }
+
+  /**
+   * Loads any variables defined in an external file.
+   * @param commandLine The command line arguments.
+   * @param executor The stellar executor.
+   * @throws IOException
+   */
+  private static void loadVariables(
+          CommandLine commandLine,
+          StellarShellExecutor executor) throws IOException {
+
+    if(commandLine.hasOption("v")) {
+
+      // load variables defined in a file
+      String variablePath = commandLine.getOptionValue("v");
+      Map<String, Object> variables = JSONUtils.INSTANCE.load(
+              new File(variablePath),
+              new TypeReference<Map<String, Object>>() {});
+
+      // for each variable...
+      for(Map.Entry<String, Object> kv : variables.entrySet()) {
+        String variable = kv.getKey();
+        Object value = kv.getValue();
+
+        // define the variable - no expression available
+        executor.assign(variable, value, Optional.empty());
+      }
+    }
+  }
+
+  /**
+   * Creates the Stellar execution environment.
+   * @param commandLine The command line arguments.
+   * @param console The console which drives the REPL.
+   * @param properties Stellar properties.
+   */
+  private StellarShellExecutor createExecutor(
+          CommandLine commandLine,
+          Console console,
+          Properties properties,
+          StellarAutoCompleter autoCompleter) throws Exception {
+
+    // setup zookeeper client
+    Optional<String> zookeeperUrl = Optional.empty();
+    if(commandLine.hasOption("z")) {
+      zookeeperUrl = Optional.of(commandLine.getOptionValue("z"));
+    }
+
+    StellarShellExecutor executor = new 
DefaultStellarShellExecutor(properties, zookeeperUrl);
+
+    // the 'CONSOLE' capability is only available with the CLI REPL
+    executor.getContext().addCapability(CONSOLE, () -> console);
+
+    // allows some Stellar functions to access Stellar internals; should 
probably use %magics instead
+    executor.getContext().addCapability(SHELL_VARIABLES, () -> 
executor.getState());
+
+    // register the auto-completer to be notified when needed
+    executor.addSpecialListener(   (special) -> 
autoCompleter.addCandidateFunction(special.getCommand()));
+    executor.addFunctionListener( (function) -> 
autoCompleter.addCandidateFunction(function.getName()));
+    executor.addVariableListener((name, val) -> 
autoCompleter.addCandidateVariable(name));
+
+    executor.init();
+    return executor;
+  }
+
+  /**
+   * Creates the REPL's console.
+   * @param commandLine The command line options.
+   */
+  private Console createConsole(CommandLine commandLine) {
+
+    // console settings
+    boolean useAnsi = !commandLine.hasOption("na");
+    SettingsBuilder settings = new SettingsBuilder().enableAlias(true)
+                                                    .enableMan(true)
+                                                    .ansi(useAnsi)
+                                                    .parseOperators(false)
+                                                    
.inputStream(PausableInput.INSTANCE);
+
+    if(commandLine.hasOption("irc")) {
+      settings = settings.inputrc(new File(commandLine.getOptionValue("irc")));
+    }
+
+    return new Console(settings.create());
+  }
+
+  /**
+   * Retrieves the Stellar properties. The properties are either loaded from a 
file in
+   * the classpath or a set of defaults are used.
+   */
+  private Properties getStellarProperties(CommandLine commandLine) throws 
IOException {
+    Properties properties = new Properties();
+
+    if (commandLine.hasOption("p")) {
+      // attempt to load properties from a file specified on the command-line
+      try (InputStream in = new 
FileInputStream(commandLine.getOptionValue("p"))) {
+        if(in != null) {
+          properties.load(in);
+        }
+      }
+
+    } else {
+      // otherwise attempt to load properties from the classpath
+      try (InputStream in = 
getClass().getClassLoader().getResourceAsStream(STELLAR_PROPERTIES_FILENAME)) {
+        if(in != null) {
+          properties.load(in);
+        }
+      }
+    }
+
+    return properties;
+  }
+
+  /**
+   * Handles the main loop for the REPL.
+   */
+  public void run() {
+    // welcome message
+    writeLine(WELCOME);
+
+    // print the globals if we got 'em
+    executor.getContext()
+            .getCapability(GLOBAL_CONFIG, false)
+            .ifPresent(conf -> writeLine(conf.toString()));
+
+    console.start();
+  }
+
+  /**
+   * Quits the console.
+   */
+  private void handleQuit() {
+    try {
+      console.stop();
+    } catch (Throwable e) {
+      e.printStackTrace();
+    }
+  }
+
+  private void writeLine(String out) {
+    console.getShell().out().println(out);
+  }
+
+  @Override
+  public int execute(ConsoleOperation output) throws InterruptedException {
+
+    // grab the user the input
+    String expression = StringUtils.trimToEmpty(output.getBuffer());
+    if(StringUtils.isNotBlank(expression) ) {
+
+      // execute the expression
+      StellarResult result = executor.execute(expression);
+
+      if(result.isSuccess()) {
+        // on success
+        result.getValue().ifPresent(v -> writeLine(v.toString()));
+
+      } else if (result.isError()) {
+        // on error
+        result.getException().ifPresent(e -> writeLine(ERROR_PROMPT + 
e.getMessage()));
+        result.getException().ifPresent(e -> e.printStackTrace());
+
+      } else if(result.isTerminate()) {
+        // on quit
+        handleQuit();
+
+      } else {
+        // should never happen
+        throw new IllegalStateException("An execution result is neither a 
success nor a failure. Please file a bug report.");
+      }
+    }
+
+    return 0;
+  }
+
+  /**
+   * Performs auto-completion for the shell.
+   * @param completeOperation The auto-complete operation.
+   */
+  @Override
+  public void complete(CompleteOperation completeOperation) {
+    String buffer = completeOperation.getBuffer();
+    final String lastToken = getLastToken(buffer);
+    Iterable<String> candidates = autoCompleter.autoComplete(buffer);
+
+    // transform the candidates into valid completions
+    if(candidates != null && !Iterables.isEmpty(candidates)) {
+      for(String candidate : candidates) {
+        String completion = stripOff(buffer, lastToken) + candidate;
+        completeOperation.addCompletionCandidate(completion);
+      }
+    }
+  }
+
+  private static String getLastToken(String buffer) {
+    String lastToken = Iterables.getLast(Splitter.on(" ").split(buffer), null);
+    return lastToken.trim();
+  }
+
+  private static String stripOff(String baseString, String lastBit) {
+    int index = baseString.lastIndexOf(lastBit);
+    if(index < 0) {
+      return baseString;
+    }
+    return baseString.substring(0, index);
+  }
+
+  /**
+   * @return The executor of Stellar expressions.
+   */
+  public StellarShellExecutor getExecutor() {
+    return executor;
+  }
+
+  /**
+   * @return The console.
+   */
+  public Console getConsole() {
+    return console;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/2dd01b17/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/StellarShellOptionsValidator.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/StellarShellOptionsValidator.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/StellarShellOptionsValidator.java
new file mode 100644
index 0000000..585add5
--- /dev/null
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/common/shell/cli/StellarShellOptionsValidator.java
@@ -0,0 +1,127 @@
+/*
+ *
+ *  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.metron.stellar.common.shell.cli;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.common.base.Splitter;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.validator.routines.InetAddressValidator;
+
+public class StellarShellOptionsValidator {
+
+  private static final Pattern validPortPattern = 
Pattern.compile("(^.*)[:](\\d+)$");
+  private static final Predicate<String> hostnameValidator = hostname -> {
+    if(StringUtils.isEmpty(hostname)) {
+      return false;
+    }
+    try {
+      InetAddress add = InetAddress.getByName(hostname);
+      return true;
+    } catch (UnknownHostException e) {
+      return false;
+    }
+  };
+
+
+
+  private static final InetAddressValidator inetAddressValidator = 
InetAddressValidator
+      .getInstance();
+
+  /**
+   * Validates Stellar CLI Options.
+   */
+  public static void validateOptions(CommandLine commandLine) throws 
IllegalArgumentException {
+    if (commandLine.hasOption('z')) {
+      validateZookeeperOption(commandLine.getOptionValue('z'));
+    }
+    // v, irc, p are files
+    if (commandLine.hasOption('v')) {
+      validateFileOption("v", commandLine.getOptionValue('v'));
+    }
+    if (commandLine.hasOption("irc")) {
+      validateFileOption("irc", commandLine.getOptionValue("irc"));
+    }
+    if (commandLine.hasOption('p')) {
+      validateFileOption("p", commandLine.getOptionValue('p'));
+    }
+
+  }
+
+  /**
+   * Zookeeper argument should be in the form [HOST|IP]:PORT.
+   *
+   * @param zMulti the zookeeper url fragment
+   */
+  private static void validateZookeeperOption(String zMulti) throws 
IllegalArgumentException {
+    for(String z : Splitter.on(",").split(zMulti)) {
+      Matcher matcher = validPortPattern.matcher(z);
+      boolean hasPort = z.contains(":");
+      if (hasPort && !matcher.matches()) {
+        throw new IllegalArgumentException(String.format("Zookeeper option 
must have valid port: %s", z));
+      }
+
+      if (hasPort && matcher.groupCount() != 2) {
+        throw new IllegalArgumentException(
+                String.format("Zookeeper Option must be in the form of 
[HOST|IP]:PORT  %s", z));
+      }
+      String name = hasPort?matcher.group(1):z;
+      Integer port = hasPort?Integer.parseInt(matcher.group(2)):null;
+
+      if (!hostnameValidator.test(name) && 
!inetAddressValidator.isValid(name)) {
+        throw new IllegalArgumentException(
+                String.format("Zookeeper Option %s is not a valid host name or 
ip address  %s", name, z));
+      }
+
+      if (hasPort && (port == 0 || port > 65535)) {
+        throw new IllegalArgumentException(
+                String.format("Zookeeper Option %s port is not valid", z));
+      }
+    }
+  }
+
+  /**
+   * File options must exist and be readable.
+   *
+   * @param option name of the option
+   * @param fileName the file name
+   */
+  private static void validateFileOption(String option, String fileName)
+      throws IllegalArgumentException {
+    File file = new File(fileName);
+    if (!file.exists()) {
+      throw new IllegalArgumentException(
+          String.format("%s: File %s doesn't exist", option, fileName));
+    }
+    if (!file.canRead()) {
+      throw new IllegalArgumentException(
+          String.format("%s: File %s is not readable", option, fileName));
+    }
+  }
+}
+
+

Reply via email to