Allow generated sections to be turned off.

If a generated section like

// GENERATED BLAH START
var s: String = "I am some awesome stuff you didn't have to write";
// GENERATED BLAH END

is replaced with

// GENERATED BLAH DISABLED

that generated section will no longer be merged in.

This currently allows the class declaration to be customized in the actionscript
stuff, if it needs to extend or implement something that can't be inferred from
the Java class.  If someone's feeling frisky, they could split the streamable
section into sections for readObject, writeObject and fields to allow for read
and write customization.

This also fixes dropping no-longer generated sections and allows for sections
to be reordered in the source.



git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6383 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Charlie Groves
2010-12-15 23:46:56 +00:00
parent 0d8e889292
commit 4d163ffd7f
2 changed files with 110 additions and 37 deletions
@@ -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 <code>previouslyGenerated</code> with marked sections updated from the same marked
* sections in <code>newlyGenerated</code> Everything outside these sections in
* <code>previouslyGenerated</code> is returned as is. A marked section starts with
* "// GENERATED <name> START" and ends with "// GENERATED <name> END"
* sections in <code>newlyGenerated</code>. Everything outside these sections in
* <code>previouslyGenerated</code> is returned as is. A marked section starts with <code>//
* GENERATED {name} START</code> and ends with <code>// GENERATED {name} END</code><p>
*
* If <code>previouslyGenerated</code> has a generated section replaced with <code>//
* GENERATED {name} DISABLED</code>, 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<String, String> sections = Maps.newLinkedHashMap();
Map<String, Section> sections = Maps.newLinkedHashMap();
Matcher m = _sectionDelimiter.matcher(newlyGenerated);
while (m.find()) {
Tuple<String, String> 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<Entry<String, String>> secs = sections.entrySet().iterator();
int currentStart = 0;
while (m.find()) {
merged.append(previouslyGenerated.substring(currentStart, m.start()));
Tuple<String, String> 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<String, String> 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<String, String> 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. <code>m</code> is at the end of the section when this returns.
*/
protected Tuple<String, String> 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");
}
@@ -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;
}