END"
+ * sections in newlyGenerated. Everything outside these sections in
+ * previouslyGenerated is returned as is. A marked section starts with //
+ * GENERATED {name} START and ends with // GENERATED {name} END
+ *
+ * If previouslyGenerated has a generated section replaced with //
+ * GENERATED {name} DISABLED, that section will no longer be updated.
*/
public String merge (String newlyGenerated, String previouslyGenerated)
throws Exception
{
// Extract the generated section names from the output and make sure they're all matched
- Map sections = Maps.newLinkedHashMap();
+ Map sections = Maps.newLinkedHashMap();
Matcher m = _sectionDelimiter.matcher(newlyGenerated);
while (m.find()) {
- Tuple section = extractGeneratedSection(m, newlyGenerated);
- Preconditions.checkArgument(!sections.containsKey(section.left),
- "Section '%s' used more than once", section.left);
- sections.put(section.left, section.right);
+ Section section = extractGeneratedSection(m, newlyGenerated);
+ Preconditions.checkArgument(!sections.containsKey(section.name),
+ "Section '%s' used more than once", section.name);
+ sections.put(section.name, section);
}
// Merge with the previously generated source
StringBuilder merged = new StringBuilder();
m = _sectionDelimiter.matcher(previouslyGenerated);
- Iterator> secs = sections.entrySet().iterator();
int currentStart = 0;
while (m.find()) {
merged.append(previouslyGenerated.substring(currentStart, m.start()));
- Tuple existingSection =
- extractGeneratedSection(m, previouslyGenerated);
- // Allow generated sections to be dropped in the template, but warn in case something
- // odd's happening
- if (!secs.hasNext()) {
+ Section existingSection = extractGeneratedSection(m, previouslyGenerated);
+ Section newSection = sections.remove(existingSection.name);
+ if (newSection == null) {
+ // Allow generated sections to be dropped in the template, but warn in case
+ // something odd's happening
System.err.println("Dropping previously generated section '" + m.group(1)
+ "' that's no longer generated by the template");
+ } else if (existingSection.disabled) {
+ // If the existing code disables this generation, add that disabled comment
+ merged.append(existingSection.contents);
+ } else {
+ // Otherwise pop in the newly generated code in the place of what was there before
+ merged.append(newSection.contents);
}
- // However, we don't allow sections to be removed or reordered in the generated code
- Entry newSection = secs.next();
- Preconditions.checkArgument(newSection.getKey().equals(existingSection.left),
- "Generated code expected '%s' section, but encountered '%s'", newSection.getKey(),
- existingSection.left);
- // Pop in the newly generated code in the place of what was there before
- merged.append(newSection.getValue());
currentStart = m.end();
}
// Add generated sections that weren't present in the old output before the last
// non-generated code. It's a 50-50 shot, so warn when this happens
- while(secs.hasNext()) {
- Entry newSection = secs.next();
+ for (Section newSection : sections.values()) {
System.err.println("Adding previously missing generated section '"
- + newSection.getKey() + "' before the last non-generated text");
- merged.append(newSection.getValue());
+ + newSection.name + "' before the last non-generated text");
+ merged.append(newSection.contents);
}
// Add any text past the last previously generated section
merged.append(previouslyGenerated.substring(currentStart));
@@ -98,20 +95,35 @@ public class GeneratedSourceMerger
* Returns a section name and its contents from the given matcher pointing to the start of a
* section. m is at the end of the section when this returns.
*/
- protected Tuple extractGeneratedSection (Matcher m, String input)
+ protected Section extractGeneratedSection (Matcher m, String input)
{
int startIdx = m.start();
- String startName = m.group(1);
+ String name = m.group(1);
+ if (m.group(2).equals("DISABLED")) {
+ return new Section(name, input.substring(startIdx, m.end()), true);
+ }
Preconditions.checkArgument(m.group(2).equals("START"), "'%s' END without START",
- startName);
- Preconditions.checkArgument(m.find(), "'%s' START without END", startName);
+ name);
+ Preconditions.checkArgument(m.find(), "'%s' START without END", name);
String endName = m.group(1);
Preconditions.checkArgument(m.group(2).equals("END"),
- "'%s' START after '%s' START", endName, startName);
- Preconditions.checkArgument(endName.equals(startName),
- "'%s' END after '%s' START", endName, startName);
- return Tuple.newTuple(startName, input.substring(startIdx, m.end()));
+ "'%s' START after '%s' START", endName, name);
+ Preconditions.checkArgument(endName.equals(name),
+ "'%s' END after '%s' START", endName, name);
+ return new Section(name, input.substring(startIdx, m.end()), false);
}
- protected final Pattern _sectionDelimiter = Pattern.compile(" *// GENERATED (\\w+) (START|END)\n");
+ protected static class Section {
+ public final String name, contents;
+ public final boolean disabled;
+
+ public Section (String name, String contents, boolean disabled) {
+ this.name = name;
+ this.contents = contents;
+ this.disabled = disabled;
+ }
+ }
+
+ protected final Pattern _sectionDelimiter =
+ Pattern.compile(" *// GENERATED (\\w+) (START|END|DISABLED)\n");
}
diff --git a/src/test/java/com/threerings/presents/tools/GeneratedSourceMergerTest.java b/src/test/java/com/threerings/presents/tools/GeneratedSourceMergerTest.java
new file mode 100644
index 000000000..459718829
--- /dev/null
+++ b/src/test/java/com/threerings/presents/tools/GeneratedSourceMergerTest.java
@@ -0,0 +1,61 @@
+package com.threerings.presents.tools;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class GeneratedSourceMergerTest
+{
+ @Test
+ public void mergeNothing ()
+ throws Exception
+ {
+ new GeneratedSourceMerger().merge("", "");
+ }
+
+ @Test
+ public void mergeUpdatedSection ()
+ throws Exception
+ {
+ String modified = before + "// GENERATED VARIABLE START\n" +
+ "var s :String = 'byte';\n" +
+ "// GENERATED VARIABLE END\n" + after;
+
+ assertEquals(generated, new GeneratedSourceMerger().merge(generated, modified));
+ }
+
+ @Test
+ public void mergeMissingSection ()
+ throws Exception
+ {
+ assertEquals(section + before, new GeneratedSourceMerger().merge(generated, before));
+ }
+
+ @Test
+ public void ignoreDisabledSection ()
+ throws Exception
+ {
+ String disabled = "// GENERATED VARIABLE DISABLED\n" + before;
+ assertEquals(disabled, new GeneratedSourceMerger().merge(generated, disabled));
+ }
+
+ @Test
+ public void dropOldSection ()
+ throws Exception
+ {
+ String previous = "// GENERATED PREVIOUS START\n" +
+ "var noLongerNeeded :String = 'hi';\n" +
+ "// GENERATED PREVIOUS END\n" +
+ generated;
+ assertEquals(generated, new GeneratedSourceMerger().merge(generated, previous));
+ }
+
+ String section = "// GENERATED VARIABLE START\n" +
+ "var s :String = 'hi';\n" +
+ "// GENERATED VARIABLE END\n";
+
+ String before = "var r :int = 7;\n";
+ String after = "var t :Array = [];\n";
+ String generated = before + section + after;
+
+}