/* Copyright (c) 2012-2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.cli;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import jline.Terminal;
import jline.UnsupportedTerminal;
import jline.console.ConsoleReader;
import jline.console.completer.AggregateCompleter;
import jline.console.completer.ArgumentCompleter;
import jline.console.completer.Completer;
import jline.console.completer.StringsCompleter;
import org.locationtech.geogig.api.GeoGIG;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.SymRef;
import org.locationtech.geogig.api.plumbing.RefParse;
import org.locationtech.geogig.api.plumbing.ResolveGeogigDir;
import org.locationtech.geogig.repository.Hints;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterDescription;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
/**
* Provides the ability to execute several commands in succession without re-initializing GeoGig or
* the command line interface.
*/
public class GeogigConsole {
private boolean interactive;
/**
* Entry point for the Geogig console.
*
* @param args unused
*/
public static void main(String... args) {
Logging.tryConfigureLogging();
try {
if (args.length == 1) {
new GeogigConsole().runFile(args[0]);
} else if (args.length == 0) {
new GeogigConsole().run();
} else {
System.out.println("Too many arguments.\nUsage: geogig-console [batch_file]");
}
System.exit(0);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
private void runFile(String filename) throws IOException {
final File file = new File(filename);
if (!file.exists()) {
System.out.println("The specified batch file does not exist");
return;
}
// take the input from the console with its input stream directly from the file
InputStream in = new FileInputStream(file);
try {
interactive = false;
run(in, System.out);
} finally {
in.close();
}
}
/**
* @throws IOException
*
*/
private void run() throws IOException {
// interactive will be false if stdin/stdout is redirected
interactive = null != System.console();
run(System.in, System.out);
}
private void run(final InputStream in, final OutputStream out) throws IOException {
final Terminal terminal;
if (interactive) {
terminal = null;/* let jline select an appropriate one */
} else {
// no colors in output
terminal = new UnsupportedTerminal();
}
ConsoleReader consoleReader = new ConsoleReader(in, out, terminal);
consoleReader.setAutoprintThreshold(20);
consoleReader.setPaginationEnabled(interactive);
consoleReader.setHistoryEnabled(interactive);
// needed for CTRL+C not to let the console broken
consoleReader.getTerminal().setEchoEnabled(interactive);
final GeogigCLI cli = new GeogigCLI(consoleReader);
if (interactive) {
addCommandCompleter(consoleReader, cli);
} else {
// no progress percent in output
cli.disableProgressListener();
}
GeogigCLI.addShutdownHook(cli);
setPrompt(cli);
cli.close();
try {
runInternal(cli);
} finally {
try {
cli.close();
} finally {
try {
if (terminal != null) {
terminal.restore();
}
consoleReader.shutdown();
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
}
}
private void addCommandCompleter(ConsoleReader consoleReader, final GeogigCLI cli) {
final JCommander globalCommandParser = cli.newCommandParser();
final Map<String, JCommander> commands = globalCommandParser.getCommands();
List<Completer> completers = new ArrayList<Completer>(commands.size());
for (Map.Entry<String, JCommander> entry : commands.entrySet()) {
String commandName = entry.getKey();
JCommander commandParser = entry.getValue();
List<ParameterDescription> parameters = commandParser.getParameters();
List<String> options = new ArrayList<String>(parameters.size());
for (ParameterDescription pd : parameters) {
String longestName = pd.getLongestName();
options.add(longestName);
}
Collections.sort(options);
ArgumentCompleter commandCompleter = new ArgumentCompleter(new StringsCompleter(
commandName), new StringsCompleter(options));
completers.add(commandCompleter);
}
completers.add(new StringsCompleter("exit", "clear"));
Completer completer = new AggregateCompleter(completers);
consoleReader.addCompleter(completer);
}
/**
* Sets the command prompt
*
* @throws IOException
*/
private void setPrompt(GeogigCLI cli) throws IOException {
if (!interactive) {
return;
}
String currentDir = new File(".").getCanonicalPath();
String currentHead = "";
GeoGIG geogig;
try {
geogig = cli.newGeoGIG(Hints.readOnly());
} catch (Exception e) {
geogig = null;
}
if (geogig != null) {
Optional<URL> dir = geogig.command(ResolveGeogigDir.class).call();
if (dir.isPresent()) {
try {
Optional<Ref> ref = geogig.command(RefParse.class).setName(Ref.HEAD).call();
if (ref.isPresent()) {
if (ref.get() instanceof SymRef) {
currentHead = ((SymRef) ref.get()).getTarget();
int idx = currentHead.lastIndexOf("/");
if (idx != -1) {
currentHead = currentHead.substring(idx + 1);
}
} else {
currentHead = ref.get().getObjectId().toString().substring(0, 7);
}
currentHead = " (" + currentHead + ")";
}
} finally {
geogig.close();
}
}
}
String prompt = "(geogig):" + currentDir + currentHead + " $ ";
cli.getConsole().setPrompt(prompt);
}
private void runInternal(final GeogigCLI cli) throws IOException {
final ConsoleReader consoleReader = cli.getConsole();
while (true) {
String line = consoleReader.readLine();
if (line == null) {
// EOF / CTRL-D
return;
}
if (line.trim().length() == 0) {
continue;
}
if (line.trim().startsWith("#")) {// comment
continue;
}
String[] args = ArgumentTokenizer.tokenize(line);
if (interactive && args != null && args.length == 1) {
if ("exit".equals(args[0])) {
return;
}
if ("clear".equals(args[0])) {
consoleReader.clearScreen();
consoleReader.redrawLine();
continue;
}
}
cli.execute(args);
setPrompt(cli);// in case HEAD has changed
cli.close();
}
}
}