From 5f44acf88f34c44e00232fbcde4ad7ad639aa3f4 Mon Sep 17 00:00:00 2001
From: Eike Cochu <eike@cochu.com>
Date: Sun, 27 Dec 2015 12:59:11 +0100
Subject: [PATCH] added console utils

added todo
added cmd scropt symlink
deleted tests
updated jgibblda logging
---
 TODO                                          |   7 +
 .../src/main/java/jgibblda/Estimator.java     |   9 +-
 .../src/main/java/jgibblda/Inferencer.java    |  10 +-
 ma-impl.sublime-workspace                     | 179 ++++++++++++++----
 vipra-cmd.sh                                  |   1 +
 .../main/java/de/vipra/cmd/CmdOptions.java    |  20 +-
 .../src/main/java/de/vipra/cmd/Main.java      |  87 ++++++---
 .../de/vipra/cmd/lda/JGibbLDAAnalyzer.java    |   2 +-
 .../de/vipra/cmd/option/ClearCommand.java     |   6 +-
 .../de/vipra/cmd/option/ImportCommand.java    |  14 +-
 .../de/vipra/cmd/option/StatsCommand.java     |  13 ++
 vipra-cmd/src/main/resources/log4j2.xml       |   2 +-
 vipra-rest/.classpath                         |   5 -
 .../org.eclipse.core.resources.prefs          |   2 -
 .../rest/resource/ArticleResourceTest.java    |  25 ---
 .../main/java/de/vipra/util/ConsoleUtils.java |  76 +++++++-
 .../main/java/de/vipra/util/ListUtils.java    |  24 +++
 .../main/java/de/vipra/util/StringUtils.java  |   9 +
 .../vipra/util/service/DatabaseService.java   |   2 +-
 19 files changed, 358 insertions(+), 135 deletions(-)
 create mode 100644 TODO
 create mode 120000 vipra-cmd.sh
 create mode 100644 vipra-cmd/src/main/java/de/vipra/cmd/option/StatsCommand.java
 delete mode 100644 vipra-rest/src/test/java/de/vipra/rest/resource/ArticleResourceTest.java
 create mode 100644 vipra-util/src/main/java/de/vipra/util/ListUtils.java

diff --git a/TODO b/TODO
new file mode 100644
index 00000000..1da64172
--- /dev/null
+++ b/TODO
@@ -0,0 +1,7 @@
+cmd
+  ☐ implement delete operation
+  ☐ implement filebase remove
+  ☐ implement elasticsearch indexing
+
+rest
+  ☐ implement etag caching
\ No newline at end of file
diff --git a/jgibblda/src/main/java/jgibblda/Estimator.java b/jgibblda/src/main/java/jgibblda/Estimator.java
index b59b77a0..be89f40a 100644
--- a/jgibblda/src/main/java/jgibblda/Estimator.java
+++ b/jgibblda/src/main/java/jgibblda/Estimator.java
@@ -53,11 +53,11 @@ public class Estimator {
 	}
 
 	public void estimate() {
-		log.info("Sampling " + trnModel.niters + " iteration!");
+		log.info("sampling " + trnModel.niters + " iterations");
 
 		int lastIter = trnModel.liter;
 		for (trnModel.liter = lastIter + 1; trnModel.liter < trnModel.niters + lastIter; trnModel.liter++) {
-			log.info("Iteration " + trnModel.liter + " ...");
+			log.info("iteration " + trnModel.liter);
 
 			// for all z_i
 			for (int m = 0; m < trnModel.M; m++) {
@@ -71,7 +71,7 @@ public class Estimator {
 
 			if (option.savestep > 0) {
 				if (trnModel.liter % option.savestep == 0) {
-					log.info("Saving the model at iteration " + trnModel.liter + " ...");
+					log.info("iteration checkpoint: " + trnModel.liter);
 					computeTheta();
 					computePhi();
 					trnModel.saveModel(option.modelName + "-" + Conversion.ZeroPad(trnModel.liter, 5));
@@ -79,8 +79,7 @@ public class Estimator {
 			}
 		} // end iterations
 
-		log.info("Gibbs sampling completed");
-		log.info("Saving the final model");
+		log.info("sampling completed, saving final model");
 		computeTheta();
 		computePhi();
 		trnModel.liter--;
diff --git a/jgibblda/src/main/java/jgibblda/Inferencer.java b/jgibblda/src/main/java/jgibblda/Inferencer.java
index 25bfa07f..2d3fb1be 100644
--- a/jgibblda/src/main/java/jgibblda/Inferencer.java
+++ b/jgibblda/src/main/java/jgibblda/Inferencer.java
@@ -57,13 +57,12 @@ public class Inferencer {
 
 	// inference new model ~ getting data from a specified dataset
 	public Model inference(LDADataset newData) {
-		log.info("init new model");
 		Model newModel = new Model();
 
 		newModel.initNewModel(option, newData, trnModel);
 		this.newModel = newModel;
 
-		log.info("Sampling " + niters + " iteration for inference!");
+		log.info("sampling " + niters + " iterations");
 		for (newModel.liter = 1; newModel.liter <= niters; newModel.liter++) {
 
 			// for all newz_i
@@ -78,7 +77,7 @@ public class Inferencer {
 
 		} // end iterations
 
-		log.info("Gibbs sampling for inference completed!");
+		log.info("sampling completed");
 
 		computeNewTheta();
 		computeNewPhi();
@@ -98,7 +97,7 @@ public class Inferencer {
 		if (!newModel.initNewModel(option, trnModel))
 			return null;
 
-		log.info("Sampling " + niters + " iteration for inference!");
+		log.info("sampling " + niters + " iterations");
 
 		for (newModel.liter = 1; newModel.liter <= niters; newModel.liter++) {
 
@@ -114,8 +113,7 @@ public class Inferencer {
 
 		} // end iterations
 
-		log.info("Gibbs sampling for inference completed!");
-		log.info("Saving the inference outputs!");
+		log.info("sampling completed, saving final model");
 
 		computeNewTheta();
 		computeNewPhi();
diff --git a/ma-impl.sublime-workspace b/ma-impl.sublime-workspace
index 287a731c..7804747e 100644
--- a/ma-impl.sublime-workspace
+++ b/ma-impl.sublime-workspace
@@ -272,11 +272,20 @@
 	"buffers":
 	[
 		{
-			"file": "/home/eike/Downloads/JGibbLDA-v.1.0/src/jgibblda/Constants.java",
+			"file": "TODO",
 			"settings":
 			{
-				"buffer_size": 1334,
-				"line_ending": "Windows"
+				"buffer_size": 134,
+				"line_ending": "Unix",
+				"name": "TODO"
+			}
+		},
+		{
+			"file": "Vagrantfile",
+			"settings":
+			{
+				"buffer_size": 955,
+				"line_ending": "Unix"
 			}
 		}
 	],
@@ -287,8 +296,8 @@
 	"build_varint": "",
 	"command_palette":
 	{
-		"height": 231.0,
-		"last_filter": "insta",
+		"height": 363.0,
+		"last_filter": "",
 		"selected_items":
 		[
 			[
@@ -457,12 +466,11 @@
 	},
 	"expanded_folders":
 	[
-		"/home/eike/Repositories/fu/ss15/ma/impl",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vm",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vm/data"
+		"/home/eike/Repositories/fu/ss15/ma/impl"
 	],
 	"file_history":
 	[
+		"/home/eike/Downloads/JGibbLDA-v.1.0/src/jgibblda/Constants.java",
 		"/home/eike/Downloads/JGibbLDA-v.1.0/models/casestudy-en/model-final.others",
 		"/home/eike/Downloads/JGibbLDA-v.1.0/models/casestudy-en/model-final.twords",
 		"/home/eike/Downloads/JGibbLDA-v.1.0/models/casestudy-en/newdocs.dat",
@@ -903,89 +911,182 @@
 	"groups":
 	[
 		{
-			"selected": 0,
+			"selected": 1,
 			"sheets":
 			[
 				{
 					"buffer": 0,
-					"file": "/home/eike/Downloads/JGibbLDA-v.1.0/src/jgibblda/Constants.java",
+					"file": "TODO",
 					"semi_transient": false,
 					"settings":
 					{
-						"buffer_size": 1334,
+						"buffer_size": 134,
 						"regions":
 						{
 						},
 						"selection":
 						[
 							[
-								1004,
-								1004
+								0,
+								0
 							]
 						],
 						"settings":
 						{
 							"BracketHighlighterBusy": false,
+							"auto_name": "TODO",
 							"bh_regions":
 							[
+								"bh_default",
+								"bh_default_center",
+								"bh_default_open",
+								"bh_default_close",
+								"bh_default_content",
+								"bh_regex",
+								"bh_regex_center",
+								"bh_regex_open",
+								"bh_regex_close",
+								"bh_regex_content",
+								"bh_double_quote",
+								"bh_double_quote_center",
+								"bh_double_quote_open",
+								"bh_double_quote_close",
+								"bh_double_quote_content",
+								"bh_square",
+								"bh_square_center",
+								"bh_square_open",
+								"bh_square_close",
+								"bh_square_content",
+								"bh_angle",
+								"bh_angle_center",
+								"bh_angle_open",
+								"bh_angle_close",
+								"bh_angle_content",
+								"bh_curly",
+								"bh_curly_center",
+								"bh_curly_open",
+								"bh_curly_close",
+								"bh_curly_content",
 								"bh_unmatched",
 								"bh_unmatched_center",
 								"bh_unmatched_open",
 								"bh_unmatched_close",
 								"bh_unmatched_content",
+								"bh_c_define",
+								"bh_c_define_center",
+								"bh_c_define_open",
+								"bh_c_define_close",
+								"bh_c_define_content",
+								"bh_single_quote",
+								"bh_single_quote_center",
+								"bh_single_quote_open",
+								"bh_single_quote_close",
+								"bh_single_quote_content",
 								"bh_round",
 								"bh_round_center",
 								"bh_round_open",
 								"bh_round_close",
 								"bh_round_content",
-								"bh_square",
-								"bh_square_center",
-								"bh_square_open",
-								"bh_square_close",
-								"bh_square_content",
+								"bh_tag",
+								"bh_tag_center",
+								"bh_tag_open",
+								"bh_tag_close",
+								"bh_tag_content"
+							],
+							"incomplete_sync": null,
+							"syntax": "Packages/PlainTasks/PlainTasks.tmLanguage"
+						},
+						"translation.x": 0.0,
+						"translation.y": 0.0,
+						"zoom_level": 1.0
+					},
+					"stack_index": 1,
+					"type": "text"
+				},
+				{
+					"buffer": 1,
+					"file": "Vagrantfile",
+					"semi_transient": true,
+					"settings":
+					{
+						"buffer_size": 955,
+						"regions":
+						{
+						},
+						"selection":
+						[
+							[
+								757,
+								757
+							]
+						],
+						"settings":
+						{
+							"BracketHighlighterBusy": false,
+							"bh_regions":
+							[
 								"bh_default",
 								"bh_default_center",
 								"bh_default_open",
 								"bh_default_close",
 								"bh_default_content",
-								"bh_c_define",
-								"bh_c_define_center",
-								"bh_c_define_open",
-								"bh_c_define_close",
-								"bh_c_define_content",
-								"bh_curly",
-								"bh_curly_center",
-								"bh_curly_open",
-								"bh_curly_close",
-								"bh_curly_content",
 								"bh_regex",
 								"bh_regex_center",
 								"bh_regex_open",
 								"bh_regex_close",
 								"bh_regex_content",
+								"bh_double_quote",
+								"bh_double_quote_center",
+								"bh_double_quote_open",
+								"bh_double_quote_close",
+								"bh_double_quote_content",
+								"bh_square",
+								"bh_square_center",
+								"bh_square_open",
+								"bh_square_close",
+								"bh_square_content",
 								"bh_angle",
 								"bh_angle_center",
 								"bh_angle_open",
 								"bh_angle_close",
 								"bh_angle_content",
-								"bh_tag",
-								"bh_tag_center",
-								"bh_tag_open",
-								"bh_tag_close",
-								"bh_tag_content",
+								"bh_curly",
+								"bh_curly_center",
+								"bh_curly_open",
+								"bh_curly_close",
+								"bh_curly_content",
+								"bh_unmatched",
+								"bh_unmatched_center",
+								"bh_unmatched_open",
+								"bh_unmatched_close",
+								"bh_unmatched_content",
+								"bh_c_define",
+								"bh_c_define_center",
+								"bh_c_define_open",
+								"bh_c_define_close",
+								"bh_c_define_content",
 								"bh_single_quote",
 								"bh_single_quote_center",
 								"bh_single_quote_open",
 								"bh_single_quote_close",
 								"bh_single_quote_content",
-								"bh_double_quote",
-								"bh_double_quote_center",
-								"bh_double_quote_open",
-								"bh_double_quote_close",
-								"bh_double_quote_content"
+								"bh_round",
+								"bh_round_center",
+								"bh_round_open",
+								"bh_round_close",
+								"bh_round_content",
+								"bh_tag",
+								"bh_tag_center",
+								"bh_tag_open",
+								"bh_tag_close",
+								"bh_tag_content"
 							],
 							"incomplete_sync": null,
-							"syntax": "Packages/Java/Java.sublime-syntax"
+							"remote_loading": false,
+							"synced": false,
+							"syntax": "Packages/Ruby/Ruby.sublime-syntax",
+							"tab_size": 2,
+							"translate_tabs_to_spaces": true
 						},
 						"translation.x": 0.0,
 						"translation.y": 0.0,
diff --git a/vipra-cmd.sh b/vipra-cmd.sh
new file mode 120000
index 00000000..86dc3e35
--- /dev/null
+++ b/vipra-cmd.sh
@@ -0,0 +1 @@
+vipra-cmd/bin/vipra-cmd.sh
\ No newline at end of file
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java b/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java
index 07fd0444..3ca745df 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java
@@ -2,7 +2,6 @@ package de.vipra.cmd;
 
 import org.apache.commons.cli.HelpFormatter;
 import org.apache.commons.cli.Option;
-import org.apache.commons.cli.OptionGroup;
 import org.apache.commons.cli.Options;
 
 public class CmdOptions extends Options {
@@ -24,19 +23,24 @@ public class CmdOptions extends Options {
 	public static final String OPT_CLEAR = "c";
 	public static final String OPT_CLEAR_LONG = "clear";
 
+	public static final String OPT_STATS = "p";
+	public static final String OPT_STATS_LONG = "print-stats";
+
+	public static final String OPT_DEFAULTS = "d";
+	public static final String OPT_DEFAULTS_LONG = "defaults";
+
 	public CmdOptions() {
 		addOption(Option.builder(OPT_HELP).longOpt(OPT_HELP_LONG).desc("print this message").build());
 		addOption(Option.builder(OPT_SHELL).longOpt(OPT_SHELL_LONG).hasArg(true).argName("name")
 				.desc("run from a shell script").build());
-
-		OptionGroup group = new OptionGroup();
-		group.addOption(Option.builder(OPT_IMPORT).longOpt(OPT_IMPORT_LONG).hasArgs().argName("files/dirs...")
+		addOption(Option.builder(OPT_IMPORT).longOpt(OPT_IMPORT_LONG).hasArgs().argName("files/dirs...")
 				.desc("import articles into the database").build());
-		group.addOption(Option.builder(OPT_DELETE).longOpt(OPT_DELETE_LONG).hasArgs().argName("files/ids...")
+		addOption(Option.builder(OPT_DELETE).longOpt(OPT_DELETE_LONG).hasArgs().argName("files/ids...")
 				.desc("delete articles from the database").build());
-		group.addOption(Option.builder(OPT_CLEAR).longOpt(OPT_CLEAR_LONG).desc("clear database and filebase").build());
-
-		addOptionGroup(group);
+		addOption(Option.builder(OPT_CLEAR).longOpt(OPT_CLEAR_LONG).desc("clear database and filebase").build());
+		addOption(Option.builder(OPT_STATS).longOpt(OPT_STATS_LONG).desc("gather database and filebase information")
+				.build());
+		addOption(Option.builder(OPT_DEFAULTS).longOpt(OPT_DEFAULTS_LONG).desc("accept default decisions").build());
 	}
 
 	public void printHelp(String cmd) {
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
index 5ffef5a0..5352b762 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
@@ -2,6 +2,10 @@ package de.vipra.cmd;
 
 import static de.vipra.cmd.CmdOptions.*;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
 import org.apache.commons.cli.DefaultParser;
@@ -13,8 +17,11 @@ import de.vipra.cmd.option.ClearCommand;
 import de.vipra.cmd.option.Command;
 import de.vipra.cmd.option.DeleteCommand;
 import de.vipra.cmd.option.ImportCommand;
+import de.vipra.cmd.option.StatsCommand;
+import de.vipra.util.ConsoleUtils;
 import de.vipra.util.StringUtils;
 import de.vipra.util.Timer;
+import de.vipra.util.ConsoleUtils.Choice;
 
 public class Main {
 
@@ -34,37 +41,65 @@ public class Main {
 			return;
 		}
 
-		try {
-			if (cline.hasOption(OPT_SHELL)) {
-				cmd = cline.getOptionValue(OPT_SHELL);
-				if (cmd == null) {
-					cmd = "vipra-cmd.sh";
-				}
+		if (cline.hasOption(OPT_SHELL)) {
+			cmd = cline.getOptionValue(OPT_SHELL);
+			if (cmd == null) {
+				cmd = "vipra-cmd.sh";
 			}
+		}
+
+		if (cline.hasOption(OPT_HELP)) {
+			options.printHelp(cmd);
+			return;
+		}
 
-			Command c = null;
+		List<Command> commands = new ArrayList<>();
 
-			if (cline.hasOption(OPT_HELP)) {
-				options.printHelp(cmd);
-			} else if (cline.hasOption(OPT_IMPORT)) {
-				c = new ImportCommand(cline.getOptionValues(OPT_IMPORT));
-			} else if (cline.hasOption(OPT_DELETE)) {
-				c = new DeleteCommand(cline.getOptionValues(OPT_DELETE));
-			} else if (cline.hasOption(OPT_CLEAR)) {
-				c = new ClearCommand();
-			}
+		if (cline.hasOption(OPT_CLEAR)) {
+			commands.add(new ClearCommand());
+		}
 
-			if (c != null) {
-				Timer t = new Timer();
-				t.start();
-				c.run();
-				long dur = t.stop();
-				out.info("done in " + StringUtils.timeString(dur));
-			} else {
-				options.printHelp(cmd);
+		if (cline.hasOption(OPT_IMPORT)) {
+			commands.add(new ImportCommand(cline.getOptionValues(OPT_IMPORT)));
+		}
+
+		if (cline.hasOption(OPT_DELETE)) {
+			commands.add(new DeleteCommand(cline.getOptionValues(OPT_DELETE)));
+		}
+
+		if (cline.hasOption(OPT_STATS)) {
+			commands.add(new StatsCommand());
+		}
+
+		if (commands.size() > 0) {
+			Timer t = new Timer();
+			t.start();
+			commandLoop: for (ListIterator<Command> it = commands.listIterator(); it.hasNext();) {
+				Command c = it.next();
+				try {
+					c.run();
+				} catch (ExecutionException e) {
+					out.error(e.getMessage());
+					ConsoleUtils.Choice choice;
+					boolean acceptDefault = cline.hasOption(OPT_DEFAULTS);
+					do {
+						choice = ConsoleUtils.prompt(Choice.CONTINUE, acceptDefault, Choice.ABORT, Choice.RETRY);
+						switch (choice) {
+							case ABORT:
+								break commandLoop;
+							case RETRY:
+								it.previous();
+							case CONTINUE:
+								continue commandLoop;
+
+						}
+					} while (choice == null);
+				}
 			}
-		} catch (ExecutionException e) {
-			log.error(e.getMessage());
+			long dur = t.stop();
+			out.info("done in " + StringUtils.timeString(dur));
+		} else {
+			options.printHelp(cmd);
 		}
 	}
 
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/lda/JGibbLDAAnalyzer.java b/vipra-cmd/src/main/java/de/vipra/cmd/lda/JGibbLDAAnalyzer.java
index 4ba72727..41e21180 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/lda/JGibbLDAAnalyzer.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/lda/JGibbLDAAnalyzer.java
@@ -53,7 +53,7 @@ public class JGibbLDAAnalyzer extends LDAAnalyzer {
 			throw new LDAAnalyzerException("model file does not exist: " + modelFile.getAbsolutePath());
 		}
 		estimate();
-		inference();
+		// inference();
 	}
 
 	private void estimate() {
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ClearCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ClearCommand.java
index 00f49a77..47e95b20 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ClearCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ClearCommand.java
@@ -50,13 +50,9 @@ public class ClearCommand implements Command {
 	public void run() throws ExecutionException {
 		out.info("to confirm clearing, type 'clear' and press enter");
 		try {
-			System.out.print("> ");
-			String in = ConsoleUtils.readLine().toLowerCase().trim();
-			if (in.equals("clear")) {
+			if (ConsoleUtils.confirm("clear")) {
 				clear();
 			}
-		} catch (IOException e) {
-			log.error("io error: " + e.getMessage());
 		} catch (ClearException | ConfigException e) {
 			throw new ExecutionException(e);
 		}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
index b23864ac..a61a8da3 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
@@ -44,27 +44,23 @@ public class ImportCommand implements Command {
 
 	ImportCommand() {}
 
-	public ImportCommand(String[] paths) throws ExecutionException {
+	public ImportCommand(String[] paths) {
 		addPaths(paths);
 	}
 
-	private void addPaths(String[] paths) throws ExecutionException {
+	private void addPaths(String[] paths) {
 		for (int i = 0; i < paths.length; i++) {
 			addPath(new File(paths[i]));
 		}
 	}
 
-	private void addPaths(File[] paths) throws ExecutionException {
+	private void addPaths(File[] paths) {
 		for (int i = 0; i < paths.length; i++) {
 			addPath(paths[i]);
 		}
 	}
 
-	private void addPath(File file) throws ExecutionException {
-		if (!file.exists()) {
-			throw new ExecutionException("file/dir does not exist");
-		}
-
+	private void addPath(File file) {
 		if (file.isFile()) {
 			files.add(file);
 		} else if (file.isDirectory()) {
@@ -87,7 +83,7 @@ public class ImportCommand implements Command {
 	 * @throws ImportException
 	 */
 	Article importArticle(JSONObject obj) throws ImportException {
-		out.info("importing \"" + StringUtils.ellipsize(obj.get("title").toString(), 80) + "\"");
+		out.debug("importing \"" + StringUtils.ellipsize(obj.get("title").toString(), 80) + "\"");
 		Article article = new Article();
 		article.fromJSON(obj);
 
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/StatsCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/StatsCommand.java
new file mode 100644
index 00000000..6cf6800b
--- /dev/null
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/StatsCommand.java
@@ -0,0 +1,13 @@
+package de.vipra.cmd.option;
+
+import de.vipra.cmd.ExecutionException;
+
+public class StatsCommand implements Command {
+
+	@Override
+	public void run() throws ExecutionException {
+		// TODO Auto-generated method stub
+		
+	}
+
+}
diff --git a/vipra-cmd/src/main/resources/log4j2.xml b/vipra-cmd/src/main/resources/log4j2.xml
index 1da679ba..2fc755c1 100644
--- a/vipra-cmd/src/main/resources/log4j2.xml
+++ b/vipra-cmd/src/main/resources/log4j2.xml
@@ -9,6 +9,6 @@
 		<Root level="ERROR">
 			<AppenderRef ref="Console" />
 		</Root>
-		<Logger name="shellout" level="ALL" />
+		<Logger name="shellout" level="INFO" />
 	</Loggers>
 </Configuration>
\ No newline at end of file
diff --git a/vipra-rest/.classpath b/vipra-rest/.classpath
index c1e791eb..12f79c80 100644
--- a/vipra-rest/.classpath
+++ b/vipra-rest/.classpath
@@ -17,11 +17,6 @@
 			<attribute name="maven.pomderived" value="true"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
-		<attributes>
-			<attribute name="maven.pomderived" value="true"/>
-		</attributes>
-	</classpathentry>
 	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
 		<attributes>
 			<attribute name="maven.pomderived" value="true"/>
diff --git a/vipra-rest/.settings/org.eclipse.core.resources.prefs b/vipra-rest/.settings/org.eclipse.core.resources.prefs
index 29abf999..abdea9ac 100644
--- a/vipra-rest/.settings/org.eclipse.core.resources.prefs
+++ b/vipra-rest/.settings/org.eclipse.core.resources.prefs
@@ -1,6 +1,4 @@
 eclipse.preferences.version=1
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
-encoding//src/test/java=UTF-8
-encoding//src/test/resources=UTF-8
 encoding/<project>=UTF-8
diff --git a/vipra-rest/src/test/java/de/vipra/rest/resource/ArticleResourceTest.java b/vipra-rest/src/test/java/de/vipra/rest/resource/ArticleResourceTest.java
deleted file mode 100644
index 9d4e60c3..00000000
--- a/vipra-rest/src/test/java/de/vipra/rest/resource/ArticleResourceTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package de.vipra.rest.resource;
-
-import static org.junit.Assert.assertEquals;
-
-import javax.ws.rs.core.Application;
-import javax.ws.rs.core.Response;
-
-import org.glassfish.jersey.server.ResourceConfig;
-import org.glassfish.jersey.test.JerseyTest;
-import org.junit.Test;
-
-public class ArticleResourceTest extends JerseyTest {
-
-	@Override
-	protected Application configure() {
-		return new ResourceConfig(ArticleResource.class);
-	}
-
-	@Test
-	public void getArticles() {
-		Response response = target("articles").request().get();
-		assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
-	}
-
-}
diff --git a/vipra-util/src/main/java/de/vipra/util/ConsoleUtils.java b/vipra-util/src/main/java/de/vipra/util/ConsoleUtils.java
index af55646b..03eda735 100644
--- a/vipra-util/src/main/java/de/vipra/util/ConsoleUtils.java
+++ b/vipra-util/src/main/java/de/vipra/util/ConsoleUtils.java
@@ -3,12 +3,84 @@ package de.vipra.util;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class ConsoleUtils {
 
-	public static String readLine() throws IOException {
+	public static final Logger log = LoggerFactory.getLogger(ConsoleUtils.class);
+
+	public static enum Choice {
+		ABORT("abort"),
+		CONTINUE("continue"),
+		RETRY("retry");
+
+		public final String choice;
+
+		Choice(String choice) {
+			this.choice = choice;
+		}
+
+		public static Choice fromString(String text) {
+			if (text != null) {
+				for (Choice b : Choice.values()) {
+					if (text.equalsIgnoreCase(b.choice)) {
+						return b;
+					}
+				}
+			}
+			return null;
+		}
+
+		@Override
+		public String toString() {
+			return choice;
+		}
+	}
+
+	public static String readLine() {
 		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
-		return in.readLine();
+		try {
+			return in.readLine();
+		} catch (IOException e) {
+			log.error("io error while reading line from console: " + e.getMessage());
+			return "";
+		}
+	}
+
+	public static boolean confirm(String confirm) {
+		System.out.print("> ");
+		String in = readLine().toLowerCase().trim();
+		return in.equals(confirm);
+	}
+
+	public static String prompt(String choice, boolean acceptDefault, String... choices) {
+		if (acceptDefault && choice != null)
+			return choice;
+		Set<String> set = new LinkedHashSet<>(Arrays.asList(choices));
+		if (choice != null)
+			set.add(choice);
+		String msg = "(" + StringUtils.join(set, ",") + "): ";
+		if (choice != null) {
+			msg = "[" + choice + "] " + msg;
+		}
+		System.out.print(msg);
+		String in = readLine().toLowerCase().trim();
+		if (set.contains(in)) {
+			return in;
+		}
+		if (choice != null && in.length() == 0)
+			return choice;
+		return null;
+	}
+
+	public static Choice prompt(Choice choice, boolean acceptDefault, Choice... choices) {
+		return Choice.fromString(
+				prompt(choice != null ? choice.choice : null, acceptDefault, ListUtils.toStringArray(choices)));
 	}
 
 }
diff --git a/vipra-util/src/main/java/de/vipra/util/ListUtils.java b/vipra-util/src/main/java/de/vipra/util/ListUtils.java
new file mode 100644
index 00000000..bddd22ab
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/ListUtils.java
@@ -0,0 +1,24 @@
+package de.vipra.util;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ListUtils {
+
+	public static <T> List<T> removeDuplicates(List<T> list) {
+		Set<T> set = new LinkedHashSet<>(list);
+		list.clear();
+		list.addAll(set);
+		return list;
+	}
+
+	public static String[] toStringArray(Object[] array) {
+		String[] strings = new String[array.length];
+		for (int i = 0; i < array.length; i++) {
+			strings[i] = array[i].toString();
+		}
+		return strings;
+	}
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/StringUtils.java b/vipra-util/src/main/java/de/vipra/util/StringUtils.java
index ffac45c9..6916fa12 100644
--- a/vipra-util/src/main/java/de/vipra/util/StringUtils.java
+++ b/vipra-util/src/main/java/de/vipra/util/StringUtils.java
@@ -1,6 +1,7 @@
 package de.vipra.util;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -31,6 +32,14 @@ public class StringUtils {
 		return join(it, " ");
 	}
 
+	public static String join(String[] arr, String separator) {
+		return join(Arrays.asList(arr), separator);
+	}
+
+	public static String join(String[] arr) {
+		return join(arr, " ");
+	}
+
 	public static String timeString(long nanos) {
 		List<String> parts = new ArrayList<String>(6);
 
diff --git a/vipra-util/src/main/java/de/vipra/util/service/DatabaseService.java b/vipra-util/src/main/java/de/vipra/util/service/DatabaseService.java
index a49f30d3..940f7e52 100644
--- a/vipra-util/src/main/java/de/vipra/util/service/DatabaseService.java
+++ b/vipra-util/src/main/java/de/vipra/util/service/DatabaseService.java
@@ -101,7 +101,7 @@ public class DatabaseService<T extends Model> implements Service<T, DatabaseExce
 		t.fromDocument(docNew);
 		return result.getModifiedCount();
 	}
-	
+
 	public void drop() {
 		collection.drop();
 	}
-- 
GitLab