diff --git a/.travis.yml b/.travis.yml index eafa63b00d..9e022cae0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,8 @@ install: - mono .nuget/nuget.exe restore src/GitVersion.sln -Verbosity detailed - mono .nuget/nuget.exe install NUnit.Runners -Version 3.2.1 -OutputDirectory ./src/packages script: - - xbuild "./src/GitVersion.sln" /property:Configuration="Debug" /verbosity:detailed -after_script: - - mono --debug --runtime=v4.0.30319 ./src/packages/NUnit.ConsoleRunner.3.2.1/tools/nunit3-console.exe ./src/GitVersionTask.Tests/bin/Debug/GitVersionTask.Tests.dll ./src/GitVersionCore.Tests/bin/Debug/GitVersionCore.Tests.dll ./src/GitVersionTask.Tests/bin/Debug/GitVersionTask.Tests.dll ./src/GitVersionExe.Tests/bin/Debug/GitVersionExe.Tests.dll -where "cat != NoMono" + - xbuild ./src/GitVersion.sln /property:Configuration="Debug" /verbosity:detailed + - mono ./src/packages/NUnit.ConsoleRunner.3.2.1/tools/nunit3-console.exe ./src/GitVersionTask.Tests/bin/Debug/GitVersionTask.Tests.dll ./src/GitVersionCore.Tests/bin/Debug/GitVersionCore.Tests.dll ./src/GitVersionTask.Tests/bin/Debug/GitVersionTask.Tests.dll ./src/GitVersionExe.Tests/bin/Debug/GitVersionExe.Tests.dll --where "cat != NoMono" --noresult + + # To run a clean build with Mono, executing just one test, do: + # xbuild ./src/GitVersion.sln /t:Clean /verbosity:quiet && xbuild ./src/GitVersion.sln /property:Configuration="Debug" /verbosity:quiet && mono ./src/packages/NUnit.ConsoleRunner.3.2.1/tools/nunit3-console.exe ./src/GitVersionTask.Tests/bin/Debug/GitVersionTask.Tests.dll ./src/GitVersionCore.Tests/bin/Debug/GitVersionCore.Tests.dll ./src/GitVersionTask.Tests/bin/Debug/GitVersionTask.Tests.dll ./src/GitVersionExe.Tests/bin/Debug/GitVersionExe.Tests.dll --noresult --where "test =~ /TheNameOfTheTest/" \ No newline at end of file diff --git a/src/GitVersionCore/VersionAssemblyInfoResources/AssemblyVersionInfoTemplates.cs b/src/GitVersionCore/VersionAssemblyInfoResources/AssemblyVersionInfoTemplates.cs index 3e99397e35..00e66ddfd8 100644 --- a/src/GitVersionCore/VersionAssemblyInfoResources/AssemblyVersionInfoTemplates.cs +++ b/src/GitVersionCore/VersionAssemblyInfoResources/AssemblyVersionInfoTemplates.cs @@ -1,5 +1,6 @@ namespace GitVersion.VersionAssemblyInfoResources { + using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,18 +8,11 @@ public class AssemblyVersionInfoTemplates { - static IDictionary assemblyInfoSourceList; + static readonly IDictionary assemblyInfoSourceList; static AssemblyVersionInfoTemplates() { - var enclosingNamespace = typeof(AssemblyVersionInfoTemplates).Namespace; - - var files = typeof(AssemblyVersionInfoTemplates) - .Assembly - .GetManifestResourceNames() - .Where(n => n.StartsWith(enclosingNamespace ?? string.Empty)).Select(f => new FileInfo(f)); - - assemblyInfoSourceList = files.ToDictionary(k => k.Extension, v => v); + assemblyInfoSourceList = GetEmbeddedVersionAssemblyFiles().ToDictionary(k => k.Extension, v => v); } public static string GetAssemblyInfoTemplateFor(string assemblyInfoFile) @@ -34,5 +28,19 @@ public static string GetAssemblyInfoTemplateFor(string assemblyInfoFile) } return null; } + + private static IEnumerable GetEmbeddedVersionAssemblyFiles() + { + var enclosingNamespace = typeof(AssemblyVersionInfoTemplates).Namespace; + + if (enclosingNamespace == null) + throw new InvalidOperationException("The AssemblyVersionInfoTemplates class is missing its namespace."); + + foreach (var name in typeof(AssemblyVersionInfoTemplates).Assembly.GetManifestResourceNames()) + { + if (name.StartsWith(enclosingNamespace)) + yield return new FileInfo(name); + } + } } } diff --git a/src/GitVersionExe.Tests/ArgumentParserTests.cs b/src/GitVersionExe.Tests/ArgumentParserTests.cs index eb84d64760..bc0d23eada 100644 --- a/src/GitVersionExe.Tests/ArgumentParserTests.cs +++ b/src/GitVersionExe.Tests/ArgumentParserTests.cs @@ -165,11 +165,12 @@ public void Wrong_number_of_arguments_should_throw() exception.Message.ShouldBe("Could not parse command line parameter 'extraArg'."); } - [Test] - public void Unknown_argument_should_throw() + [TestCase("targetDirectoryPath -x logFilePath")] + [TestCase("/invalid-argument")] + public void Unknown_arguments_should_throw(string arguments) { - var exception = Assert.Throws(() => ArgumentParser.ParseArguments("targetDirectoryPath -x logFilePath")); - exception.Message.ShouldBe("Could not parse command line parameter '-x'."); + var exception = Assert.Throws(() => ArgumentParser.ParseArguments(arguments)); + exception.Message.ShouldStartWith("Could not parse command line parameter"); } [TestCase("-updateAssemblyInfo true")] @@ -266,23 +267,23 @@ public void can_log_to_console() public void nofetch_true_when_defined() { var arguments = ArgumentParser.ParseArguments("-nofetch"); - arguments.NoFetch = true; + arguments.NoFetch.ShouldBe(true); } [Test] public void other_arguments_can_be_parsed_before_nofetch() { var arguments = ArgumentParser.ParseArguments("targetpath -nofetch "); - arguments.TargetPath = "targetpath"; - arguments.NoFetch = true; + arguments.TargetPath.ShouldBe("targetpath"); + arguments.NoFetch.ShouldBe(true); } [Test] public void other_arguments_can_be_parsed_after_nofetch() { var arguments = ArgumentParser.ParseArguments("-nofetch -proj foo.sln"); - arguments.NoFetch = true; - arguments.Proj = "foo.sln"; + arguments.NoFetch.ShouldBe(true); + arguments.Proj.ShouldBe("foo.sln"); } [Test] diff --git a/src/GitVersionExe/ArgumentParser.cs b/src/GitVersionExe/ArgumentParser.cs index ffc544e221..2f00922a4a 100644 --- a/src/GitVersionExe/ArgumentParser.cs +++ b/src/GitVersionExe/ArgumentParser.cs @@ -6,18 +6,24 @@ namespace GitVersion using System.ComponentModel; using System.IO; using System.Linq; - using System.Text.RegularExpressions; public class ArgumentParser { public static Arguments ParseArguments(string commandLineArguments) { - return ParseArguments(commandLineArguments.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList()); + var arguments = commandLineArguments + .Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) + .ToList(); + + return ParseArguments(arguments); } static void EnsureArgumentValueCount(string[] values, int maxArguments = 1) { - if(values != null && values.Length > maxArguments) throw new WarningException(string.Format("Could not parse command line parameter '{0}'.", values[1])); + if (values != null && values.Length > maxArguments) + { + throw new WarningException(string.Format("Could not parse command line parameter '{0}'.", values[1])); + } } public static Arguments ParseArguments(List commandLineArguments) @@ -31,14 +37,16 @@ public static Arguments ParseArguments(List commandLineArguments) } var firstArgument = commandLineArguments.First(); - if (IsHelp(firstArgument)) + + if (firstArgument.IsHelp()) { return new Arguments { IsHelp = true }; } - if (IsInit(firstArgument)) + + if (firstArgument.IsInit()) { return new Arguments { @@ -47,145 +55,120 @@ public static Arguments ParseArguments(List commandLineArguments) }; } - if (commandLineArguments.Count == 1 && !(commandLineArguments[0].StartsWith("-") || - (commandLineArguments[0].StartsWith("/") && Path.DirectorySeparatorChar != '/'))) - { - return new Arguments - { - TargetPath = firstArgument - }; - } - - List namedArguments; var arguments = new Arguments(); - if (firstArgument.StartsWith("-") || (firstArgument.StartsWith("/") && Path.DirectorySeparatorChar != '/')) - { - arguments.TargetPath = Environment.CurrentDirectory; - namedArguments = commandLineArguments; - } - else - { - arguments.TargetPath = firstArgument; - namedArguments = commandLineArguments.Skip(1).ToList(); - } + bool firstArgumentIsSwitch; + var switchesAndValues = CollectSwitchesAndValuesFromArguments(commandLineArguments, out firstArgumentIsSwitch); - var args = CollectSwitchesAndValuesFromArguments(namedArguments); - - foreach (var name in args.AllKeys) + for (var i = 0; i < switchesAndValues.AllKeys.Length; i++) { - var values = args.GetValues(name); - - string value = null; + var name = switchesAndValues.AllKeys[i]; + var values = switchesAndValues.GetValues(name); + var value = values != null ? values.FirstOrDefault() : null; - if (values != null) - { - //Currently, no arguments use more than one value, so having multiple values is an input error. - //In the future, this exception can be removed to support multiple values for a switch. - // if (values.Length > 1) throw new WarningException(string.Format("Could not parse command line parameter '{0}'.", values[1])); - - value = values.FirstOrDefault(); - } - - if (IsSwitch("l", name)) + if (name.IsSwitch("l")) { EnsureArgumentValueCount(values); arguments.LogFilePath = value; continue; } - if (IsSwitch("targetpath", name)) + if (name.IsSwitch("targetpath")) { EnsureArgumentValueCount(values); arguments.TargetPath = value; continue; } - if (IsSwitch("dynamicRepoLocation", name)) + if (name.IsSwitch("dynamicRepoLocation")) { EnsureArgumentValueCount(values); arguments.DynamicRepositoryLocation = value; continue; } - if (IsSwitch("url", name)) + if (name.IsSwitch("url")) { EnsureArgumentValueCount(values); arguments.TargetUrl = value; continue; } - if (IsSwitch("b", name)) + if (name.IsSwitch("b")) { EnsureArgumentValueCount(values); arguments.TargetBranch = value; continue; } - if (IsSwitch("u", name)) + if (name.IsSwitch("u")) { EnsureArgumentValueCount(values); arguments.Authentication.Username = value; continue; } - if (IsSwitch("p", name)) + if (name.IsSwitch("p")) { EnsureArgumentValueCount(values); arguments.Authentication.Password = value; continue; } - if (IsSwitch("c", name)) + if (name.IsSwitch("c")) { EnsureArgumentValueCount(values); arguments.CommitId = value; continue; } - if (IsSwitch("exec", name)) + if (name.IsSwitch("exec")) { EnsureArgumentValueCount(values); arguments.Exec = value; continue; } - if (IsSwitch("execargs", name)) + if (name.IsSwitch("execargs")) { EnsureArgumentValueCount(values); arguments.ExecArgs = value; continue; } - if (IsSwitch("proj", name)) + if (name.IsSwitch("proj")) { EnsureArgumentValueCount(values); arguments.Proj = value; continue; } - if (IsSwitch("projargs", name)) + if (name.IsSwitch("projargs")) { EnsureArgumentValueCount(values); arguments.ProjArgs = value; continue; } - if (IsSwitch("updateAssemblyInfo", name)) + + if (name.IsSwitch("updateAssemblyInfo")) { - if (new[] { "1", "true" }.Contains(value, StringComparer.OrdinalIgnoreCase)) + if (value.IsTrue()) { arguments.UpdateAssemblyInfo = true; } - else if (new[] { "0", "false" }.Contains(value, StringComparer.OrdinalIgnoreCase)) + else if (value.IsFalse()) { arguments.UpdateAssemblyInfo = false; } else if (values != null && values.Length > 1) { arguments.UpdateAssemblyInfo = true; - foreach(var v in values) arguments.AddAssemblyInfoFileName(v); + foreach (var v in values) + { + arguments.AddAssemblyInfoFileName(v); + } } - else if (!IsSwitchArgument(value)) + else if (!value.IsSwitchArgument()) { arguments.UpdateAssemblyInfo = true; arguments.AddAssemblyInfoFileName(value); @@ -203,12 +186,12 @@ public static Arguments ParseArguments(List commandLineArguments) continue; } - if (IsSwitch("assemblyversionformat", name)) + if (name.IsSwitch("assemblyversionformat")) { throw new WarningException("assemblyversionformat switch removed, use AssemblyVersioningScheme configuration value instead"); } - if (IsSwitch("v", name) || IsSwitch("showvariable", name)) + if (name.IsSwitch("v") || name.IsSwitch("showvariable")) { string versionVariable = null; @@ -228,13 +211,13 @@ public static Arguments ParseArguments(List commandLineArguments) continue; } - if (IsSwitch("showConfig", name)) + if (name.IsSwitch("showConfig")) { - if (new[] { "1", "true" }.Contains(value, StringComparer.OrdinalIgnoreCase)) + if (value.IsTrue()) { arguments.ShowConfig = true; } - else if (new[] { "0", "false" }.Contains(value, StringComparer.OrdinalIgnoreCase)) + else if (value.IsFalse()) { arguments.UpdateAssemblyInfo = false; } @@ -245,7 +228,7 @@ public static Arguments ParseArguments(List commandLineArguments) continue; } - if (IsSwitch("output", name)) + if (name.IsSwitch("output")) { OutputType outputType; if (!Enum.TryParse(value, true, out outputType)) @@ -257,19 +240,19 @@ public static Arguments ParseArguments(List commandLineArguments) continue; } - if (IsSwitch("nofetch", name)) + if (name.IsSwitch("nofetch")) { arguments.NoFetch = true; continue; } - if (IsSwitch("ensureassemblyinfo", name)) + if (name.IsSwitch("ensureassemblyinfo")) { - if (new[] { "1", "true" }.Contains(value, StringComparer.OrdinalIgnoreCase)) + if (value.IsTrue()) { arguments.EnsureAssemblyInfo = true; } - else if (new[] { "0", "false" }.Contains(value, StringComparer.OrdinalIgnoreCase)) + else if (value.IsFalse()) { arguments.EnsureAssemblyInfo = false; } @@ -285,12 +268,12 @@ public static Arguments ParseArguments(List commandLineArguments) continue; } - if (IsSwitch("overrideconfig", name)) + if (name.IsSwitch("overrideconfig")) { foreach (var item in value.Split(';')) { var configOverride = item.Split('='); - + switch (configOverride[0]) { case "tag-prefix": @@ -305,95 +288,85 @@ public static Arguments ParseArguments(List commandLineArguments) continue; } - throw new WarningException(string.Format("Could not parse command line parameter '{0}'.", name)); + var couldNotParseMessage = string.Format("Could not parse command line parameter '{0}'.", name); + + // If we've reached through all argument switches without a match, we can relatively safely assume that the first argument isn't a switch, but the target path. + if (i == 0) + { + if (name.StartsWith("/")) + { + if (Path.DirectorySeparatorChar == '/' && name.IsValidPath()) + { + arguments.TargetPath = name; + continue; + } + } + else if (!name.IsSwitchArgument()) + { + arguments.TargetPath = name; + continue; + } + + couldNotParseMessage += " If it is the target path, make sure it exists."; + } + + throw new WarningException(couldNotParseMessage); + } + + if (arguments.TargetPath == null) + { + // If the first argument is a switch, it should already have been consumed in the above loop, + // or else a WarningException should have been thrown and we wouldn't end up here. + arguments.TargetPath = firstArgumentIsSwitch + ? Environment.CurrentDirectory + : firstArgument; } return arguments; } - static NameValueCollection CollectSwitchesAndValuesFromArguments(List namedArguments) + static NameValueCollection CollectSwitchesAndValuesFromArguments(IList namedArguments, out bool firstArgumentIsSwitch) { - var args = new NameValueCollection(); - + firstArgumentIsSwitch = true; + var switchesAndValues = new NameValueCollection(); string currentKey = null; - var isBooleanArgument = true; - for (var index = 0; index < namedArguments.Count; index = index + 1) + var argumentRequiresValue = false; + + for (var i = 0; i < namedArguments.Count; i = i + 1) { - var arg = namedArguments[index]; - // If the current (previous) argument doesn't require a parameter and this is a switch, create new name/value entry for it, with a null value. - if (isBooleanArgument && IsSwitchArgument(arg)) + var arg = namedArguments[i]; + + // If the current (previous) argument doesn't require a value parameter and this is a switch, create new name/value entry for it, with a null value. + if (!argumentRequiresValue && arg.IsSwitchArgument()) { currentKey = arg; - isBooleanArgument = IsBooleanArgument(arg); - args.Add(currentKey, null); + argumentRequiresValue = arg.ArgumentRequiresValue(i); + switchesAndValues.Add(currentKey, null); } - //If this is a value (not a switch) - else + // If this is a value (not a switch) + else if (currentKey != null) { - //And if the current switch does not have a value yet, set it's value to this argument. - if (string.IsNullOrEmpty(args[currentKey])) + // And if the current switch does not have a value yet and the value is not itself a switch, set its value to this argument. + if (string.IsNullOrEmpty(switchesAndValues[currentKey])) { - args[currentKey] = arg; + switchesAndValues[currentKey] = arg; } - //Otherwise add the value under the same switch. + // Otherwise add the value under the same switch. else { - args.Add(currentKey, arg); + switchesAndValues.Add(currentKey, arg); } // Reset the boolean argument flag so the next argument won't be ignored. - isBooleanArgument = true; + argumentRequiresValue = false; + } + else if (i == 0) + { + firstArgumentIsSwitch = false; } } - return args; - } - - static bool IsSwitchArgument(string value) - { - return value != null - && (value.StartsWith("-") || value.StartsWith("/")) - && !Regex.Match(value, @"/\w+:").Success; //Exclude msbuild & project parameters in form /blah:, which should be parsed as values, not switch names. - } - - static bool IsSwitch(string switchName, string value) - { - if (value.StartsWith("-")) - { - value = value.Remove(0, 1); - } - - if (value.StartsWith("/")) - { - value = value.Remove(0, 1); - } - - return (string.Equals(switchName, value, StringComparison.OrdinalIgnoreCase)); - } - - static bool IsInit(string singleArgument) - { - return singleArgument.Equals("init", StringComparison.OrdinalIgnoreCase); - } - - static bool IsHelp(string singleArgument) - { - return (singleArgument == "?") || - IsSwitch("h", singleArgument) || - IsSwitch("help", singleArgument) || - IsSwitch("?", singleArgument); - } - - static bool IsBooleanArgument(string switchName) - { - var booleanArguments = new[] - { - "init", - "updateassemblyinfo", - "ensureassemblyinfo", - "nofetch" - }; - return booleanArguments.Contains(switchName.Substring(1), StringComparer.OrdinalIgnoreCase); + return switchesAndValues; } } } \ No newline at end of file diff --git a/src/GitVersionExe/Extensions.cs b/src/GitVersionExe/Extensions.cs new file mode 100644 index 0000000000..e5ea80e9ca --- /dev/null +++ b/src/GitVersionExe/Extensions.cs @@ -0,0 +1,119 @@ +namespace GitVersion +{ + using System; + using System.IO; + using System.Linq; + using System.Text.RegularExpressions; + + public static class Extensions + { + private static string[] trues; + private static string[] falses; + + + static Extensions() + { + trues = new[] + { + "1", + "true" + }; + + falses = new[] + { + "0", + "false" + }; + } + + public static bool IsTrue(this string value) + { + return trues.Contains(value, StringComparer.OrdinalIgnoreCase); + } + + public static bool IsFalse(this string value) + { + return falses.Contains(value, StringComparer.OrdinalIgnoreCase); + } + + public static bool IsValidPath(this string path) + { + if (path == null) + return false; + + try + { + Path.GetFullPath(path); + } + catch + { + path = Path.Combine(Environment.CurrentDirectory, path); + + try + { + Path.GetFullPath(path); + } + catch + { + return false; + } + } + + return Directory.Exists(path); + } + + public static bool IsSwitchArgument(this string value) + { + return value != null + && (value.StartsWith("-") || value.StartsWith("/")) + && !Regex.Match(value, @"/\w+:").Success; //Exclude msbuild & project parameters in form /blah:, which should be parsed as values, not switch names. + } + + public static bool IsSwitch(this string value, string switchName) + { + if (value == null) + return false; + + if (value.StartsWith("-")) + { + value = value.Substring(1); + } + + if (value.StartsWith("/")) + { + value = value.Substring(1); + } + + return string.Equals(switchName, value, StringComparison.OrdinalIgnoreCase); + } + + public static bool IsInit(this string singleArgument) + { + return singleArgument.Equals("init", StringComparison.OrdinalIgnoreCase); + } + + public static bool IsHelp(this string singleArgument) + { + return (singleArgument == "?") || singleArgument.IsSwitch("h") || singleArgument.IsSwitch("help") || singleArgument.IsSwitch("?"); + } + + public static bool ArgumentRequiresValue(this string argument, int argumentIndex) + { + var booleanArguments = new[] + { + "init", + "updateassemblyinfo", + "ensureassemblyinfo", + "nofetch" + }; + + var argumentMightRequireValue = !booleanArguments.Contains(argument.Substring(1), StringComparer.OrdinalIgnoreCase); + + // If this is the first argument that might be a target path, the argument starts with slash and we're on an OS that supports paths with slashes, the argument does not require a value. + if (argumentMightRequireValue && argumentIndex == 0 && argument.StartsWith("/") && Path.DirectorySeparatorChar == '/' && argument.IsValidPath()) + return false; + + return argumentMightRequireValue; + } + } +} \ No newline at end of file diff --git a/src/GitVersionExe/GitVersionExe.csproj b/src/GitVersionExe/GitVersionExe.csproj index 6383a6271e..f80047facd 100644 --- a/src/GitVersionExe/GitVersionExe.csproj +++ b/src/GitVersionExe/GitVersionExe.csproj @@ -65,6 +65,7 @@ + diff --git a/src/GitVersionExe/Program.cs b/src/GitVersionExe/Program.cs index e9198d827f..03e7589dde 100644 --- a/src/GitVersionExe/Program.cs +++ b/src/GitVersionExe/Program.cs @@ -37,32 +37,26 @@ static int VerifyArgumentsAndRun() try { var fileSystem = new FileSystem(); - var argumentsWithoutExeName = GetArgumentsWithoutExeName(); + try { arguments = ArgumentParser.ParseArguments(argumentsWithoutExeName); } - catch (WarningException ex) + catch (Exception exception) { Console.WriteLine("Failed to parse arguments: {0}", string.Join(" ", argumentsWithoutExeName)); - if (!string.IsNullOrWhiteSpace(ex.Message)) + if (!string.IsNullOrWhiteSpace(exception.Message)) { Console.WriteLine(); - Console.WriteLine(ex.Message); + Console.WriteLine(exception.Message); Console.WriteLine(); } HelpWriter.Write(); return 1; } - catch (Exception) - { - Console.WriteLine("Failed to parse arguments: {0}", string.Join(" ", argumentsWithoutExeName)); - HelpWriter.Write(); - return 1; - } if (arguments.IsHelp) { HelpWriter.Write();