From 206b4b09c872f3e9f078383c9a5c701b1549ff5c Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 16 Dec 2013 09:36:57 -0500
Subject: [PATCH] Sync bugtraq support for spec v0.3

---
 src/test/bugtraq/com/syntevo/bugtraq/BugtraqParserTest.java    |   49 ++++---
 src/main/bugtraq/com/syntevo/bugtraq/BugtraqEntry.java         |   13 +
 src/test/bugtraq/com/syntevo/bugtraq/BugtraqFormatterTest.java |   51 +++++---
 src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java        |  115 +++++++++++-------
 .gitbugtraq                                                    |   11 +
 src/main/bugtraq/com/syntevo/bugtraq/BugtraqFormatter.java     |   13 +
 src/main/bugtraq/com/syntevo/bugtraq/BugtraqParser.java        |  107 +++++++++--------
 7 files changed, 211 insertions(+), 148 deletions(-)

diff --git a/.gitbugtraq b/.gitbugtraq
index 9a2670c..cd5b228 100644
--- a/.gitbugtraq
+++ b/.gitbugtraq
@@ -1,7 +1,10 @@
 [bugtraq "issues"]
   url = http://code.google.com/p/gitblit/issues/detail?id=%BUGID%
-  logRegex = "[Ii]ssue[-#:\\s]{1}\\d+"
-  logRegex1 = "\\d+"
-[bugtraq "[pullrequests"]
+  loglinkregex = "[Ii]ssue[-#:\\s]{1}\\d+"
+  logregex = "\\d+"
+  loglinktext = issue-%BUGID%
+[bugtraq "pullrequests"]
   url = "https://github.com/gitblit/gitblit/pull/%BUGID%"
-  logRegex = "(?:pull request|pull|pr)\\s*[-#]?([0-9]+)"
+  loglinkregex = "(?:pull request|pull|pr)\\s*[-#]?[0-9]+"
+  logregex = "\\d+"
+  loglinktext = "pull request #%BUGID%"
diff --git a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java
index e1b6d5f..8ba957b 100644
--- a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java
+++ b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java
@@ -29,80 +29,104 @@
  */
 package com.syntevo.bugtraq;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.io.*;
+import java.util.*;
 
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
+import org.eclipse.jgit.errors.*;
+import org.eclipse.jgit.lib.*;
+import org.eclipse.jgit.revwalk.*;
+import org.eclipse.jgit.storage.file.*;
+import org.eclipse.jgit.treewalk.*;
+import org.eclipse.jgit.treewalk.filter.*;
+import org.jetbrains.annotations.*;
 
 public final class BugtraqConfig {
 
 	// Constants ==============================================================
 
 	private static final String DOT_GIT_BUGTRAQ = ".gitbugtraq";
+	private static final String DOT_TGITCONFIG = ".tgitconfig";
 
 	private static final String BUGTRAQ = "bugtraq";
 
 	private static final String URL = "url";
 	private static final String ENABLED = "enabled";
-	private static final String LOG_REGEX = "logRegex";
+	private static final String LOG_REGEX = "logregex";
+	private static final String LOG_FILTERREGEX = "logfilterregex";
+	private static final String LOG_LINKREGEX = "loglinkregex";
+	private static final String LOG_LINKTEXT = "loglinktext";
 
 	// Static =================================================================
 
 	@Nullable
 	public static BugtraqConfig read(@NotNull Repository repository) throws IOException, ConfigInvalidException {
-		final Config baseConfig = getBaseConfig(repository);
+		Config baseConfig = getBaseConfig(repository, DOT_GIT_BUGTRAQ);
+		if (baseConfig == null) {
+			baseConfig = getBaseConfig(repository, DOT_TGITCONFIG);
+		}
+
 		final Set<String> allNames = new HashSet<String>();
 		final Config config = repository.getConfig();
-		allNames.addAll(config.getSubsections(BUGTRAQ));
-		if (baseConfig != null) {
-			allNames.addAll(baseConfig.getSubsections(BUGTRAQ));
+		if (getString(null, URL, config, baseConfig) != null) {
+			allNames.add(null);
+		}
+		else {
+			allNames.addAll(config.getSubsections(BUGTRAQ));
+			if (baseConfig != null) {
+				allNames.addAll(baseConfig.getSubsections(BUGTRAQ));
+			}
 		}
 
 		final List<BugtraqEntry> entries = new ArrayList<BugtraqEntry>();
 		for (String name : allNames) {
 			final String url = getString(name, URL, config, baseConfig);
+			if (url == null) {
+				continue;
+			}
+
 			final String enabled = getString(name, ENABLED, config, baseConfig);
 			if (enabled != null && !"true".equals(enabled)) {
 				continue;
 			}
 
-			final String logIdRegex = getString(name, LOG_REGEX, config, baseConfig);
-			if (url == null || logIdRegex == null) {
+			String idRegex = getString(name, LOG_REGEX, config, baseConfig);
+			if (idRegex == null) {
 				return null;
 			}
 
-			final List<String> logIdRegexs = new ArrayList<String>();
-			logIdRegexs.add(logIdRegex);
-
-			for (int index = 1; index < Integer.MAX_VALUE; index++) {
-				final String logIdRegexN = getString(name, LOG_REGEX + index, config, baseConfig);
-				if (logIdRegexN == null) {
-					break;
+			String filterRegex = getString(name, LOG_FILTERREGEX, config, baseConfig);
+			final String linkRegex = getString(name, LOG_LINKREGEX, config, baseConfig);
+			if (filterRegex == null && linkRegex == null) {
+				final String[] split = idRegex.split("\n", Integer.MAX_VALUE);
+				if (split.length == 2) {
+					// Compatibility with TortoiseGit
+					filterRegex = split[0];
+					idRegex = split[1];
 				}
+				else {
+					// Backwards compatibility with specification version < 0.3
+					final List<String> logIdRegexs = new ArrayList<String>();
+					for (int index = 1; index < Integer.MAX_VALUE; index++) {
+						final String logIdRegexN = getString(name, LOG_REGEX + index, config, baseConfig);
+						if (logIdRegexN == null) {
+							break;
+						}
 
-				logIdRegexs.add(logIdRegexN);
+						logIdRegexs.add(logIdRegexN);
+					}
+
+					if (logIdRegexs.size() > 1) {
+						throw new ConfigInvalidException("More than three " + LOG_REGEX + " entries found. This is not supported anymore since bugtraq version 0.3, use " + LOG_FILTERREGEX + " and " + LOG_LINKREGEX + " instead.");
+					}
+					else if (logIdRegexs.size() == 1) {
+						filterRegex = idRegex;
+						idRegex = logIdRegexs.get(0);
+					}
+				}
 			}
 
-			entries.add(new BugtraqEntry(url, logIdRegexs));
+			final String linkText = getString(name, LOG_LINKTEXT, config, baseConfig);
+			entries.add(new BugtraqEntry(url, idRegex, linkRegex, filterRegex, linkText));
 		}
 
 		if (entries.isEmpty()) {
@@ -133,14 +157,14 @@
 	// Utils ==================================================================
 
 	@Nullable
-	private static Config getBaseConfig(Repository repository) throws IOException, ConfigInvalidException {
+	private static Config getBaseConfig(@NotNull Repository repository, @NotNull String configFileName) throws IOException, ConfigInvalidException {
 		final Config baseConfig;
 		if (repository.isBare()) {
 			// read bugtraq config directly from the repository
 			String content = null;
 			RevWalk rw = new RevWalk(repository);
 			TreeWalk tw = new TreeWalk(repository);
-			tw.setFilter(PathFilterGroup.createFromStrings(DOT_GIT_BUGTRAQ));
+			tw.setFilter(PathFilterGroup.createFromStrings(configFileName));
 			try {
 				ObjectId headId = repository.getRef(Constants.HEAD).getTarget().getObjectId();
 				RevCommit commit = rw.parseCommit(headId);
@@ -155,7 +179,8 @@
 						break;
 					}
 				}
-			} finally {
+			}
+			finally {
 				rw.dispose();
 				tw.release();
 			}
@@ -173,7 +198,7 @@
 		}
 		else {
 			// read bugtraq config from work tree
-			final File baseFile = new File(repository.getWorkTree(), DOT_GIT_BUGTRAQ);
+			final File baseFile = new File(repository.getWorkTree(), configFileName);
 			if (baseFile.isFile()) {
 				FileBasedConfig fileConfig = new FileBasedConfig(baseFile, repository.getFS());
 				fileConfig.load();
@@ -187,7 +212,7 @@
 	}
 
 	@Nullable
-	private static String getString(@NotNull String subsection, @NotNull String key, @NotNull Config config, @Nullable Config baseConfig) {
+	private static String getString(@Nullable String subsection, @NotNull String key, @NotNull Config config, @Nullable Config baseConfig) {
 		final String value = config.getString(BUGTRAQ, subsection, key);
 		if (value != null) {
 			return trimMaybeNull(value);
@@ -197,8 +222,8 @@
 			return trimMaybeNull(baseConfig.getString(BUGTRAQ, subsection, key));
 		}
 
-		return value;
-	}
+			return value;
+		}
 
 	@Nullable
 	private static String trimMaybeNull(@Nullable String string) {
diff --git a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqEntry.java b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqEntry.java
index 7ecd8bf..62e496b 100644
--- a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqEntry.java
+++ b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqEntry.java
@@ -29,8 +29,6 @@
  */
 package com.syntevo.bugtraq;
 
-import java.util.*;
-
 import org.jetbrains.annotations.*;
 
 final class BugtraqEntry {
@@ -38,13 +36,15 @@
 	// Fields =================================================================
 
 	private final String url;
+	private final String logLinkText;
 	private final BugtraqParser parser;
 
 	// Setup ==================================================================
 
-	public BugtraqEntry(@NotNull String url, @NotNull List<String> logIdRegexs) throws BugtraqException {
+	public BugtraqEntry(@NotNull String url, @NotNull String logIdRegex, @Nullable String logLinkRegex, @Nullable String logFilterRegex, @Nullable String logLinkText) throws BugtraqException {
 		this.url = url;
-		this.parser = BugtraqParser.createInstance(logIdRegexs);
+		this.logLinkText = logLinkText;
+		this.parser = BugtraqParser.createInstance(logIdRegex, logLinkRegex, logFilterRegex);
 	}
 
 	// Accessing ==============================================================
@@ -54,6 +54,11 @@
 		return url;
 	}
 
+	@Nullable
+	public String getLogLinkText() {
+		return logLinkText;
+	}
+
 	@NotNull
 	public BugtraqParser getParser() {
 		return parser;
diff --git a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqFormatter.java b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqFormatter.java
index 978cd8c..5bb2c1c 100644
--- a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqFormatter.java
+++ b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqFormatter.java
@@ -59,10 +59,6 @@
 
 		for (BugtraqEntry entry : config.getEntries()) {
 			final List<BugtraqParserIssueId> ids = entry.getParser().parse(message);
-			if (ids == null) {
-				continue;
-			}
-
 			for (BugtraqParserIssueId id : ids) {
 				allIds.add(new IssueId(entry, id));
 			}
@@ -76,8 +72,15 @@
 			}
 
 			appendText(message.substring(lastIdEnd + 1, id.getFrom()), outputHandler);
+			final String logLinkText = issueId.entry.getLogLinkText();
+			final String linkText;
+			if (logLinkText != null) {
+				linkText = logLinkText.replace("%BUGID%", id.getId());
+			}
+			else {
+				linkText = message.substring(id.getFrom(), id.getTo() + 1);
+			}
 
-			final String linkText = message.substring(id.getFrom(), id.getTo() + 1);
 			final String target = issueId.entry.getUrl().replace("%BUGID%", id.getId());
 			outputHandler.appendLink(linkText, target);
 			lastIdEnd = id.getTo();
diff --git a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqParser.java b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqParser.java
index 8619c4c..9dc38e5 100644
--- a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqParser.java
+++ b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqParser.java
@@ -39,9 +39,9 @@
 	// Static =================================================================
 
 	@NotNull
-	public static BugtraqParser createInstance(@NotNull List<String> regexs) throws BugtraqException {
+	public static BugtraqParser createInstance(@NotNull String idRegex, @Nullable String linkRegex, @Nullable String filterRegex) throws BugtraqException {
 		try {
-			return new BugtraqParser(regexs);
+			return new BugtraqParser(idRegex, linkRegex, filterRegex);
 		}
 		catch (PatternSyntaxException ex) {
 			throw new BugtraqException(ex);
@@ -50,16 +50,16 @@
 
 	// Fields =================================================================
 
-	private final List<Pattern> patterns;
+	private final Pattern idPattern;
+	private final Pattern linkPattern;
+	private final Pattern filterPattern;
 
 	// Setup ==================================================================
 
-	private BugtraqParser(List<String> regexs) {
-		this.patterns = new ArrayList<Pattern>();
-
-		for (String regex : regexs) {
-			patterns.add(compilePatternSafe(regex));
-		}
+	private BugtraqParser(@NotNull String idRegex, @Nullable String linkRegex, @Nullable String filterRegex) {
+		idPattern = compilePatternSafe(idRegex);
+		linkPattern = linkRegex != null ? compilePatternSafe(linkRegex) : null;
+		filterPattern = filterRegex != null ? compilePatternSafe(filterRegex) : null;
 	}
 
 	// Accessing ==============================================================
@@ -69,49 +69,45 @@
 		List<Part> parts = new ArrayList<Part>();
 		parts.add(new Part(message, 0, message.length() - 1));
 
-		boolean firstMatch = false;
-
-		for (Pattern pattern : patterns) {
-			final List<Part> newParts = new ArrayList<Part>();
-			for (Part part : parts) {
-				final Matcher matcher = pattern.matcher(part.text);
-				while (matcher.find()) {
-					firstMatch = true;
-					if (matcher.groupCount() == 0) {
-						addNewPart(part, matcher, 0, newParts);
-					}
-					else {
-						addNewPart(part, matcher, 1, newParts);
-					}
-				}
-			}
-
-			parts = newParts;
-			if (parts.isEmpty()) {
-				parts = null;
-				break;
-			}
+		if (filterPattern != null) {
+			parts = collectParts(parts, filterPattern);
 		}
 
-		if (!firstMatch) {
-			return null;
-		}
-
-		if (parts == null) {
-			parts = new ArrayList<Part>();
+		if (linkPattern != null) {
+			parts = collectParts(parts, linkPattern);
 		}
 
 		final List<BugtraqParserIssueId> ids = new ArrayList<BugtraqParserIssueId>();
-		for (Part part : parts) {
-			final BugtraqParserIssueId id = new BugtraqParserIssueId(part.from, part.to, part.text);
-			if (ids.size() > 0) {
-				final BugtraqParserIssueId lastId = ids.get(ids.size() - 1);
-				if (id.getFrom() <= lastId.getTo()) {
+		for (final Part part : parts) {
+			final Matcher matcher = idPattern.matcher(part.text);
+			while (matcher.find()) {
+				final Part subPart = createSubPart(part, matcher, matcher.groupCount() == 0 ? 0 : 1);
+				if (subPart == null) {
 					continue;
 				}
-			}
+				
+				final BugtraqParserIssueId id;
+				if (linkPattern == null) {
+					id = new BugtraqParserIssueId(subPart.from, subPart.to, subPart.text);
+				}
+				else {
+					if (matcher.find()) {
+						// If we are using links, the last pattern (link) must produce exactly one id.
+						continue;
+					}
+					
+					id = new BugtraqParserIssueId(part.from, part.to, subPart.text);
+				}
 
-			ids.add(id);
+				if (ids.size() > 0) {
+					final BugtraqParserIssueId lastId = ids.get(ids.size() - 1);
+					if (id.getFrom() <= lastId.getTo()) {
+						continue;
+					}
+				}
+				
+				ids.add(id);
+			}
 		}
 
 		return ids;
@@ -119,15 +115,30 @@
 
 	// Utils ==================================================================
 
-	private static void addNewPart(Part part, Matcher matcher, int group, List<Part> newParts) {
+	private static List<Part> collectParts(@NotNull List<Part> mainParts, @NotNull Pattern pattern) {
+		final List<Part> subParts = new ArrayList<Part>();
+		for (final Part part : mainParts) {
+			final Matcher matcher = pattern.matcher(part.text);
+			while (matcher.find()) {
+				final Part newPart = createSubPart(part, matcher, matcher.groupCount() == 0 ? 0 : 1);
+				if (newPart != null) {
+					subParts.add(newPart);
+				}
+			}
+		}
+
+		return subParts;
+	}
+
+	@Nullable
+	private static Part createSubPart(Part part, Matcher matcher, int group) {
 		final int textStart = matcher.start(group) + part.from;
 		final int textEnd = matcher.end(group) - 1 + part.from;
 		if (textEnd < 0) {
-			return;
+			return null;
 		}
 
-		final Part newPart = new Part(matcher.group(group), textStart, textEnd);
-		newParts.add(newPart);
+		return new Part(matcher.group(group), textStart, textEnd);
 	}
 
 	private static Pattern compilePatternSafe(String pattern) throws PatternSyntaxException {
diff --git a/src/test/bugtraq/com/syntevo/bugtraq/BugtraqFormatterTest.java b/src/test/bugtraq/com/syntevo/bugtraq/BugtraqFormatterTest.java
index 54f0e42..6bcf987 100644
--- a/src/test/bugtraq/com/syntevo/bugtraq/BugtraqFormatterTest.java
+++ b/src/test/bugtraq/com/syntevo/bugtraq/BugtraqFormatterTest.java
@@ -29,34 +29,42 @@
  */
 package com.syntevo.bugtraq;
 
-import junit.framework.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
-import java.util.*;
+import junit.framework.TestCase;
 
-import org.jetbrains.annotations.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 public class BugtraqFormatterTest extends TestCase {
 
 	// Accessing ==============================================================
 
-	public void testSimple() throws BugtraqException {
-		final BugtraqFormatter formatter = createFormatter(createEntry("https://jira.atlassian.com/browse/%BUGID%", "JRA-\\d+"));
+	public void testSimpleWithExtendedLink() throws BugtraqException {
+		final BugtraqFormatter formatter = createFormatter(createEntry("https://jira.atlassian.com/browse/JRA-%BUGID%", null, "JRA-\\d+", "\\d+", null));
 		doTest(formatter, "JRA-7399: Email subject formatting", l("JRA-7399", "https://jira.atlassian.com/browse/JRA-7399"), t(": Email subject formatting"));
 		doTest(formatter, " JRA-7399, JRA-7398: Email subject formatting", t(" "), l("JRA-7399", "https://jira.atlassian.com/browse/JRA-7399"), t(", "), l("JRA-7398", "https://jira.atlassian.com/browse/JRA-7398"), t(": Email subject formatting"));
 		doTest(formatter, "Fixed JRA-7399", t("Fixed "), l("JRA-7399", "https://jira.atlassian.com/browse/JRA-7399"));
 	}
 
+	public void testLinkText() throws BugtraqException {
+		final BugtraqFormatter formatter = createFormatter(createEntry("https://jira.atlassian.com/browse/JRA-%BUGID%", null, "JRA-\\d+", "\\d+", "JIRA-%BUGID%"));
+		doTest(formatter, " JRA-7399, JRA is text, JRA-7398: Email subject formatting", t(" "), l("JIRA-7399", "https://jira.atlassian.com/browse/JRA-7399"), t(", JRA is text, "), l("JIRA-7398", "https://jira.atlassian.com/browse/JRA-7398"), t(": Email subject formatting"));
+	}
+
 	public void testTwoNonIntersectingConfigurations() throws BugtraqException {
-		final BugtraqFormatter formatter = createFormatter(createEntry("https://jira.atlassian.com/browse/%BUGID%", "JRA-\\d+"),
-		                                                   createEntry("https://issues.apache.org/jira/browse/%BUGID%", "VELOCITY-\\d+"));
+		final BugtraqFormatter formatter = createFormatter(createEntry("https://jira.atlassian.com/browse/%BUGID%", null, null, "JRA-\\d+", null),
+		                                                   createEntry("https://issues.apache.org/jira/browse/%BUGID%", null, null, "VELOCITY-\\d+", null));
 		doTest(formatter, "JRA-7399, VELOCITY-847: fix", l("JRA-7399", "https://jira.atlassian.com/browse/JRA-7399"), t(", "), l("VELOCITY-847", "https://issues.apache.org/jira/browse/VELOCITY-847"), t(": fix"));
 		doTest(formatter, " JRA-7399: fix/VELOCITY-847", t(" "), l("JRA-7399", "https://jira.atlassian.com/browse/JRA-7399"), t(": fix/"), l("VELOCITY-847", "https://issues.apache.org/jira/browse/VELOCITY-847"));
 		doTest(formatter, "JRA-7399VELOCITY-847", l("JRA-7399", "https://jira.atlassian.com/browse/JRA-7399"), l("VELOCITY-847", "https://issues.apache.org/jira/browse/VELOCITY-847"));
 	}
 
 	public void testTwoIntersectingConfigurations() throws BugtraqException {
-		final BugtraqFormatter formatter = createFormatter(createEntry("https://host1/%BUGID%", "A[AB]"),
-		                                                   createEntry("https://host2/%BUGID%", "BA[A]?"));
+		final BugtraqFormatter formatter = createFormatter(createEntry("https://host1/%BUGID%", null, null, "A[AB]", null),
+		                                                   createEntry("https://host2/%BUGID%", null, null, "BA[A]?", null));
 		doTest(formatter, "AA: fix", l("AA", "https://host1/AA"), t(": fix"));
 		doTest(formatter, "AB: fix", l("AB", "https://host1/AB"), t(": fix"));
 		doTest(formatter, "BA: fix", l("BA", "https://host2/BA"), t(": fix"));
@@ -76,33 +84,36 @@
 	private BugtraqFormatter createFormatter(BugtraqEntry ... entries) {
 		return new BugtraqFormatter(new BugtraqConfig(Arrays.asList(entries)));
 	}
-	
-	private BugtraqEntry createEntry(String url, String ... logRegexs) throws BugtraqException {
-		return new BugtraqEntry(url, Arrays.asList(logRegexs));
+
+	private BugtraqEntry createEntry(String url, @Nullable String filterRegex, @Nullable String linkRegex, @NotNull String idRegex, @Nullable String linkText) throws BugtraqException {
+		return new BugtraqEntry(url, idRegex, linkRegex, filterRegex, linkText);
 	}
-	
+
 	private Text t(String text) {
 		return new Text(text);
 	}
-	
+
 	private Link l(String text, String url) {
 		return new Link(text, url);
 	}
-	
+
 	private void doTest(BugtraqFormatter formatter, String message, Atom ... expectedAtoms) {
 		final List<Atom> actualAtoms = new ArrayList<Atom>();
+		final StringBuilder sb = new StringBuilder();
 		formatter.formatLogMessage(message, new BugtraqFormatter.OutputHandler() {
 			@Override
 			public void appendText(@NotNull String text) {
 				actualAtoms.add(t(text));
+				sb.append(text);
 			}
 
 			@Override
 			public void appendLink(@NotNull String name, @NotNull String target) {
 				actualAtoms.add(l(name, target));
+				sb.append(name);
 			}
 		});
-		
+
 		assertEquals(Arrays.asList(expectedAtoms), actualAtoms);
 	}
 
@@ -110,7 +121,7 @@
 
 	private static interface Atom {
 	}
-	
+
 	private static class Text implements Atom {
 		private final String text;
 
@@ -133,11 +144,11 @@
 			if (obj == null || obj.getClass() != getClass()) {
 				return false;
 			}
-			
+
 			return text.equals(((Text)obj).text);
 		}
 	}
-	
+
 	private static class Link implements Atom {
 		private final String text;
 		private final String url;
@@ -162,7 +173,7 @@
 			if (obj == null || obj.getClass() != getClass()) {
 				return false;
 			}
-			
+
 			return text.equals(((Link)obj).text)
 					&& url.equals(((Link)obj).url);
 		}
diff --git a/src/test/bugtraq/com/syntevo/bugtraq/BugtraqParserTest.java b/src/test/bugtraq/com/syntevo/bugtraq/BugtraqParserTest.java
index 424fd1c..14b2a72 100644
--- a/src/test/bugtraq/com/syntevo/bugtraq/BugtraqParserTest.java
+++ b/src/test/bugtraq/com/syntevo/bugtraq/BugtraqParserTest.java
@@ -33,79 +33,84 @@
 
 import java.util.*;
 
+import org.jetbrains.annotations.*;
+
 public class BugtraqParserTest extends TestCase {
 
 	// Accessing ==============================================================
 
 	public void testSimple1() throws BugtraqException {
-		final BugtraqParser parser = createParser("\\d");
-		assertNull(parser.parse(""));
+		final BugtraqParser parser = createParser(null, null, "\\d+");
+		doTest("", parser);
 		doTest("1", parser, id(0, 0, "1"));
 		doTest("1 2 3", parser, id(0, 0, "1"), id(2, 2, "2"), id(4, 4, "3"));
 	}
 
 	public void testSimple2() throws BugtraqException {
-		final BugtraqParser parser = createParser("(\\d)");
-		assertNull(parser.parse(""));
+		final BugtraqParser parser = createParser(null, null, "(\\d+)");
 		doTest("1", parser, id(0, 0, "1"));
 		doTest("1 2 3", parser, id(0, 0, "1"), id(2, 2, "2"), id(4, 4, "3"));
 	}
 
 	public void testSimple3() throws BugtraqException {
-		final BugtraqParser parser = createParser("(SG-\\d)");
-		assertNull(parser.parse(""));
+		final BugtraqParser parser = createParser(null, null, "(SG-\\d+)");
 		doTest("SG-1", parser, id(0, 3, "SG-1"));
 		doTest("SG-1 SG-2 SG-3", parser, id(0, 3, "SG-1"), id(5, 8, "SG-2"), id(10, 13, "SG-3"));
 	}
 
 	public void testSimple4() throws BugtraqException {
-		final BugtraqParser parser = createParser("SG-(\\d)");
-		assertNull(parser.parse(""));
+		final BugtraqParser parser = createParser(null, null, "SG-(\\d+)");
 		doTest("SG-1", parser, id(3, 3, "1"));
 		doTest("SG-1 SG-2 SG-3", parser, id(3, 3, "1"), id(8, 8, "2"), id(13, 13, "3"));
 	}
 
-	public void testTwoLevel1() throws BugtraqException {
-		final BugtraqParser parser = createParser("(SG-\\d)", "\\d");
+	public void testFilter1() throws BugtraqException {
+		final BugtraqParser parser = createParser("(SG-\\d+)", null, "\\d+");
 		doTest("SG-1", parser, id(3, 3, "1"));
 		doTest("SG-1 SG-2 SG-3", parser, id(3, 3, "1"), id(8, 8, "2"), id(13, 13, "3"));
 	}
 
-	public void testTwoLevel2() throws BugtraqException {
-		final BugtraqParser parser = createParser("xSG-\\dx", "\\d");
+	public void testFilter2() throws BugtraqException {
+		final BugtraqParser parser = createParser("xSG-\\d+x", null, "\\d+");
 		doTest("SG-1 xSG-2x SG-3", parser, id(9, 9, "2"));
 	}
 
-	public void testTwoLevel3() throws BugtraqException {
-		final BugtraqParser parser = createParser("[Ii]ssues?:?((\\s*(,|and)?\\s*#\\d+)+)", "\\d");
+	public void testFilter3() throws BugtraqException {
+		final BugtraqParser parser = createParser("[Ii]ssues?:?((\\s*(,|and)?\\s*#\\d+)+)", null, "\\d+");
 		doTest("Issues #3, #4 and #5: Git Bugtraq Configuration options (see #12)", parser, id(8, 8, "3"), id(12, 12, "4"), id(19, 19, "5"));
 	}
 
-	public void testThreeLevel() throws BugtraqException {
+	public void testLink() throws BugtraqException {
+		final BugtraqParser parser = createParser(null, "(SG-\\d+)", "\\d+");
+		doTest("SG-1", parser, id(0, 3, "1"));
+		doTest("SG-1 SG-2 SG-3", parser, id(0, 3, "1"), id(5, 8, "2"), id(10, 13, "3"));
+	}
+
+	public void testLinkAndFilter() throws BugtraqException {
 		final BugtraqParser parser = createParser("[ab]\\d[cd]", "a\\dc|b\\dd", "\\d");
-		doTest("a1c a2d b3c b4d", parser, id(1, 1, "1"), id(13, 13, "4"));
+		doTest("a1c a2d b3c b4d", parser, id(0, 2, "1"), id(12, 14, "4"));
 	}
 
 	public void testFogBugz() throws BugtraqException {
-		final BugtraqParser parser = createParser("(?:Bug[zs]?\\s*IDs?\\s*|Cases?)[#:; ]+((\\d+[ ,:;#]*)+)", "\\d");
-		doTest("Bug IDs: 3, 4, 5", parser, id(9, 9, "3"), id(12, 12, "4"), id(15, 15, "5"));
+		final BugtraqParser parser = createParser("(?:Bug[zs]?\\s*IDs?\\s*|Cases?)[#:; ]+((\\d+[ ,:;#]*)+)", "[#]?\\d+", "\\d+");
+		doTest("Bug IDs: 3, #4, 5", parser, id(9, 9, "3"), id(12, 13, "4"), id(16, 16, "5"));
 	}
 
 	public void testFogBugzInvalid() throws BugtraqException {
-		final BugtraqParser parser = createParser("Bug[zs]?\\s*IDs?\\s*|Cases?[#:; ]+((\\d+[ ,:;#]*)+)", "\\d");
+		final BugtraqParser parser = createParser("Bug[zs]?\\s*IDs?\\s*|Cases?[#:; ]+((\\d+[ ,:;#]*)+)", null, "\\d+");
 		doTest("Bug IDs: 3, 4, 5", parser);
 	}
 
 	// Utils ==================================================================
 
-	private BugtraqParser createParser(String ... regexs) throws BugtraqException {
-		return BugtraqParser.createInstance(Arrays.asList(regexs));
+	private BugtraqParser createParser(@Nullable String filterRegex, @Nullable String linkRegex, @NotNull String idRegex) throws BugtraqException {
+		return BugtraqParser.createInstance(idRegex, linkRegex, filterRegex);
 	}
 	
 	private BugtraqParserIssueId id(int from, int to, String id) {
 		return new BugtraqParserIssueId(from, to, id);
 	} 
-	
+
 	private void doTest(String message, BugtraqParser parser, BugtraqParserIssueId... expectedIds) {
 		final List<BugtraqParserIssueId> actualIds = parser.parse(message);
 		assertEquals(expectedIds.length, actualIds.size());

--
Gitblit v1.9.1