/*******************************************************************************
* Copyright (c) 2011 Arapiki Solutions Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* "Peter Smith <psmith@arapiki.com>" - initial API and
* implementation and/or initial documentation
*******************************************************************************/
package com.buildml.utils.string;
/**
* Utility methods for manipulating strings that represent shell command lines.
*
* @author "Peter Smith <psmith@arapiki.com>"
*/
public class ShellCommandUtils {
/*=====================================================================================*
* PUBLIC METHODS
*=====================================================================================*/
/**
* Given a (possibly) multi-line shell command, merge that command into a single line.
* If a particular line is terminated by a \ character, the next line is considered
* to be the same command. If not, the current line and next line will be joined together
* with "&&".
* <p>
* Finally, leading spaces/tabs are removed off all lines.
*
* @param cmdLine The (possibly) multi-line shell command.
* @return The joined-together shell command.
*/
public static String joinCommandLine(String cmdLine) {
/*
* Create a StringBuffer for storing the result. We'll append to this
* as we progress through the lines.
*/
StringBuffer sb = new StringBuffer(256);
/*
* We'll traverse the string, line by line, with startPos being the start
* index of the next line.
*/
int startPos = 0;
/* we'll track whether the next line should be prepend by && */
boolean andAndRequiredNextTime = false;
/* repeat until we've processed every line of the input */
int cmdLineLen = cmdLine.length();
while (startPos < cmdLineLen) {
/*
* Find the index of the end of the current line (possibly the end of the whole
* string).
*/
int nlPos = cmdLine.indexOf('\n', startPos);
if (nlPos == -1) {
nlPos = cmdLine.length();
}
/* trim any leading spaces/tabs */
while (startPos < nlPos) {
char ch = cmdLine.charAt(startPos);
if ((ch != ' ') && (ch != '\t')){
break;
}
startPos++;
}
/* ignore lines that are empty */
if (startPos < nlPos) {
/* if the previous line didn't end with \, we should insert a && */
if (andAndRequiredNextTime) {
sb.append(" && ");
}
/*
* Does this line end with \? If so, we discard the \ and merge this
* line with the next (no && required)
*/
if (cmdLine.charAt(nlPos - 1) == '\\') {
sb.append(cmdLine.substring(startPos, nlPos - 1));
andAndRequiredNextTime = false;
}
/*
* No it doesn't, so print a && before the next line
*/
else {
sb.append(cmdLine.substring(startPos, nlPos));
andAndRequiredNextTime = true;
}
}
/*
* Empty lines require a && before the next command, since it's
* definitely the case that the previous command has ended.
*/
else {
andAndRequiredNextTime = true;
}
/* the next line starts immediately after this one */
startPos = nlPos + 1;
}
return sb.toString();
}
/*-------------------------------------------------------------------------------------*/
/**
* Given a string containing a shell command argument, escape the string so that it may be
* entered on the shell command line. For example, an argument that contains spaces must
* be surrounded by quotation marks. Also, a quotation mark in an argument must be quoted
* with a backslash (\").
*
* @param input The unescaped shell command arguments.
* @return The shell-escaped version of the argument.
*/
public static String shellEscapeString(String input) {
boolean needsQuoting = false;
int inputLen = input.length();
for (int i = 0; i != inputLen; i++) {
char ch = input.charAt(i);
if (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t' ||
ch == '(' || ch == ')' || ch == '<' || ch == '>' ||
ch == ';' || ch == '&' || ch == '!' || ch == '$' ||
ch == '!' || ch == '$' || ch == '\'' || ch == '*' ||
ch == '?' || ch == '[' || ch == ']' || ch == '{' ||
ch == '}') {
needsQuoting = true;
break;
}
}
/*
* If there are no special characters, no quoting is required. Simply
* return the original string.
*/
if (!needsQuoting) {
return input;
}
/*
* Else, create a replacement string, with added '...' and any internal
* ' characters prefixed by \.
*/
StringBuffer sb = new StringBuffer(inputLen + 10); /* allow room for quotes */
sb.append('\'');
for (int i = 0; i != inputLen; i++) {
char ch = input.charAt(i);
/*
* Single quotes behave oddly. You need to terminate the first quote, then
* have the \', then restart the quoted string. Therefore, Hello'World
* must be escaped as 'Hello'\''World'
*/
if (ch == '\'') {
sb.append("'\\''");
} else {
sb.append(ch);
}
}
sb.append('\'');
return sb.toString();
}
/*-------------------------------------------------------------------------------------*/
/**
* Summarize a shell command (possibly multi-line) by truncating it (if needed) to the
* specified width and putting "..." to indicate that it continues.
*
* @param shellCommand The original shell command string (possibly multi-line).
* @param width The number of characters to truncate it to.
* @return The summary string.
*
*/
public static String getCommandSummary(String shellCommand, int width) {
/*
* For now, we treat all commands as being the same, and simply return the
* first 'width' characters from the action's command string.
*/
String command = ShellCommandUtils.joinCommandLine(shellCommand);
/* for strings that are longer than 'width', truncate them and suffix them with "..." */
boolean dotsNeeded = false;
int stringLen = command.length();
if (stringLen > width - 3) {
stringLen = width - 3;
dotsNeeded = true;
}
/* form the summary string, possibly with ... */
StringBuffer sb = new StringBuffer(command.substring(0, stringLen));
if (dotsNeeded){
sb.append("...");
}
/* return the summary string */
return sb.toString();
}
/*-------------------------------------------------------------------------------------*/
}