Skip to content

Commit

Permalink
Merge pull request #48 from max-ieremenko/feature/command-line
Browse files Browse the repository at this point in the history
SqlDatabase.CommandLine
  • Loading branch information
max-ieremenko committed Apr 13, 2024
2 parents 111962e + 61deeba commit e5913d1
Show file tree
Hide file tree
Showing 111 changed files with 1,661 additions and 2,580 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ bin/
build.out/

launchSettings.json
launchSettings.txt
*.sln.DotSettings.user
*.csproj.user

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace SqlDatabase.Configuration;
namespace SqlDatabase.Adapter;

public enum TransactionMode
{
Expand Down
149 changes: 149 additions & 0 deletions Sources/SqlDatabase.CommandLine.Test/CommandLineParserTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using NUnit.Framework;
using Shouldly;
using SqlDatabase.Adapter;
using SqlDatabase.CommandLine.Internal;

namespace SqlDatabase.CommandLine;

[TestFixture]
public class CommandLineParserTest
{
private readonly HostedRuntime _testRuntime = new(false, false, FrameworkVersion.Net8);

[Test]
[TestCase("", null, true)]
[TestCase("-help", null, true)]
[TestCase("create", "create", true)]
[TestCase("export -h", "export", true)]
[TestCase("execute -help", "execute", true)]
[TestCase("Upgrade -arg1 -arg2", "Upgrade", false)]
[TestCase("Upgrade -help -arg1 -arg2", "Upgrade", false)]
public void HelpRequested(string args, string? expectedCommand, bool expectedHelp)
{
CommandLineParser.HelpRequested(args.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries), out var actualCommand).ShouldBe(expectedHelp);

actualCommand.ShouldBe(expectedCommand);
}

[Test]
public void ParseCreateCommand()
{
var args = ToArgs(
"create",
new Arg("database", "Data Source=.;Initial Catalog=test"),
new Arg("from", @"c:\folder"),
new Arg("varX", "1 2 3"),
new Arg("varY", "value"),
new Arg("configuration", "app.config"),
new Arg("usePowerShell", @"c:\PowerShell"),
new Arg("whatIf", null));

var actual = CommandLineParser.Parse(_testRuntime, args).ShouldBeOfType<CreateCommandLine>();

actual.From.ShouldBe([new(false, @"c:\folder")]);

actual.Database.ShouldBe("Data Source=.;Initial Catalog=test");

actual.Variables.Keys.ShouldBe(new[] { "X", "Y" });
actual.Variables["x"].ShouldBe("1 2 3");
actual.Variables["y"].ShouldBe("value");

actual.Configuration.ShouldBe("app.config");
actual.UsePowerShell.ShouldBe(@"c:\PowerShell");
actual.WhatIf.ShouldBeTrue();
}

[Test]
public void ParseExecuteCommand()
{
var args = ToArgs(
"execute",
new Arg("database", "Data Source=.;Initial Catalog=test"),
new Arg("from", @"c:\folder"),
new Arg("fromSql", "drop 1"),
new Arg("varX", "1 2 3"),
new Arg("varY", "value"),
new Arg("configuration", "app.config"),
new Arg("transaction", "perStep"),
new Arg("usePowerShell", @"c:\PowerShell"),
new Arg("whatIf", null));

var actual = CommandLineParser.Parse(_testRuntime, args).ShouldBeOfType<ExecuteCommandLine>();

actual.From.Count.ShouldBe(2);
actual.From[0].ShouldBe(new(false, @"c:\folder"));
actual.From[1].ShouldBe(new(true, "drop 1"));

actual.Database.ShouldBe("Data Source=.;Initial Catalog=test");

actual.Variables.Keys.ShouldBe(new[] { "X", "Y" });
actual.Variables["x"].ShouldBe("1 2 3");
actual.Variables["y"].ShouldBe("value");

actual.Configuration.ShouldBe("app.config");
actual.Transaction.ShouldBe(TransactionMode.PerStep);
actual.UsePowerShell.ShouldBe(@"c:\PowerShell");
actual.WhatIf.ShouldBeTrue();
}

[Test]
public void ParseUpgradeCommand()
{
var args = ToArgs(
"upgrade",
new Arg("database", "Data Source=.;Initial Catalog=test"),
new Arg("from", @"c:\folder"),
new Arg("varX", "1 2 3"),
new Arg("varY", "value"),
new Arg("configuration", "app.config"),
new Arg("transaction", "perStep"),
new Arg("folderAsModuleName", null),
new Arg("usePowerShell", @"c:\PowerShell"),
new Arg("whatIf", null));

var actual = CommandLineParser.Parse(_testRuntime, args).ShouldBeOfType<UpgradeCommandLine>();

actual.From.ShouldBe([new(false, @"c:\folder")]);

actual.Database.ShouldBe("Data Source=.;Initial Catalog=test");

actual.Variables.Keys.ShouldBe(new[] { "X", "Y" });
actual.Variables["x"].ShouldBe("1 2 3");
actual.Variables["y"].ShouldBe("value");

actual.Configuration.ShouldBe("app.config");
actual.Transaction.ShouldBe(TransactionMode.PerStep);
actual.UsePowerShell.ShouldBe(@"c:\PowerShell");
actual.WhatIf.ShouldBeTrue();
actual.FolderAsModuleName.ShouldBeTrue();
}

[Test]
public void ParseExportCommand()
{
var args = ToArgs(
"export",
new Arg("database", "Data Source=.;Initial Catalog=test"),
new Arg("fromSql", "select 1"),
new Arg("from", @"c:\folder"),
new Arg("toTable", "dbo.ExportedData"),
new Arg("toFile", "file path"));

var actual = CommandLineParser.Parse(_testRuntime, args).ShouldBeOfType<ExportCommandLine>();

actual.From.Count.ShouldBe(2);
actual.From[0].ShouldBe(new(true, "select 1"));
actual.From[1].ShouldBe(new(false, @"c:\folder"));

actual.Database.ShouldBe("Data Source=.;Initial Catalog=test");
actual.DestinationTableName.ShouldBe("dbo.ExportedData");
actual.DestinationFileName.ShouldBe("file path");
}

private static string[] ToArgs(string command, params Arg[] args)
{
var result = new List<string>(args.Length + 1) { command };
result.AddRange(args.Select(i => '-' + i.ToString()));
return result.ToArray();
}
}
27 changes: 27 additions & 0 deletions Sources/SqlDatabase.CommandLine.Test/Internal/ArgTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using NUnit.Framework;
using Shouldly;

namespace SqlDatabase.CommandLine.Internal;

[TestFixture]
public class ArgTest
{
[Test]
[TestCase("command", null, null)]
[TestCase("-", null, null)]
[TestCase("-=", null, null)]
[TestCase("-arg", "arg", null)]
[TestCase("-arg =", "arg", null)]
[TestCase("-arg = ", "arg", null)]
[TestCase("-arg= value", "arg", "value")]
public void TryParse(string value, string? expectedKey, string? expectedValue)
{
Arg.TryParse(value, out var actual).ShouldBe(expectedKey != null);

if (expectedKey != null)
{
actual.Key.ShouldBe(expectedKey);
actual.Value.ShouldBe(expectedValue);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net472;net6.0;net7.0;net8.0</TargetFrameworks>
<RootNamespace>SqlDatabase.CommandLine</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SqlDatabase.CommandLine\SqlDatabase.CommandLine.csproj" />
<ProjectReference Include="..\SqlDatabase.TestApi\SqlDatabase.TestApi.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace System.Diagnostics.CodeAnalysis;

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)]
internal sealed class AllowNullAttribute : Attribute;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace System.Diagnostics.CodeAnalysis;

internal sealed class NotNullWhenAttribute : Attribute
{
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;

public bool ReturnValue { get; }
}
157 changes: 157 additions & 0 deletions Sources/SqlDatabase.CommandLine/CommandLineParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using SqlDatabase.Adapter;
using SqlDatabase.CommandLine.Internal;

namespace SqlDatabase.CommandLine;

public static class CommandLineParser
{
private const string CommandUpgrade = "Upgrade";
private const string CommandCreate = "Create";
private const string CommandExecute = "Execute";
private const string CommandExport = "Export";

public static bool HelpRequested(string[] args, out string? command)
{
command = null;

if (args.Length == 0)
{
return true;
}

// -h
// execute
// execute -h
if (args.Length == 1)
{
if (IsHelpArg(args[0]))
{
return true;
}

return IsCommand(args[0], out command);
}

if (!IsCommand(args[0], out command))
{
return false;
}

return IsHelpArg(args[1]) && args.Length == 2;
}

public static ICommandLine Parse(HostedRuntime runtime, string[] args)
{
if (Arg.TryParse(args[0], out _))
{
throw new InvalidCommandLineException("<command> not found.");
}

var command = args[0];
var commandArgs = args.Skip(1);

if (CommandCreate.Equals(command, StringComparison.OrdinalIgnoreCase))
{
return ParseCreate(runtime, commandArgs);
}

if (CommandExecute.Equals(command, StringComparison.OrdinalIgnoreCase))
{
return ParseExecute(runtime, commandArgs);
}

if (CommandUpgrade.Equals(command, StringComparison.OrdinalIgnoreCase))
{
return ParseUpgrade(runtime, commandArgs);
}

if (CommandExport.Equals(command, StringComparison.OrdinalIgnoreCase))
{
return ParseExport(commandArgs);
}

throw new InvalidCommandLineException($"Unknown command [{args[0]}].");
}

public static void AddVariable(Dictionary<string, string> target, string nameValue)
{
if (!Arg.TryParse(ArgNames.Sign + nameValue, out var arg))
{
throw new InvalidCommandLineException($"Invalid variable value definition [{nameValue}].");
}

if (target.ContainsKey(arg.Key))
{
throw new InvalidCommandLineException($"Variable [{arg.Key}] is duplicated.");
}

target.Add(arg.Key, arg.Value ?? string.Empty);
}

private static bool IsCommand(string value, out string? command)
{
if (CommandUpgrade.Equals(value, StringComparison.OrdinalIgnoreCase)
|| CommandCreate.Equals(value, StringComparison.OrdinalIgnoreCase)
|| CommandExecute.Equals(value, StringComparison.OrdinalIgnoreCase)
|| CommandExport.Equals(value, StringComparison.OrdinalIgnoreCase))
{
command = value;
return true;
}

command = null;
return false;
}

private static bool IsHelpArg(string value) =>
Arg.TryParse(value, out var arg)
&& arg.Value == null
&& (arg.Is(ArgNames.Help) || arg.Is(ArgNames.HelpShort));

private static CreateCommandLine ParseCreate(HostedRuntime runtime, IEnumerable<string> args) =>
new CommandLineBinder<CreateCommandLine>(new())
.BindDatabase((i, value) => i.Database = value)
.BindScripts(i => i.From)
.BindVariables(i => i.Variables)
.BindConfiguration((i, value) => i.Configuration = value)
.BindLog((i, value) => i.Log = value)
.BindUsePowerShell(runtime, (i, value) => i.UsePowerShell = value)
.BindWhatIf((i, value) => i.WhatIf = value)
.Build(args);

private static ExecuteCommandLine ParseExecute(HostedRuntime runtime, IEnumerable<string> args) =>
new CommandLineBinder<ExecuteCommandLine>(new())
.BindDatabase((i, value) => i.Database = value)
.BindScripts(i => i.From)
.BindTransaction((i, value) => i.Transaction = value)
.BindVariables(i => i.Variables)
.BindConfiguration((i, value) => i.Configuration = value)
.BindLog((i, value) => i.Log = value)
.BindUsePowerShell(runtime, (i, value) => i.UsePowerShell = value)
.BindWhatIf((i, value) => i.WhatIf = value)
.Build(args);

private static UpgradeCommandLine ParseUpgrade(HostedRuntime runtime, IEnumerable<string> args) =>
new CommandLineBinder<UpgradeCommandLine>(new())
.BindDatabase((i, value) => i.Database = value)
.BindScripts(i => i.From)
.BindTransaction((i, value) => i.Transaction = value)
.BindVariables(i => i.Variables)
.BindConfiguration((i, value) => i.Configuration = value)
.BindLog((i, value) => i.Log = value)
.BindUsePowerShell(runtime, (i, value) => i.UsePowerShell = value)
.BindWhatIf((i, value) => i.WhatIf = value)
.BindFolderAsModuleName((i, value) => i.FolderAsModuleName = value)
.Build(args);

private static ExportCommandLine ParseExport(IEnumerable<string> args) =>
new CommandLineBinder<ExportCommandLine>(new())
.BindDatabase((i, value) => i.Database = value)
.BindScripts(i => i.From)
.BindExportToTable((i, value) => i.DestinationTableName = value)
.BindExportToFile((i, value) => i.DestinationFileName = value)
.BindVariables(i => i.Variables)
.BindConfiguration((i, value) => i.Configuration = value)
.BindLog((i, value) => i.Log = value)
.Build(args);
}
Loading

0 comments on commit e5913d1

Please sign in to comment.