using System; using System.Collections; using System.Globalization; using System.IO; using System.Text.RegularExpressions; namespace ThoughtWorks.CruiseControl.Core.Sourcecontrol { public class MksHistoryParser : IHistoryParser { private static Regex SUBPROJECT_SEARCH_REGEX = new Regex(@"^\s*((Subproject\schanged|Dropped\ssubproject|Added\ssubproject):\s+(?.*?)(\s+at.*(\r\n|$)|\s+was.*(\r\n|$)|\s+now.*(\r\n|$)|\s+from.*(\r\n|$)|(\r\n|$)))", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Multiline); private static Regex MODIFICATION_SEARCH_REGEX = new Regex(@"^\s*(?(Member\srevision|Revision|Added|Dropped|Deleted))\s(changed|member):\s+(?.*?)\s+(at|was|now|from).*\s(?\d+(\.\d+)*)(\r\n|$)", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Multiline); private static Regex MEMBER_INFO_REGEX = new Regex(@".*?\s+Created\sBy:\s+.*\((?.*)\)\son\s(?.*)\r\n(.|\r\n)*?\s+Revision\sDescription:\r\n\s*(?(.|\r\n)*?)\s+Labels:(.|\r\n|$)*?", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); public virtual Modification[] Parse(TextReader history, DateTime from, DateTime to) { ArrayList result = new ArrayList(); string modifications = history.ReadToEnd(); MatchCollection subprojectMatches = SUBPROJECT_SEARCH_REGEX.Matches(modifications); MatchCollection revisionMatches = MODIFICATION_SEARCH_REGEX.Matches(modifications); foreach (Match revisionMatch in revisionMatches) { string SubProjectPath = string.Empty; for (int i = subprojectMatches.Count - 1; i >= 0; i--) { if (subprojectMatches[i].Index < revisionMatch.Index) { SubProjectPath = ParseSubprojectFolder(subprojectMatches[i].Groups["SubprojectName"].Value.Trim()); break; } } Modification modification = CreateModification(revisionMatch, SubProjectPath); result.Add(modification); } return (Modification[]) result.ToArray(typeof (Modification)); } public virtual void ParseMemberInfoAndAddToModification(Modification modification, StringReader reader) { MatchCollection memberInfoMatches = MEMBER_INFO_REGEX.Matches(reader.ReadToEnd()); foreach (Match match in memberInfoMatches) { modification.UserName = match.Groups["UserName"].Value.Trim(); modification.ModifiedTime = ParseDate(match.Groups["ModifiedTime"].Value.Trim()); modification.Comment = match.Groups["Comment"].Value.Trim(); } } // Dates returned from some versions of MKS seem to be in format Aug 26, 2005 - 5:32 AM, but I haven't been // able to verify this for all locales. This format is not supported by DateTime.Parse under .NET 2.0. // So we TryParseExact to see if just this format can be read. // // Later versions of MKS (like 2007) return dates in format Aug 26, 2005 05:32:23 AM CST. For this case, // trim off the time zone, because again, DateTime.Parse will get unhappy. private static DateTime ParseDate(string dateString) { DateTime date; string ParsedDateString = dateString.Remove(22).Trim(); if (DateTime.TryParseExact(ParsedDateString, "MMM d, yyyy - h:mm tt", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out date)) return date; ParsedDateString = dateString.Trim().Substring(0, dateString.Trim().Length - 4); // Trim the timezone off the end of the date/time return Convert.ToDateTime(ParsedDateString); } private static string ParseSubprojectFolder(string subprojectfile) { int lastIndexOfFrontSlash = subprojectfile.LastIndexOf("/"); if (lastIndexOfFrontSlash > 0) { return subprojectfile.Substring(0, lastIndexOfFrontSlash + 1); } // If we get here, we didn't have a valid subproject filepath. return string.Empty; } private static Modification CreateModification(Match match, string subprojectPath) { Modification modification = new Modification(); ParseFileAndFolderName(subprojectPath + match.Groups["Filename"].Value.Trim(), modification); modification.Version = match.Groups["NewRevision"].Value.Trim(); modification.Type = ParseModificationType(match.Groups["ModificationType"].Value.Trim()); return modification; } private static string ParseModificationType(string modificationType) { switch (modificationType) { case "Revision": case "Member revision": return "Modified"; case "Dropped": return "Deleted"; default: return modificationType; } } private static void ParseFileAndFolderName(string file, Modification modification) { int lastIndexOfFrontSlash = file.LastIndexOf("/"); modification.FileName = file.Substring(lastIndexOfFrontSlash + 1); if (lastIndexOfFrontSlash > 0) { // We need to trim the leading slash and project name from the project path int secondIndexOfSlash = file.Substring(1).IndexOf("/"); if (secondIndexOfSlash > 0) { modification.FolderName = file.Substring(secondIndexOfSlash + 2, lastIndexOfFrontSlash - secondIndexOfSlash - 2); } } } } }