Skip to content

Commit 61deeba

Browse files
committed
SqlDatabase.CommandLine
1 parent f3e68e5 commit 61deeba

File tree

91 files changed

+1391
-2392
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+1391
-2392
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ bin/
44
build.out/
55

66
launchSettings.json
7+
launchSettings.txt
78
*.sln.DotSettings.user
89
*.csproj.user
910

Sources/SqlDatabase/Configuration/TransactionMode.cs renamed to Sources/SqlDatabase.Adapter/TransactionMode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace SqlDatabase.Configuration;
1+
namespace SqlDatabase.Adapter;
22

33
public enum TransactionMode
44
{
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using NUnit.Framework;
2+
using Shouldly;
3+
using SqlDatabase.Adapter;
4+
using SqlDatabase.CommandLine.Internal;
5+
6+
namespace SqlDatabase.CommandLine;
7+
8+
[TestFixture]
9+
public class CommandLineParserTest
10+
{
11+
private readonly HostedRuntime _testRuntime = new(false, false, FrameworkVersion.Net8);
12+
13+
[Test]
14+
[TestCase("", null, true)]
15+
[TestCase("-help", null, true)]
16+
[TestCase("create", "create", true)]
17+
[TestCase("export -h", "export", true)]
18+
[TestCase("execute -help", "execute", true)]
19+
[TestCase("Upgrade -arg1 -arg2", "Upgrade", false)]
20+
[TestCase("Upgrade -help -arg1 -arg2", "Upgrade", false)]
21+
public void HelpRequested(string args, string? expectedCommand, bool expectedHelp)
22+
{
23+
CommandLineParser.HelpRequested(args.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries), out var actualCommand).ShouldBe(expectedHelp);
24+
25+
actualCommand.ShouldBe(expectedCommand);
26+
}
27+
28+
[Test]
29+
public void ParseCreateCommand()
30+
{
31+
var args = ToArgs(
32+
"create",
33+
new Arg("database", "Data Source=.;Initial Catalog=test"),
34+
new Arg("from", @"c:\folder"),
35+
new Arg("varX", "1 2 3"),
36+
new Arg("varY", "value"),
37+
new Arg("configuration", "app.config"),
38+
new Arg("usePowerShell", @"c:\PowerShell"),
39+
new Arg("whatIf", null));
40+
41+
var actual = CommandLineParser.Parse(_testRuntime, args).ShouldBeOfType<CreateCommandLine>();
42+
43+
actual.From.ShouldBe([new(false, @"c:\folder")]);
44+
45+
actual.Database.ShouldBe("Data Source=.;Initial Catalog=test");
46+
47+
actual.Variables.Keys.ShouldBe(new[] { "X", "Y" });
48+
actual.Variables["x"].ShouldBe("1 2 3");
49+
actual.Variables["y"].ShouldBe("value");
50+
51+
actual.Configuration.ShouldBe("app.config");
52+
actual.UsePowerShell.ShouldBe(@"c:\PowerShell");
53+
actual.WhatIf.ShouldBeTrue();
54+
}
55+
56+
[Test]
57+
public void ParseExecuteCommand()
58+
{
59+
var args = ToArgs(
60+
"execute",
61+
new Arg("database", "Data Source=.;Initial Catalog=test"),
62+
new Arg("from", @"c:\folder"),
63+
new Arg("fromSql", "drop 1"),
64+
new Arg("varX", "1 2 3"),
65+
new Arg("varY", "value"),
66+
new Arg("configuration", "app.config"),
67+
new Arg("transaction", "perStep"),
68+
new Arg("usePowerShell", @"c:\PowerShell"),
69+
new Arg("whatIf", null));
70+
71+
var actual = CommandLineParser.Parse(_testRuntime, args).ShouldBeOfType<ExecuteCommandLine>();
72+
73+
actual.From.Count.ShouldBe(2);
74+
actual.From[0].ShouldBe(new(false, @"c:\folder"));
75+
actual.From[1].ShouldBe(new(true, "drop 1"));
76+
77+
actual.Database.ShouldBe("Data Source=.;Initial Catalog=test");
78+
79+
actual.Variables.Keys.ShouldBe(new[] { "X", "Y" });
80+
actual.Variables["x"].ShouldBe("1 2 3");
81+
actual.Variables["y"].ShouldBe("value");
82+
83+
actual.Configuration.ShouldBe("app.config");
84+
actual.Transaction.ShouldBe(TransactionMode.PerStep);
85+
actual.UsePowerShell.ShouldBe(@"c:\PowerShell");
86+
actual.WhatIf.ShouldBeTrue();
87+
}
88+
89+
[Test]
90+
public void ParseUpgradeCommand()
91+
{
92+
var args = ToArgs(
93+
"upgrade",
94+
new Arg("database", "Data Source=.;Initial Catalog=test"),
95+
new Arg("from", @"c:\folder"),
96+
new Arg("varX", "1 2 3"),
97+
new Arg("varY", "value"),
98+
new Arg("configuration", "app.config"),
99+
new Arg("transaction", "perStep"),
100+
new Arg("folderAsModuleName", null),
101+
new Arg("usePowerShell", @"c:\PowerShell"),
102+
new Arg("whatIf", null));
103+
104+
var actual = CommandLineParser.Parse(_testRuntime, args).ShouldBeOfType<UpgradeCommandLine>();
105+
106+
actual.From.ShouldBe([new(false, @"c:\folder")]);
107+
108+
actual.Database.ShouldBe("Data Source=.;Initial Catalog=test");
109+
110+
actual.Variables.Keys.ShouldBe(new[] { "X", "Y" });
111+
actual.Variables["x"].ShouldBe("1 2 3");
112+
actual.Variables["y"].ShouldBe("value");
113+
114+
actual.Configuration.ShouldBe("app.config");
115+
actual.Transaction.ShouldBe(TransactionMode.PerStep);
116+
actual.UsePowerShell.ShouldBe(@"c:\PowerShell");
117+
actual.WhatIf.ShouldBeTrue();
118+
actual.FolderAsModuleName.ShouldBeTrue();
119+
}
120+
121+
[Test]
122+
public void ParseExportCommand()
123+
{
124+
var args = ToArgs(
125+
"export",
126+
new Arg("database", "Data Source=.;Initial Catalog=test"),
127+
new Arg("fromSql", "select 1"),
128+
new Arg("from", @"c:\folder"),
129+
new Arg("toTable", "dbo.ExportedData"),
130+
new Arg("toFile", "file path"));
131+
132+
var actual = CommandLineParser.Parse(_testRuntime, args).ShouldBeOfType<ExportCommandLine>();
133+
134+
actual.From.Count.ShouldBe(2);
135+
actual.From[0].ShouldBe(new(true, "select 1"));
136+
actual.From[1].ShouldBe(new(false, @"c:\folder"));
137+
138+
actual.Database.ShouldBe("Data Source=.;Initial Catalog=test");
139+
actual.DestinationTableName.ShouldBe("dbo.ExportedData");
140+
actual.DestinationFileName.ShouldBe("file path");
141+
}
142+
143+
private static string[] ToArgs(string command, params Arg[] args)
144+
{
145+
var result = new List<string>(args.Length + 1) { command };
146+
result.AddRange(args.Select(i => '-' + i.ToString()));
147+
return result.ToArray();
148+
}
149+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using NUnit.Framework;
2+
using Shouldly;
3+
4+
namespace SqlDatabase.CommandLine.Internal;
5+
6+
[TestFixture]
7+
public class ArgTest
8+
{
9+
[Test]
10+
[TestCase("command", null, null)]
11+
[TestCase("-", null, null)]
12+
[TestCase("-=", null, null)]
13+
[TestCase("-arg", "arg", null)]
14+
[TestCase("-arg =", "arg", null)]
15+
[TestCase("-arg = ", "arg", null)]
16+
[TestCase("-arg= value", "arg", "value")]
17+
public void TryParse(string value, string? expectedKey, string? expectedValue)
18+
{
19+
Arg.TryParse(value, out var actual).ShouldBe(expectedKey != null);
20+
21+
if (expectedKey != null)
22+
{
23+
actual.Key.ShouldBe(expectedKey);
24+
actual.Value.ShouldBe(expectedValue);
25+
}
26+
}
27+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net472;net6.0;net7.0;net8.0</TargetFrameworks>
5+
<RootNamespace>SqlDatabase.CommandLine</RootNamespace>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
10+
<PackageReference Include="NUnit" />
11+
<PackageReference Include="NUnit3TestAdapter" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\SqlDatabase.CommandLine\SqlDatabase.CommandLine.csproj" />
16+
<ProjectReference Include="..\SqlDatabase.TestApi\SqlDatabase.TestApi.csproj" />
17+
</ItemGroup>
18+
19+
</Project>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
namespace System.Diagnostics.CodeAnalysis;
2+
3+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)]
4+
internal sealed class AllowNullAttribute : Attribute;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace System.Diagnostics.CodeAnalysis;
2+
3+
internal sealed class NotNullWhenAttribute : Attribute
4+
{
5+
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
6+
7+
public bool ReturnValue { get; }
8+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
using SqlDatabase.Adapter;
2+
using SqlDatabase.CommandLine.Internal;
3+
4+
namespace SqlDatabase.CommandLine;
5+
6+
public static class CommandLineParser
7+
{
8+
private const string CommandUpgrade = "Upgrade";
9+
private const string CommandCreate = "Create";
10+
private const string CommandExecute = "Execute";
11+
private const string CommandExport = "Export";
12+
13+
public static bool HelpRequested(string[] args, out string? command)
14+
{
15+
command = null;
16+
17+
if (args.Length == 0)
18+
{
19+
return true;
20+
}
21+
22+
// -h
23+
// execute
24+
// execute -h
25+
if (args.Length == 1)
26+
{
27+
if (IsHelpArg(args[0]))
28+
{
29+
return true;
30+
}
31+
32+
return IsCommand(args[0], out command);
33+
}
34+
35+
if (!IsCommand(args[0], out command))
36+
{
37+
return false;
38+
}
39+
40+
return IsHelpArg(args[1]) && args.Length == 2;
41+
}
42+
43+
public static ICommandLine Parse(HostedRuntime runtime, string[] args)
44+
{
45+
if (Arg.TryParse(args[0], out _))
46+
{
47+
throw new InvalidCommandLineException("<command> not found.");
48+
}
49+
50+
var command = args[0];
51+
var commandArgs = args.Skip(1);
52+
53+
if (CommandCreate.Equals(command, StringComparison.OrdinalIgnoreCase))
54+
{
55+
return ParseCreate(runtime, commandArgs);
56+
}
57+
58+
if (CommandExecute.Equals(command, StringComparison.OrdinalIgnoreCase))
59+
{
60+
return ParseExecute(runtime, commandArgs);
61+
}
62+
63+
if (CommandUpgrade.Equals(command, StringComparison.OrdinalIgnoreCase))
64+
{
65+
return ParseUpgrade(runtime, commandArgs);
66+
}
67+
68+
if (CommandExport.Equals(command, StringComparison.OrdinalIgnoreCase))
69+
{
70+
return ParseExport(commandArgs);
71+
}
72+
73+
throw new InvalidCommandLineException($"Unknown command [{args[0]}].");
74+
}
75+
76+
public static void AddVariable(Dictionary<string, string> target, string nameValue)
77+
{
78+
if (!Arg.TryParse(ArgNames.Sign + nameValue, out var arg))
79+
{
80+
throw new InvalidCommandLineException($"Invalid variable value definition [{nameValue}].");
81+
}
82+
83+
if (target.ContainsKey(arg.Key))
84+
{
85+
throw new InvalidCommandLineException($"Variable [{arg.Key}] is duplicated.");
86+
}
87+
88+
target.Add(arg.Key, arg.Value ?? string.Empty);
89+
}
90+
91+
private static bool IsCommand(string value, out string? command)
92+
{
93+
if (CommandUpgrade.Equals(value, StringComparison.OrdinalIgnoreCase)
94+
|| CommandCreate.Equals(value, StringComparison.OrdinalIgnoreCase)
95+
|| CommandExecute.Equals(value, StringComparison.OrdinalIgnoreCase)
96+
|| CommandExport.Equals(value, StringComparison.OrdinalIgnoreCase))
97+
{
98+
command = value;
99+
return true;
100+
}
101+
102+
command = null;
103+
return false;
104+
}
105+
106+
private static bool IsHelpArg(string value) =>
107+
Arg.TryParse(value, out var arg)
108+
&& arg.Value == null
109+
&& (arg.Is(ArgNames.Help) || arg.Is(ArgNames.HelpShort));
110+
111+
private static CreateCommandLine ParseCreate(HostedRuntime runtime, IEnumerable<string> args) =>
112+
new CommandLineBinder<CreateCommandLine>(new())
113+
.BindDatabase((i, value) => i.Database = value)
114+
.BindScripts(i => i.From)
115+
.BindVariables(i => i.Variables)
116+
.BindConfiguration((i, value) => i.Configuration = value)
117+
.BindLog((i, value) => i.Log = value)
118+
.BindUsePowerShell(runtime, (i, value) => i.UsePowerShell = value)
119+
.BindWhatIf((i, value) => i.WhatIf = value)
120+
.Build(args);
121+
122+
private static ExecuteCommandLine ParseExecute(HostedRuntime runtime, IEnumerable<string> args) =>
123+
new CommandLineBinder<ExecuteCommandLine>(new())
124+
.BindDatabase((i, value) => i.Database = value)
125+
.BindScripts(i => i.From)
126+
.BindTransaction((i, value) => i.Transaction = value)
127+
.BindVariables(i => i.Variables)
128+
.BindConfiguration((i, value) => i.Configuration = value)
129+
.BindLog((i, value) => i.Log = value)
130+
.BindUsePowerShell(runtime, (i, value) => i.UsePowerShell = value)
131+
.BindWhatIf((i, value) => i.WhatIf = value)
132+
.Build(args);
133+
134+
private static UpgradeCommandLine ParseUpgrade(HostedRuntime runtime, IEnumerable<string> args) =>
135+
new CommandLineBinder<UpgradeCommandLine>(new())
136+
.BindDatabase((i, value) => i.Database = value)
137+
.BindScripts(i => i.From)
138+
.BindTransaction((i, value) => i.Transaction = value)
139+
.BindVariables(i => i.Variables)
140+
.BindConfiguration((i, value) => i.Configuration = value)
141+
.BindLog((i, value) => i.Log = value)
142+
.BindUsePowerShell(runtime, (i, value) => i.UsePowerShell = value)
143+
.BindWhatIf((i, value) => i.WhatIf = value)
144+
.BindFolderAsModuleName((i, value) => i.FolderAsModuleName = value)
145+
.Build(args);
146+
147+
private static ExportCommandLine ParseExport(IEnumerable<string> args) =>
148+
new CommandLineBinder<ExportCommandLine>(new())
149+
.BindDatabase((i, value) => i.Database = value)
150+
.BindScripts(i => i.From)
151+
.BindExportToTable((i, value) => i.DestinationTableName = value)
152+
.BindExportToFile((i, value) => i.DestinationFileName = value)
153+
.BindVariables(i => i.Variables)
154+
.BindConfiguration((i, value) => i.Configuration = value)
155+
.BindLog((i, value) => i.Log = value)
156+
.Build(args);
157+
}

0 commit comments

Comments
 (0)