summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbculkin2442 <bjculkin@mix.wvu.edu>2019-06-18 17:44:32 -0400
committerbculkin2442 <bjculkin@mix.wvu.edu>2019-06-18 17:44:32 -0400
commitd86cbcbabc4b251956bd2c5bf4dfa459a00bb239 (patch)
tree082ad1a1aa76d2519a3226bbaf2f190f6aa931a9
parent1e01c5df99434be2e44bcac1d6c79486082935b1 (diff)
Lots of frontend work
-rw-r--r--src/changes/changes.xml3
-rw-r--r--src/main/java/bjc/everge/Everge.java417
-rw-r--r--src/main/java/bjc/everge/IntHolder.java66
-rw-r--r--src/main/java/bjc/everge/ReplError.java30
-rw-r--r--src/main/java/bjc/everge/ReplOpts.java63
-rw-r--r--src/main/java/bjc/everge/ReplPair.java641
-rw-r--r--src/main/java/bjc/everge/ReplParseException.java58
-rw-r--r--src/main/java/bjc/everge/StringUtils.java65
-rw-r--r--src/test/java/bjc/everge/EvergeTest.java71
-rw-r--r--src/test/java/bjc/everge/StringUtilsTest.java44
-rw-r--r--src/test/java/bjc/everge/TestUtils.java115
11 files changed, 1291 insertions, 282 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index f5bc768..b084b81 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -9,6 +9,9 @@
<action dev="Ben Culkin" type="add">
Initial release :)
</action>
+ <action dev="Ben Culkin" type="add">
+ Added a basic CLI front-end
+ </action>
</release>
</body>
</document>
diff --git a/src/main/java/bjc/everge/Everge.java b/src/main/java/bjc/everge/Everge.java
index 257b242..9b5a235 100644
--- a/src/main/java/bjc/everge/Everge.java
+++ b/src/main/java/bjc/everge/Everge.java
@@ -1,5 +1,30 @@
package bjc.everge;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+import java.nio.charset.Charset;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Scanner;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
/**
* Everge front-end application.
*
@@ -7,12 +32,404 @@ package bjc.everge;
*/
public class Everge {
/**
+ * Details how we handle our input.
+ */
+ public static enum InputStatus {
+ /**
+ * Process the input as a single string.
+ */
+ ALL,
+ /**
+ * Process the input line-by-line.
+ */
+ LINE,
+ /**
+ * Process the input, splitting it around occurances of a regex.
+ */
+ REGEX;
+ }
+
+ // Options for doing repl-pairs
+ private ReplOpts ropts = new ReplOpts();
+
+ // Loaded repl-pairs
+ private List<ReplPair> lrp = new ArrayList<>();
+
+ // Input status
+ private InputStatus inputStat = InputStatus.ALL;
+
+ // Are we processing CLI args? (haven't seen a -- yet)
+ private boolean doingArgs = true;
+
+ // Should an NL be printed after each replace?
+ private boolean printNL = true;
+
+ // Verbosity level
+ private int verbosity = 0;
+
+ // The pattern to use for REGEX input mode
+ private String pattern;
+
+ // The queue of arguments to process
+ private Deque<String> argQue = new LinkedList<>();
+
+ // Used to prevent inter-mixing argument alterations with input processing.
+ private ReadWriteLock argLock = new ReentrantReadWriteLock();
+
+ // Input/output streams
+ public PrintStream outStream = System.out;
+ public PrintStream errStream = System.err;
+
+ /**
* Main method for front end,
*
* @param args
* The CLI arguments.
*/
public static void main(String[] args) {
+ Everge evg = new Everge();
+
+ evg.processArgs(args);
+ }
+
+ /**
+ * Process one or more command line arguments.
+ *
+ * @param args
+ * The arguments to process.
+ * @return Whether we processed succesfully or not.
+ */
+ public boolean processArgs(String... args) {
+ List<String> errs = new ArrayList<>();
+
+ boolean stat = processArgs(errs, args);
+ if (!stat) {
+ for (String err : errs) {
+ errStream.println(err);
+ }
+ }
+
+ return stat;
+ }
+
+ /**
+ * Process one or more command line arguments.
+ *
+ * @param args
+ * The arguments to process.
+ * @param errs
+ * The list to stash errors in.
+ * @return Whether we processed succesfully or not.
+ */
+ public boolean processArgs(List<String> errs, String... args) {
+ argLock.writeLock().lock();
+
+ boolean retStat = true;
+
+ try {
+ loadQueue(args);
+
+ // Process CLI args
+ while(argQue.size() > 0) {
+ String arg = argQue.pop();
+
+ if (arg.equals("--")) {
+ doingArgs = false;
+ continue;
+ }
+
+ // Process an argument
+ if (doingArgs && arg.startsWith("-")) {
+ String argName = arg;
+ String argBody = "";
+
+ // Process arguments to arguments
+ int idx = arg.indexOf("=");
+ if (idx != -1) {
+ argName = arg.substring(0, idx);
+ argBody = arg.substring(idx + 1);
+ }
+
+ switch (argName) {
+ case "-n":
+ case "--newline":
+ printNL = true;
+ break;
+ case "-N":
+ case "--no-newline":
+ printNL = false;
+ break;
+ case "-v":
+ case "--verbose":
+ verbosity += 1;
+ break;
+ case "-q":
+ case "--quiet":
+ verbosity -= 1;
+ break;
+ case "--verbosity":
+ if (argQue.size() < 1) {
+ errs.add("[ERROR] No parameter to --verbosity");
+ retStat = false;
+ break;
+ }
+ argBody = argQue.pop();
+ break;
+ case "-V":
+ try {
+ verbosity = Integer.parseInt(argBody);
+ } catch (NumberFormatException nfex) {
+ String msg = String.format("[ERROR] Invalid verbosity: '%s' is not an integer",
+ argBody);
+ errs.add(msg);
+ retStat = false;
+ }
+ break;
+ case "--pattern":
+ if (argQue.size() < 1) {
+ errs.add("[ERROR] No parameter to --pattern");
+ retStat = false;
+ break;
+ }
+ argBody = argQue.pop();
+ case "-p":
+ try {
+ pattern = argBody;
+
+ Pattern.compile(argBody);
+ } catch (PatternSyntaxException psex) {
+ String msg = String.format("[ERROR] Pattern '%s' is invalid: %s",
+ pattern, psex.getMessage());
+ errs.add(msg);
+ retStat = false;
+ }
+ break;
+ case "--file":
+ if (argQue.size() < 1) {
+ errs.add("[ERROR] No argument to --file");
+ retStat = false;
+ break;
+ }
+ argBody = argQue.pop();
+ case "-f":
+ try (FileInputStream fis = new FileInputStream(argBody);
+ Scanner scn = new Scanner(fis)) {
+ List<ReplError> ferrs = new ArrayList<>();
+
+ lrp = ReplPair.readList(lrp, scn, ferrs, ropts);
+
+ if (ferrs.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+
+ String errString = "an error";
+ if (ferrs.size() > 1) errString = String.format("%d errors");
+
+ {
+ String msg = String.format(
+ "[ERROR] Encountered errors parsing data file'%s'\n",
+ argBody);
+ sb.append(msg);
+ }
+
+ for (ReplError err : ferrs) {
+ sb.append(String.format("\t%s\n", err));
+ }
+
+ errs.add(sb.toString());
+ retStat = false;
+ }
+ } catch (FileNotFoundException fnfex) {
+ String msg = String.format("[ERROR] Could not open data file '%s' for input",
+ argBody);
+ errs.add(msg);
+ retStat = false;
+ } catch (IOException ioex) {
+ String msg = String.format("[ERROR] Unknown I/O error reading data file '%s': %s",
+ argBody, ioex.getMessage());
+ errs.add(msg);
+ retStat = false;
+ }
+ break;
+ case "--arg-file":
+ if (argQue.size() < 1) {
+ errs.add("[ERROR] No argument to --arg-file");
+ break;
+ }
+ argBody = argQue.pop();
+ case "-F":
+ try (FileInputStream fis = new FileInputStream(argBody);
+ Scanner scn = new Scanner(fis)) {
+ List<String> sl = new ArrayList<>();
+
+ while (scn.hasNextLine()) {
+ String ln = scn.nextLine().trim();
+
+ if (ln.equals("")) continue;
+ if (ln.startsWith("#")) continue;
+
+ sl.add(ln);
+ }
+
+ processArgs(sl.toArray(new String[0]));
+ } catch (FileNotFoundException fnfex) {
+ String msg = String.format("[ERROR] Could not open argument file '%s' for input", argBody);
+ errs.add(msg);
+ retStat = false;
+ } catch (IOException ioex) {
+ String msg = String.format("[ERROR] Unknown I/O error reading input file '%s': %s",
+ argBody, ioex.getMessage());
+ errs.add(msg);
+ retStat = false;
+ }
+ break;
+ default:
+ {
+ String msg = String.format("[ERROR] Unrecognised CLI argument name '%s'\n", argName);
+ errs.add(msg);
+ retStat = false;
+ }
+ }
+ } else {
+ // Strip off an escaped initial dash
+ if (arg.startsWith("\\-")) arg = arg.substring(1);
+
+ processInputFile(arg);
+ }
+ }
+ } finally {
+ argLock.writeLock().unlock();
+ }
+
+ return retStat;
+ }
+
+ /**
+ * Process a input file.
+ *
+ * @param fle
+ * Input file to process.
+ * @return Whether we processed succesfully or not.
+ */
+ public boolean processInputFile(String fle) {
+ List<String> errs = new ArrayList<>();
+
+ boolean stat = processInputFile(errs, fle);
+ if (!stat) {
+ for (String err : errs) {
+ errStream.println(err);
+ }
+ }
+
+ return stat;
+ }
+
+ /**
+ * Process a input file.
+ *
+ * @param fle
+ * Input file to process.
+ * @param errs
+ * List to accumulate errors in.
+ * @return Whether we processed succesfully or not.
+ */
+ public boolean processInputFile(List<String> errs, String fle) {
+ argLock.readLock().lock();
+
+ // Read in and do replacements on a file
+ try {
+ if (verbosity > 2) {
+ errStream.printf("[TRACE] Reading file (%s) in mode (%s)\n", fle, inputStat);
+ }
+
+ if (inputStat == InputStatus.ALL) {
+ Path pth = Paths.get(fle);
+
+ if (!Files.isReadable(pth)) {
+ String msg = String.format("[ERROR] File '%s' is not readable\n", fle);
+ errs.add(msg);
+ return false;
+ } else {
+ byte[] inp = Files.readAllBytes(pth);
+
+ String strang = new String(inp, Charset.forName("UTF-8"));
+
+ processString(strang);
+ }
+ } else if (inputStat == InputStatus.LINE) {
+ try (FileInputStream fis = new FileInputStream(fle); Scanner scn = new Scanner(fis)) {
+ while(scn.hasNextLine()) {
+ processString(scn.nextLine());
+ }
+ }
+ } else if (inputStat == InputStatus.REGEX) {
+ try (FileInputStream fis = new FileInputStream(fle); Scanner scn = new Scanner(fis)) {
+ scn.useDelimiter(pattern);
+
+ while(scn.hasNext()) {
+ processString(scn.next());
+ }
+ }
+ } else {
+ String msg = String.format("[INTERNAL-ERROR] Input status '%s' is not yet implemented\n",
+ inputStat);
+ errs.add(msg);
+ return false;
+ }
+ } catch (IOException ioex) {
+ String msg = String.format("[ERROR] Unknown I/O related error for file '%s'\n\tError was %s",
+ fle, ioex.getMessage());
+ errs.add(msg);
+ return false;
+ } finally {
+ argLock.readLock().unlock();
+ }
+
+ return true;
+ }
+
+ /**
+ * Process an input string.
+ *
+ * @param inp
+ * The input string to process.
+ */
+ public void processString(String inp) {
+ argLock.readLock().lock();
+
+ try {
+ String strang = inp;
+
+ for (ReplPair rp : lrp) {
+ strang = rp.apply(strang);
+ }
+
+ outStream.print(strang);
+ if (printNL) outStream.println();
+ } finally {
+ argLock.readLock().unlock();
+ }
+ }
+
+ // Load arguments into the argument queue.
+ private void loadQueue(String... args) {
+ boolean doArgs = true;
+ for (String arg : args) {
+ if (arg.equals("--")) doArgs = false;
+ // Handle things like -nNv correctly
+ if (doArgs) {
+ if (arg.startsWith("-") && !arg.startsWith("--")) {
+ char[] car = arg.substring(1).toCharArray();
+ for (char c : car) {
+ String argstr = String.format("-%c", c);
+ argQue.add(argstr);
+ }
+ } else {
+ argQue.add(arg);
+ }
+ } else {
+ argQue.add(arg);
+ }
+ }
}
}
diff --git a/src/main/java/bjc/everge/IntHolder.java b/src/main/java/bjc/everge/IntHolder.java
new file mode 100644
index 0000000..e137d61
--- /dev/null
+++ b/src/main/java/bjc/everge/IntHolder.java
@@ -0,0 +1,66 @@
+package bjc.everge;
+
+/**
+ * Utility class for ints by ref.
+ *
+ * @author Ben Culkin
+ */
+public class IntHolder {
+ /**
+ * The int value.
+ */
+ public int val;
+
+ /**
+ * Create a new int-holder set to 0.
+ */
+ public IntHolder() {
+ val = 0;
+ }
+
+ /**
+ * Create a new int-holder set to a value.
+ *
+ * @param i
+ * The value to set the int to.
+ */
+ public IntHolder(int i) {
+ val = i;
+ }
+
+ /**
+ * Increment the value by one, and return it.
+ *
+ * @return The value of the holder.
+ */
+ public int incr() {
+ return incr(1);
+ }
+
+ /**
+ * Increment the value by an amount and return it.
+ *
+ * @param i
+ * The amount to increment by.
+ *
+ * @return The value of the holder.
+ */
+ public int incr(int i) {
+ val += 1;
+
+ return val;
+ }
+
+ /**
+ * Get the value.
+ *
+ * @return The value.
+ */
+ public int get() {
+ return val;
+ }
+
+ public void set(int i) {
+ val = i;
+ }
+}
diff --git a/src/main/java/bjc/everge/ReplError.java b/src/main/java/bjc/everge/ReplError.java
index e5a4dd0..6e58539 100644
--- a/src/main/java/bjc/everge/ReplError.java
+++ b/src/main/java/bjc/everge/ReplError.java
@@ -36,6 +36,22 @@ public class ReplError {
* @param txt
* The text that caused the error.
*/
+ public ReplError(IntHolder lne, IntHolder nPairs, String msg, String txt) {
+ this(lne.get(), nPairs.get(), msg, txt);
+ }
+
+ /**
+ * Create a new ReplPair parse error.
+ *
+ * @param lne
+ * The line the error occured on.
+ * @param nPairs
+ * The number of pairs processed up to this point.
+ * @param msg
+ * The message detailing the error.
+ * @param txt
+ * The text that caused the error.
+ */
public ReplError(int lne, int nPairs, String msg, String txt) {
line = lne;
numPairs = nPairs;
@@ -53,4 +69,18 @@ public class ReplError {
return String.format("line %d, pair %d:%s\n\t%s", line, numPairs, msg, errString);
}
+
+ public String toPrintString() {
+ return toPrintString("");
+ }
+
+ public String toPrintString(String hdr) {
+ String errString;
+ if (txt == null) errString = "No associated line";
+ else if (txt.equals("")) errString = "Text of line was empty";
+ else errString = "Text of line was: " + txt;
+
+ return String.format("[ERROR] line %d, pair %d: %s\n%s\tContext: %s",
+ line, numPairs, msg, hdr, errString);
+ }
}
diff --git a/src/main/java/bjc/everge/ReplOpts.java b/src/main/java/bjc/everge/ReplOpts.java
index aa836b0..1ddfc79 100644
--- a/src/main/java/bjc/everge/ReplOpts.java
+++ b/src/main/java/bjc/everge/ReplOpts.java
@@ -1,5 +1,7 @@
package bjc.everge;
+import java.io.PrintStream;
+
/**
* Options for processing ReplPairs.
*
@@ -9,61 +11,60 @@ public class ReplOpts {
/**
* The default priority.
*/
- public int defPrior;
+ public int defPrior = 0;
+
/**
* The default stage.
*/
- public int defStage;
+ public int defStage = 0;
/**
* Whether to process multi-line defns.
*/
- public boolean defMulti;
+ public boolean defMulti = false;
/**
* Default status.
*/
- public StageStatus defStatus;
+ public StageStatus defStatus = StageStatus.BOTH;
/**
* Enable debug info.
*/
- public boolean isDebug;
+ public boolean isDebug = true;
/**
- * Create a default set of options.
+ * Enable trace info.
*/
- public ReplOpts() {
- defPrior = 0;
- defStage = 0;
+ public boolean isTrace = false;
- defMulti = false;
+ /**
+ * Enable performance info.
+ */
+ public boolean isPerf = false;
- defStatus = StageStatus.BOTH;
+ public PrintStream outStream = System.out;
+ public PrintStream errStream = System.err;
- isDebug = false;
- }
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) return false;
- /**
- * Create a new set of repl. opts
- *
- * @param p
- * The default priority to use
- * @param s
- * The default stage to use
- * @param m
- * Whether to process multi-line defns.
- * @param t
- * The default status.
- */
- public ReplOpts(int p, int s, boolean m, StageStatus t, boolean d) {
- defPrior = p;
- defStage = s;
+ if (!getClass().equals(o.getClass())) return false;
+
+ ReplOpts ro = (ReplOpts)o;
+
+ if (isPerf != ro.isPerf) return false;
+
+ if (isDebug != ro.isDebug) return false;
+ if (isTrace != ro.isTrace) return false;
- defMulti = m;
+ if (defPrior != ro.defPrior) return false;
+ if (defStage != ro.defStage) return false;
+ if (defMulti != ro.defMulti) return false;
- defStatus = t;
+ if (defStatus != ro.defStatus) return false;
- isDebug = d;
+ return true;
}
}
diff --git a/src/main/java/bjc/everge/ReplPair.java b/src/main/java/bjc/everge/ReplPair.java
index 4f6b2ad..6acc6ad 100644
--- a/src/main/java/bjc/everge/ReplPair.java
+++ b/src/main/java/bjc/everge/ReplPair.java
@@ -13,6 +13,8 @@ import java.util.function.UnaryOperator;
*/
public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
private int lno;
+ private int stage;
+ private StageStatus stat = StageStatus.BOTH;
/**
* The priority for this replacement.
@@ -35,8 +37,6 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
*/
public String replace;
- private StageStatus stat = StageStatus.BOTH;
-
/**
* Create a new blank replacement pair.
*/
@@ -136,11 +136,7 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
List<ReplPair> rplPar = readList(detals, scn, errList);
if (errList.size() != 0) {
- String errString;
- if (errList.size() == 0) errString = "An error";
- else errString = "Errors";
-
- throw new IllegalArgumentException(String.format("%s occured parsing replacement pairs:\n%s", errString, errList));
+ throw new ReplParseException("", errList);
}
return rplPar;
@@ -165,17 +161,8 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
public static List<ReplPair> readList(List<ReplPair> detals, Scanner scn,
List<ReplError> errs, ReplOpts ropts) {
- int lno = 0;
- int pno = 0;
-
- int defPrior = ropts.defPrior;
- int defStage = ropts.defStage;
-
- boolean defMulti = ropts.defMulti;
-
- StageStatus defStatus = ropts.defStatus;
-
- boolean isDebug = ropts.isDebug;
+ IntHolder lno = new IntHolder();
+ IntHolder pno = new IntHolder();
List<List<ReplPair>> stages = new ArrayList<>();
stages.add(new ArrayList<ReplPair>());
@@ -183,7 +170,7 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
// For every line in the source...
while (scn.hasNextLine()) {
String name = scn.nextLine().trim();
- lno += 1;
+ lno.incr();
// If its commented or blank, skip it
if (name.equals("")) continue;
@@ -191,188 +178,24 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
// Global control. Process it.
if (name.startsWith("|//")) {
- name = name.substring(3);
-
- // Split out each control
- String[] bits = name.split(";");
-
- for (String bit : bits) {
- String bitHead = bit.toUpperCase();
- String bitBody = bit;
-
- int idx = bit.indexOf('/');
- if (idx != -1) {
- bitHead = bit.substring(0, idx).toUpperCase();
- bitBody = bit.substring(idx + 1);
- }
-
- switch (bitHead) {
- case "P":
- try {
- defPrior = Integer.parseInt(bitBody);
- } catch (NumberFormatException nfex) {
- String errMsg = String.format("'%s' is not a valid priority (must be an integer)", bitBody);
- errs.add(new ReplError(lno, pno, errMsg, name));
- }
- break;
- case "S":
- try {
- int tmpStage = Integer.parseInt(bitBody);
- if (tmpStage < 0) {
- String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)", bitBody);
- errs.add(new ReplError(lno, pno, errMsg, name));
-
- break;
- }
- defStage = tmpStage;
- } catch (NumberFormatException nfex) {
- String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)", bitBody);
- errs.add(new ReplError(lno, pno, errMsg, name));
- }
- break;
- case "MT":
- defMulti = true;
- break;
- case "MF":
- defMulti = false;
- break;
- case "M":
- defMulti = Boolean.parseBoolean(bitBody);
- break;
- case "I":
- defStatus = StageStatus.INTERNAL;
- break;
- case "E":
- defStatus = StageStatus.EXTERNAL;
- break;
- case "B":
- defStatus = StageStatus.BOTH;
- break;
- case "DT":
- isDebug = true;
- break;
- case "DF":
- isDebug = false;
- break;
- case "D":
- isDebug = Boolean.parseBoolean(bitBody);
- break;
- default:
- errs.add(new ReplError(lno, pno, String.format("Invalid control name '%s'", bitHead), name));
- break;
- }
- }
+ readGlobal(name, scn, errs, ropts, lno, pno);
continue;
}
ReplPair rp = new ReplPair();
- rp.priority = defPrior;
- rp.stat = defStatus;
- rp.lno = lno;
-
- int stage = defStage;
- boolean isMulti = defMulti;
-
- // Name has attached controls, process them.
- if (name.startsWith("//")) {
- name = name.substring(2);
-
- int idx = name.indexOf("//");
- if (idx == -1) {
- String msg = "Did not find control terminator (//) in name where it should be";
-
- errs.add(new ReplError(lno, pno, msg, name));
- continue;
- }
+ rp.priority = ropts.defPrior;
+ rp.stat = ropts.defStatus;
+ rp.lno = lno.get();
+ rp.stage = ropts.defStage;
- String contName = name.substring(0, idx);
- String actName = name.substring(idx + 2);
+ boolean isMulti = ropts.defMulti;
- // Split out each control
- String[] bits = contName.split(";");
-
- for (String bit : bits) {
- String bitHead = bit.toUpperCase();
- String bitBody = bit;
-
- idx = bit.indexOf('/');
- if (idx != -1) {
- bitHead = bit.substring(0, idx).toUpperCase();
- bitBody = bit.substring(idx + 1);
- }
-
- switch (bitHead) {
- case "N":
- rp.name = bitBody;
- break;
- case "P":
- try {
- rp.priority = Integer.parseInt(bitBody);
- } catch (NumberFormatException nfex) {
- String errMsg = String.format("'%s' is not a valid priority (must be an integer)", bitBody);
- errs.add(new ReplError(lno, pno, errMsg, name));
- }
- break;
- case "S":
- try {
- int tmpStage = Integer.parseInt(bitBody);
- if (tmpStage < 0) {
- String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)", bitBody);
- errs.add(new ReplError(lno, pno, errMsg, name));
-
- break;
- }
- stage = tmpStage;
- } catch (NumberFormatException nfex) {
- String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)", bitBody);
- errs.add(new ReplError(lno, pno, errMsg, name));
- }
- break;
- case "MT":
- isMulti = true;
- break;
- case "MF":
- isMulti = false;
- break;
- case "M":
- isMulti = Boolean.parseBoolean(bitBody);
- break;
- case "I":
- rp.stat = StageStatus.INTERNAL;
- break;
- case "E":
- rp.stat = StageStatus.EXTERNAL;
- break;
- case "B":
- rp.stat = StageStatus.BOTH;
- break;
- default:
- errs.add(new ReplError(lno, pno, String.format("Unknown control name '%s'", bitHead), name));
- break;
- }
- }
-
- // Multi-line name with a trailer
- if (isMulti) {
- String tmp = actName;
-
- while (tmp.endsWith("\\")) {
- boolean incNL = tmp.endsWith("|\\");
-
- if (!scn.hasNextLine()) break;
-
- tmp = scn.nextLine().trim();
-
- if (tmp.equals("")) continue;
- if (tmp.startsWith("#")) continue;
-
- actName = String.format("%s%s%s", actName, incNL ? "\n" : "", tmp);
- }
- }
-
- name = actName;
+ {
+ String tmpName = readName(name, scn, errs, rp, ropts, lno, pno);
+ if (tmpName == null) continue;
+ name = tmpName;
}
rp.find = name;
@@ -380,60 +203,67 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
// We started to process the pair, mark it as being
// started
- pno += 1;
+ pno.incr();
String body = null;
// Read in the next uncommented line
do {
- if (!scn.hasNextLine()) {
- String msg =
- "Ran out of input looking for replacement body for raw name " + name;
-
- errs.add(new ReplError(lno, pno, msg, null));
- break;
- }
+ if (!scn.hasNextLine()) break;
body = scn.nextLine().trim();
- lno += 1;
+ lno.incr();
} while (body.startsWith("#"));
- isMulti = defMulti;
+ if (body == null) {
+ String msg =
+ "Ran out of input looking for replacement body for raw name '" + name + "'";
+
+ errs.add(new ReplError(lno, pno, msg, null));
+ break;
+ }
+
+ isMulti = ropts.defMulti;
// Body has attached controls, process them.
if (body.startsWith("//")) {
body = body.substring(2);
- int idx = body.indexOf("//");
- if (idx == -1) {
+ String[] bodyBits = StringUtils.escapeSplit("|", "//", body);
+ if (bodyBits.length < 2) {
String msg = "Did not find control terminator (//) in body where it should be";
errs.add(new ReplError(lno, pno, msg, body));
continue;
}
- String contBody = body.substring(0, idx);
- String actBody = body.substring(idx + 2);
+ String contBody = bodyBits[0];
+ String actBody = bodyBits[1];
// Split out each control
- String[] bits = actBody.split(";");
+ String[] bits = StringUtils.escapeSplit("|", ";", actBody);
for (String bit : bits) {
String bitHead = bit.toUpperCase();
String bitBody = bit;
- idx = bit.indexOf('/');
- if (idx != -1) {
- bitHead = bit.substring(0, idx).toUpperCase();
- bitBody = bit.substring(idx + 1);
+ String[] bots = StringUtils.escapeSplit("|", "/", bit);
+ if (bots.length > 1) {
+ bitHead = bots[0].toUpperCase();
+ bitBody = bots[1];
}
switch (bitHead) {
+ case "MULTITRUE":
+ case "MULTIT":
case "MT":
isMulti = true;
break;
+ case "MULTIFALSE":
+ case "MULTIF":
case "MF":
isMulti = false;
break;
+ case "MULTI":
case "M":
isMulti = Boolean.parseBoolean(bitBody);
break;
@@ -443,48 +273,37 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
}
}
- // Multi-line name with a trailer
- if (isMulti) {
- String tmp = actBody;
-
- while (tmp.endsWith("\\")) {
- boolean incNL = tmp.endsWith("|\\");
-
- if (!scn.hasNextLine()) break;
-
- tmp = scn.nextLine().trim();
-
- if (tmp.startsWith("#")) continue;
-
- actBody = String.format("%s%s%s", actBody, incNL ? "\n" : "", tmp);
- }
- }
-
body = actBody;
}
+ if (isMulti) {
+ String tmp = readMultiLine(body, scn, ropts, errs, "body", lno);
+ if (tmp == null) continue;
+ body = tmp;
+ }
+
rp.replace = body;
List<ReplPair> stageList = null;
- if (stage == 0 || stages.size() < (stage - 1)) {
- stageList = stages.get(stage);
+ if (rp.stage == 0 || stages.size() < (rp.stage - 1)) {
+ stageList = stages.get(rp.stage);
if (stageList == null) {
stageList = new ArrayList<>();
- stages.add(stage, stageList);
+ stages.add(rp.stage, stageList);
}
} else {
- for (int i = stages.size(); i <= stage; i++) {
+ for (int i = stages.size(); i <= rp.stage; i++) {
stages.add(new ArrayList<>());
}
- stageList = stages.get(stage);
+ stageList = stages.get(rp.stage);
}
- if (isDebug) {
- System.err.printf("\t[DEBUG] Stage %d: Added %s\n\t\tContents: %s\n",
- stage, rp, stageList);
+ if (ropts.isTrace) {
+ ropts.errStream.printf("\t[DEBUG] Stage %d: Added %s\n\t\tContents: %s\n",
+ rp.stage, rp, stageList);
}
stageList.add(rp);
@@ -492,11 +311,11 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
// Special-case one-stage processing.
if (stages.size() == 1) {
- if (isDebug) System.err.printf("\t[DEBUG] Executing single-stage bypass\n");
+ if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Executing single-stage bypass\n");
for (ReplPair rp : stages.iterator().next()) {
if (rp.stat == StageStatus.INTERNAL) {
- if (isDebug) System.err.printf("\t[DEBUG] Excluding internal RP %s\n", rp);
+ if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Excluding internal RP %s\n", rp);
continue;
}
@@ -513,14 +332,14 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
List<ReplPair> tmpList = new ArrayList<>();
tmpList.addAll(detals);
- if (isDebug) System.err.printf("\t[DEBUG] Stages: %s\n", stages);
+ if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Stages: %s\n", stages);
int procStg = 0;
for (List<ReplPair> stageList : stages) {
procStg += 1;
List<ReplPair> curStage = new ArrayList<>();
- if (isDebug) System.err.printf("\t[DEBUG] Staging stage %d of %d: %s\n",
+ if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Staging stage %d of %d: %s\n",
procStg, stageList.size(), stageList);
for (ReplPair rp : stageList) {
@@ -529,8 +348,8 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
for (ReplPair curPar : tmpList) {
String tmp = rp.replace.replaceAll(curPar.find, curPar.replace);
- if (isDebug && !rp.replace.equals(tmp)) {
- System.err.printf("\t[DEBUG] Staged '%s' -> '%s'\t%s\n",
+ if (ropts.isTrace && !rp.replace.equals(tmp)) {
+ ropts.errStream.printf("\t[DEBUG] Staged '%s' -> '%s'\t%s\n",
rp.replace, tmp, curPar);
}
@@ -539,15 +358,15 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
// If we're external; add straight to the output
if (rp.stat == StageStatus.EXTERNAL) {
- if (isDebug) {
- System.err.printf("\t[DEBUG] Skipped external for staging: %s\n",
+ if (ropts.isTrace) {
+ ropts.errStream.printf("\t[DEBUG] Skipped external for staging: %s\n",
rp);
}
detals.add(rp);
} else {
- if (isDebug) {
- System.err.printf("\t[DEBUG] Added to stage %d: %s\n\t\tContents: %s\n",
+ if (ropts.isTrace) {
+ ropts.errStream.printf("\t[DEBUG] Added to stage %d: %s\n\t\tContents: %s\n",
procStg, rp, curStage);
}
@@ -562,7 +381,7 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
// Copy over to output, excluding internals
for (ReplPair rp : tmpList) {
if (rp.stat == StageStatus.INTERNAL) {
- if (isDebug) System.err.printf("\t[DEBUG] Excluded internal: %s\n", rp);
+ if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Excluded internal: %s\n", rp);
continue;
}
@@ -572,13 +391,51 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
detals.sort(null);
- if (isDebug) {
- System.err.printf("\t[DEBUG] Final output: %s\n", detals);
+ if (ropts.isTrace) {
+ ropts.errStream.printf("\t[DEBUG] Final output: %s\n", detals);
}
return detals;
}
+ private static String readMultiLine(String lead, Scanner src, ReplOpts ropts,
+ List<ReplError> errs, String typ, IntHolder lno) {
+ String tmp = lead;
+
+ if (ropts.isTrace && tmp.endsWith("\\"))
+ ropts.errStream.printf("\t[TRACE] Starting multi-line parse for %s '%s'\n", typ, tmp);
+
+ boolean didMulti = tmp.endsWith("\\");
+ while (tmp.endsWith("\\")) {
+ boolean incNL = tmp.endsWith("|\\");
+
+ if (!src.hasNextLine()) break;
+
+ String nxt = src.nextLine().trim();
+ lno.incr();
+
+ if (nxt.startsWith("#")) continue;
+
+ String nlStr = incNL ? "\n" : "";
+
+ if (tmp.endsWith("\\")) {
+ if (incNL) {
+ tmp = tmp.substring(0, tmp.length() - 2);
+ } else {
+ tmp = tmp.substring(0, tmp.length() - 1);
+ }
+ }
+
+ tmp = String.format("%s%s%s", tmp, nlStr, nxt);
+ }
+
+ if (ropts.isTrace && didMulti)
+ ropts.errStream.printf("\t[TRACE] Finished multi-line parse for %s:\n%s\n.\n",
+ typ, tmp);
+
+ return tmp;
+ }
+
@Override
public String apply(String inp) {
return inp.replaceAll(find, replace);
@@ -592,11 +449,293 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
return String.format("%ss/%s/%s/p(%d)", nameStr, find, replace, priority);
}
-
+
@Override
public int compareTo(ReplPair rp) {
if (this.priority == rp.priority) return this.lno - rp.lno;
return rp.priority - this.priority;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) return false;
+
+ if (!getClass().equals(o.getClass())) return false;
+
+ ReplPair ro = (ReplPair)o;
+
+ if (!find.equals(ro.find)) return false;
+ // lno is not a field we consider for equality
+ if (!name.equals(ro.name)) return false;
+ if (priority != ro.priority) return false;
+ if (!replace.equals(ro.name)) return false;
+ // stat is not a field we consider for equality
+
+ return true;
+ }
+
+ private static String readName(String nam, Scanner scn, List<ReplError> errs,
+ ReplPair rp, ReplOpts ropts, IntHolder lno, IntHolder pno) {
+ String name = nam;
+
+ boolean isMulti = ropts.defMulti;
+
+ // Name has attached controls, process them.
+ if (name.startsWith("//")) {
+ name = name.substring(2);
+
+ String[] nameBits = StringUtils.escapeSplit("|", "//", name);
+
+ if (nameBits.length < 2) {
+ String msg = "Did not find control terminator (//) in name where it should be";
+
+ errs.add(new ReplError(lno, pno, msg, name));
+ return null;
+ }
+
+ String contName = nameBits[0];
+ String actName = nameBits[1];
+
+ // Split out each control
+ String[] bits = StringUtils.escapeSplit("|", ";", contName);
+
+ for (String bit : bits) {
+ String bitHead = bit.toUpperCase();
+ String bitBody = bit;
+
+ String[] bots = StringUtils.escapeSplit("|", "/", bit);
+
+ if (bots.length > 1) {
+ bitHead = bots[0].toUpperCase();
+ bitBody = bots[1];
+ }
+
+ switch (bitHead) {
+ case "NAME":
+ case "N":
+ rp.name = bitBody;
+ break;
+ case "PRIORITY":
+ case "PRIOR":
+ case "P":
+ try {
+ rp.priority = Integer.parseInt(bitBody);
+ } catch (NumberFormatException nfex) {
+ String errMsg = String.format("'%s' is not a valid priority (must be an integer)", bitBody);
+ errs.add(new ReplError(lno, pno, errMsg, name));
+ }
+ break;
+ case "STAGE":
+ case "S":
+ try {
+ int tmpStage = Integer.parseInt(bitBody);
+ if (tmpStage < 0) {
+ String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)", bitBody);
+ errs.add(new ReplError(lno, pno, errMsg, name));
+
+ break;
+ }
+ rp.stage = tmpStage;
+ } catch (NumberFormatException nfex) {
+ String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)", bitBody);
+ errs.add(new ReplError(lno, pno, errMsg, name));
+ }
+ break;
+ case "MULTITRUE":
+ case "MULTIT":
+ case "MT":
+ isMulti = true;
+ break;
+ case "MULTIFALSE":
+ case "MULTIF":
+ case "MF":
+ isMulti = false;
+ break;
+ case "MULTI":
+ case "M":
+ isMulti = Boolean.parseBoolean(bitBody);
+ break;
+ case "INTERNAL":
+ case "INT":
+ case "I":
+ rp.stat = StageStatus.INTERNAL;
+ break;
+ case "EXTERNAL":
+ case "EXT":
+ case "E":
+ rp.stat = StageStatus.EXTERNAL;
+ break;
+ case "BOTH":
+ case "B":
+ rp.stat = StageStatus.BOTH;
+ break;
+ default:
+ {
+ ReplError erd = new ReplError(lno, pno,
+ String.format("Unknown control name '%s' for name '%s'",
+ bitHead, name), name);
+
+ errs.add(erd);
+ }
+ break;
+ }
+
+ name = actName;
+ }
+
+ // Multi-line name with a trailer
+ if (isMulti) {
+ String tmp = readMultiLine(name, scn, ropts, errs, "name", lno);
+ if (tmp == null) return null;
+ name = tmp;
+ }
+ }
+
+ return name;
+ }
+
+ private static void readGlobal(String nam, Scanner scn, List<ReplError> errs,
+ ReplOpts ropts, IntHolder lno, IntHolder pno) {
+ String name = nam.substring(3);
+
+ // Split out each control
+ String[] bits = StringUtils.escapeSplit("|", ";", name);
+ if (ropts.isTrace) {
+ ropts.errStream.printf("\t[TRACE] Split control bits are: \n");
+ for (String bit : bits) {
+ ropts.errStream.printf("%s, ", bit);
+ }
+ ropts.errStream.println();
+ }
+ for (String bit : bits) {
+ String bitHead = bit.toUpperCase();
+ String bitBody = bit;
+
+ String[] bots = StringUtils.escapeSplit("|", "/", bit);
+ if (bots.length > 1) {
+ bitHead = bots[0];
+ bitBody = bots[1];
+ }
+
+ switch (bitHead) {
+ case "PRIORITY":
+ case "PRIOR":
+ case "P":
+ try {
+ int tmp = Integer.parseInt(bitBody);
+ ropts.defPrior = tmp;
+ } catch (NumberFormatException nfex) {
+ String errMsg = String.format("'%s' is not a valid priority (must be an integer)",
+ bitBody);
+
+ errs.add(new ReplError(lno, pno, errMsg, name));
+ }
+ break;
+ case "STAGE":
+ case "S":
+ try {
+ int tmpStage = Integer.parseInt(bitBody);
+
+ if (tmpStage < 0) {
+ String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)",
+ bitBody);
+
+ errs.add(new ReplError(lno, pno, errMsg, name));
+ break;
+ }
+ ropts.defStage = tmpStage;
+ } catch (NumberFormatException nfex) {
+ String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)",
+ bitBody);
+
+ errs.add(new ReplError(lno, pno, errMsg, name));
+ }
+ break;
+ case "MULTITRUE":
+ case "MULTIT":
+ case "MT":
+ ropts.defMulti = true;
+ break;
+ case "MULTIFALSE":
+ case "MULTIF":
+ case "MF":
+ ropts.defMulti = false;
+ break;
+ case "MULTI":
+ case "M":
+ ropts.defMulti = Boolean.parseBoolean(bitBody);
+ break;
+ case "INTERNAL":
+ case "INT":
+ case "I":
+ ropts.defStatus = StageStatus.INTERNAL;
+ break;
+ case "EXTERNAL":
+ case "EXT":
+ case "E":
+ ropts.defStatus = StageStatus.EXTERNAL;
+ break;
+ case "BOTH":
+ case "B":
+ ropts.defStatus = StageStatus.BOTH;
+ break;
+ case "DEBUGTRUE":
+ case "DEBUGT":
+ case "DT":
+ ropts.isDebug = true;
+ break;
+ case "DEBUGFALSE":
+ case "DEBUGF":
+ case "DF":
+ ropts.isDebug = false;
+ break;
+ case "DEBUG":
+ case "D":
+ ropts.isDebug = Boolean.parseBoolean(bitBody);
+ break;
+ case "TRACETRUE":
+ case "TRACET":
+ case "TT":
+ ropts.isTrace = true;
+ break;
+ case "TRACEFALSE":
+ case "TRACEF":
+ case "TF":
+ ropts.isTrace = false;
+ break;
+ case "TRACE":
+ case "T":
+ ropts.isTrace = Boolean.parseBoolean(bitBody);
+ break;
+ case "PERFTRUE":
+ case "PERFT":
+ case "PRFT":
+ ropts.isPerf = true;
+ break;
+ case "PERFFALSE":
+ case "PERFF":
+ case "PRFF":
+ ropts.isPerf = false;
+ break;
+ case "PERF":
+ case "PRF":
+ ropts.isPerf = Boolean.parseBoolean(bitBody);
+ break;
+ default:
+ {
+ String msg = String.format("Invalid global control name '%s'", bitHead);
+ ReplError err = new ReplError(lno, pno, msg, name);
+ errs.add(err);
+ }
+ break;
+ }
+
+ if (ropts.isTrace)
+ ropts.errStream.printf("\t[TRACE] Processed global control '%s':'%s'\n",
+ bitHead, bitBody);
+ }
+
+ return;
+ }
}
diff --git a/src/main/java/bjc/everge/ReplParseException.java b/src/main/java/bjc/everge/ReplParseException.java
new file mode 100644
index 0000000..0091b83
--- /dev/null
+++ b/src/main/java/bjc/everge/ReplParseException.java
@@ -0,0 +1,58 @@
+package bjc.everge;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ReplParseException extends RuntimeException {
+ public List<ReplError> errs;
+
+ public ReplParseException(String msg) {
+ this(msg, new ArrayList<>());
+ }
+
+ public ReplParseException(String msg, List<ReplError> errs) {
+ super(msg);
+
+ this.errs = errs;
+ }
+
+ @Override
+ public String toString() {
+ String errString;
+ if (errs.size() == 0) errString = "An error";
+ else errString = "Errors";
+
+ return String.format("%s occured parsing replacement pairs: %s\n%s",
+ errString, getMessage(), errs);
+ }
+
+ public String toPrintString() {
+ StringBuilder errString = new StringBuilder("[ERROR] ");
+
+ if (errs.size() == 0) {
+ errString.append("No specific errors");
+ } else if (errs.size() == 1) {
+ errString.append("An error");
+ } else {
+ errString.append(errs.size());
+ errString.append(" errors");
+ }
+
+ errString.append(" occured parsing replacement pairs:");
+ if (!getMessage().equals("")) {
+ errString.append(" ");
+ errString.append(getMessage());
+ }
+
+ if (errs.size() > 0) {
+ errString.append("\n\t");
+
+ for (ReplError err : errs) {
+ errString.append(err.toPrintString("\t"));
+ errString.append("\n\t");
+ }
+ }
+
+ return errString.toString().trim();
+ }
+}
diff --git a/src/main/java/bjc/everge/StringUtils.java b/src/main/java/bjc/everge/StringUtils.java
new file mode 100644
index 0000000..3f19b8f
--- /dev/null
+++ b/src/main/java/bjc/everge/StringUtils.java
@@ -0,0 +1,65 @@
+package bjc.everge;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility methods for strings.
+ *
+ * @author Ben Culkin.
+ */
+public class StringUtils {
+ /**
+ * Split a string on every occurance of a string not preceeded by an escape.
+ *
+ * @param escape
+ * The escape that stops splitting.
+ * @param splat
+ * The string to split on.
+ * @param inp
+ * The string to split.
+ * @return The string split as specified above.
+ */
+ public static String[] escapeSplit(String escape, String splat, String inp) {
+ if (escape == null || escape.equals("")) {
+ return inp.split(splat);
+ }
+
+ List<String> ret = new ArrayList<>();
+
+ String wrk = inp;
+ int idx = wrk.indexOf(splat);
+
+ // System.err.printf("[DEBUG] 'hard' escapeSplit: (%s) (%s) (%s) init: %d\n",
+ // escape, splat, inp, idx);
+
+ while (idx != -1) {
+ while (idx != -1 && wrk.regionMatches(idx - 1, escape, 0, escape.length())) {
+ int oidx = wrk.indexOf(splat, idx + 1);
+ // System.err.printf("[TRACE] idx: %d, oidx: %d\n", idx, oidx);
+ idx = oidx;
+ }
+
+ if (idx == -1) {
+ break;
+ }
+
+ // System.err.printf("[TRACE] sliced string into (%s) and (%s) at %d\n",
+ // wrk.substring(0, idx), wrk.substring(idx), idx);
+ String tmp = wrk.substring(0, idx);
+ ret.add(tmp);
+ if (wrk.endsWith(tmp)) {
+ wrk = "";
+ } else {
+ wrk = wrk.substring(idx + splat.length());
+ }
+
+ idx = wrk.indexOf(splat);
+ }
+
+ if (!wrk.equals("")) ret.add(wrk);
+
+ return ret.toArray(new String[0]);
+ }
+}
diff --git a/src/test/java/bjc/everge/EvergeTest.java b/src/test/java/bjc/everge/EvergeTest.java
new file mode 100644
index 0000000..109992a
--- /dev/null
+++ b/src/test/java/bjc/everge/EvergeTest.java
@@ -0,0 +1,71 @@
+package bjc.everge;
+
+import bjc.everge.TestUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+import static bjc.everge.TestUtils.*;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for Everge front-end.
+ *
+ * @author Ben Culkin.
+ */
+public class EvergeTest {
+ @Test
+ public void testConstruct() {
+ Everge evg = new Everge();
+
+ assertTrue(true);
+ }
+
+ @Test
+ public void testArgs() {
+ Everge evg = new Everge();
+
+ List<String> errs = new ArrayList<>();
+
+ boolean stat = evg.processArgs(errs, "-v");
+ if (!stat) {
+ for (String err : errs) {
+ System.err.println(err);
+ }
+
+ assertTrue(false);
+ }
+ }
+
+ @Test
+ public void testLoad() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ Everge evg = new Everge();
+ evg.outStream = new PrintStream(baos);
+
+ List<String> errs = new ArrayList<>();
+ boolean stat = evg.processArgs(errs, "-vv", "--file", "data/test/evg-test1.rp",
+ "data/test/evg-test1.inp");
+ if (!stat) {
+ System.err.println("[ERROR] Did not succesfully process args");
+ for (String err : errs) {
+ System.err.println(err);
+ }
+
+ assertTrue(false);
+ }
+
+ String outp = baos.toString().trim();
+ assertEquals("b\nb", outp);
+ }
+}
diff --git a/src/test/java/bjc/everge/StringUtilsTest.java b/src/test/java/bjc/everge/StringUtilsTest.java
new file mode 100644
index 0000000..9edc028
--- /dev/null
+++ b/src/test/java/bjc/everge/StringUtilsTest.java
@@ -0,0 +1,44 @@
+package bjc.everge;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class StringUtilsTest {
+ @Test
+ public void testNullSplit() {
+ assertSplitsTo("a", null, " ", "a");
+ assertSplitsTo("a b", null, " ", "a", "b");
+ assertSplitsTo("a b cd", null, " ", "a", "b", "cd");
+ }
+
+ @Test
+ public void testNoEscapeSplit() {
+ assertSplitsTo("a", "/", " ", "a");
+ assertSplitsTo("a b", "/", " ", "a", "b");
+ assertSplitsTo("a b/c", "/", " ", "a", "b/c");
+ }
+
+ @Test
+ public void testEscapeSplit() {
+ assertSplitsTo("a / b/c", "/", " ", "a", "/ ", "b/c");
+ }
+
+ private void assertSplitsTo(String inp, String esc, String splat, String... right) {
+ try {
+ String[] lst = StringUtils.escapeSplit(esc, splat, inp);
+
+ assertArrayEquals(right, lst);
+ } catch (Exception ex) {
+ System.err.println("EXCEPTION");
+ ex.printStackTrace();
+ System.err.println();
+
+ assertTrue(false);
+ }
+ }
+}
diff --git a/src/test/java/bjc/everge/TestUtils.java b/src/test/java/bjc/everge/TestUtils.java
new file mode 100644
index 0000000..2b9dc8d
--- /dev/null
+++ b/src/test/java/bjc/everge/TestUtils.java
@@ -0,0 +1,115 @@
+package bjc.everge;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+import static org.junit.Assert.*;
+
+/**
+ * Utility methods for testing.
+ *
+ * @author Ben Culkin
+ */
+public class TestUtils {
+ /**
+ * Assert that a ReplParseException is thrown with a given message.
+ *
+ * @param msg
+ * The message.
+ * @param fle
+ * The file to load input from.
+ */
+ public static void assertThrownMessage(String msg, String fle) {
+ assertThrownMessage(false, msg, fle);
+ }
+
+ /**
+ * Assert that a ReplParseException is thrown with a given message.
+ *
+ * @param logMsg
+ * Log the exception message.
+ * @param msg
+ * The message.
+ * @param fle
+ * The file to load input from.
+ */
+ public static void assertThrownMessage(boolean logMsg, String msg, String fle) {
+ try (FileInputStream fis = new FileInputStream(fle); Scanner scn = new Scanner(fis)) {
+ ReplPair.readList(new ArrayList<>(), scn);
+
+ assertTrue(false);
+ } catch (ReplParseException rpex) {
+ if (logMsg) System.err.println(rpex.toPrintString());
+
+ assertEquals(msg, rpex.toPrintString());
+ } catch (Exception ex) {
+ System.err.println("EXCEPTION");
+ ex.printStackTrace();
+
+ assertTrue(false);
+
+ return;
+ }
+ }
+
+ public static void assertMultiReplace(String fle, String... inps) {
+ assertMultiReplace(false, fle, inps);
+ }
+
+ public static void assertMultiReplace(boolean logRep, String fle, String... inps) {
+ if (inps.length < 2) throw new IllegalArgumentException("ERROR: Must provide at least two strings to assertMultiReplace");
+ if (inps.length % 2 != 0) throw new IllegalArgumentException("ERROR: Odd number of strings passed to assertMultiReplace");
+
+ List<ReplPair> lrp = null;
+
+ try (FileInputStream fis = new FileInputStream(fle); Scanner scn = new Scanner(fis)) {
+ lrp = ReplPair.readList(scn);
+ } catch (ReplParseException rpex) {
+ System.err.println(rpex.toPrintString());
+
+ assertTrue(false);
+ } catch (Exception ex) {
+ System.err.println("EXCEPTION");
+ ex.printStackTrace();
+
+ assertTrue(false);
+
+ return;
+ }
+
+ for (int i = 0; i < inps.length; i += 2) {
+ String right = inps[i];
+ String inp = inps[i + 1];
+
+ assertReplacesTo(logRep, right, lrp, inp);
+ }
+ }
+
+ public static void assertReplacesFrom(String right, String inp, String fle) {
+ assertMultiReplace(fle, right, inp);
+ }
+
+ public static void assertReplacesTo(String right, List<ReplPair> rps, String inp) {
+ assertReplacesTo(false, right, rps, inp);
+ }
+
+ public static void assertReplacesTo(boolean logRep, String right, List<ReplPair> rps, String inp) {
+ if (logRep) System.err.printf("\t[LOG] Checking '%s' -> '%s'\n", inp, right);
+
+ String tmp = inp;
+
+ for (ReplPair rp : rps) {
+ String oldTmp = tmp;
+
+ tmp = rp.apply(tmp);
+
+ if (logRep) System.err.printf("\t[LOG] '%s' -> '%s'\t%s\n", oldTmp, tmp, rp);
+ }
+
+ assertEquals(right, tmp);
+ }
+}