Package restx.shell

Source Code of restx.shell.RestxShell$WatchListener

package restx.shell;

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.io.ByteProcessor;
import com.google.common.io.ByteStreams;
import jline.console.ConsoleReader;
import jline.console.completer.Completer;
import restx.build.MavenSupport;
import restx.build.ModuleDescriptor;
import restx.build.RestxJsonSupport;
import restx.common.Version;
import restx.factory.Factory;
import restx.shell.commands.HelpCommand;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static java.util.Arrays.asList;
import static jline.console.ConsoleReader.RESET_LINE;

/**
* User: xavierhanin
* Date: 4/9/13
* Time: 9:42 PM
*/
public class RestxShell implements Appendable {
    public static final String DEFAULT_PROMPT = "restx";
    private final ConsoleReader consoleReader;
    private final Factory factory;
    private final ImmutableSet<ShellCommand> commands;

    private WatchDir watcher;
    private final List<WatchListener> listeners = new CopyOnWriteArrayList<>();
    private final ExecutorService watcherExecutorService = Executors.newSingleThreadExecutor();
    private Path currentLocation = Paths.get(System.getProperty("user.dir"));
    private ExecMode execMode = ExecMode.INTERACTIVE;
    private Optional<String> restXProjectName = Optional.absent();

    public RestxShell(ConsoleReader consoleReader) {
        this(consoleReader, Factory.getInstance());
    }

    public RestxShell(ConsoleReader consoleReader, Factory factory) {
        this.consoleReader = consoleReader;
        this.factory = factory;

        this.commands = ImmutableSet.copyOf(findCommands());

        initConsole(consoleReader);


    }

    public ConsoleReader getConsoleReader() {
        return consoleReader;
    }

    public Factory getFactory() {
        return factory;
    }

    public ImmutableSet<ShellCommand> getCommands() {
        return commands;
    }

    public void start() throws IOException {
        execMode = ExecMode.INTERACTIVE;
        banner();
        checkIsRestXProjectDirectory();
        showPrompt(consoleReader);

        try {
            for (ShellCommand command : commands) {
                command.start(this);
            }
        } catch (ExitShell e) {
            terminate();
            return;
        }

        installCompleters();

        boolean exit = false;
        while (!exit) {
            String line = consoleReader.readLine();
            exit = exec(line);
        }

        terminate();
    }

    private void terminate() throws IOException {
        consoleReader.println("Bye.");
        synchronized (this) {
            if (watcher != null) {
                watcherExecutorService.shutdownNow();
                watcher = null;
            }
        }
        consoleReader.shutdown();
    }

    public void exec(Iterable<String> commands) throws Exception {
        execMode = ExecMode.BATCH;
        banner();

        try {
            for (String command : commands) {
                printIn("> " + command, AnsiCodes.ANSI_PURPLE);
                println("");
                doExec(command);
            }
        } finally {
            terminate();
        }

    }

    public static void printIn(Appendable appendable, String msg, String ansiCode) throws IOException {
        appendable.append(ansiCode + msg + AnsiCodes.ANSI_RESET);
    }

    public static List<String> splitArgs(String line) {
        return ImmutableList.copyOf(Splitter.on(" ").omitEmptyStrings().split(line));
    }

    public void printIn(String msg, String ansiCode) throws IOException {
        consoleReader.print(ansiCode + msg + AnsiCodes.ANSI_RESET);
    }

    @Override
    public Appendable append(CharSequence csq) throws IOException {
        consoleReader.print(csq);
        return this;
    }

    @Override
    public Appendable append(CharSequence csq, int start, int end) throws IOException {
        if (csq != null) {
            append(csq.subSequence(start, end));
        }
        return this;
    }

    @Override
    public Appendable append(char c) throws IOException {
        consoleReader.print(String.valueOf(c));
        return this;
    }

    public String ask(String msg, String defaultValue) throws IOException {
        return ask(msg, defaultValue,
                "No help provided for that question, sorry, try to figure it out or ask to the community...");
    }

    public String ask(String msg, String defaultValue, String help) throws IOException {
        while (true) {
            String value = consoleReader.readLine(String.format(msg, defaultValue));
            if (value.trim().isEmpty()) {
                return defaultValue;
            } else if (value.trim().equals("??")) {
                printIn(help, AnsiCodes.ANSI_YELLOW);
                println("");
            } else {
                return value.trim();
            }
        }
    }

    public boolean askBoolean(String message, String defaultValue) throws IOException {
        return asList("y", "yes", "true", "on").contains(ask(message, defaultValue).toLowerCase(Locale.ENGLISH));
    }

    public boolean askBoolean(String message, String defaultValue, String help) throws IOException {
        return asList("y", "yes", "true", "on").contains(ask(message, defaultValue, help).toLowerCase(Locale.ENGLISH));
    }


    public void print(String s) throws IOException {
        append(s);
    }

    public void println(String msg) throws IOException {
        consoleReader.println(msg);
        consoleReader.flush();
    }

    public void printError(String msg, Exception ex) {
        System.err.println(msg);
        ex.printStackTrace();
    }

    public Path currentLocation() {
        return currentLocation;
    }

    public void cd(Path path) {
        restXProjectName(Optional.<String>absent());
        currentLocation = path;
        checkIsRestXProjectDirectory();
    }

    private void checkIsRestXProjectDirectory() {
        File currentDirectory = currentLocation.toFile();

        Optional<ModuleDescriptor> moduleDescriptor = getModuleDescriptor(currentDirectory);
        if (moduleDescriptor.isPresent()) {
            restXProjectName(Optional.of(moduleDescriptor.get().getGav().getArtifactId()));
        }
    }

    private Optional<ModuleDescriptor> getModuleDescriptor(File currentDirectory) {
        File restXProjectDescriptor = new File(currentDirectory, "md.restx.json");
        if (restXProjectDescriptor.exists()) {
            RestxJsonSupport restxJsonSupport = new RestxJsonSupport();
            try {
                return Optional.of(restxJsonSupport.parse(restXProjectDescriptor.toPath()));
            } catch (IOException e) {
                printError("Failed to read md.restx.json", e);
            }
        }
        File mavenProjectDescriptor = new File(currentDirectory, "pom.xml");
        if (mavenProjectDescriptor.exists()) {
            MavenSupport mavenSupport = new MavenSupport();
            try {
                return Optional.of(mavenSupport.parse(mavenProjectDescriptor.toPath()));
            } catch (IOException e) {
                printError("Failed to read pom.xml", e);
            }
        }

        return Optional.absent();
    }

    public Path installLocation() {
        return Paths.get(System.getProperty("restx.shell.home", ".")).normalize();
    }

    public void restart() {
        try {
            if (execMode == ExecMode.BATCH) {
                printIn("TERMINATING SHELL [NO AUTO RESTART IN BATCH MODE]...", AnsiCodes.ANSI_GREEN);
                println("");
                throw new ExitShell();
            } else {
                new File(installLocation().toFile(), ".restart").createNewFile();
                printIn("RESTARTING SHELL...", AnsiCodes.ANSI_RED);
                println("");
                throw new ExitShell();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void watchFile(WatchListener listener) {
        synchronized (this) {
            if (watcher == null) {
                final Path dir = currentLocation();
                watcherExecutorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            watcher = new WatchDir(dir, true) {
                                @Override
                                protected void onEvent(WatchEvent.Kind<?> kind, Path path) {
                                    for (WatchListener watchListener : listeners) {
                                        try {
                                            watchListener.onEvent(RestxShell.this, kind, path);
                                        } catch (Exception ex) {
                                            printError("FS event propagation to " + watchListener +
                                                    " raised an exception: " + ex, ex);
                                        }
                                    }
                                }
                            };
                            watcher.processEvents();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                });
            }
        }
        listeners.add(listener);
    }

    protected void initConsole(ConsoleReader consoleReader) {
        consoleReader.setPrompt(DEFAULT_PROMPT + "> ");
        consoleReader.setHistoryEnabled(true);
    }

    protected void banner() throws IOException {
        consoleReader.println("===============================================================================");
        consoleReader.println("== WELCOME TO RESTX SHELL - " + version()
                + (execMode == ExecMode.INTERACTIVE
                ? (" - type `help` for help on available commands")
                : " - BATCH MODE"));
        consoleReader.println("===============================================================================");
    }

    /**
     * Executes the given command line.
     *
     * Note: this won't raise an exception except in case of IOException with the console itself.
     *
     * @param line the command line to execute
     * @return true if the shell should exit after this command, false otherwise.
     * @throws IOException
     */
    protected boolean exec(String line) throws IOException {
        try {
            doExec(line);
            return false;
        } catch (CommandNotFoundException e) {
            consoleReader.println("command not found. use `help` to get the list of commands.");
            return false;
        } catch (ExitShell e) {
            return true;
        } catch (Exception e) {
            consoleReader.println("command " + line + " raised an exception: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Executes the given command.
     *
     * Exception raised by command are propagated if any, including the ExitShell exception.
     *
     * If command is not found, it raises a CommandNotFoundException.
     *
     * @param line the command line to execute
     * @throws Exception
     */
    protected void doExec(String line) throws Exception {
        for (ShellCommand command : commands) {
            Optional<? extends ShellCommandRunner> match = command.match(line);
            if (match.isPresent()) {
                // store current completers and clean them, so that executing command can perform in a clean env
                Collection<Completer> storedCompleters = ImmutableList.copyOf(consoleReader.getCompleters());
                for (Completer completer : storedCompleters) {
                    consoleReader.removeCompleter(completer);
                }

                try {
                    match.get().run(this);
                } finally {
                    for (Completer completer : ImmutableList.copyOf(consoleReader.getCompleters())) {
                        consoleReader.removeCompleter(completer);
                    }
                    for (Completer completer : storedCompleters) {
                        consoleReader.addCompleter(completer);
                    }
                    showPrompt(consoleReader);
                }
                return;
            }
        }
        throw new CommandNotFoundException(line);
    }

    private void showPrompt(ConsoleReader consoleReader) {
        if (restXProjectName.isPresent()) {
            consoleReader.setPrompt(AnsiCodes.ANSI_CYAN + restXProjectName.get() + AnsiCodes.ANSI_RESET + "> ");
        } else {
            consoleReader.setPrompt(DEFAULT_PROMPT + "> ");
        }
    }


    protected void installCompleters() {
        for (ShellCommand command : commands) {
            for (Completer completer : command.getCompleters()) {
                consoleReader.addCompleter(completer);
            }
        }
    }

    protected Set<ShellCommand> findCommands() {
        Set<ShellCommand> commands = factory.queryByClass(ShellCommand.class).findAsComponents();
        HelpCommand helpCommand = new HelpCommand(commands);
        commands = Sets.newLinkedHashSet(commands);
        commands.add(helpCommand);
        return commands;
    }

    public static void main(String[] args) throws Exception {
        ConsoleReader consoleReader = new ConsoleReader();
        RestxShell restxShell = new RestxShell(consoleReader);
        if (args.length > 0) {
            try {
                restxShell.exec(Splitter.on("+").trimResults().split(Joiner.on(" ").join(args)));
            } catch (CommandNotFoundException e) {
                System.out.println("command not found: " + e.getLine());
                System.exit(1);
            }
        } else {
            restxShell.start();
        }
    }

    public void restXProjectName(Optional<String> restXProjectName) {
        this.restXProjectName = restXProjectName;
    }

    public Optional<String> restXProjectName() {
        return restXProjectName;
    }

    public String version() {
        return Version.getVersion("io.restx", "restx-shell");
    }

    public void download(final URL source, File destination) throws IOException {
        final String name = source.toString();
        println("downloading " + (name.length() <= 70 ? name : "[...]" + name.substring(name.length() - 65)));
        URLConnection connection = source.openConnection();
        final int total = connection.getContentLength();
        final int[] progress = new int[]{0};
        startProgress(name, total);
        try (InputStream stream = connection.getInputStream();
             final OutputStream out = new FileOutputStream(destination)) {
            ByteStreams.readBytes(stream,
                    new ByteProcessor<Void>() {
                        public boolean processBytes(byte[] buffer, int offset, int length)
                                throws IOException {
                            out.write(buffer, offset, length);
                            progress[0] += length;
                            updateProgress(name, progress[0], total);
                            return true;
                        }

                        public Void getResult() {
                            return null;
                        }
                    });
            endProgress(name);
        }
    }

    public void endProgress(String name) throws IOException {
        consoleReader.println();
    }

    public void startProgress(String name, long total) throws IOException {
        updateProgress(name, 0, total);
    }

    public void updateProgress(String name, long progress, long total) throws IOException {
        int barWidth = 70;
        StringBuilder line = new StringBuilder();

        if (progress >= total) {
            line.append("[").append(Strings.repeat("=", barWidth)).append("]");
        } else {
            int p = (int) Math.min(progress * barWidth / total, barWidth - 1);
            line.append("[").append(Strings.repeat("=", p)).append(">").append(Strings.repeat(" ", barWidth - p - 1)).append("]");
        }

        line.append(String.format(" %3d", (progress >= total) ? (100) : (progress * 100 / total))).append("%");

        consoleReader.print("" + RESET_LINE + line);
    }


    public static final class ExitShell extends RuntimeException { }

    public static class AnsiCodes {
        public static final String ANSI_RESET = "\u001B[0m";
        public static final String ANSI_BLACK = "\u001B[30m";
        public static final String ANSI_RED = "\u001B[31m";
        public static final String ANSI_GREEN = "\u001B[32m";
        public static final String ANSI_YELLOW = "\u001B[33m";
        public static final String ANSI_BLUE = "\u001B[34m";
        public static final String ANSI_PURPLE = "\u001B[35m";
        public static final String ANSI_CYAN = "\u001B[36m";
        public static final String ANSI_WHITE = "\u001B[37m";
    }

    public static interface WatchListener {
        public void onEvent(RestxShell shell, WatchEvent.Kind<?> kind, Path path);
    }

    public static enum ExecMode {
        INTERACTIVE, BATCH
    }

    private static class CommandNotFoundException extends RuntimeException {
        private final String line;

        public CommandNotFoundException(String line) {
            super("command not found: " + line);
            this.line = line;
        }

        public String getLine() {
            return line;
        }

        @Override
        public String toString() {
            return "CommandNotFoundException{" +
                    "line='" + line + '\'' +
                    '}';
        }
    }
}
TOP

Related Classes of restx.shell.RestxShell$WatchListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.