mirror of
https://github.com/nosqlbench/nosqlbench.git
synced 2025-02-25 18:55:28 -06:00
argsfile support
This commit is contained in:
parent
3587a8413d
commit
a9b0dc9dd0
@ -1,29 +1,217 @@
|
|||||||
package io.nosqlbench.engine.cli;
|
package io.nosqlbench.engine.cli;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.LinkedList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <H1>Synopsis</H1>
|
||||||
|
*
|
||||||
|
* ArgsFile is a command-line modifier which can take linked list of
|
||||||
|
* command args and modify it, and/or modify argsfile refrenced in this way.
|
||||||
|
*
|
||||||
|
* <H1>ArgsFile Selection</H1>
|
||||||
|
*
|
||||||
|
* During processing, any occurence of '-argsfile' selects the active argsfile and loads
|
||||||
|
* it into the command line in place of the '-argsfile' argument. By default the args file
|
||||||
|
* will be loaded if it exists, and a warning will be given if it does not.
|
||||||
|
*
|
||||||
|
* The '-argsfile-required <somepath>' version will throw an error if the args file
|
||||||
|
* is not present, but it will not report any warnings or details otherwise.
|
||||||
|
*
|
||||||
|
* The `-argsfile-optional <somepath> version will not throw an error if the args
|
||||||
|
* file is not present, and it will not report any warnings or details otherwise.
|
||||||
|
*
|
||||||
|
* A prefix command line can be given to ArgsFile to pre-load any settings. In this way
|
||||||
|
* it is possible to easily provide a default args file which will be loaded. For example,
|
||||||
|
* A prefix command of '-argsfile-optional <somepath>' will load options if they are
|
||||||
|
* available in the specified file, but will otherwise provide no feedback to the user.
|
||||||
|
*
|
||||||
|
* <H1>ArgsFile Injection</H1>
|
||||||
|
*
|
||||||
|
* When an argsfile is loaded, it reads a command from each line into the current position
|
||||||
|
* of the command line. No parsing is done. Blank lines are ignored. Newlines are used as the
|
||||||
|
* argument delimiter, and lines that end with a backslash before the newline are automatically
|
||||||
|
* joined together.
|
||||||
|
*
|
||||||
|
* <H1>ArgsFile Diagnostics</H1>
|
||||||
|
*
|
||||||
|
* All modifications to the command line should be reported to the logging facility at
|
||||||
|
* INFO level. This assumes that the calling layer wants to inform users of command line injections,
|
||||||
|
* and that the user can select to be notified of warnings only if desired.
|
||||||
|
*
|
||||||
|
* <H1>Environment Variables</H1>
|
||||||
|
*
|
||||||
|
* Simple environment variable substitution is attempted for any pattern which appears as '$' followed
|
||||||
|
* by all uppercase letters and underscores. Any references of this type which are not resolvable
|
||||||
|
* will cause an error to be thrown.
|
||||||
|
*/
|
||||||
public class ArgsFile {
|
public class ArgsFile {
|
||||||
private final Path argsPath;
|
private final static Logger logger = LoggerFactory.getLogger(ArgsFile.class);
|
||||||
|
|
||||||
public ArgsFile(String path) {
|
private Path argsPath;
|
||||||
this.argsPath = Path.of(path);
|
private LinkedList<String> preload;
|
||||||
|
|
||||||
|
public ArgsFile() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkedList<String> doArgsFile(String argsfileSpec, LinkedList<String> arglist) {
|
public ArgsFile preload(String... preload) {
|
||||||
return null;
|
this.preload = new LinkedList<String>(Arrays.asList(preload));
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LinkedList<String> spliceArgs(String argsfileSpec, LinkedList<String> arglist) {
|
private enum Selection {
|
||||||
Pattern envpattern = Pattern.compile("(?<envvar>\\$[A-Za-z_]+)");
|
// Ignore if not present, show injections at info
|
||||||
Matcher matcher = envpattern.matcher(argsfileSpec);
|
IgnoreIfMissing,
|
||||||
|
// Warn if not present, but show injections at info
|
||||||
|
WarnIfMissing,
|
||||||
|
// throw error if not present, show injections at info
|
||||||
|
ErrorIfMissing
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedList<String> process(String... args) {
|
||||||
|
return process(new LinkedList<String>(Arrays.asList(args)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedList<String> process(LinkedList<String> commandline) {
|
||||||
|
if (preload != null) {
|
||||||
|
LinkedList<String> modified = new LinkedList<String>();
|
||||||
|
modified.addAll(preload);
|
||||||
|
modified.addAll(commandline);
|
||||||
|
preload = null;
|
||||||
|
commandline = modified;
|
||||||
|
}
|
||||||
|
LinkedList<String> composed = new LinkedList<>();
|
||||||
|
while (commandline.peekFirst() != null) {
|
||||||
|
String arg = commandline.peekFirst();
|
||||||
|
switch (arg) {
|
||||||
|
case "-argsfile":
|
||||||
|
commandline.removeFirst();
|
||||||
|
String argspath = readWordOrThrow(commandline, "path to an args file");
|
||||||
|
setArgsFile(argspath, Selection.WarnIfMissing);
|
||||||
|
commandline = loadArgs(this.argsPath, Selection.WarnIfMissing, commandline);
|
||||||
|
break;
|
||||||
|
case "-argsfile-required":
|
||||||
|
commandline.removeFirst();
|
||||||
|
String argspathRequired = readWordOrThrow(commandline, "path to an args file");
|
||||||
|
setArgsFile(argspathRequired, Selection.ErrorIfMissing);
|
||||||
|
commandline = loadArgs(this.argsPath, Selection.ErrorIfMissing, commandline);
|
||||||
|
break;
|
||||||
|
case "-argsfile-optional":
|
||||||
|
commandline.removeFirst();
|
||||||
|
String argspathOptional = readWordOrThrow(commandline, "path to an args file");
|
||||||
|
setArgsFile(argspathOptional, Selection.IgnoreIfMissing);
|
||||||
|
commandline = loadArgs(this.argsPath, Selection.IgnoreIfMissing, commandline);
|
||||||
|
break;
|
||||||
|
case "-pin":
|
||||||
|
commandline.removeFirst();
|
||||||
|
commandline = pinArg(commandline);
|
||||||
|
break;
|
||||||
|
case "-unpin":
|
||||||
|
commandline.removeFirst();
|
||||||
|
commandline = unpinArg(commandline);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
composed.addLast(commandline.removeFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return composed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedList<String> loadArgs(Path argspath, Selection mode, LinkedList<String> commandline) {
|
||||||
|
if (!assertArgsFileExists(argspath, mode)) {
|
||||||
|
return commandline;
|
||||||
|
}
|
||||||
|
List<String> lines = null;
|
||||||
|
try {
|
||||||
|
lines = Files.readAllLines(argspath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
List<String> content = lines.stream()
|
||||||
|
.filter(s -> !s.startsWith("#"))
|
||||||
|
.filter(s -> !s.startsWith("/"))
|
||||||
|
.filter(s -> !s.isBlank())
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
StringBuilder splitword = new StringBuilder();
|
||||||
|
LinkedList<String> loaded = new LinkedList<>();
|
||||||
|
for (String s : content) {
|
||||||
|
splitword.append(s);
|
||||||
|
if (!s.endsWith("\\")) {
|
||||||
|
loaded.addLast(splitword.toString());
|
||||||
|
splitword.setLength(0);
|
||||||
|
} else {
|
||||||
|
splitword.setLength(splitword.length() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (splitword.length() > 0) {
|
||||||
|
throw new RuntimeException("unqualified line continuation for '" + splitword.toString() + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<String> injections = loaded.descendingIterator();
|
||||||
|
while (injections.hasNext()) {
|
||||||
|
String injection = injections.next();
|
||||||
|
injection = injectEnv(injection);
|
||||||
|
commandline.addFirst(injection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return commandline;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean assertArgsFileExists(Path argspath, Selection mode) {
|
||||||
|
if (!Files.exists(argsPath)) {
|
||||||
|
switch (mode) {
|
||||||
|
case ErrorIfMissing:
|
||||||
|
throw new RuntimeException("A required argsfile was specified, but it does not exist: '" + argspath + "'");
|
||||||
|
case WarnIfMissing:
|
||||||
|
logger.warn("An argsfile was specified, but it does not exist: '" + argspath + "'");
|
||||||
|
case IgnoreIfMissing:
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setArgsFile(String argspath, Selection mode) {
|
||||||
|
this.argsPath = Path.of(argspath);
|
||||||
|
// assertIfMissing(this.argsPath,mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readWordOrThrow(LinkedList<String> commandline, String description) {
|
||||||
|
String found = commandline.peekFirst();
|
||||||
|
if (found == null) {
|
||||||
|
throw new RuntimeException("Unable to read argument top option for " + description);
|
||||||
|
}
|
||||||
|
return commandline.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedList<String> pinArg(LinkedList<String> commandline) {
|
||||||
|
if (this.argsPath == null) {
|
||||||
|
throw new RuntimeException("No argsfile has been selected before using the pin option.");
|
||||||
|
}
|
||||||
|
return commandline;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedList<String> unpinArg(LinkedList<String> commandline) {
|
||||||
|
if (this.argsPath == null) {
|
||||||
|
throw new RuntimeException("No argsfile has been selected before using the unpin option.");
|
||||||
|
}
|
||||||
|
return commandline;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String injectEnv(String word) {
|
||||||
|
Pattern envpattern = Pattern.compile("(?<envvar>\\$[A-Z_]+)");
|
||||||
|
Matcher matcher = envpattern.matcher(word);
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String envvar = matcher.group("envvar");
|
String envvar = matcher.group("envvar");
|
||||||
String value = System.getenv(envvar);
|
String value = System.getenv(envvar);
|
||||||
@ -33,18 +221,8 @@ public class ArgsFile {
|
|||||||
matcher.appendReplacement(sb, value);
|
matcher.appendReplacement(sb, value);
|
||||||
}
|
}
|
||||||
matcher.appendTail(sb);
|
matcher.appendTail(sb);
|
||||||
Path argfilePath = Path.of(sb.toString());
|
return sb.toString();
|
||||||
List<String> lines = null;
|
|
||||||
try {
|
|
||||||
lines = Files.readAllLines(argfilePath);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
// TODO: finish update logic here
|
|
||||||
return arglist;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public LinkedList<String> pin(LinkedList<String> arglist) {
|
public LinkedList<String> pin(LinkedList<String> arglist) {
|
||||||
return arglist;
|
return arglist;
|
||||||
|
@ -163,7 +163,8 @@ public class NBCLIOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private LinkedList<String> parseGlobalOptions(String[] args) {
|
private LinkedList<String> parseGlobalOptions(String[] args) {
|
||||||
ArgsFile argsfile = new ArgsFile(ARGS_FILE_DEFAULT);
|
ArgsFile argsfile = new ArgsFile();
|
||||||
|
argsfile.preload("-argsfile-optional", ARGS_FILE_DEFAULT);
|
||||||
|
|
||||||
LinkedList<String> arglist = new LinkedList<>() {{
|
LinkedList<String> arglist = new LinkedList<>() {{
|
||||||
addAll(Arrays.asList(args));
|
addAll(Arrays.asList(args));
|
||||||
@ -188,18 +189,9 @@ public class NBCLIOptions {
|
|||||||
|
|
||||||
switch (word) {
|
switch (word) {
|
||||||
case ARGS_FILE:
|
case ARGS_FILE:
|
||||||
arglist.removeFirst();
|
|
||||||
String argsfileSpec = readWordOrThrow(arglist, "argsfile");
|
|
||||||
argsfile = new ArgsFile(argsfileSpec);
|
|
||||||
arglist = argsfile.doArgsFile(argsfileSpec, arglist);
|
|
||||||
break;
|
|
||||||
case ARGS_PIN:
|
case ARGS_PIN:
|
||||||
arglist.removeFirst();
|
|
||||||
arglist = argsfile.pin(arglist);
|
|
||||||
break;
|
|
||||||
case ARGS_UNPIN:
|
case ARGS_UNPIN:
|
||||||
arglist.removeFirst();
|
arglist = argsfile.process(arglist);
|
||||||
arglist = argsfile.unpin(arglist);
|
|
||||||
break;
|
break;
|
||||||
case ANNOTATE_EVENTS:
|
case ANNOTATE_EVENTS:
|
||||||
arglist.removeFirst();
|
arglist.removeFirst();
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package io.nosqlbench.engine.cli;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class ArgsFileTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadingArgs() {
|
||||||
|
LinkedList<String> result;
|
||||||
|
ArgsFile argsFile = new ArgsFile();
|
||||||
|
result = argsFile.process("-argsfile", "src/test/resources/argsfiles/nonextant.cli");
|
||||||
|
assertThat(result).containsExactly();
|
||||||
|
result = argsFile.process("-argsfile", "src/test/resources/argsfiles/alphagamma.cli");
|
||||||
|
assertThat(result).containsExactly("alpha", "gamma");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = RuntimeException.class)
|
||||||
|
public void testLoadingMissingRequiredFails() {
|
||||||
|
LinkedList<String> result;
|
||||||
|
ArgsFile argsFile = new ArgsFile();
|
||||||
|
result = argsFile.process("-argsfile-required", "src/test/resources/argsfiles/nonextant.cli");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadingInPlace() {
|
||||||
|
LinkedList<String> result;
|
||||||
|
LinkedList<String> commands = new LinkedList<>(List.of("--abc", "--def", "-argsfile", "src/test/resources/argsfiles/alphagamma.cli"));
|
||||||
|
ArgsFile argsFile = new ArgsFile().preload("-argsfile-optional", "src/test/resources/argsfiles/alphagamma.cli");
|
||||||
|
result = argsFile.process(commands);
|
||||||
|
assertThat(result).containsExactly("alpha", "gamma", "--abc", "--def", "alpha", "gamma");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
2
engine-cli/src/test/resources/argsfiles/alphagamma.cli
Normal file
2
engine-cli/src/test/resources/argsfiles/alphagamma.cli
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
alpha
|
||||||
|
gamma
|
Loading…
Reference in New Issue
Block a user