<commit_before>/*
 * Copyright 2012-2014 the original author or authors.
 *
 * 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 org.springframework.boot.cli.command.shell;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.Stack;

import jline.console.ConsoleReader;
import jline.console.completer.CandidateListCompletionHandler;

import org.fusesource.jansi.AnsiRenderer.Code;
import org.springframework.boot.cli.CommandRunner;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.CommandFactory;
import org.springframework.boot.cli.command.core.VersionCommand;
import org.springframework.util.StringUtils;

import sun.misc.Signal;
import sun.misc.SignalHandler;

/**
 * A shell for Spring Boot. Drops the user into an event loop (REPL) where command line
 * completion and history are available without relying on OS shell features.
 * 
 * @author <NAME>
 * @author <NAME>
 * @author <NAME>
 */
@SuppressWarnings("restriction")
public class Shell {

	private static final Set<Class<?>> NON_FORKED_COMMANDS;
	static {
		Set<Class<?>> nonForked = new HashSet<Class<?>>();
		nonForked.add(VersionCommand.class);
		NON_FORKED_COMMANDS = Collections.unmodifiableSet(nonForked);
	}

	private static final Signal SIG_INT = new Signal("INT");

	private static final String DEFAULT_PROMPT = "$ ";

	private ShellCommandRunner commandRunner;

	private ConsoleReader consoleReader;

	private EscapeAwareWhiteSpaceArgumentDelimiter argumentDelimiter = new EscapeAwareWhiteSpaceArgumentDelimiter();

	private Stack<String> prompts = new Stack<String>();

	/**
	 * Create a new {@link Shell} instance.
	 * @throws IOException
	 */
	public Shell() throws IOException {
		attachSignalHandler();
		this.consoleReader = new ConsoleReader();
		this.commandRunner = createCommandRunner();
		initializeConsoleReader();
	}

	private ShellCommandRunner createCommandRunner() {
		ShellCommandRunner runner = new ShellCommandRunner();
		runner.addHelpCommand();
		runner.addCommands(getCommands());
		runner.addAliases("exit", "quit");
		runner.addAliases("help", "?");
		runner.addAliases("clear", "cls");
		return runner;
	}

	private Iterable<Command> getCommands() {
		List<Command> commands = new ArrayList<Command>();
		ServiceLoader<CommandFactory> factories = ServiceLoader.load(
				CommandFactory.class, getClass().getClassLoader());
		for (CommandFactory factory : factories) {
			for (Command command : factory.getCommands()) {
				commands.add(convertToForkCommand(command));
			}
		}
		commands.add(new PromptCommand(this));
		commands.add(new ClearCommand(this.consoleReader));
		commands.add(new ExitCommand());
		return commands;
	}

	private Command convertToForkCommand(Command command) {
		for (Class<?> nonForked : NON_FORKED_COMMANDS) {
			if (nonForked.isInstance(command)) {
				return command;
			}
		}
		return new ForkProcessCommand(command);
	}

	private void initializeConsoleReader() {
		this.consoleReader.setHistoryEnabled(true);
		this.consoleReader.setBellEnabled(false);
		this.consoleReader.setExpandEvents(false);
		this.consoleReader.addCompleter(new CommandCompleter(this.consoleReader,
				this.argumentDelimiter, this.commandRunner));
		this.consoleReader.setCompletionHandler(new CandidateListCompletionHandler());
	}

	private void attachSignalHandler() {
		Signal.handle(SIG_INT, new SignalHandler() {
			@Override
			public void handle(sun.misc.Signal signal) {
				handleSigInt();
			}
		});
	}

	/**
	 * Push a new prompt to be used by the shell.
	 * @param prompt the prompt
	 * @see #popPrompt()
	 */
	public void pushPrompt(String prompt) {
		this.prompts.push(prompt);
	}

	/**
	 * Pop a previously pushed prompt, returning to the previous value.
	 * @see #pushPrompt(String)
	 */
	public void popPrompt() {
		this.prompts.pop();
	}

	/**
	 * Run the shell until the user exists.
	 * @throws Exception on error
	 */
	public void run() throws Exception {
		printBanner();
		try {
			runInputLoop();
		}
		catch (Exception ex) {
			if (!(ex instanceof ShellExitException)) {
				throw ex;
			}
		}
	}

	private void printBanner() {
		String version = ShellCommand.class.getPackage().getImplementationVersion();
		version = (version == null ? "" : " (v" + version + ")");
		System.out.println(ansi("Spring Boot", Code.BOLD).append(version, Code.FAINT));
		System.out.println(ansi("Hit TAB to complete. Type 'help' and hit "
				+ "RETURN for help, and 'exit' to quit."));
	}

	private void runInputLoop() throws Exception {
		while (true) {
			String line = this.consoleReader.readLine(getPrompt());
			while (line.endsWith("\\")) {
				line = line.substring(0, line.length() - 1);
				line += this.consoleReader.readLine("> ");
			}
			if (StringUtils.hasLength(line)) {<commit_msg>Prevent CTRL-D from causing NPE in shell<commit_after>/*
 * Copyright 2012-2014 the original author or authors.
 *
 * 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 org.springframework.boot.cli.command.shell;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.Stack;

import jline.console.ConsoleReader;
import jline.console.completer.CandidateListCompletionHandler;

import org.fusesource.jansi.AnsiRenderer.Code;
import org.springframework.boot.cli.CommandRunner;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.CommandFactory;
import org.springframework.boot.cli.command.core.VersionCommand;
import org.springframework.util.StringUtils;

import sun.misc.Signal;
import sun.misc.SignalHandler;

/**
 * A shell for Spring Boot. Drops the user into an event loop (REPL) where command line
 * completion and history are available without relying on OS shell features.
 * 
 * @author <NAME>
 * @author <NAME>
 * @author <NAME>
 */
@SuppressWarnings("restriction")
public class Shell {

	private static final Set<Class<?>> NON_FORKED_COMMANDS;
	static {
		Set<Class<?>> nonForked = new HashSet<Class<?>>();
		nonForked.add(VersionCommand.class);
		NON_FORKED_COMMANDS = Collections.unmodifiableSet(nonForked);
	}

	private static final Signal SIG_INT = new Signal("INT");

	private static final String DEFAULT_PROMPT = "$ ";

	private ShellCommandRunner commandRunner;

	private ConsoleReader consoleReader;

	private EscapeAwareWhiteSpaceArgumentDelimiter argumentDelimiter = new EscapeAwareWhiteSpaceArgumentDelimiter();

	private Stack<String> prompts = new Stack<String>();

	/**
	 * Create a new {@link Shell} instance.
	 * @throws IOException
	 */
	public Shell() throws IOException {
		attachSignalHandler();
		this.consoleReader = new ConsoleReader();
		this.commandRunner = createCommandRunner();
		initializeConsoleReader();
	}

	private ShellCommandRunner createCommandRunner() {
		ShellCommandRunner runner = new ShellCommandRunner();
		runner.addHelpCommand();
		runner.addCommands(getCommands());
		runner.addAliases("exit", "quit");
		runner.addAliases("help", "?");
		runner.addAliases("clear", "cls");
		return runner;
	}

	private Iterable<Command> getCommands() {
		List<Command> commands = new ArrayList<Command>();
		ServiceLoader<CommandFactory> factories = ServiceLoader.load(
				CommandFactory.class, getClass().getClassLoader());
		for (CommandFactory factory : factories) {
			for (Command command : factory.getCommands()) {
				commands.add(convertToForkCommand(command));
			}
		}
		commands.add(new PromptCommand(this));
		commands.add(new ClearCommand(this.consoleReader));
		commands.add(new ExitCommand());
		return commands;
	}

	private Command convertToForkCommand(Command command) {
		for (Class<?> nonForked : NON_FORKED_COMMANDS) {
			if (nonForked.isInstance(command)) {
				return command;
			}
		}
		return new ForkProcessCommand(command);
	}

	private void initializeConsoleReader() {
		this.consoleReader.setHistoryEnabled(true);
		this.consoleReader.setBellEnabled(false);
		this.consoleReader.setExpandEvents(false);
		this.consoleReader.addCompleter(new CommandCompleter(this.consoleReader,
				this.argumentDelimiter, this.commandRunner));
		this.consoleReader.setCompletionHandler(new CandidateListCompletionHandler());
	}

	private void attachSignalHandler() {
		Signal.handle(SIG_INT, new SignalHandler() {
			@Override
			public void handle(sun.misc.Signal signal) {
				handleSigInt();
			}
		});
	}

	/**
	 * Push a new prompt to be used by the shell.
	 * @param prompt the prompt
	 * @see #popPrompt()
	 */
	public void pushPrompt(String prompt) {
		this.prompts.push(prompt);
	}

	/**
	 * Pop a previously pushed prompt, returning to the previous value.
	 * @see #pushPrompt(String)
	 */
	public void popPrompt() {
		this.prompts.pop();
	}

	/**
	 * Run the shell until the user exists.
	 * @throws Exception on error
	 */
	public void run() throws Exception {
		printBanner();
		try {
			runInputLoop();
		}
		catch (Exception ex) {
			if (!(ex instanceof ShellExitException)) {
				throw ex;
			}
		}
	}

	private void printBanner() {
		String version = ShellCommand.class.getPackage().getImplementationVersion();
		version = (version == null ? "" : " (v" + version + ")");
		System.out.println(ansi("Spring Boot", Code.BOLD).append(version, Code.FAINT));
		System.out.println(ansi("Hit TAB to complete. Type 'help' and hit "
				+ "RETURN for help, and 'exit' to quit."));
	}

	private void runInputLoop() throws Exception {
		String line;
		while ((line = this.consoleReader.readLine(getPrompt())) != null) {
			while (line.endsWith("\\")) {
				line = line.substring(0, line.length() - 1);
				line += this.consoleReader.readLine("> ");
			}
			if (StringUtils.hasLength(line)) {
{'fraction_non_alphanumeric': 0.11700965215807685, 'fraction_numerical': 0.002549626661810235, 'mean_word_length': 4.606431852986217, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4673768', 'n_tokens_mistral': 3483, 'n_tokens_neox': 3190, 'n_words': 855}