diff --git a/.gitignore b/.gitignore index e4872dbc..ee76c2a6 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,6 @@ Test *.nupkg *.msi *.chm +.vs /Tools /build.txt diff --git a/Documentation/Overview.html b/Documentation/Overview.html index be3416c2..c9890bec 100644 --- a/Documentation/Overview.html +++ b/Documentation/Overview.html @@ -278,6 +278,11 @@

Current Community Tasks

Task to filter an Input list with a Regex expression. Output list contains items from Input list that matched given expression + + RegexMatchGroups + Task to filter an Input list and to match groups with a Regex expression. Output list contains + matched group values together with corresponding group names and original values from Input list that matched given expression + RegexReplace Task to replace portions of strings within the Input list Output list diff --git a/Documentation/TaskDocs.md b/Documentation/TaskDocs.md index f1107fc0..399e45e0 100644 --- a/Documentation/TaskDocs.md +++ b/Documentation/TaskDocs.md @@ -2310,6 +2310,36 @@ Matches from TestGroup those names ending in a, b or c +## RegexMatchGroups +### Description +Task to filter an Input list and to match groups with a Regex expression. + Output list contains matched group values together with corresponding group names and original values from Input list that matched given expression +### Example +Matches TestGroup items containing at lease two numeric groups connected by a dot and returns the first two groups with values + + + + + + + + + + + + + + + + + + + + + +* * * + + ## RegexReplace ### Description Task to replace portions of strings within the Input list diff --git a/Source/MSBuild.Community.Tasks.Tests/Regex/RegexTest.cs b/Source/MSBuild.Community.Tasks.Tests/Regex/RegexTest.cs index 98fdcdb8..4b7b1c2f 100644 --- a/Source/MSBuild.Community.Tasks.Tests/Regex/RegexTest.cs +++ b/Source/MSBuild.Community.Tasks.Tests/Regex/RegexTest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using NUnit.Framework; using Microsoft.Build.Framework; @@ -9,96 +10,139 @@ namespace MSBuild.Community.Tasks.Tests { - /// - /// Tests for RegexMatch and RegexReplace functions - /// - [TestFixture] - public class RegexTest - { - /// - /// Tests RegExMatch finds correct number of matches in a known sample set - /// - [Test(Description = "RegexMatch test")] - public void RegexMatchExecute() - { - RegexMatch task = new RegexMatch(); - task.BuildEngine = new MockBuild(); - - task.Input = TaskUtility.StringArrayToItemArray("foo.my.foo.foo.test.o", "foo.my.faa.foo.test.a", "foo.my.fbb.foo.test.b", "foo.my.fcc.foo.test.c", "foo.my.fdd.foo.test.d"); - task.Expression = new TaskItem("[a-c]$"); - - Assert.IsTrue(task.Execute(), "Execute Failed"); - - Assert.AreEqual(3, task.Output.Length, "Match should have matched three items"); - - } - - /// - /// Tests RegexReplace by removing the first "foo." string from a list of items - /// - [Test(Description = "RegexReplace test, removes first 'foo.' from a list of test items")] - public void RegexReplaceRemoveFirstFoo() - { - RegexReplace task = new RegexReplace(); - task.BuildEngine = new MockBuild(); - - task.Input = TaskUtility.StringArrayToItemArray("foo.my.foo.foo.test.o", "foo.my.faa.foo.test.a", "foo.my.fbb.foo.test.b", "foo.my.fcc.foo.test.c", "foo.my.fdd.foo.test.d"); - task.Expression = new TaskItem("foo\\."); - task.Count = new TaskItem("1"); - - Assert.IsTrue(task.Execute(), "Execute Failed"); - - foreach (ITaskItem item in task.Output) - { - Assert.IsTrue(!item.ItemSpec.StartsWith("foo."), string.Format("Item still starts with foo: {0}", item.ItemSpec)); - } - } - - /// - /// Tests RegexReplace by replacing the string "foo." appearing after the 6th character with the string "oop." from a list of items - /// - [Test(Description = "Tests RegexReplace by replacing the string 'foo.' appearing after the 6th character with the string 'oop.' from a list of items")] - public void RegexReplaceFooForOop() - { - RegexReplace task = new RegexReplace(); - task.BuildEngine = new MockBuild(); - string[] expectedResult = new string[] { "foo.my.oop.oop.test.o", "foo.my.faa.oop.test.a", "foo.my.fbb.oop.test.b", "foo.my.fcc.oop.test.c", "foo.my.fdd.oop.test.d" }; - - task.Input = TaskUtility.StringArrayToItemArray("foo.my.foo.foo.test.o", "foo.my.faa.foo.test.a", "foo.my.fbb.foo.test.b", "foo.my.fcc.foo.test.c", "foo.my.fdd.foo.test.d"); - task.Expression = new TaskItem("foo\\."); - task.StartAt = new TaskItem("6"); - task.Replacement = new TaskItem("oop."); - - Assert.IsTrue(task.Execute(), "Execute Failed"); - - for (int ndx = 0; ndx < task.Output.Length; ndx++) - { - Assert.AreEqual(expectedResult[ndx], task.Output[ndx].ItemSpec, "Results did not match expectations"); - } - } - - - /// - /// Tests RegexReplace by replacing the first "." with a "!" starting from the right from a list of items - /// - [Test(Description = "Tests RegexReplace by replacing the first '.' with a '!' starting from the right from a list of items")] - public void RegexReplaceLastDotForBang() - { - RegexReplace task = new RegexReplace(); - task.BuildEngine = new MockBuild(); - - task.Input = TaskUtility.StringArrayToItemArray("foo.my.foo.foo.test.o", "foo.my.faa.foo.test.a", "foo.my.fbb.foo.test.b", "foo.my.fcc.foo.test.c", "foo.my.fdd.foo.test.d"); - task.Expression = new TaskItem("\\."); - task.Replacement = new TaskItem("!"); - task.Count = new TaskItem("1"); - task.Options = TaskUtility.StringArrayToItemArray("RightToLeft"); - - Assert.IsTrue(task.Execute(), "Execute Failed"); - - foreach (ITaskItem item in task.Output) - { - Assert.AreEqual("!", item.ItemSpec.Substring(19, 1), string.Format("Item not replaced properly: {0}", item.ItemSpec)); - } - } - } -} + /// + /// Tests for RegexMatch and RegexReplace functions + /// + [TestFixture] + public class RegexTest + { + /// + /// Tests RegExMatch finds correct number of matches in a known sample set + /// + [Test(Description = "RegexMatch test")] + public void RegexMatchExecute() + { + RegexMatch task = new RegexMatch(); + task.BuildEngine = new MockBuild(); + + task.Input = TaskUtility.StringArrayToItemArray("foo.my.foo.foo.test.o", "foo.my.faa.foo.test.a", "foo.my.fbb.foo.test.b", "foo.my.fcc.foo.test.c", "foo.my.fdd.foo.test.d"); + task.Expression = new TaskItem("[a-c]$"); + + Assert.IsTrue(task.Execute(), "Execute Failed"); + + Assert.AreEqual(3, task.Output.Length, "Match should have matched three items"); + + } + + /// + /// Tests RegexReplace by removing the first "foo." string from a list of items + /// + [Test(Description = "RegexReplace test, removes first 'foo.' from a list of test items")] + public void RegexReplaceRemoveFirstFoo() + { + RegexReplace task = new RegexReplace(); + task.BuildEngine = new MockBuild(); + + task.Input = TaskUtility.StringArrayToItemArray("foo.my.foo.foo.test.o", "foo.my.faa.foo.test.a", "foo.my.fbb.foo.test.b", "foo.my.fcc.foo.test.c", "foo.my.fdd.foo.test.d"); + task.Expression = new TaskItem("foo\\."); + task.Count = new TaskItem("1"); + + Assert.IsTrue(task.Execute(), "Execute Failed"); + + foreach (ITaskItem item in task.Output) + { + Assert.IsTrue(!item.ItemSpec.StartsWith("foo."), string.Format("Item still starts with foo: {0}", item.ItemSpec)); + } + } + + /// + /// Tests RegexReplace by replacing the string "foo." appearing after the 6th character with the string "oop." from a list of items + /// + [Test(Description = "Tests RegexReplace by replacing the string 'foo.' appearing after the 6th character with the string 'oop.' from a list of items")] + public void RegexReplaceFooForOop() + { + RegexReplace task = new RegexReplace(); + task.BuildEngine = new MockBuild(); + string[] expectedResult = new string[] { "foo.my.oop.oop.test.o", "foo.my.faa.oop.test.a", "foo.my.fbb.oop.test.b", "foo.my.fcc.oop.test.c", "foo.my.fdd.oop.test.d" }; + + task.Input = TaskUtility.StringArrayToItemArray("foo.my.foo.foo.test.o", "foo.my.faa.foo.test.a", "foo.my.fbb.foo.test.b", "foo.my.fcc.foo.test.c", "foo.my.fdd.foo.test.d"); + task.Expression = new TaskItem("foo\\."); + task.StartAt = new TaskItem("6"); + task.Replacement = new TaskItem("oop."); + + Assert.IsTrue(task.Execute(), "Execute Failed"); + + for (int ndx = 0; ndx < task.Output.Length; ndx++) + { + Assert.AreEqual(expectedResult[ndx], task.Output[ndx].ItemSpec, "Results did not match expectations"); + } + } + + + /// + /// Tests RegexReplace by replacing the first "." with a "!" starting from the right from a list of items + /// + [Test(Description = "Tests RegexReplace by replacing the first '.' with a '!' starting from the right from a list of items")] + public void RegexReplaceLastDotForBang() + { + RegexReplace task = new RegexReplace(); + task.BuildEngine = new MockBuild(); + + task.Input = TaskUtility.StringArrayToItemArray("foo.my.foo.foo.test.o", "foo.my.faa.foo.test.a", "foo.my.fbb.foo.test.b", "foo.my.fcc.foo.test.c", "foo.my.fdd.foo.test.d"); + task.Expression = new TaskItem("\\."); + task.Replacement = new TaskItem("!"); + task.Count = new TaskItem("1"); + task.Options = TaskUtility.StringArrayToItemArray("RightToLeft"); + + Assert.IsTrue(task.Execute(), "Execute Failed"); + + foreach (ITaskItem item in task.Output) + { + Assert.AreEqual("!", item.ItemSpec.Substring(19, 1), string.Format("Item not replaced properly: {0}", item.ItemSpec)); + } + } + + /// + /// Tests RegexMatchGroups by extracting first and/or second numeric parts from numeric expressions concantenated by a dot + /// + [Test(Description = "Tests RegexMatchGroups by extracting first and/or second numeric parts from numeric expressions concantenated by a dot")] + public void RegexExtractValuesForDefinedGroup() + { + var task = new RegexMatchGroups + { + BuildEngine = new MockBuild(), + Expression = new TaskItem(@"(?\d+)\.(?\d+)"), + Input = TaskUtility.StringArrayToItemArray("1.2", "erster.zweiter", "1.2.3") + }; + + Assert.IsTrue(task.Execute(), "Execute Failed"); + + Assert.IsNotNull( + task.Output.FirstOrDefault(q => + q.GetMetadata("OriginalItem").Equals("1.2") && q.GetMetadata("GroupName").Equals("FirstGroup") && + q.ItemSpec.Equals("1")) + ); + + task = new RegexMatchGroups + { + BuildEngine = new MockBuild(), + Expression = new TaskItem(@"(?\d+)\.(?\d+)"), + Input = TaskUtility.StringArrayToItemArray("1","1.2.3") + }; + + Assert.IsTrue(task.Execute(), "Execute Failed"); + + Assert.IsNull( + task.Output.FirstOrDefault(q => + q.GetMetadata("OriginalItem").Equals("1") && q.GetMetadata("GroupName").Equals("FirstGroup") && + q.ItemSpec.Equals("1")) + ); + + Assert.IsNotNull( + task.Output.FirstOrDefault(q => + q.GetMetadata("OriginalItem").Equals("1.2.3") && q.GetMetadata("GroupName").Equals("FirstGroup") && + q.ItemSpec.Equals("1")) + ); + } + } +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.Targets b/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.Targets index 8d24076c..d07662d3 100644 --- a/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.Targets +++ b/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.Targets @@ -47,6 +47,7 @@ + diff --git a/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj b/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj index 80644a2a..aed03b7b 100644 --- a/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj +++ b/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj @@ -141,6 +141,7 @@ + Code diff --git a/Source/MSBuild.Community.Tasks/Regex/RegexMatchGroups.cs b/Source/MSBuild.Community.Tasks/Regex/RegexMatchGroups.cs new file mode 100644 index 00000000..e4df83eb --- /dev/null +++ b/Source/MSBuild.Community.Tasks/Regex/RegexMatchGroups.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace MSBuild.Community.Tasks +{ + /// + /// Task to filter an Input list and to match groups with a Regex expression. + /// Output list contains matched group values together with corresponding group names and original values from Input list that matched given expression + /// + /// Matches TestGroup items containing at lease two numeric groups connected by a dot and returns the first two groups with values + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// ]]> + /// + public class RegexMatchGroups : RegexBase + { + private class ResultEntry + { + public String OriginalItem { get; set; } + public String GroupName { get; set; } + public String Value { get; set; } + } + + private Regex Regex { get; set; } + /// + /// Performs the Match task + /// + /// if the task ran successfully; + /// otherwise . + public override bool Execute() + { + Regex = new Regex(Expression.ItemSpec, ExpressionOptions); + + var results = Input.SelectMany(MatchInputItem); + + Output = CreateOutputItems(results); + + return !Log.HasLoggedErrors; + } + + private IEnumerable MatchInputItem(ITaskItem inputItem) + { + var sourceItem = inputItem.ItemSpec; + + var match = Regex.Match(sourceItem); + + return match.Success ? + Regex.GetGroupNames().Select(groupName => + { + var group = match.Groups[groupName]; + + return group.Success + ? new ResultEntry() { OriginalItem = sourceItem, GroupName = groupName, Value = group.Value } + : null; + }).Where(q => q != null) : new ResultEntry[0]; + } + + private ITaskItem[] CreateOutputItems(IEnumerable results) + { + return results.Select((result) => + { + ITaskItem resultItem = new TaskItem(result.Value); + resultItem.SetMetadata("OriginalItem", result.OriginalItem); + resultItem.SetMetadata("GroupName", result.GroupName); + return resultItem; + }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/Source/MSBuild.Community.Tasks/SqlExecute.cs b/Source/MSBuild.Community.Tasks/SqlExecute.cs index 52078f26..705a22be 100644 --- a/Source/MSBuild.Community.Tasks/SqlExecute.cs +++ b/Source/MSBuild.Community.Tasks/SqlExecute.cs @@ -36,202 +36,235 @@ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. using System.Data.SqlClient; using System.IO; - - namespace MSBuild.Community.Tasks { - /// - /// Executes a SQL command. - /// - /// - /// Execute a SQL command against a database. Target attributes to set: - /// ConnectionString (required), Command (required, the SQL to execute), - /// SelectMode (NonQuery, Scalar, or ScalarXml, default is NonQuery), - /// OutputFile (required when SelectMode is Scalar or ScalarXml). - /// - /// Note: ScalarXml was created because of the 2033 byte limit on the sql return. - /// See http://aspnetresources.com/blog/executescalar_truncates_xml.aspx for details. - /// - /// - /// Example of returning a count of items in a table. Uses the default SelectMode of NonQuery. - /// - /// ]]> - /// - /// Example of returning the items of a table in an xml format. - /// - /// ]]> - /// - public class SqlExecute : Task - { - - private const string NONQUERY = "NonQuery"; - private const string SCALAR = "Scalar"; - private const string SCALARXML = "ScalarXml"; - - /// - /// When overridden in a derived class, executes the task. - /// - /// - /// true if the task successfully executed; otherwise, false. - /// - public override bool Execute() - { - SqlConnection con = null; - SqlCommand cmd = null; - _result = -1; - - try - { - con = new SqlConnection(ConnectionString); - cmd = new SqlCommand(Command, con); - cmd.CommandTimeout = CommandTimeout; - con.Open(); - - switch (SelectMode) - { - case SCALAR: - if (!IsOutputFileSpecified(SCALAR)) - { - return false; - } - object scalar = cmd.ExecuteScalar(); - Log.LogMessage("Successfully executed SQL command."); - File.WriteAllText(this.OutputFile, scalar.ToString()); - break; - case SCALARXML: - if (!IsOutputFileSpecified(SCALARXML)) - { - return false; - } - - System.Xml.XmlReader rdr = cmd.ExecuteXmlReader(); - using (TextWriter tw = new StreamWriter(OutputFile)) - { - while (rdr.Read()) - { - tw.Write(rdr.ReadOuterXml()); - } - tw.Close(); - } - break; - case NONQUERY: - _result = cmd.ExecuteNonQuery(); - Log.LogMessage("Successfully executed SQL command with result = : " + _result.ToString()); - break; - default: - Log.LogError("Unrecognized SelectMode: " + SelectMode); - return false; - } - return true; - } - catch (Exception ex) - { - Log.LogError("Error executing SQL command: {0}\n{1}", Command, ex.Message); - return false; - } - finally - { - if (con != null) - con.Close(); - } - } - - #region private decls - private string _conStr; - private string _cmd; - private string _mode; - private int _result; - private string _output; - private int _commandTimeout; - #endregion - - /// - /// The connection string - /// - [Required] - public string ConnectionString - { - get { return _conStr; } - set { _conStr = value; } - } - - /// - /// The command to execute - /// - [Required] - public string Command - { - get { return _cmd; } - set { _cmd = value; } - } - - /// - /// Command Timeout - /// - /// Defaults to 30 seconds. Set to 0 for an infinite timeout period. - [DefaultValue(30)] - public int CommandTimeout - { - get { return _commandTimeout; } - set { _commandTimeout = value; } - } - - /// - /// The SQL Selection Mode. Set to NonQuery, Scalar, or ScalarXml. Default is NonQuery. - /// - public string SelectMode - { - get - { - if (_mode == null) - { - return NONQUERY; - } - else - { - return _mode; - } - } - set { _mode = value; } - } - - /// - /// The file name to write to - /// - public string OutputFile - { - get { return _output; } - set { _output = value; } - } - - /// - /// Determines if an output file was specified. - /// - private bool IsOutputFileSpecified(string selectionMode) - { - if (this.OutputFile == null || this.OutputFile == string.Empty) - { - Log.LogError("When using SelectMode=\"{0}\" you must specify an OutputFile.", selectionMode); - return false; - } - - return true; - } - - - /// - /// Output the return count/value - /// - [Output] - public int Result - { - get { return _result; } - } - } + /// + /// Executes a SQL command. + /// + /// + /// Execute a SQL command against a database. Target attributes to set: + /// ConnectionString (required), Command (required, the SQL to execute), + /// SelectMode (NonQuery, Scalar, or ScalarXml, default is NonQuery), + /// OutputFile (required when SelectMode is Scalar or ScalarXml). + /// + /// Note: ScalarXml was created because of the 2033 byte limit on the sql return. + /// See http://aspnetresources.com/blog/executescalar_truncates_xml.aspx for details. + /// + /// + /// Example of returning a count of items in a table. Uses the default SelectMode of NonQuery. + /// + /// ]]> + /// + /// Example of returning the items of a table in an xml format. + /// + /// ]]> + /// + public class SqlExecute : Task + { + private const string NONQUERY = "NonQuery"; + private const string SCALAR = "Scalar"; + private const string SCALARXML = "ScalarXml"; + + #region Methods + + #region Public + + /// + /// When overridden in a derived class, executes the task. + /// + /// + /// true if the task successfully executed; otherwise, false. + /// + public override bool Execute() + { + SqlConnection con = null; + SqlCommand cmd = null; + _result = -1; + + try + { + con = new SqlConnection(ConnectionString); + cmd = new SqlCommand(Command, con); + cmd.CommandTimeout = CommandTimeout; + con.Open(); + + switch (SelectMode) + { + case SCALAR: + object scalar = cmd.ExecuteScalar(); + + ResultBuilder = new StringBuilder(scalar.ToString()); + WriteOutputToFileIfSpecified(); + + Log.LogMessage("Successfully executed SQL command."); + break; + + case SCALARXML: + System.Xml.XmlReader rdr = cmd.ExecuteXmlReader(); + + ResultBuilder = new StringBuilder(); + + using (TextWriter outputWriter = new StringWriter(ResultBuilder)) + { + while (rdr.Read()) + { + outputWriter.Write(rdr.ReadOuterXml()); + } + outputWriter.Close(); + + } + + WriteOutputToFileIfSpecified(); + + break; + case NONQUERY: + _result = cmd.ExecuteNonQuery(); + Log.LogMessage("Successfully executed SQL command with result = : " + _result.ToString()); + break; + default: + Log.LogError("Unrecognized SelectMode: " + SelectMode); + return false; + } + return true; + } + catch (Exception ex) + { + Log.LogError("Error executing SQL command: {0}\n{1}", Command, ex.Message); + return false; + } + finally + { + if (con != null) + con.Close(); + } + } + + #endregion + + #region Private + + private bool IsOutputFileSpecified() + { + return !string.IsNullOrWhiteSpace(OutputFile); + } + + private void WriteOutputToFileIfSpecified() + { + if (IsOutputFileSpecified()) + { + File.WriteAllText(this.OutputFile, ResultBuilder.ToString()); + } + } + + #endregion + #endregion + + #region Properties + + #region Public parameters + + /// + /// The connection string + /// + [Required] + public string ConnectionString + { + get { return _conStr; } + set { _conStr = value; } + } + + /// + /// The command to execute + /// + [Required] + public string Command + { + get { return _cmd; } + set { _cmd = value; } + } + + /// + /// Command Timeout + /// + /// Defaults to 30 seconds. Set to 0 for an infinite timeout period. + [DefaultValue(30)] + public int CommandTimeout + { + get { return _commandTimeout; } + set { _commandTimeout = value; } + } + + /// + /// The SQL Selection Mode. Set to NonQuery, Scalar, or ScalarXml. Default is NonQuery. + /// + public string SelectMode + { + get + { + if (_mode == null) + { + return NONQUERY; + } + else + { + return _mode; + } + } + set { _mode = value; } + } + + /// + /// The file name to write to + /// + public string OutputFile + { + get { return _output; } + set { _output = value; } + } + + /// + /// Output the scalar/ xmlscalar result output if no output file specified + /// + [Output] + public string ResultValue + { + get + { + return ResultBuilder.ToString(); + } + } + + /// + /// Output the return count/value + /// + [Output] + public int Result + { + get { return _result; } + } + + #endregion + + #region Private + private StringBuilder ResultBuilder { get; set; } + + #endregion + + #endregion + + #region private decls + private string _conStr; + private string _cmd; + private string _mode; + private int _result; + private string _output; + private int _commandTimeout; + #endregion + } }