CrestCli { public void run(final String... args) throws Exception { final AtomicReference<InputReader> inputReaderRef = new AtomicReference<>(); final AtomicReference<History> historyRef = new AtomicReference<>(); mainEnvironment = createMainEnvironment(inputReaderRef, historyRef); Environment.ENVIRONMENT_THREAD_LOCAL.set(mainEnvironment); final DefaultsContext ctx = new SystemPropertiesDefaultsContext(); final Main main = newMain(ctx); final Map<String, String> aliasesMapping = new HashMap<>(); final File aliases = aliasesFile(); if (aliases != null && aliases.isFile()) { aliasesMapping.putAll(Map.class.cast(IO.readProperties(aliases))); } final InputReader readerFacade; final History history; if (args == null || args.length == 0) { final ConsoleReader reader = new ConsoleReader(mainEnvironment.getInput(), mainEnvironment.getOutput()); reader.setHandleUserInterrupt(true); final File historyFile = cliHistoryFile(); history = historyFile != null && historyFile.isFile() ? new FileHistory(historyFile) : new MemoryHistory(); reader.setHistory(history); reader.addCompleter(new Completer() { @Override public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) { candidates.addAll(main.complete(buffer, cursor)); return buffer.lastIndexOf(' ', cursor) + 1; } }); readerFacade = new InputReader() { @Override public String readLine(final String prompt) throws IOException { return reader.readLine(prompt); } @Override public String readPassword(final String prompt) throws IOException { return reader.readLine(prompt, '*'); } @Override public void clear() { try { reader.clearScreen(); } catch (final IOException e) { throw new IllegalStateException(e); } } @Override public void close() throws Exception { } }; } else { history = null; readerFacade = new FileInputReader(args["-f".equals(args[0]) ? 1 : 0]); } inputReaderRef.set(readerFacade); historyRef.set(history); final int nThreads = Integer.getInteger("crest.cli.pipping.threads", 16); final ExecutorService es = Executors.newFixedThreadPool(nThreads, new ThreadFactory() { private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(); { final SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); } @Override public Thread newThread(final Runnable r) { final Thread t = new Thread(group, r, "cli-" + threadNumber.incrementAndGet(), 0); if (t.isDaemon()) { t.setDaemon(false); } if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); } return t; } }); exitHook = new Runnable() { @Override public void run() { beforeExit(); if (FileHistory.class.isInstance(history)) { try { FileHistory.class.cast(history).flush(); } catch (final IOException e) { } } try { readerFacade.close(); } catch (final Exception e) { } es.shutdownNow(); } }; final CommandParser parser = new CommandParser(); try { String line; boolean quit; do { quit = true; try { while ((line = readerFacade.readLine(nextPrompt())) != null) { if (line.trim().isEmpty() || line.startsWith("#")) { continue; } final String actualCmd = aliasesMapping.get(line.trim()); if (actualCmd != null) { line = actualCmd; } line = transformCommand(line); final long start; if (line.startsWith("time ")) { start = System.nanoTime(); line = line.substring("time ".length()); } else { start = -1; } try { final CommandParser.Command[] commands = parser.toArgs(line); if (commands.length == 1) { try { main.main(mainEnvironment, commands[0].getArgs()); } catch (final Exception error) { if (ExitException.class.isInstance(error.getCause())) { break; } error.printStackTrace(mainEnvironment.getError()); throw error; } } else { if (nThreads < commands.length) { throw new IllegalArgumentException("We dont support more than " + nThreads + " pipping commands, use -Dcrest.cli.pipping.threads to update it"); } final InputStream[] ins = new InputStream[commands.length]; final OutputStream[] outs = new OutputStream[commands.length]; for (int i = 0; i < commands.length; i++) { ins[i] = i == 0 ? mainEnvironment.getInput() : new PipedInputStream(); outs[i] = i == commands.length - 1 ? mainEnvironment.getOutput() : new PipedOutputStream(); } for (int i = 0; i < commands.length; i++) { if (PipedInputStream.class.isInstance(ins[i])) { PipedInputStream.class.cast(ins[i]).connect(PipedOutputStream.class.cast(outs[i - 1])); } } final Collection<Future<?>> tasks = new ArrayList<>(commands.length); for (int i = 0; i < commands.length; i++) { final int idx = i; final PrintStream out = new PrintStream(outs[idx]); tasks.add(es.submit(new Runnable() { @Override public void run() { try { main.main(pipeEnvironment(mainEnvironment, ins[idx], out), commands[idx].getArgs()); } catch (final Exception error) { error.printStackTrace(mainEnvironment.getError()); } finally { if (PipedInputStream.class.isInstance(ins[idx])) { try { ins[idx].close(); } catch (final IOException e) { } } try { if (PipedOutputStream.class.isInstance(outs[idx])) { outs[idx].close(); } else { outs[idx].flush(); } } catch (final IOException e) { } } } })); } for (final Future<?> t : tasks) { try { t.get(); } catch (final InterruptedException e) { Thread.interrupted(); } catch (final ExecutionException e) { } } mainEnvironment.getOutput().flush(); } } catch (final Exception error) { error.printStackTrace(mainEnvironment.getError()); mainEnvironment.getError().flush(); } finally { if (start > 0) { final long end = System.nanoTime(); final long sec = TimeUnit.NANOSECONDS.toSeconds(end - start); final long msec = TimeUnit.NANOSECONDS.toMillis((end - start) - TimeUnit.SECONDS.toNanos(sec)); mainEnvironment.getOutput().println("Time " + sec + "s " + msec + "ms"); } } } } catch (final UserInterruptException uie) { quit = false; } } while (!quit); } finally { if (exitHook != null) { exitHook.run(); exitHook = null; } } }  @Command static void exit(); @Command static void history(final CliEnvironment env, @Out final PrintStream out); @Command static void clear(final CliEnvironment env); static void main(final String[] args); void run(final String... args);  }
@Test public void cli() throws Exception { final String input = "help\n" + "test\n" + "test | jgrep 2\n" + "exit"; final ByteArrayOutputStream out = new ByteArrayOutputStream(); final CrestCli cli = newTestCli(input, out, null); cli.run(); assertEquals( portable("prompt$help\n" + "Commands: \n" + " \n" + " clear \n" + " exit \n" + " help \n" + " history \n" + " jgrep \n" + " jsed \n" + " pretty \n" + " test \n" + " wc \n" + "\n" + "prompt$test\n" + "line1\n" + "line 2\n" + "end\n" + "\n" + "prompt$test | jgrep 2\n" + "line 2\n" + "prompt$exit"), portable(new String(out.toByteArray()))); }
@Test public void alias() throws Exception { final File file = new File("target/clitest/alias.txt"); file.getParentFile().mkdirs(); final FileWriter w = new FileWriter(file); w.write("al=test"); w.close(); final String input = "al\n" + "exit"; final ByteArrayOutputStream out = new ByteArrayOutputStream(); newTestCli(input, out, file).run(); assertEquals( "prompt$al\n" + "line1\n" + "line 2\n" + "end\n" + "\n" + "prompt$exit", portable(new String(out.toByteArray()))); }
@Test public void time() throws Exception { final String input = "time test\n" + "exit"; final ByteArrayOutputStream out = new ByteArrayOutputStream(); newTestCli(input, out, null).run(); assertEquals( portable("prompt$time test\n" + "line1\n" + "line 2\n" + "end\n" + "\n" + "Time 0s Xms\n" + "prompt$exit"), portable(new String(out.toByteArray()).replaceAll("[0-9]+ms", "Xms"))); }