de.undercouch.citeproc.tool.ShellCommand.java Source code

Java tutorial

Introduction

Here is the source code for de.undercouch.citeproc.tool.ShellCommand.java

Source

// Copyright 2014 Michel Kraemer
//
// Licensed 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 de.undercouch.citeproc.tool;

import java.beans.IntrospectionException;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;

import jline.console.ConsoleReader;
import jline.console.history.FileHistory;
import de.undercouch.citeproc.CSLTool;
import de.undercouch.citeproc.helper.tool.Command;
import de.undercouch.citeproc.helper.tool.InputReader;
import de.undercouch.citeproc.helper.tool.Option;
import de.undercouch.citeproc.helper.tool.OptionGroup;
import de.undercouch.citeproc.helper.tool.OptionIntrospector;
import de.undercouch.citeproc.helper.tool.OptionIntrospector.ID;
import de.undercouch.citeproc.helper.tool.OptionParserException;
import de.undercouch.citeproc.tool.shell.ConsoleInputReader;
import de.undercouch.citeproc.tool.shell.ErrorOutputStream;
import de.undercouch.citeproc.tool.shell.ShellCommandCompleter;
import de.undercouch.citeproc.tool.shell.ShellCommandParser;
import de.undercouch.citeproc.tool.shell.ShellCommandParser.Result;
import de.undercouch.citeproc.tool.shell.ShellContext;
import de.undercouch.citeproc.tool.shell.ShellExitCommand;
import de.undercouch.citeproc.tool.shell.ShellQuitCommand;

/**
 * Runs the tool in interactive mode
 * @author Michel Kraemer
 */
public class ShellCommand extends AbstractCSLToolCommand {
    /**
     * Commands that should not be available in the interactive shell
     */
    public static final List<Class<? extends Command>> EXCLUDED_COMMANDS;
    static {
        {
            List<Class<? extends Command>> ec = new ArrayList<Class<? extends Command>>();
            ec.add(HelpCommand.class);
            ec.add(ShellCommand.class);
            EXCLUDED_COMMANDS = Collections.unmodifiableList(ec);
        }
    };

    @Override
    public String getUsageName() {
        return "shell";
    }

    @Override
    public String getUsageDescription() {
        return "Run " + CSLToolContext.current().getToolName() + " in interactive mode";
    }

    @Override
    public int doRun(String[] remainingArgs, InputReader in, PrintWriter out)
            throws OptionParserException, IOException {
        //prepare console
        final ConsoleReader reader = new ConsoleReader();
        reader.setPrompt("> ");
        reader.addCompleter(new ShellCommandCompleter(EXCLUDED_COMMANDS));
        FileHistory history = new FileHistory(
                new File(CSLToolContext.current().getConfigDir(), "shell_history.txt"));
        reader.setHistory(history);

        //enable colored error stream for ANSI terminals
        if (reader.getTerminal().isAnsiSupported()) {
            OutputStream errout = new ErrorOutputStream(reader.getTerminal().wrapOutIfNeeded(System.out));
            System.setErr(new PrintStream(errout, false, ((OutputStreamWriter) reader.getOutput()).getEncoding()));
        }

        PrintWriter cout = new PrintWriter(reader.getOutput(), true);

        //print welcome message
        cout.println("Welcome to " + CSLToolContext.current().getToolName() + " " + CSLTool.getVersion());
        cout.println();
        cout.println("Type `help' for a list of commands and `help " + "<command>' for information");
        cout.println("on a specific command. Type `quit' to exit " + CSLToolContext.current().getToolName() + ".");
        cout.println();

        String line;
        ShellContext.enter();
        try {
            line = mainLoop(reader, cout);
        } finally {
            ShellContext.exit();

            //make sure we save the history before we exit
            history.flush();
        }

        //print Goodbye message
        if (line == null) {
            //user pressed Ctrl+D
            cout.println();
        }
        cout.println("Bye!");

        return 0;
    }

    /**
     * Runs the shell's main loop
     * @param reader the console reader used to read user input from
     * the command line
     * @param cout the output stream
     * @return the last line read or null if the user pressed Ctrl+D and
     * the input stream has ended
     * @throws IOException if an I/O error occurs
     */
    private String mainLoop(ConsoleReader reader, PrintWriter cout) throws IOException {
        InputReader lr = new ConsoleInputReader(reader);

        String line;
        while ((line = reader.readLine()) != null) {
            if (line.isEmpty()) {
                continue;
            }

            String[] args = ShellCommandParser.split(line);

            Result pr;
            try {
                pr = ShellCommandParser.parse(args, EXCLUDED_COMMANDS);
            } catch (OptionParserException e) {
                //there is an option, only commands are allowed in the
                //interactive shell
                error(e.getMessage());
                continue;
            } catch (IntrospectionException e) {
                //should never happen
                throw new RuntimeException(e);
            }

            Class<? extends Command> cmdClass = pr.getFirstCommand();

            if (cmdClass == ShellExitCommand.class || cmdClass == ShellQuitCommand.class) {
                break;
            } else if (cmdClass == null) {
                error("unknown command `" + args[0] + "'");
                continue;
            }

            Command cmd;
            try {
                cmd = cmdClass.newInstance();
            } catch (Exception e) {
                //should never happen
                throw new RuntimeException(e);
            }

            boolean acceptsInputFile = false;
            if (cmd instanceof ProviderCommand) {
                cmd = new InputFileCommand((ProviderCommand) cmd);
                acceptsInputFile = true;
            }

            args = ArrayUtils.subarray(args, 1, args.length);
            args = augmentCommand(args, pr.getLastCommand(), acceptsInputFile);

            try {
                cmd.run(args, lr, cout);
            } catch (OptionParserException e) {
                error(e.getMessage());
            }
        }

        return line;
    }

    /**
     * Augments the given command line with context variables
     * @param args the current command line
     * @param cmd the last parsed command in the command line
     * @param acceptsInputFile true if the given command accepts an input file
     * @return the new command line
     */
    private String[] augmentCommand(String[] args, Class<? extends Command> cmd, boolean acceptsInputFile) {
        OptionGroup<ID> options;
        try {
            if (acceptsInputFile) {
                options = OptionIntrospector.introspect(cmd, InputFileCommand.class);
            } else {
                options = OptionIntrospector.introspect(cmd);
            }
        } catch (IntrospectionException e) {
            //should never happen
            throw new RuntimeException(e);
        }

        ShellContext sc = ShellContext.current();
        for (Option<ID> o : options.getOptions()) {
            if (o.getLongName().equals("style")) {
                args = ArrayUtils.add(args, "--style");
                args = ArrayUtils.add(args, sc.getStyle());
            } else if (o.getLongName().equals("locale")) {
                args = ArrayUtils.add(args, "--locale");
                args = ArrayUtils.add(args, sc.getLocale());
            } else if (o.getLongName().equals("format")) {
                args = ArrayUtils.add(args, "--format");
                args = ArrayUtils.add(args, sc.getFormat());
            } else if (o.getLongName().equals("input") && sc.getInputFile() != null
                    && !sc.getInputFile().isEmpty()) {
                args = ArrayUtils.add(args, "--input");
                args = ArrayUtils.add(args, sc.getInputFile());
            }
        }

        return args;
    }
}