diff --git a/src/main/java/com/threerings/presents/tools/GeneratedSourceMerger.java b/src/main/java/com/threerings/presents/tools/GeneratedSourceMerger.java index 42e5aaf1d..f57c195c3 100644 --- a/src/main/java/com/threerings/presents/tools/GeneratedSourceMerger.java +++ b/src/main/java/com/threerings/presents/tools/GeneratedSourceMerger.java @@ -21,15 +21,12 @@ package com.threerings.presents.tools; -import java.util.Iterator; import java.util.Map; -import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; -import com.samskivert.util.Tuple; /** * Merges updates to a generated source file into a previously generated version of that source @@ -39,54 +36,54 @@ public class GeneratedSourceMerger { /** * Returns previouslyGenerated with marked sections updated from the same marked - * sections in newlyGenerated Everything outside these sections in - * previouslyGenerated is returned as is. A marked section starts with - * "// GENERATED START" and ends with "// GENERATED 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; + +}