package hudson.plugins.trac; import hudson.Extension; import hudson.MarkupText; import hudson.MarkupText.SubText; import hudson.model.AbstractBuild; import hudson.scm.ChangeLogAnnotator; import hudson.scm.ChangeLogSet.Entry; import java.util.regex.Pattern; /** * Annotates <a href="http://trac.edgewall.org/wiki/TracLinks">TracLink</a> * and <a href="http://trac.edgewall.org/wiki/InterTrac">InterTrac</a> * notation in changelog messages. * * @author Kohsuke Kawaguchi * @author Rick Riemer */ @Extension public class TracLinkAnnotator extends ChangeLogAnnotator { @Override public void annotate(AbstractBuild<?,?> build, Entry change, MarkupText text) { TracProjectProperty tpp = build.getProject().getProperty(TracProjectProperty.class); if(tpp==null || tpp.tracWebsite==null) return; // not configured annotate(tpp.tracWebsite, text); } void annotate(String url, MarkupText text) { for (LinkMarkup markup : MARKUPS) { markup.process(text, url); } } private static final class LinkMarkup { private final Pattern pattern; private final String href; LinkMarkup(String pattern, String href) { pattern = NUM_PATTERN.matcher(pattern).replaceAll("(\\\\d+)"); // \\\\d becomes \\d when in the expanded text. pattern = ANYWORD_PATTERN.matcher(pattern).replaceAll("([\\\\w.-]+)"); this.pattern = Pattern.compile(pattern); this.href = href; } void process(MarkupText text, String url) { for(SubText st : text.findTokens(pattern)) { st.surroundWith( "<a href='"+url+href+"'>", "</a>"); } } private static final Pattern NUM_PATTERN = Pattern.compile("NUM"); private static final Pattern ANYWORD_PATTERN = Pattern.compile("ANYWORD"); } private static final LinkMarkup[] MARKUPS = new LinkMarkup[] { new LinkMarkup( "(?<!\\:)(?:#|ticket:)NUM", // "#123" or "ticket:123" but not ":#123" or ":ticket:123" "ticket/$1"), new LinkMarkup( "comment:ticket:NUM:NUM", "ticket/$1#comment:$2"), new LinkMarkup( "\\{NUM\\}|report:NUM", "report/$1$2"), // only $1 or $2 matches, and the other will expand to "" new LinkMarkup( "rNUM:NUM|\\[NUM:NUM\\]|(?<!\\:)log:@NUM:NUM", "log/?rev=$2$4$6&stop_ver=$1$3$5"), new LinkMarkup( "rNUM(?!:)|\\[NUM\\]|(?<!\\:)changeset:NUM", // (?!:) is a position match with negative look ahead, so that "r5" portion of "r5:6" won't match. "changeset/$1$2$3"), // TODO: log:trunk@1:3 format // TODO: diffs new LinkMarkup( "(?<!\\:)(?:((?:[A-Z][a-z]+){2,})|wiki:ANYWORD)", "wiki/$1$2"), new LinkMarkup( "milestone:ANYWORD", "milestone/$1"), // TODO: attachment and file. new LinkMarkup( // InterTrac ticket links (short: #T123) "#([a-zA-Z])NUM", "search?q=%23$1$2"), new LinkMarkup( // InterTrac changeset links (short: [T123]) "\\[([a-zA-Z])NUM\\]", "search?q=%5B$1$2%5D"), new LinkMarkup( // InterTrac ticket links (medium: trac:#123) "ANYWORD\\:#NUM", "search?q=$1%3A%23$2"), new LinkMarkup( // InterTrac ticket or changeset links (full: trac:ticket:123) "ANYWORD\\:((?:ticket)|(?:changeset))\\:NUM", "search?q=$1%3A$2%3A$3"), new LinkMarkup( // InterTrac wiki links (full: trac:wiki:PageName) "ANYWORD\\:wiki\\:ANYWORD", "search?q=$1%3Awiki%3A$2") }; }