package alma.acs.releasedoc;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import alma.acs.releasedoc.Cvs2clXmlEntry.EntryFile;
import alma.acs.util.XmlNormalizer;
/**
* Produces output in twiki format which can be pasted to the release notes ChangeLog pages,
* such as http://almasw.hq.eso.org/almasw/bin/view/ACS/ChangeLog-7_0_2.
* @author hsommer
*/
public class TWikiFormatter
{
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
private DateFormat dateFormat = SimpleDateFormat.getDateTimeInstance();
private HeadingInserter headingInserter;
List<Cvs2clXmlEntry> sortByDate(Collection<Cvs2clXmlEntry> entries) {
return sort(entries, new Comparator<Cvs2clXmlEntry>() {
public int compare(Cvs2clXmlEntry entry1, Cvs2clXmlEntry entry2) {
return entry1.getDate().compareTo(entry2.getDate());
}
});
}
List<Cvs2clXmlEntry> sortByAuthor(Collection<Cvs2clXmlEntry> entries) {
return sort(entries, new Comparator<Cvs2clXmlEntry>() {
public int compare(Cvs2clXmlEntry entry1, Cvs2clXmlEntry entry2) {
if (!entry1.getAuthor().equals(entry2.getAuthor())) {
return entry1.getAuthor().compareTo(entry2.getAuthor());
}
// fallback to sort by date
return entry1.getDate().compareTo(entry2.getDate());
}
} );
}
/**
* Returns the {@code entries} as a sorted list with the order given by {@code comp},
* without modifying the original collection.
*/
List<Cvs2clXmlEntry> sort(Collection<Cvs2clXmlEntry> entries, Comparator<Cvs2clXmlEntry> comp) {
List<Cvs2clXmlEntry> list = new ArrayList<Cvs2clXmlEntry>(entries);
Collections.sort(list, comp);
return list;
}
void printTwiki(List<Cvs2clXmlEntry> entryList) {
for (Cvs2clXmlEntry entry : entryList) {
printTwikiByCheckin(entry);
}
}
void printTwikiByCheckin(Cvs2clXmlEntry entry) {
if (headingInserter != null) {
headingInserter.processRecord(entry);
}
String output = dateFormat.format(entry.getDate());
output += " ";
output += entry.getAuthor();
output += LINE_SEPARATOR;
String wikiFileIndent = getWikiIndentBullet1();
if (entry.getCommonDir() != null) {
output += " * " + entry.getCommonDir();
output += ": " + LINE_SEPARATOR;
wikiFileIndent = " " + wikiFileIndent;
}
Iterator<EntryFile> fileIter = entry.getFiles().iterator();
while (fileIter.hasNext()) {
EntryFile file = fileIter.next();
output += wikiFileIndent;
if (file.getCvsstate().equals("dead")) {
output += "<strike>" + file.getPathName() + "</strike>";
}
else {
output += "=" + file.getPathName() + "=";
}
output += LINE_SEPARATOR;
}
// @TODO perhaps use <blockquote> around the message for indenting, instead of the silly bullet.
output += " * " + formatMessage(" ", entry.getMessage()) + LINE_SEPARATOR;
System.out.println(output);
}
void printTwikiByFile(String fileName, List<Cvs2clXmlEntry> entries) {
if (entries == null || entries.size() < 1) {
throw new IllegalArgumentException("empty entries arg");
}
Cvs2clXmlEntry firstEntry = entries.get(0);
Cvs2clXmlEntry lastEntry = entries.get(entries.size() - 1);
if (headingInserter != null) {
headingInserter.processRecord(firstEntry);
}
String output = lastEntry.getFiles().get(0).getCvsstate().equals("dead")
? "<strike>" + fileName + "</strike>"
: fileName;
output += LINE_SEPARATOR;
for (Cvs2clXmlEntry entry : entries) {
output += getWikiIndentBullet1() + dateFormat.format(entry.getDate()) + " " + entry.getAuthor() + "<br>" + LINE_SEPARATOR;
// @TODO perhaps use <blockquote> around the message for indenting, instead of the silly bullet.
output += getWikiIndentBullet1_subseqLines() + formatMessage(getWikiIndentBullet1_subseqLines(), entry.getMessage()) + LINE_SEPARATOR;
}
System.out.println(output);
}
protected String formatMessage(String indentAfterFirstLine, String message) {
String xmlMaskedMessage = XmlNormalizer.normalize(message);
StringTokenizer tok = new StringTokenizer(xmlMaskedMessage, "\n\r\f");
String ret = maskWikiWords(tok.nextToken());
while (tok.hasMoreTokens()) {
ret += " <br>" + LINE_SEPARATOR;
ret += indentAfterFirstLine;
ret += maskWikiWords(tok.nextToken());
}
return ret;
}
/**
* @TODO: try to use <code><noautolink></code> pair around our text and stop masking individual words
*/
protected String maskWikiWords(String msgLine) {
StringTokenizer wordTok = new StringTokenizer(msgLine, " (", true);
String ret = "";
while (wordTok.hasMoreTokens()) {
String word = wordTok.nextToken();
if (isWikiWord(word)) {
ret += "!";
}
ret += word;
}
return ret;
}
/**
* @param word
*/
protected boolean isWikiWord(String word) {
if (word == null || word.length() <= 2) {
return false;
}
// skip leading '(' and uppercase chars
boolean hasLeadingUppercase = false;
int pos = 0;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (Character.isUpperCase(word.charAt(i))) {
hasLeadingUppercase = true;
}
else {
break;
}
pos++;
}
if (hasLeadingUppercase && pos < word.length() && Character.isLowerCase(word.charAt(pos))) {
for (int i = 2; i < word.length(); i++) {
if (Character.isUpperCase(word.charAt(i))) {
// got a wiki word
return true;
}
}
}
// for some reason "CDB" is considered a wiki word
if (word.equals("CDB")) {
return true;
}
return false;
}
String getWikiIndentBullet1() {
return " * ";
}
String getWikiIndentBullet1_subseqLines() {
return " ";
}
abstract static class HeadingInserter {
protected Cvs2clXmlEntry lastEntry;
abstract boolean needsHeading(Cvs2clXmlEntry entry);
abstract String headingText(Cvs2clXmlEntry entry);
void processRecord(Cvs2clXmlEntry entry) {
if (needsHeading(entry)) {
System.out.println();
System.out.println("---++ " + headingText(entry));
}
lastEntry = entry;
}
}
protected void setHeadingInserter(HeadingInserter hi) {
headingInserter = hi;
}
}