Automatic class discovery

Thanks to the automatic class discovery (provided by jclasslocator), you don't have to register anything, you only have to place the command in the same package as the abstract superclass or interface.

Command-line

It is quite easy to add new commands to the tool:

  • create a class derived from com.github.fracpete.wekavirtualenv.command.AbstractCommand
  • place the class in package com.github.fracpete.wekavirtualenv.command

Dataset filenames

If your command should handle additional arguments as dataset filenames, then implement the indicator interface com.github.fracpete.wekavirtualenv.command.DatasetHandler.

This interface is used by the com.github.fracpete.wekavirtualenv.gui.ArffCommandSelector tool, listing all the tools in the dropdown box that implement this interface. By default, these are by default only the Explorer and ArffViewer.

Handling arguments

The Delete command just takes a single option, which is the name of the environment to delete. In order for the parsing to work, you have to define a ArgumentParser object in the getParser method and then access the parsed options in the doExecute method, using the Namespace object.

Here is the Delete command's code:

public class Delete extends AbstractCommand {

  public String getName() {
    return "delete";
  }

  public String getHelp() {
    return "Deletes an existing environment.";
  }

  public ArgumentParser getParser() {
    ArgumentParser result = new ArgumentParser(getName());
    result.addOption("--name")
      .dest("name")
      .help("the name of the environment to delete")
      .required(true);
    return result;
  }

  protected boolean doExecute(Namespace ns, String[] options) {
    String msg = Environments.delete(ns.getString("name"));
    if (msg != null)
      addError("Failed to delete environment '" + ns.getString("name") + "':\n" + msg);
    else
      System.out.println("Environment successfully deleted: " + ns.getString("name"));
    return (msg == null);
  }
}

For more information on the parsing, check out the simple-argparse4j project page.

Executing class with main method

The AbstractLaunchCommand class makes it easier to execute a class that has a main method.

The class for launching the Weka Explorer is quite simple:

public class Explorer extends AbstractLaunchCommand implements DatasetHandler {

  public String getName() {
    return "explorer";
  }

  public String getHelp() {
    return "Launches the Weka Explorer.\n"
      + "You can supply a dataset filename to load immediately in the Explorer.";
  }

  public boolean supportsAdditionalArguments() {
    return true;
  }

  protected boolean doExecute(Namespace ns, String[] options) {
    return launch(build("weka.gui.explorer.Explorer", options));
  }
}

In the doExecute method, the build method takes the class name and any options that the class should process. In the Explorer's case an optional dataset to load.

Script command

If you want to add a script command, you just have to derive it from the com.github.fracpete.wekavirtualenv.command.script.AbstractScriptCommand super class and place it in the com.github.fracpete.wekavirtualenv.command.script package.

Here is the code for the DirName command:

public class DirName extends AbstractScriptCommand {

  public String getName() {
    return "dir_name";
  }

  public String getHelp() {
    return "Extracts the path from the specified file variable.";
  }

  public ArgumentParser getParser() {
    ArgumentParser result = new ArgumentParser(getName());
    result.addOption("--file")
      .dest("file")
      .help("the full path to extract the path from.")
      .required(true);
    result.addOption("--dest")
      .dest("dest")
      .help("the name of the variable to store the result in.")
      .required(true);
    return result;
  }

  protected boolean evalCommand(Namespace ns, String[] options) {
    File file = new File(ns.getString("file"));
    getContext().getVariables().set(ns.getString("dest"), file.getParentFile().getAbsolutePath());
    return true;
  }
}

Filter

If you want to add a custom output filter (like grep or tee), then you only have to implement the com.github.fracpete.wekavirtualenv.command.filter.Filter interface (or use the abstract superclass AbstractFilter for convenience) and place it in the com.github.fracpete.wekavirtualenv.command.filter package.

Here is the code for the Grep filter:

public class Grep
  extends AbstractFilter {

  /** the pattern for matching. */
  protected Pattern m_RegExp;

  protected boolean m_Invert;

  public String getName() {
    return "grep";
  }

  @Override
  public String getHelp() {
    return "For capturing strings that match a regular expression.";
  }

  public ArgumentParser getParser() {
    ArgumentParser result = super.getParser();
    result.addOption("--regexp")
      .dest("regexp")
      .help("the regular expression that the output must match to be kept.")
      .required(true);
    result.addOption("--invert")
      .dest("invert")
      .help("whether to invert the matching sense.")
      .argument(false);
    return result;
  }

  public boolean initialize(Namespace ns) {
    boolean result = super.initialize(ns);
    if (result) {
      try {
    m_RegExp = Pattern.compile(ns.getString("regexp"));
      }
      catch (Exception e) {
        addError("Invalid regular expression: " + ns.getString("regexp"), e);
        return false;
      }
      m_Invert = ns.getBoolean("invert");
    }
    return result;
  }

  protected String doIntercept(String line, boolean stdout) {
    if ((!m_Invert && m_RegExp.matcher(line).matches())
      || (m_Invert && !m_RegExp.matcher(line).matches())) {
      return line;
    }
    return null;
  }
}

User interface

For adding a command in the user interface, you have to subclass the abstract class com.github.fracpete.wekavirtualenv.gui.command.AbstractGUICommand and place the class in the com.github.fracpete.wekavirtualenv.gui.command package.

If you want to capture the output of the process, then let the method generatesOutput() return true.

If you the command is to be run in the context of a Weka environment, then the method requiresEnvironment() needs to return true. Methods that return true automatically show up in the drop-down list of an environment. Ones that return false, show up in the main menu.

The destroy() method is used for stopping any process that got launched.

Here is the code for launching the Weka Explorer, which requires an environment and also captures the output of the launched process:

public class Explorer
  extends AbstractGUICommand {

  protected com.github.fracpete.wekavirtualenv.command.Explorer m_Command;

  public String getName() {
    return "Explorer";
  }

  public String getGroup() {
    return "gui";
  }

  public boolean requiresEnvironment() {
    return true;
  }

  public boolean generatesOutput() {
    return true;
  }

  @Override
  protected String doExecute() {
    String result = null;
    m_Command = new com.github.fracpete.wekavirtualenv.command.Explorer();
    m_Command.setEnv(m_Environment);
    transferOutputListeners(m_Command);
    if (!m_Command.execute(new String[0])) {
      if (m_Command.hasErrors())
        result = m_Command.getErrors();
      else
        result = "Failed to launch Explorer!";
    }
    m_Command = null;
    return result;
  }

  public void destroy() {
    if (m_Command != null)
      m_Command.destroy();
  }
}