Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-316: Enable Specifying Target without Switch #317

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
85 changes: 85 additions & 0 deletions src/Cake.Tests/Unit/Arguments/ArgumentParserTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Cake.Arguments;
using Cake.Core.Diagnostics;
using Cake.Core.IO;
Expand Down Expand Up @@ -258,6 +259,90 @@ public void Can_Find_Default_Scripts(string scriptName)
// Then
Assert.NotNull(result.Script);
}

[Theory]
[InlineData(".cakefile")]
[InlineData("build.cake")]
public void Can_Accept_Default_Target(string scriptName)
{
// Given
var fixture = new ArgumentParserFixture();
var parser = new ArgumentParser(fixture.Log, fixture.FileSystem);
var file = Substitute.For<IFile>();
file.Exists.Returns(true);

fixture.FileSystem.GetFile(Arg.Is<FilePath>(fp => fp.FullPath == scriptName))
.Returns(file);

var target = "Default";

// When
var result = parser.Parse(new [] { target });

// Then
Assert.NotNull(result.Script);
Assert.True(result.Arguments.ContainsKey("Target"));
}

[Fact]
public void Should_Return_Error_If_Script_And_Target_Are_In_Wrong_Order()
{
// Given
var fixture = new ArgumentParserFixture();
var parser = new ArgumentParser(fixture.Log, fixture.FileSystem);

// When
var result = parser.Parse( new [] { "Default", "build.csx" });

// Then
Assert.Null(result);
Assert.True(fixture.Log.Messages.Contains("The script path must be first argument."));
}

[Fact]
public void Should_Return_Error_If_Two_Positional_Targets()
{
// Given
var fixture = new ArgumentParserFixture();
var parser = new ArgumentParser(fixture.Log, fixture.FileSystem);

// When
var result = parser.Parse(new[] { "Default", "Default" });

// Then
Assert.Null(result);
Assert.True(fixture.Log.Messages.Contains("Attempted to add two targets: \"Default\" and \"Default\"."));
}

[Fact]
public void Should_Return_Error_If_Two_Targets()
{
// Given
var fixture = new ArgumentParserFixture();
var parser = new ArgumentParser(fixture.Log, fixture.FileSystem);

// When
var result = parser.Parse(new[] { "Default", "-Target=Default" });

// Then
Assert.Null(result);
Assert.True(fixture.Log.Messages.Contains("Multiple arguments with the same name (Target)."));
}

[Fact]
public void Should_Return_Error_If_More_Than_Two_Positional_Arguments()
{
// Given
var fixture = new ArgumentParserFixture();
var parser = new ArgumentParser(fixture.Log, fixture.FileSystem);

// When
var result = parser.Parse(new[] { "build.csx", "Default", "unknown" });

// Then
Assert.Null(result);
Assert.True(fixture.Log.Messages.Contains("Attempted to add unknown argument \"unknown\" at position 2."));
}
}
}
}
20 changes: 20 additions & 0 deletions src/Cake/Arguments/Argument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Cake.Arguments
{
internal class Argument
{
/// <summary>
/// Gets or sets the key for this argument.
/// </summary>
public string Key { get; set; }

/// <summary>
/// Gets or sets the string value for this argument.
/// </summary>
public string Value { get; set; }

/// <summary>
/// Gets or sets the position in which the argument appeared.
/// </summary>
public int Position { get; set; }
}
}
172 changes: 91 additions & 81 deletions src/Cake/Arguments/ArgumentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public CakeOptions Parse(IEnumerable<string> args)
}

var options = new CakeOptions();
var isParsingOptions = false;

var arguments = args.ToList();

Expand All @@ -37,53 +36,22 @@ public CakeOptions Parse(IEnumerable<string> args)
options.Script = GetDefaultScript();
}

var processedArguments = new List<Argument>();
var position = 0;
foreach (var arg in arguments)
{
var value = arg.UnQuote();
processedArguments.Add(IsOption(arg) ? ParseOption(arg, position) : new Argument { Key = string.Empty, Value = value, Position = position });
position++;
}

if (isParsingOptions)
foreach (var arg in processedArguments)
{
if (!ProcessOption(arg, options))
{
if (IsOption(value))
{
if (!ParseOption(value, options))
{
return null;
}
}
else
{
_log.Error("More than one build script specified.");
return null;
}
}
else
{
try
{
// If they didn't provide a specific build script, search for a defualt.
if (IsOption(arg))
{
// Make sure we parse the option
if (!ParseOption(value, options))
{
return null;
}

options.Script = GetDefaultScript();
continue;
}

// Quoted?
options.Script = new FilePath(value);
}
finally
{
// Start parsing options.
isParsingOptions = true;
}
return null;
}
}

return options;
}

Expand All @@ -96,7 +64,7 @@ private static bool IsOption(string arg)
return arg[0] == '-';
}

private bool ParseOption(string arg, CakeOptions options)
private Argument ParseOption(string arg, int position)
{
string name, value;

Expand All @@ -119,56 +87,98 @@ private bool ParseOption(string arg, CakeOptions options)
value = value.Substring(1, value.Length - 2);
}
}

return ParseOption(name, value, options);
return new Argument { Key = name, Value = value, Position = position };
}

private bool ParseOption(string name, string value, CakeOptions options)
private bool ProcessOption(Argument argument, CakeOptions options)
{
if (name.Equals("verbosity", StringComparison.OrdinalIgnoreCase)
|| name.Equals("v", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrWhiteSpace(argument.Key))
{
// Parse verbosity.
var converter = TypeDescriptor.GetConverter(typeof(Verbosity));
var verbosity = converter.ConvertFromInvariantString(value);
if (verbosity != null)
// If it's at position zero, it's either a file or target.
switch (argument.Position)
{
options.Verbosity = (Verbosity)verbosity;
}
}

if (name.Equals("showdescription", StringComparison.OrdinalIgnoreCase) ||
name.Equals("s", StringComparison.OrdinalIgnoreCase))
{
options.ShowDescription = true;
}
case 0:
// If we have a dot, we're a script file.
if (argument.Value.Contains("."))
{
options.Script = new FilePath(argument.Value);
}
else
{
options.Arguments.Add("Target", argument.Value);
options.Script = GetDefaultScript();
}
break;
case 1:
// If we have a dot, we're a script file.
if (argument.Value.Contains("."))
{
if (options.Script != null)
{
_log.Error("More than one build script specified.");
return false;
}

if (name.Equals("dryrun", StringComparison.OrdinalIgnoreCase) ||
name.Equals("noop", StringComparison.OrdinalIgnoreCase) ||
name.Equals("whatif", StringComparison.OrdinalIgnoreCase))
{
options.PerformDryRun = true;
}
_log.Error("The script path must be first argument.");
return false;
}

if (name.Equals("help", StringComparison.OrdinalIgnoreCase) ||
name.Equals("?", StringComparison.OrdinalIgnoreCase))
{
options.ShowHelp = true;
if (options.Arguments.ContainsKey("Target"))
{
_log.Error("Attempted to add two targets: \"{0}\" and \"{1}\".", options.Arguments["Target"],
argument.Value);
return false;
}
options.Arguments.Add("Target", argument.Value);
break;
default:
_log.Error("Attempted to add unknown argument \"{0}\" at position {1}.", argument.Value,
argument.Position);
return false;
}
}

if (name.Equals("version", StringComparison.OrdinalIgnoreCase) ||
name.Equals("ver", StringComparison.OrdinalIgnoreCase))
else
{
options.ShowVersion = true;
}
switch (argument.Key.ToLowerInvariant())
{
case "verbosity":
case "v":
// Parse verbosity.
var converter = TypeDescriptor.GetConverter(typeof(Verbosity));
var verbosity = converter.ConvertFromInvariantString(argument.Value);
if (verbosity != null)
{
options.Verbosity = (Verbosity)verbosity;
}
break;
case "showdescription":
case "s":
options.ShowDescription = true;
break;
case "dryrun":
case "noop":
case "whatif":
options.PerformDryRun = true;
break;
case "help":
case "?":
options.ShowHelp = true;
break;
case "version":
case "ver":
options.ShowVersion = true;
break;
default:
if (options.Arguments.ContainsKey(argument.Key))
{
_log.Error("Multiple arguments with the same name ({0}).", argument.Key);
return false;
}

if (options.Arguments.ContainsKey(name))
{
_log.Error("Multiple arguments with the same name ({0}).", name);
return false;
options.Arguments.Add(argument.Key, argument.Key);
break;
}
}

options.Arguments.Add(name, value);
return true;
}

Expand Down
1 change: 1 addition & 0 deletions src/Cake/Cake.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Arguments\Argument.cs" />
<Compile Include="Arguments\ArgumentParser.cs" />
<Compile Include="Arguments\ArgumentTokenizer.cs" />
<Compile Include="Arguments\IArgumentParser.cs">
Expand Down