dev.jbang.util.CommandBuffer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jbang-cli Show documentation
Show all versions of jbang-cli Show documentation
JBang Command Line Interface
The newest version!
package dev.jbang.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class CommandBuffer {
private List arguments;
// 8192 character command line length limit imposed by CMD.EXE
public static final int MAX_LENGTH_WINCLI = 8000;
// Windows API has a limit of 32,768 characters for the lpCommandLine parameter
public static final int MAX_LENGTH_WINPROCBUILDER = 32000;
// NB: This might not be a definitive list of safe characters
// cmdSafeChars = Pattern.compile("[^\\Q&()[]{}^=;!'+,`~<>|\\E]*");
static Pattern cmdSafeChars = Pattern.compile("[a-zA-Z0-9.,_+=:;@()-\\\\]*");
// TODO: Figure out what the real list of safe characters is for PowerShell
static Pattern pwrSafeChars = Pattern.compile("[a-zA-Z0-9.,_+=:@()\\\\-]*");
static Pattern shellSafeChars = Pattern.compile("[a-zA-Z0-9._+=:@%/-]*");
static Pattern cmdNeedQuotesChars = Pattern.compile("[\\Q&()[]{}^=;!'+,`~\\E]");
public static CommandBuffer of() {
return new CommandBuffer();
}
public static CommandBuffer of(Collection arguments) {
return new CommandBuffer(arguments);
}
public static CommandBuffer of(String... arguments) {
return new CommandBuffer(arguments);
}
public CommandBuffer() {
arguments = new ArrayList<>();
}
public CommandBuffer(Collection arguments) {
this.arguments = new ArrayList<>(arguments);
}
public CommandBuffer(String... arguments) {
this.arguments = new ArrayList<>(Arrays.asList(arguments));
}
public ProcessBuilder asProcessBuilder() {
return asProcessBuilder(Util.getShell());
}
public ProcessBuilder asProcessBuilder(Util.Shell shell) {
List args = arguments .stream()
.map(a -> escapeProcessBuilderArgument(a, shell))
.collect(Collectors.toList());
return new ProcessBuilder(args);
}
public String asCommandLine() {
return asCommandLine(Util.getShell());
}
public String asCommandLine(Util.Shell shell) {
return String.join(" ", escapeShellArguments(arguments, shell));
}
public CommandBuffer usingArgsFile() throws IOException {
// @-files avoid problems on Windows with very long command lines
final Path argsFile = Files.createTempFile("jbang", ".args");
try (PrintWriter pw = new PrintWriter(argsFile.toFile())) {
// write all arguments except the first to the file
for (int i = 1; i < arguments.size(); ++i) {
pw.println(escapeArgsFileArgument(arguments.get(i)));
}
}
return CommandBuffer.of(arguments.get(0), "@" + argsFile);
}
public CommandBuffer applyWindowsMaxLengthLimit(int maxLength, Util.Shell shell) throws IOException {
String args = asCommandLine(shell);
// Check if we can and need to use @-files on Windows
if (args.length() > maxLength && Util.getShell() != Util.Shell.bash) {
return usingArgsFile();
} else {
return this;
}
}
/**
* Escapes list of arguments where necessary using the way of escaping that is
* appropriate for the shell we're running in
*/
private static List escapeShellArguments(List args, Util.Shell shell) {
return args.stream().map(arg -> escapeShellArgument(arg, shell)).collect(Collectors.toList());
}
public static String escapeShellArgument(String arg, Util.Shell shell) {
switch (shell) {
case bash:
return escapeBashArgument(arg);
case cmd:
return escapeCmdArgument(arg);
case powershell:
return escapePowershellArgument(arg);
}
return arg;
}
private static String escapeBashArgument(String arg) {
if (!shellSafeChars.matcher(arg).matches()) {
arg = arg.replaceAll("(['])", "'\\\\''");
arg = "'" + arg + "'";
}
return arg;
}
private static String escapeCmdArgument(String arg) {
if (!cmdSafeChars.matcher(arg).matches()) {
// Windows quoting is just weird
arg = arg.replaceAll("([()!^<>&|% ])", "^$1");
arg = arg.replaceAll("([\"])", "\\\\^$1");
arg = "^\"" + arg + "^\"";
}
return arg;
}
private static String escapePowershellArgument(String arg) {
if (!pwrSafeChars.matcher(arg).matches()) {
arg = arg.replaceAll("(['])", "''");
arg = "'" + arg + "'";
}
return arg;
}
private static String escapeArgsFileArgument(String arg) {
if (!shellSafeChars.matcher(arg).matches()) {
arg = arg.replaceAll("([\"'\\\\])", "\\\\$1");
arg = "\"" + arg + "\"";
}
return arg;
}
private static String escapeProcessBuilderArgument(String arg, Util.Shell shell) {
// This code is meant to deal with special characters on Windows
// that get treated as argument separators if left unquoted.
// (For a list of those characters see the output of `CMD /?`)
// The code is NOT able to handle double quotes as part of the
// argument, this is close to impossible to handle on Windows.
// (see
// https://stackoverflow.com/questions/30157414/batch-argument-with-quotes-and-spaces)
if (shell != Util.Shell.bash && cmdNeedQuotesChars.matcher(arg).find()) {
arg = "\"" + arg + "\"";
}
return arg;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy