diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000000..3e2257721b
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,8 @@
+# .git-blame-ignore-revs
+# To configure git to use this file, run
+# git config blame.ignoreRevsFile .git-blame-ignore-revs
+#
+# Testing git-blame-ingore-revs on small scale
+# before considering broader use, by reverting
+# bad autoformatter changes
+1aca22a0350e800b72f2275df12a58f0a1f72101
diff --git a/Directory.Build.props b/Directory.Build.props
index 62f3090979..41b9b11ba1 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -7,7 +7,7 @@
$(NoWarn);NU5125;CS0618MIT
- 10.0
+ 12.0
@@ -15,7 +15,7 @@
- net7.0
+ net8.0
diff --git a/OpenQuestions.md b/OpenQuestions.md
new file mode 100644
index 0000000000..9dd1770bd4
--- /dev/null
+++ b/OpenQuestions.md
@@ -0,0 +1,59 @@
+# Open questions
+
+Please also include TODO in appropriate locations. This is intended as a wrap up.
+
+Also, include whether the question is to add or remove something, and add date/initials
+
+## NotNulWhen on TryGetValue in ValueSource and ValueProvider
+
+Things to consider:
+
+* Within System.CommandLine all TryGetValue should probably be the same
+* TryGetValue on dictionary can return null
+* For nullable values, the actual value can be null
+* For nullable or non-nullable ref types, the default for the type is null
+* Allowing null out values keeps a single meaning to "not found" and allows "found but null". Conflating these blocks expressing which happened
+
+The recovery is the same as with Dictionary.TryGetValue. The first line of the block that handles the return Boolean is a guard.
+
+## The extensibility story for ValueSource
+
+The proposal and current code seal our value sources and expect people to make additional ones based on ValueSource. The classes are public and sealed, the constructors are internal.
+
+Reasons to reconsider: Aggregate value source has a logical precedence or an as entered one. If someone adds a new value source, it is always last in the logic precedence.There are likely to be other similar cases.
+
+Possible resolution: Have this be case by case and allow aggregate values to be unsealed and have a mechanism for overriding. Providing a non-inheritance based solution could make this look like a normal operation when it is a rare one.
+
+## Contexts [RESOLVED]
+
+We had two different philosophies at different spots in subsystems. "Give folks everything they might need" and "Give folks only what we know they need".
+
+The first probably means we pass around `PipelineResult`. The second means that each purpose needs a special context. Sharing contexts is likely to mean that something will be added to one context that is unneeded by the other. Known expected contexts are:
+
+- `AnnotationProviderContext`
+- `ValueSourceContext`
+- `ValidationContext` (includes ability to report diagnostics)
+- `CompletionContext`
+- `HelpContext`
+
+## Which contexts should allow diagnostic reporting?
+
+## Should we have both Validators and IValidator on Conditions? [RESOLVED]
+
+We started with `Validators` and then added the IValidator interface to allow conditions to do validation because they have the strong type. Checking for this first also avoids a dictionary lookup.
+
+Our default validations will be on the Condition for the shortcut. Users can offer alternatives by creaing custom validators. The dictionary for custom validators will be lazy, and lookups will be pay for play when the user has custom validators. (This is not yet implemented.)
+
+When present, custom validators have precedence. There is no cost when they are not present.
+
+## Should conditions be public
+
+Since there are factory methods and validators could still access them, current behavior could be supported with internal conditions.
+
+However, the point of conditions is that they are a statement about the symbol and not an implementation. They are known to be used by completions and completions are expected to be extended. Thus, to get the values held in the condition (such as environment variable name) need to be available outside the external scope.
+
+Suggestion: Use internal constructors and leave conditions public
+
+## Should `ValueCondition` be called `Condition`?
+
+They may apply to commands.
\ No newline at end of file
diff --git a/StringCase.cs b/StringCase.cs
new file mode 100644
index 0000000000..de1c6bf578
--- /dev/null
+++ b/StringCase.cs
@@ -0,0 +1,77 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.CommandLine.Parsing;
+using System.CommandLine.Validation;
+using System.CommandLine.ValueSources;
+using System.Globalization;
+
+namespace System.CommandLine.ValueConditions;
+
+/////
+///// Declares the string casing condition for the option or argument.
+/////
+/////
+///// The non-generic version is used by the
+///
+/// The type of the symbol the range applies to.
+public class StringCase : ValueCondition, IValueValidator
+{
+ internal StringCase(ValueSource? casing) : base(nameof(StringCase))
+ {
+ Casing = casing;
+ }
+
+ ///
+ public void Validate(object? value,
+ CliValueSymbol valueSymbol,
+ CliValueResult? valueResult,
+ ValueCondition valueCondition,
+ ValidationContext validationContext)
+ {
+ if (valueCondition != this) throw new InvalidOperationException("Unexpected value condition type");
+ if (value is not string stringValue) throw new InvalidOperationException("Unexpected value type");
+
+ if (stringValue is null) return; // nothing to do
+
+ // TODO: Replace the strings we are comparing with a diagnostic ID when we update ParseError
+ if (Casing is not null
+ && validationContext.TryGetTypedValue(Casing, out var casingValue))
+ {
+ if (casingValue is null) return;
+ if (casingValue == "lower"
+ && !stringValue.Equals(stringValue.ToLower(CultureInfo.CurrentCulture)))
+ {
+ validationContext.AddError(new ParseError($"The value for '{valueSymbol.Name}' is not in lower case."));
+ }
+ if (casingValue == "upper"
+ && !stringValue.Equals(stringValue.ToUpper(CultureInfo.CurrentCulture)))
+ {
+ validationContext.AddError(new ParseError($"The value for '{valueSymbol.Name}' is not in upper case."));
+ }
+ }
+ }
+
+ ///
+ /// The expected casing.
+ ///
+ public ValueSource? Casing { get; init; }
+}
diff --git a/StringValidationTests.cs b/StringValidationTests.cs
new file mode 100644
index 0000000000..c957e3af2a
--- /dev/null
+++ b/StringValidationTests.cs
@@ -0,0 +1,129 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using FluentAssertions;
+using System.CommandLine.Parsing;
+using System.CommandLine.ValueSources;
+using Xunit;
+
+namespace System.CommandLine.Subsystems.Tests;
+
+public class StringValidationTests
+{
+ private PipelineResult ExecutedPipelineResultForCommand(CliCommand command, string input)
+ {
+ var validationSubsystem = ValidationSubsystem.Create();
+ var parseResult = CliParser.Parse(command, input, new CliConfiguration(command));
+ var pipelineResult = new PipelineResult(parseResult, input, Pipeline.CreateEmpty());
+ validationSubsystem.Execute(pipelineResult);
+ return pipelineResult;
+ }
+
+ [Fact]
+ public void StringValidationShouldPassWhenInputInLowerCase()
+ {
+ var option = new CliOption("--opt");
+ option.SetCasing("lower");
+ var command = new CliCommand("cmd");
+ command.Options.Add(option);
+
+ var input = "--opt letuswin";
+
+ var pipelineResult = ExecutedPipelineResultForCommand(command, input);
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void StringValidationShouldFailWhenInputNotInLowerCase()
+ {
+ var option = new CliOption("--opt");
+ option.SetCasing("lower");
+ var command = new CliCommand("cmd");
+ command.Options.Add(option);
+
+ var input = "--opt LetUsWin";
+
+ var pipelineResult = ExecutedPipelineResultForCommand(command, input);
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void StringValidationShouldFailWhenInputNotInUpperCase()
+ {
+ var option = new CliOption("--opt");
+ option.SetCasing("lower");
+ var command = new CliCommand("cmd");
+ command.Options.Add(option);
+
+ var input = "--opt LetUsWin";
+
+ var pipelineResult = ExecutedPipelineResultForCommand(command, input);
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().HaveCount(1);
+ }
+
+ [Fact]
+ public void StringValidationShouldPassWhenInputInUpperCase()
+ {
+ var option = new CliOption("--opt");
+ option.SetCasing("upper");
+ var command = new CliCommand("cmd");
+ command.Options.Add(option);
+
+ var input = "--opt GOODWORK!";
+
+ var pipelineResult = ExecutedPipelineResultForCommand(command, input);
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void StringValidationShouldThrowExceptionWhenInputIsEmptyAndCasingIsSet()
+ {
+ var option = new CliOption("--opt");
+ option.SetCasing("lower");
+ var command = new CliCommand("cmd");
+ command.Options.Add(option);
+
+ var input = "--opt ";
+
+ Assert.Throws(() => ExecutedPipelineResultForCommand(command, input));
+ }
+
+ [Fact]
+ public void StringValidationShouldPassWhenInputIsAllNumericWithLower()
+ {
+ var option = new CliOption("--opt");
+ option.SetCasing("lower");
+ var command = new CliCommand("cmd");
+ command.Options.Add(option);
+
+ var input = "--opt fuzzy123";
+
+ var pipelineResult = ExecutedPipelineResultForCommand(command, input);
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void StringValidationShouldPassWhenInputIsAllNumericWithUpper()
+ {
+ var option = new CliOption("--opt");
+ option.SetCasing("upper");
+ var command = new CliCommand("cmd");
+ command.Options.Add(option);
+
+ var input = "--opt FUZZY123";
+
+ var pipelineResult = ExecutedPipelineResultForCommand(command, input);
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().BeEmpty();
+ }
+}
diff --git a/System.CommandLine.sln b/System.CommandLine.sln
index 2b4452b1d5..b3d9488c91 100644
--- a/System.CommandLine.sln
+++ b/System.CommandLine.sln
@@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Packages.props = Directory.Packages.props
global.json = global.json
LICENSE.md = LICENSE.md
+ OpenQuestions.md = OpenQuestions.md
README.md = README.md
restore.cmd = restore.cmd
restore.sh = restore.sh
@@ -29,37 +30,21 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine", "src\S
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Tests", "src\System.CommandLine.Tests\System.CommandLine.Tests.csproj", "{F843CCCA-4CC9-422C-A881-3AE6A998B53F}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{6749FB3E-39DE-4321-A39E-525278E9408D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DragonFruit", "samples\DragonFruit\DragonFruit.csproj", "{8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.DragonFruit", "src\System.CommandLine.DragonFruit\System.CommandLine.DragonFruit.csproj", "{EEC30462-078F-45EB-AA70-12E3170CD51E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.DragonFruit.Tests", "src\System.CommandLine.DragonFruit.Tests\System.CommandLine.DragonFruit.Tests.csproj", "{1F4B2074-F651-4A02-A860-7DDA74B2CC5F}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-suggest", "src\System.CommandLine.Suggest\dotnet-suggest.csproj", "{E23C760E-B826-4B4F-BE76-916D86BAD2DB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-suggest.Tests", "src\System.CommandLine.Suggest.Tests\dotnet-suggest.Tests.csproj", "{E41F0471-B14D-4FA0-9D8B-1E7750695AE9}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RenderingPlayground", "samples\RenderingPlayground\RenderingPlayground.csproj", "{8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Rendering", "src\System.CommandLine.Rendering\System.CommandLine.Rendering.csproj", "{27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Rendering.Tests", "src\System.CommandLine.Rendering.Tests\System.CommandLine.Rendering.Tests.csproj", "{9E574595-A9CD-441A-9328-1D4DD5B531E8}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Hosting", "src\System.CommandLine.Hosting\System.CommandLine.Hosting.csproj", "{644C4B4A-4A32-4307-9F71-C3BF901FFB66}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Hosting.Tests", "src\System.CommandLine.Hosting.Tests\System.CommandLine.Hosting.Tests.csproj", "{39483140-BC26-4CAD-BBAE-3DC76C2F16CF}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.ApiCompatibility.Tests", "src\System.CommandLine.ApiCompatibility.Tests\System.CommandLine.ApiCompatibility.Tests.csproj", "{A54EE328-D456-4BAF-A180-84E77E6409AC}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostingPlayground", "samples\HostingPlayground\HostingPlayground.csproj", "{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Extended", "src\System.CommandLine.Extended\System.CommandLine.Extended.csproj", "{D77D8EE4-7FBA-425C-AEE6-D6908998E228}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Generator.CommandHandler", "src\System.CommandLine.Generator.CommandHandler\System.CommandLine.Generator.CommandHandler.csproj", "{591EF370-7AD7-4624-8B9D-FD15010CA657}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Extended.Tests", "src\System.CommandLine.Extended.Tests\System.CommandLine.Extended.Tests.csproj", "{9E93F66A-6099-4675-AF53-FC10DE01925B}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.NamingConventionBinder", "src\System.CommandLine.NamingConventionBinder\System.CommandLine.NamingConventionBinder.csproj", "{10DFE204-B027-49DA-BD77-08ECA18DD357}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Subsystems", "src\System.CommandLine.Subsystems\System.CommandLine.Subsystems.csproj", "{D750F504-DEBB-47B1-89AC-BB12B796E7B9}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.NamingConventionBinder.Tests", "src\System.CommandLine.NamingConventionBinder.Tests\System.CommandLine.NamingConventionBinder.Tests.csproj", "{789A05F2-5EF6-4FE8-9609-4706207E047E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Subsystems.Tests", "src\System.CommandLine.Subsystems.Tests\System.CommandLine.Subsystems.Tests.csproj", "{7D6F74A4-28E4-4B57-8A4B-415A533729A7}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.ApiCompatibility.Tests", "src\System.CommandLine.ApiCompatibility.Tests\System.CommandLine.ApiCompatibility.Tests.csproj", "{A54EE328-D456-4BAF-A180-84E77E6409AC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EndToEndTestApp", "src\System.CommandLine.Suggest.Tests\EndToEndTestApp\EndToEndTestApp.csproj", "{8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -95,42 +80,6 @@ Global
{F843CCCA-4CC9-422C-A881-3AE6A998B53F}.Release|x64.Build.0 = Release|Any CPU
{F843CCCA-4CC9-422C-A881-3AE6A998B53F}.Release|x86.ActiveCfg = Release|Any CPU
{F843CCCA-4CC9-422C-A881-3AE6A998B53F}.Release|x86.Build.0 = Release|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Debug|x64.ActiveCfg = Debug|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Debug|x64.Build.0 = Debug|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Debug|x86.ActiveCfg = Debug|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Debug|x86.Build.0 = Debug|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Release|Any CPU.Build.0 = Release|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Release|x64.ActiveCfg = Release|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Release|x64.Build.0 = Release|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Release|x86.ActiveCfg = Release|Any CPU
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00}.Release|x86.Build.0 = Release|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Debug|x64.ActiveCfg = Debug|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Debug|x64.Build.0 = Debug|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Debug|x86.ActiveCfg = Debug|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Debug|x86.Build.0 = Debug|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Release|Any CPU.Build.0 = Release|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Release|x64.ActiveCfg = Release|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Release|x64.Build.0 = Release|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Release|x86.ActiveCfg = Release|Any CPU
- {EEC30462-078F-45EB-AA70-12E3170CD51E}.Release|x86.Build.0 = Release|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Debug|x64.ActiveCfg = Debug|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Debug|x64.Build.0 = Debug|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Debug|x86.ActiveCfg = Debug|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Debug|x86.Build.0 = Debug|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Release|Any CPU.Build.0 = Release|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Release|x64.ActiveCfg = Release|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Release|x64.Build.0 = Release|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Release|x86.ActiveCfg = Release|Any CPU
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F}.Release|x86.Build.0 = Release|Any CPU
{E23C760E-B826-4B4F-BE76-916D86BAD2DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E23C760E-B826-4B4F-BE76-916D86BAD2DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E23C760E-B826-4B4F-BE76-916D86BAD2DB}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -155,114 +104,6 @@ Global
{E41F0471-B14D-4FA0-9D8B-1E7750695AE9}.Release|x64.Build.0 = Release|Any CPU
{E41F0471-B14D-4FA0-9D8B-1E7750695AE9}.Release|x86.ActiveCfg = Release|Any CPU
{E41F0471-B14D-4FA0-9D8B-1E7750695AE9}.Release|x86.Build.0 = Release|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Debug|x64.ActiveCfg = Debug|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Debug|x64.Build.0 = Debug|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Debug|x86.ActiveCfg = Debug|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Debug|x86.Build.0 = Debug|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Release|Any CPU.Build.0 = Release|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Release|x64.ActiveCfg = Release|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Release|x64.Build.0 = Release|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Release|x86.ActiveCfg = Release|Any CPU
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E}.Release|x86.Build.0 = Release|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Debug|x64.ActiveCfg = Debug|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Debug|x64.Build.0 = Debug|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Debug|x86.ActiveCfg = Debug|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Debug|x86.Build.0 = Debug|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Release|Any CPU.Build.0 = Release|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Release|x64.ActiveCfg = Release|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Release|x64.Build.0 = Release|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Release|x86.ActiveCfg = Release|Any CPU
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E}.Release|x86.Build.0 = Release|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Debug|x64.ActiveCfg = Debug|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Debug|x64.Build.0 = Debug|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Debug|x86.ActiveCfg = Debug|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Debug|x86.Build.0 = Debug|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Release|Any CPU.Build.0 = Release|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Release|x64.ActiveCfg = Release|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Release|x64.Build.0 = Release|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Release|x86.ActiveCfg = Release|Any CPU
- {9E574595-A9CD-441A-9328-1D4DD5B531E8}.Release|x86.Build.0 = Release|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Debug|x64.ActiveCfg = Debug|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Debug|x64.Build.0 = Debug|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Debug|x86.ActiveCfg = Debug|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Debug|x86.Build.0 = Debug|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Release|Any CPU.Build.0 = Release|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Release|x64.ActiveCfg = Release|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Release|x64.Build.0 = Release|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Release|x86.ActiveCfg = Release|Any CPU
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66}.Release|x86.Build.0 = Release|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Debug|x64.ActiveCfg = Debug|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Debug|x64.Build.0 = Debug|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Debug|x86.ActiveCfg = Debug|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Debug|x86.Build.0 = Debug|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Release|Any CPU.Build.0 = Release|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Release|x64.ActiveCfg = Release|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Release|x64.Build.0 = Release|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Release|x86.ActiveCfg = Release|Any CPU
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF}.Release|x86.Build.0 = Release|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|x64.ActiveCfg = Debug|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|x64.Build.0 = Debug|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|x86.ActiveCfg = Debug|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Debug|x86.Build.0 = Debug|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|Any CPU.Build.0 = Release|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x64.ActiveCfg = Release|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x64.Build.0 = Release|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x86.ActiveCfg = Release|Any CPU
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x86.Build.0 = Release|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Debug|x64.ActiveCfg = Debug|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Debug|x64.Build.0 = Debug|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Debug|x86.ActiveCfg = Debug|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Debug|x86.Build.0 = Debug|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Release|Any CPU.Build.0 = Release|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Release|x64.ActiveCfg = Release|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Release|x64.Build.0 = Release|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Release|x86.ActiveCfg = Release|Any CPU
- {591EF370-7AD7-4624-8B9D-FD15010CA657}.Release|x86.Build.0 = Release|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Debug|x64.ActiveCfg = Debug|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Debug|x64.Build.0 = Debug|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Debug|x86.ActiveCfg = Debug|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Debug|x86.Build.0 = Debug|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Release|Any CPU.Build.0 = Release|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Release|x64.ActiveCfg = Release|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Release|x64.Build.0 = Release|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Release|x86.ActiveCfg = Release|Any CPU
- {10DFE204-B027-49DA-BD77-08ECA18DD357}.Release|x86.Build.0 = Release|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Debug|x64.ActiveCfg = Debug|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Debug|x64.Build.0 = Debug|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Debug|x86.ActiveCfg = Debug|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Debug|x86.Build.0 = Debug|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Release|Any CPU.Build.0 = Release|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Release|x64.ActiveCfg = Release|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Release|x64.Build.0 = Release|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Release|x86.ActiveCfg = Release|Any CPU
- {789A05F2-5EF6-4FE8-9609-4706207E047E}.Release|x86.Build.0 = Release|Any CPU
{A54EE328-D456-4BAF-A180-84E77E6409AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A54EE328-D456-4BAF-A180-84E77E6409AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A54EE328-D456-4BAF-A180-84E77E6409AC}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -275,6 +116,66 @@ Global
{A54EE328-D456-4BAF-A180-84E77E6409AC}.Release|x64.Build.0 = Release|Any CPU
{A54EE328-D456-4BAF-A180-84E77E6409AC}.Release|x86.ActiveCfg = Release|Any CPU
{A54EE328-D456-4BAF-A180-84E77E6409AC}.Release|x86.Build.0 = Release|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Debug|x64.Build.0 = Debug|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Debug|x86.Build.0 = Debug|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Release|x64.ActiveCfg = Release|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Release|x64.Build.0 = Release|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Release|x86.ActiveCfg = Release|Any CPU
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228}.Release|x86.Build.0 = Release|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Debug|x64.Build.0 = Debug|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Debug|x86.Build.0 = Debug|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Release|x64.ActiveCfg = Release|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Release|x64.Build.0 = Release|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Release|x86.ActiveCfg = Release|Any CPU
+ {9E93F66A-6099-4675-AF53-FC10DE01925B}.Release|x86.Build.0 = Release|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Debug|x64.Build.0 = Debug|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Debug|x86.Build.0 = Debug|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Release|x64.ActiveCfg = Release|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Release|x64.Build.0 = Release|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Release|x86.ActiveCfg = Release|Any CPU
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9}.Release|x86.Build.0 = Release|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Debug|x64.Build.0 = Debug|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Debug|x86.Build.0 = Debug|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Release|x64.ActiveCfg = Release|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Release|x64.Build.0 = Release|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Release|x86.ActiveCfg = Release|Any CPU
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7}.Release|x86.Build.0 = Release|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Debug|x64.Build.0 = Debug|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Debug|x86.Build.0 = Debug|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Release|x64.ActiveCfg = Release|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Release|x64.Build.0 = Release|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Release|x86.ActiveCfg = Release|Any CPU
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -282,21 +183,14 @@ Global
GlobalSection(NestedProjects) = preSolution
{0BE8E56E-7580-4526-BE24-D304E1779724} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
{F843CCCA-4CC9-422C-A881-3AE6A998B53F} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
- {8A592CB0-5FB9-4E70-A68A-BE5B5BE23C00} = {6749FB3E-39DE-4321-A39E-525278E9408D}
- {EEC30462-078F-45EB-AA70-12E3170CD51E} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
- {1F4B2074-F651-4A02-A860-7DDA74B2CC5F} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
{E23C760E-B826-4B4F-BE76-916D86BAD2DB} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
{E41F0471-B14D-4FA0-9D8B-1E7750695AE9} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
- {8D9A8DCB-DC74-4B3A-B1C6-046C9C4F458E} = {6749FB3E-39DE-4321-A39E-525278E9408D}
- {27E3BFFC-4412-4E4C-A656-B9D35B8A0F3E} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
- {9E574595-A9CD-441A-9328-1D4DD5B531E8} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
- {644C4B4A-4A32-4307-9F71-C3BF901FFB66} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
- {39483140-BC26-4CAD-BBAE-3DC76C2F16CF} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
- {0BF6958D-9EE3-4623-B3D6-4DA77EAC1906} = {6749FB3E-39DE-4321-A39E-525278E9408D}
- {591EF370-7AD7-4624-8B9D-FD15010CA657} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
- {10DFE204-B027-49DA-BD77-08ECA18DD357} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
- {789A05F2-5EF6-4FE8-9609-4706207E047E} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
{A54EE328-D456-4BAF-A180-84E77E6409AC} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
+ {D77D8EE4-7FBA-425C-AEE6-D6908998E228} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
+ {9E93F66A-6099-4675-AF53-FC10DE01925B} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
+ {D750F504-DEBB-47B1-89AC-BB12B796E7B9} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
+ {7D6F74A4-28E4-4B57-8A4B-415A533729A7} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
+ {8DAAEC0E-0B7C-4BEB-BD04-E197820E3568} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5C159F93-800B-49E7-9905-EE09F8B8434A}
diff --git a/ValidationSubsystemTests.cs b/ValidationSubsystemTests.cs
new file mode 100644
index 0000000000..4d6d97e8bb
--- /dev/null
+++ b/ValidationSubsystemTests.cs
@@ -0,0 +1,187 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using FluentAssertions;
+using System.CommandLine.Parsing;
+using System.CommandLine.ValueSources;
+using Xunit;
+
+namespace System.CommandLine.Subsystems.Tests;
+
+public class ValidationSubsystemTests
+{
+ // Running exactly the same code is important here because missing a step will result in a false positive. Ask me how I know
+ private CliOption GetOptionWithSimpleRange(T lowerBound, T upperBound)
+ where T : IComparable
+ {
+ var option = new CliOption("--intOpt");
+ option.SetRange(lowerBound, upperBound);
+ return option;
+ }
+
+ private CliOption GetOptionWithRangeBounds(ValueSource lowerBound, ValueSource upperBound)
+ where T : IComparable
+ {
+ var option = new CliOption("--intOpt");
+ option.SetRange(lowerBound, upperBound);
+ return option;
+ }
+
+ private PipelineResult ExecutedPipelineResultForRangeOption(CliOption option, string input)
+ {
+ var command = new CliRootCommand { option };
+ return ExecutedPipelineResultForCommand(command, input);
+ }
+
+ private PipelineResult ExecutedPipelineResultForCommand(CliCommand command, string input)
+ {
+ var validationSubsystem = ValidationSubsystem.Create();
+ var parseResult = CliParser.Parse(command, input, new CliConfiguration(command));
+ var pipelineResult = new PipelineResult(parseResult, input, Pipeline.CreateEmpty());
+ validationSubsystem.Execute(pipelineResult);
+ return pipelineResult;
+ }
+
+ [Fact]
+ public void Int_values_in_specified_range_do_not_have_errors()
+ {
+ var option = GetOptionWithSimpleRange(0, 50);
+
+ var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt 42");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void Int_values_above_upper_bound_report_error()
+ {
+ var option = GetOptionWithSimpleRange(0, 5);
+
+ var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt 42");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().HaveCount(1);
+ var error = pipelineResult.GetErrors().First();
+ // TODO: Create test mechanism for CliDiagnostics
+ }
+
+ [Fact]
+ public void Int_below_lower_bound_report_error()
+ {
+ var option = GetOptionWithSimpleRange(0, 5);
+
+ var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt -42");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().HaveCount(1);
+ var error = pipelineResult.GetErrors().First();
+ // TODO: Create test mechanism for CliDiagnostics
+ }
+
+ [Fact]
+ public void Int_values_on_lower_range_bound_do_not_report_error()
+ {
+ var option = GetOptionWithSimpleRange(42, 50);
+
+ var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt 42");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void Int_values_on_upper_range_bound_do_not_report_error()
+ {
+ var option = GetOptionWithSimpleRange(0, 42);
+
+ var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt 42");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void Values_below_calculated_lower_bound_report_error()
+ {
+ var option = GetOptionWithRangeBounds(ValueSource.Create(() => (true, 1)), 50);
+
+ var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt 0");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().HaveCount(1);
+ var error = pipelineResult.GetErrors().First();
+ // TODO: Create test mechanism for CliDiagnostics
+ }
+
+
+ [Fact]
+ public void Values_within_calculated_range_do_not_report_error()
+ {
+ var option = GetOptionWithRangeBounds(ValueSource.Create(() => (true, 1)), ValueSource.Create(() => (true, 50)));
+
+ var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt 42");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void Values_above_calculated_upper_bound_report_error()
+ {
+ var option = GetOptionWithRangeBounds(0, ValueSource.Create(() => (true, 40)));
+
+ var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt 42");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().HaveCount(1);
+ var error = pipelineResult.GetErrors().First();
+ // TODO: Create test mechanism for CliDiagnostics
+ }
+
+ [Fact]
+ public void Values_below_relative_lower_bound_report_error()
+ {
+ var otherOption = new CliOption("-all");
+ var option = GetOptionWithRangeBounds(ValueSource.Create(otherOption, o => (true, (int)o + 1)), 50);
+ var command = new CliCommand("cmd") { option, otherOption };
+
+ var pipelineResult = ExecutedPipelineResultForCommand(command, "--intOpt 0 -all 0");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().HaveCount(1);
+ var error = pipelineResult.GetErrors().First();
+ // TODO: Create test mechanism for CliDiagnostics
+ }
+
+
+ [Fact]
+ public void Values_within_relative_range_do_not_report_error()
+ {
+ var otherOption = new CliOption("--all");
+ var option = GetOptionWithRangeBounds(ValueSource.Create(otherOption, o => (true, (int)o + 1)), ValueSource.Create(otherOption, o => (true, (int)o + 10)));
+ var command = new CliCommand("cmd") { option, otherOption };
+
+ var pipelineResult = ExecutedPipelineResultForCommand(command, "--intOpt 11 --all 3");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void Values_above_relative_upper_bound_report_error()
+ {
+ var otherOption = new CliOption("-all");
+ var option = GetOptionWithRangeBounds(0, ValueSource.Create(otherOption, o => (true, (int)o + 10)));
+ var command = new CliCommand("cmd") { option, otherOption };
+
+ var pipelineResult = ExecutedPipelineResultForCommand(command, "--intOpt 9 -all -2");
+
+ pipelineResult.Should().NotBeNull();
+ pipelineResult.GetErrors().Should().HaveCount(1);
+ var error = pipelineResult.GetErrors().First();
+ // TODO: Create test mechanism for CliDiagnostics
+ }
+
+
+}
diff --git a/ValueConditionAnnotationExtensions.cs b/ValueConditionAnnotationExtensions.cs
new file mode 100644
index 0000000000..08adac9da4
--- /dev/null
+++ b/ValueConditionAnnotationExtensions.cs
@@ -0,0 +1,146 @@
+using System.CommandLine.Subsystems.Annotations;
+using System.CommandLine.Validation;
+using System.CommandLine.ValueConditions;
+using System.CommandLine.ValueSources;
+
+namespace System.CommandLine;
+
+///
+/// Contains the extension methods that are used to create value conditions
+///
+public static class ValueConditionAnnotationExtensions
+{
+ ///
+ /// Set the upper and/or lower bound values of the range.
+ ///
+ /// The type of the bounds.
+ /// The option or argument the range applies to.
+ /// The lower bound of the range.
+ /// The upper bound of the range.
+ // TODO: Add RangeBounds
+ // TODO: You should not have to set both...why not nullable?
+ public static void SetRange(this CliValueSymbol symbol, T lowerBound, T upperBound)
+ where T : IComparable
+ {
+ var range = new Range(lowerBound, upperBound);
+
+ symbol.SetValueCondition(range);
+ }
+
+ ///
+ /// Set the possible casing value for the string input value.
+ ///
+ /// The type of the bounds.
+ /// The option or argument the range applies to.
+ /// This could either be lower or upper.
+ public static void SetCasing(this CliValueSymbol symbol, string casing)
+ {
+ symbol.SetValueCondition(new StringCase(casing));
+ }
+
+ ///
+ /// Set the upper and/or lower bound via ValueSource. Implicit conversions means this
+ /// generally just works with any .
+ ///
+ /// The type of the bounds.
+ /// The option or argument the range applies to.
+ /// The that is the lower bound of the range.
+ /// The that is the upper bound of the range.
+ // TODO: Add RangeBounds
+ // TODO: You should not have to set both...why not nullable?
+ public static void SetRange(this CliValueSymbol symbol, ValueSource lowerBound, ValueSource upperBound)
+ where T : IComparable
+ // TODO: You should not have to set both...why not nullable?
+ {
+ var range = new Range(lowerBound, upperBound);
+
+ symbol.SetValueCondition(range);
+ }
+
+ ///
+ /// Indicates that there is an inclusive group of options and arguments for the command. All
+ /// members of an inclusive must be present, or none can be present.
+ ///
+ /// The command the inclusive group applies to.
+ /// The group of options and arguments that must all be present, or none can be present.
+ public static void SetInclusiveGroup(this CliCommand command, IEnumerable group)
+ => command.SetValueCondition(new InclusiveGroup(group));
+
+ // TODO: This should not be public if ValueConditions are not public
+ public static void SetValueCondition(this TValueSymbol symbol, TValueCondition valueCondition)
+ where TValueSymbol : CliValueSymbol
+ where TValueCondition : ValueCondition
+ {
+ if (!symbol.TryGetAnnotation>(ValueConditionAnnotations.ValueConditions, out var valueConditions))
+ {
+ valueConditions = [];
+ symbol.SetAnnotation(ValueConditionAnnotations.ValueConditions, valueConditions);
+ }
+ valueConditions.Add(valueCondition);
+ }
+
+ // TODO: This should not be public if ValueConditions are not public
+ public static void SetValueCondition(this CliCommand symbol, TCommandCondition commandCondition)
+ where TCommandCondition : CommandCondition
+ {
+ if (!symbol.TryGetAnnotation>(ValueConditionAnnotations.ValueConditions, out var valueConditions))
+ {
+ valueConditions = [];
+ symbol.SetAnnotation(ValueConditionAnnotations.ValueConditions, valueConditions);
+ }
+ valueConditions.Add(commandCondition);
+ }
+
+ ///
+ /// Gets a list of conditions on an option or argument.
+ ///
+ /// The option or argument to get the conditions for.
+ /// The conditions that have been applied to the option or argument.
+ ///
+ // TODO: This is public because it will be used by other subsystems we might not own. It could be an extension method the subsystem namespace
+ public static List? GetValueConditions(this CliValueSymbol symbol)
+ => symbol.TryGetAnnotation>(ValueConditionAnnotations.ValueConditions, out var valueConditions)
+ ? valueConditions
+ : null;
+
+ ///
+ /// Gets a list of conditions on a command.
+ ///
+ /// The command to get the conditions for.
+ /// The conditions that have been applied to the command.
+ ///
+ // TODO: This is public because it will be used by other subsystems we might not own. It could be an extension method the subsystem namespace
+ public static List? GetCommandConditions(this CliCommand command)
+ => command.TryGetAnnotation>(ValueConditionAnnotations.ValueConditions, out var valueConditions)
+ ? valueConditions
+ : null;
+
+ ///
+ /// Gets the condition that matches the type, if it exists on this option or argument.
+ ///
+ /// The type of condition to return.
+ /// The option or argument that may contain the condition.
+ /// The condition if it exists on the option or argument, otherwise null.
+ // This method feels useful because it clarifies that last should win and returns one, when only one should be applied
+ // TODO: Consider removing user facing naming, other than the base type, that is Value or CommandCondition and just use Condition
+ public static TCondition? GetValueCondition(this CliValueSymbol symbol)
+ where TCondition : ValueCondition
+ => !symbol.TryGetAnnotation(ValueConditionAnnotations.ValueConditions, out List? valueConditions)
+ ? null
+ : valueConditions.OfType().LastOrDefault();
+
+ ///
+ /// Gets the condition that matches the type, if it exists on this command.
+ ///
+ /// The type of condition to return.
+ /// The command that may contain the condition.
+ /// The condition if it exists on the command, otherwise null.
+ // This method feels useful because it clarifies that last should win and returns one, when only one should be applied
+ public static TCondition? GetCommandCondition(this CliCommand symbol)
+ where TCondition : CommandCondition
+ => !symbol.TryGetAnnotation(ValueConditionAnnotations.ValueConditions, out List? valueConditions)
+ ? null
+ : valueConditions.OfType().LastOrDefault();
+
+
+}
diff --git a/docs/proposals/extensibility-overview.md b/docs/proposals/extensibility-overview.md
new file mode 100644
index 0000000000..af6c6fd983
--- /dev/null
+++ b/docs/proposals/extensibility-overview.md
@@ -0,0 +1,369 @@
+# System.CommandLine Extensibility
+
+## Overview
+
+`System.CommandLine` is still in preview, yet it had 1.3 million downloads over the past 6 weeks, and is a key dependency of the .NET CLI. There is no other stable first-party story for handling command-line options. This is a problem for authors of command-line tools in the .NET team and in the community, as handling command-line input in a way that is correct and consistent with other tools in the ecosystem is nontrivial.
+
+However, the main reason `System.CommandLine` is in preview is that it has not passed API review and has some open questions and opinionated decisions that have not been fully validated with customers. Due to the large amount of functionality, this is currently difficult to resolve. Some functionality is certain to change, but this is commingled with the parser, which cannot change behavior without breaking scripts that use CLI tools.
+
+This document proposes a progressive approach to landing `System.CommandLine` by breaking it up into layered pieces that can be individually finalized. These pieces are intended to be composable, with tightly scoped default implementations. Developers with custom needs and developers of other command-line libraries (such as `Spectre.Console`) will be able to build on top of these pieces and/or provide richer drop-in replacements.
+
+## Layering
+
+### Parser
+
+Implementing command-line parsing in a complete and consistent way is difficult due to many details such as POSIX behavior, aliases, option bundling, escaping, arity, etc. `System.CommandLine` has a robust and well-validated CLI parser, but it is somewhat coupled with `System.CommandLine`’s implementation of higher-level concerns such as validation, help, and invocation.
+
+The parser has an API for constructing a “grammar”, a tree of nodes describing the CLI’s commands, options and arguments. The parser uses this grammar to parse the args array, producing a parse result.
+
+The parser layer is a low-level API that is not intended to be replaceable and is minimally extensible. It’s a foundational component for higher-level APIs, third-party command-line libraries and developers with custom needs, providing them with a command-line parsing implementation that is complete and correct. This will help promote consistent command-line parsing behavior across the .NET ecosystem.
+
+#### Parse Result
+
+The parser produces a ParseResult that is a structured representation of the argument array analogous to an AST. It contains `CommandResult`, `OptionResult` and `ArgumentResult` objects that represent the commands, arguments and options that were parsed from the args array and preserve the order from the args array. Commands, options and arguments that were defined in the grammar but were not found in the args array will not be present in the `ParseResult`. The result is purely a representation of what was parsed, and determining the value of a specific option or argument (including handling of default values) is the responsibility of higher layers.
+
+These nodes have `ArgsOffset` and `ArgsLength` properties that indicate the portion of the args array from which they were parsed. This allows advanced scenarios such as printing errors with a position marker.
+
+If an option has an optional value, such as bool options treating `--enabled` as equivalent to `--enabled=true`, then the `Option` will have a property indicating whether this optional value was provided or not.
+
+Determining the value of a specific option or argument may involve inspecting other options, or options on parent commands, or using a default value. This will generally be done via the subsystem extensibility layer.
+
+The result also contains a collection of any errors that were found during parsing. These errors use a Roslyn-like descriptor model, where the prototypical error that defines the format string and error code is separate from the error instance, which contains a reference to the descriptor along with position data and format data.
+
+#### Type Conversion
+
+The parser will support C# primitive types in its `Option` and `Argument`. We may also choose to add support for commonly used types such as `DateTime`, `FileInfo`, and `DirectoryInfo`.
+
+Developers will be able to use other types if they provide a converter. However, this type conversion will not use the current CustomParser extensibility model, as it exposes a low-level Symbol/Token API that we would like to keep internal for now. Instead, it will have a simple, reflection-free type converter that allows will allow `Option` and `Argument` to be used with non-primitive types, without exposing a complex symbol/token API. This model would allow converting any primitive type or array of primitive types:
+
+```csharp
+delegate TOutput TypeConverter(TInput input);
+
+Option locationOpt = new Option(“--location”) {
+ TypeConverter = (int[] s) => new Point(s[0],s[1]))
+}
+```
+
+A standard `ParserErrorException` exception type would be used for returning user-visible error messages, and the parser would catch these and add them to its error collection. For convenience, the parser would also collect all other exceptions from type conversion and add a generic “Invalid format” error.
+
+Type converters are only expected to perform parsing and construction. They are not expected to provide any other kind of validation, as that should be handled in the validation subsystem, which is also responsible for printing any `ParserErrors` in the `ParseResult`.
+
+#### Reusable Type Converters
+
+Perhaps we could also allow registering these type converters on the RootCommand so that they do not need to be provided for every option/argument that uses the same type.
+
+```csharp
+
+var rootCommand = new RootCommand()
+ .WithTypeConverter(s => new Point(s[0],s[1]));
+```
+
+This would also allow libraries to provide helper methods to register converters on the RootCommand:
+
+```csharp
+static RootCommand WithPointConverter(this RootCommand rootCommand)
+ => root.WithTypeConverter(s => new Point(s[0],s[1]));
+```
+
+Alternatively, developers using might choose to subclass the option or argument type:
+
+```csharp
+class PointOption : Option {
+ public PointOption(string name) : base(name) {
+ TypeConverter = s => new Point(s[0],s[1]));
+ }
+}
+```
+
+### Subsystems
+
+A subsystem operates on the parse result and performs some action based on the parser grammar and parse result. The core subsystems are help, validation, invocation, and completion. Developers using the low-level parser API may use any or all the subsystems directly, and higher-level command-line APIs may use subsystems internally.
+
+The subsystems envisaged are:
+
+* Error handling: print formatted errors.
+* Help: set help information, handle the `--help` option, and print formatted help.
+* Completion: handle the completion option.
+* Validation: set constraints, and check values respect constraints.
+* Invocation: set handler delegates, and dispatch to the correct one.
+* Defaults: set default values, and determine values for all arguments and options that take these defaults into account.
+
+Subsystems are intended to allow drop-in replacements that go beyond the functionality of the default implementations. For example, an alternate help subsystem may format the help output differently. A developer may obtain an alternate subsystem from a NuGet package or implement their own. Higher-level command-line APIs are expected to use relevant subsystems internally and allow developers to optionally override them with alternate subsystems.
+
+Subsystems may require or be influenced by additional information associated with parser nodes. For example:
+
+* The invocation subsystem requires handler delegates to be attached to command grammar nodes so they can be invoked when that command is present in the parse result.
+* The help subsystem’s output can be enriched by adding help descriptions to the command, option and argument grammar nodes.
+* An alternate help subsystem may support additional information such as examples or hyperlinks.
+* The existing System.CommandLine.NamingConventionBinder could become an alternate invocation layer, allowing strongly typed command handler delegates with parameters corresponding to the command’s options and arguments, and binding them automatically when the command is invoked.
+
+These subsystem annotations do not influence parsing so do not need to be coupled with the parser layer. High-performance scenarios may wish to lazily provide some annotations, such as only loading help descriptions when help is invoked, and alternate subsystems may define and use additional annotations. For this reason, an extensible model for subsystem annotations will be provided at the subsystem layer and is detailed later in this document.
+
+Although alternate subsystems may have additional annotations, they are expected to use the annotations of the default subsystems where possible, so that when alternate subsystems are dropped into an existing application, they use any relevant information that the developer has already provided.
+
+### Binding/Model
+
+A binding/model layer wraps the parser and subsystem layers in a high-level, user-friendly API. In the long term, most developers of CLI apps should be using a model/binding layer. A binding/model layer is by nature opinionated, and there are many different idioms it could use. However, they can all build on top of the parser and subsystems layers, and allow drop-in replacement of subsystems.
+
+An example of a model/binding layer is `System.CommandLine`’s `DragonFruit` API, which allows developers to define their CLI by writing a Main method with strongly typed parameters corresponding to the CLI’s options and arguments. Using a Roslyn generator, DragonFruit internally constructs a parser grammar using the method’s signature, converting default parameter values into default option values, and converting doc comments into help descriptions. On invocation it binds the command-line options and arguments to the method’s arguments and invokes the method.
+
+## Stabilization Status
+
+The parser layer’s API is almost shovel-ready for inclusion in the BCL in .NET 9, and we should be able to re-use much of the implementation and tests from System.CommandLine.
+
+The next target will be to stabilize the subsystems. The subsystems may not become part of the BCL but will be a stable package. When the .NET CLI migrates to this package, this will eliminate its preview dependency, as it does not use a binding/model layer.
+The default subsystems will be a straightforward transformation of the existing functionality used by the .NET CLI. However, the subsystem API and annotation APIs will require some discussion.
+
+The model/binding layer requires more experimentation with different forms of binding/model layer to validate with developers before committing to using one for the Console App templates in Visual Studio and the .NET SDK. We may end up with multiple, and there will no doubt be third party ones.
+
+## Subsystem API Pattern
+
+The subsystems follow a common pattern for initialization, annotations, and invocation.
+
+### Initialization
+
+All subsystems have an initialization call to create the subsystem and apply any settings. This initialization call may require a RootCommand instance so it can add options, such as the --help option required by the help subsystem.
+
+One question here is whether subsystems should be locals or should be attached to the `RootCommand` or some other collection such as a new `CliPipeline` class. Ideally the subsystems would not be stored on the `RootCommand` as that would require either putting the concepts of subsystems in the parser layer (e.g. an abstract `CliSubsystem` class and a `Dictionary` on `RootCommand`) or having a completely generic `PropertyBag`-like object storage mechanism on `RootCommand`, which is not generally considered a good pattern for the BCL. As developers will need to create an instance of a subsystem to opt into using that subsystem, it seems reasonable to store them in locals. The value of storing the subsystem instances in a standard place would be if that made it easier for extension methods to locate the subsystem instance.
+
+The developer must be able to provide an instance of an alternate subsystem, or an instance of the subsystem configured with custom options. The subsystem may also have an optional parameter for an annotation provider, which allows performance-sensitive developers to perform lazy lookup of annotations instead of setting them upfront.
+
+For example, a local-based initialization call might simply be a constructor call:
+
+```csharp
+var help = new MyAlternateHelpSubsystem(helpAnnotationProvider);
+```
+
+### Annotations
+
+#### Annotation Storage
+
+A subsystem must also provide methods to annotate `CliSymbol` grammar nodes (`CliCommand`, `CliOption` and `CLiArgument`) with arbitrary string-keyed data. An open question is how this data should be attached.
+
+It would be desirable to allow setting symbol-specific annotations directly on the symbol, e.g.
+
+```csharp
+command.SetDescription("This is a description");
+```
+
+However, this would require the parser layer to be aware of the concept of subsystem annotations and to expose a `PropertyBag`-like model for storage of arbitrary data, which is very unlikely to pass BCL API review. Alternatively the subsystem layer could add subclasses for all the `CliSymbol` derived classes to store this data, but this creates a bifurcation in the usage of the parser API. The last option would be to use a hidden static `ConditionalWeakTable` to associate annotation data with symbol instances, but magically storing instance data in a hidden static field is not a good pattern, and has problematic implications around performance and threading.
+
+Instead, we could make each subsystem responsible for storing its own annotation data. For example, the base `CliSubsystem` could expose the following annotation API:
+
+```csharp
+void SetAnnotation(CliSymbol symbol, string id, T value);
+T GetAnnotation(CliSymbol symbol, string id);
+```
+
+These would internally store the annotation values on a dictionary keyed on the symbol and the annotation ID.
+
+#### Annotation Accessors
+
+Developers would not expected to use these base annotation accessors directly unless they are writing an alternate subsystem that has its own additional annotations. The default subsystem and alternate subsystems should provider wrapper methods for specific annotations.
+
+For example, for help descriptions, the `HelpSubsystem` could have the following accessors:
+
+```csharp
+void SetDescription(CliSymbol symbol, string description)
+ => SetAnnotation(symbol, HelpAnnotations.Description, description);
+string GetDescription(CliSymbol symbol)
+ => GetAnnotation(symbol, HelpAnnotations.Description);
+```
+
+There would also be static classes defining the IDs of well-known annotations for use by subsystems and annotation providers, such as `HelpAnnotations.Description`.
+
+#### Fluent Annotations
+
+Unfortunately it is not easy to add fluent helpers such as `command.WithHelpDescription(“Some description”)` as such an extension method would not be able to locate the annotation storage unless the annotations were stored on the symbol or accessible via a hidden static `ConditionalWeakTable`, which are problematic for the reasons described earlier.
+
+Even storing the subsystem on the `RootCommand` would not help with this, as in the following example, the `SetHelpDescription` extension methods would not have access to the `RootCommand` instance as the `Command`’s parent is not set until after the `WithHelpDescription` extension method is called:
+
+```csharp
+rootCommand.Add(
+ new Command(“--hello”)
+ .WithHelpDescription(“Hello”));
+```
+
+However, a different approach to annotation wrappers would enable a pattern for fluently setting annotations on grammar nodes when constructing the grammar.
+
+The following `AnnotationAccessor` wrapper struct encapsulates a reference to the subsystem and the annotation ID:
+
+```csharp
+record struct AnnotationAccessor (Subsystem Subsystem, string Id) {
+ public void Set(CliSymbol symbol, T value) => subsystem.SetAnnotation(symbol, Id, value);
+ public T Get(CliSymbol symbol) => subsystem.GetAnnotation(symbol, Id);
+}
+```
+
+Subsystems would be expected to provide properties that expose instances of this wrapper for individual annotations:
+
+```csharp
+AnnotationAccessor Description => new (this, HelpAnnotations.Description);
+```
+
+This would allow setting an annotation value for a node via these annotation wrappers with the following pattern, replacing the earlier `SetDescription` wrapper method pattern:
+
+```csharp
+help.Description.Set(thingCommand, “This is a thing”);
+```
+
+Using this pattern instead of the earlier `Set`/`GetDescription` style wrappers would allow the implementation of the following extension method on the grammar nodes:
+
+```csharp
+static Command With(this CliSymbol symbol, AnnotationAccessor accessor, T value)
+ => accessor.SetValue(symbol, value);
+```
+
+This extension method would allow fluently setting the help description in a relatively discoverable and easily readable way:
+
+```csharp
+var rootCommand = new RootCommand();
+var help = new HelpSubsystem();
+rootCommand.Add (
+ new Command (“greet”)
+ .With(help.Description, “Greet the user”)
+);
+```
+
+#### Annotation Providers
+
+The annotation provider model allows performance-sensitive developers to opt into lazily fetching annotations when needed. Developers may provide an instance of this provider to a subsystem when initializing the subsystem.
+
+```csharp
+interface AnnotationProvider {
+ Get(CliSymbol symbol, string id, object value);
+}
+```
+
+An implementation of one of these methods might look as follows:
+
+```csharp
+GetCommand command, string id, object value)
+ => (command.Name, id) switch {
+ (“greet”, HelpAnnotation.Description) => “Greet the user”,
+ _ => null
+};
+```
+
+It would even be possible to implement a source generator for optimizing CLI apps by converting fluent annotations into lazy annotations. It would collect values passed to the `With(Annotation,T)` extension method, generate annotation provider implementations that provide those value lazily, and elide the `With` method calls with an interceptor.
+
+### Subsystem Invocation
+
+Subsystems provide a method that must be called after parsing to invoke the subsystem. Invocation uses the `ParseResult`, subsystem annotations, and any settings provided when initializing the subsystem.
+
+When invoked subsystems should print any warnings and errors using the error handler subsystem. If not provided an error handler subsystem, they should use the default one, which prints to stderr. An alternate error handler implementation could customize how errors are rendered, or it could collect the errors so that they could be inspected or printed a later point.
+
+If subsystem invocation determines that the app should be terminated, it should return an `ExitDescriptor`, otherwise `null`. This `ExitDescriptor` encapsulates an exit code and a description of the code’s meaning that is intended to be printed only when showing information about all available exit codes and their meanings.
+
+For example, the `HelpSubsystem`’s `ShowIfNeeded` invocation method checks whether the parseResult contains the help option, and if so, it prints help based on the grammar and annotations, and returns `ExitDescriptor.Success` to indicate that help was invoked and the program should exit.
+
+### Subsystem Pipeline
+
+Here is an example of a full result handling pipeline that uses all the subsystems:
+
+```csharp
+// if there are any parsing errors, print them, and determines
+// which error to use for the exit code and return it
+if (errorHandler.TryPrintErrors(parseResult) is CliExit exit) {
+ // ExitDescriptor has implicit cast to exit code int
+ return error;
+}
+
+// if result contains help option, show help and return success exit.
+// may use values from the validation and default value
+// subsystem to enrich output.
+if (help.ShowIfNeeded(parseResult, validation, defaults, errorHandler) is CliExit exit) {
+ return exit;
+}
+
+// if result contains completion directive, print completion
+// and return success exit. may return an error exit if there is some
+// internal error.
+if (completion.Handle(parseResult, errorHandler) is CliExit exit) {
+ return exit;
+}
+
+// validate all values in the parse result and print validation
+// errors to the errorHandler. if any errors, return appropriate exit.
+if (validation.Validate(parseResult, errorHandler) is CliExit exit) {
+ return exit
+}
+// create a collection that can return values for all options
+// and arguments, even if they were not present in the ParseResult.
+// if any default value delegate throws exceptions,
+// print them using the errorHandler, and returns error exit.
+if (CliValues.Create(parseResult, defaults, errorHandler, out CliValues values) is CliExit exit) {
+ return exit;
+}
+
+// determine which handler delegate to use and invoke it.
+// depending how the delegate was registered, may pass the values
+// collection to the invocation delegate directly, or bind
+// the delegate’s arguments to values from this collection.
+// returns the exit descriptor returned from the invoked delegate,
+// or null if it did not find a delegate to invoke.
+if (invocation.Dispatch(parseResult, values, errorHandler) is CliExit exit) {
+ return exit;
+}
+
+// creates a customized ExitDescriptor that also prints
+// a short form of the help
+CliExit noCommandExit = help.CreateNoCommandExit ();
+errorHandler.WriteError(noCommandExit);
+return noCommandExit;
+```
+
+There would be several extension methods that encapsulate the standard subsystem invocation pipeline shown above. The most important is `Invoke`:
+
+```csharp
+ExitDescriptor Invoke(
+ this parseResult result,
+ InvocationSubsystem invocation,
+ HelpSubsystem? helpSubsystem = null,
+ DefaultValuesSubsystem? defaultValues = null,
+ ValidationSubsystem? validationSubsystem = null,
+ CompletionSubsystem? completion = null,
+ ErrorHandler? errorHandler = null
+)
+```
+
+Note that the `InvocationSubsystem` subsystem cannot be null for the `Invoke` helper. Note also that the arguments are in the order of most to least likely to be provided , making it more likely arguments can be omitted without passing nulls or using named arguments.
+
+The `GetValues` variant of this helper omits the invocation subsystem, and returns the CliValues and the command, for users who want to perform invocation manually:
+
+```csharp
+var (exitDescriptor, command, values) = parseResult.GetValues(
+ help, defaultValues, validation, completion, errorHandler
+);
+```
+
+Note that any of the subsystems passed to these helpers may be null. If the error handle, help, completion, or default value subsystems are null, an instance of the default implementation will be used. The validation and invocation subsystem invocations will be skipped if they are not provided, as they do nothing without annotations so the default instance would be redundant.
+
+Although most users would use these helpers, some very advanced cases may wish to use any or all of the subsystems in a custom pipeline. The main value of this subsystem model is that apps can use alternate implementations for any or all of the subsystems, either written specifically for the app or obtained from NuGet.
+
+## End-to-End Example
+
+Here is an end-to-end example of an entire CLI application that initializes subsystems, constructs a simple command, attaches annotations, and runs the subsystem pipeline:
+
+```csharp
+var rootCommand = new RootCommand();
+
+var help = new HelpSubsystem(rootCommand);
+var invocation = new InvocationSubsystem(rootCommand);
+var defaults = new DefaultValuesSubsystem(rootCommand);
+
+rootCommand.Add (
+ new Command (“greet”,
+ new Argument(“name”),
+ .With (help.Description, “The name of the person to greet”),
+ .With (defaults.Provider, () => Environment.UserName)
+ )
+ .With(help.Description, “Greet the user”)
+ .With(invocation.Handler,
+ name => Console.WriteLine($“Hello {name}!”))
+);
+
+var parseResult = rootCommand.Parse(args);
+
+return parseResult.Invoke (invocation, help, defaults);
+```
diff --git a/samples/DragonFruit/DragonFruit.csproj b/samples/DragonFruit/DragonFruit.csproj
index 7c72c4da2b..92e9a9cd37 100644
--- a/samples/DragonFruit/DragonFruit.csproj
+++ b/samples/DragonFruit/DragonFruit.csproj
@@ -6,8 +6,4 @@
true
-
-
-
-
diff --git a/samples/HostingPlayground/HostingPlayground.csproj b/samples/HostingPlayground/HostingPlayground.csproj
index 54bc18bb67..b0060a43ee 100644
--- a/samples/HostingPlayground/HostingPlayground.csproj
+++ b/samples/HostingPlayground/HostingPlayground.csproj
@@ -8,8 +8,6 @@
-
-
diff --git a/samples/RenderingPlayground/RenderingPlayground.csproj b/samples/RenderingPlayground/RenderingPlayground.csproj
index 109f8622e1..0fdc93b388 100644
--- a/samples/RenderingPlayground/RenderingPlayground.csproj
+++ b/samples/RenderingPlayground/RenderingPlayground.csproj
@@ -10,9 +10,4 @@
-
-
-
-
-
diff --git a/sourcebuild.slnf b/sourcebuild.slnf
index dc155a4d4e..22123b62b2 100644
--- a/sourcebuild.slnf
+++ b/sourcebuild.slnf
@@ -2,10 +2,7 @@
"solution": {
"path": "System.CommandLine.sln",
"projects": [
- "src\\System.CommandLine\\System.CommandLine.csproj",
- "src\\System.CommandLine.DragonFruit\\System.CommandLine.DragonFruit.csproj",
- "src\\System.CommandLine.NamingConventionBinder\\System.CommandLine.NamingConventionBinder.csproj",
- "src\\System.CommandLine.Rendering\\System.CommandLine.Rendering.csproj"
+ "src\\System.CommandLine\\System.CommandLine.csproj"
]
}
}
\ No newline at end of file
diff --git a/src/System.CommandLine/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMemberTypes.cs b/src/Common/Compat/CodeAnalysis/DynamicallyAccessedMemberTypes.cs
similarity index 99%
rename from src/System.CommandLine/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMemberTypes.cs
rename to src/Common/Compat/CodeAnalysis/DynamicallyAccessedMemberTypes.cs
index a139bdc050..a077f34c51 100644
--- a/src/System.CommandLine/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMemberTypes.cs
+++ b/src/Common/Compat/CodeAnalysis/DynamicallyAccessedMemberTypes.cs
@@ -1,6 +1,6 @@
// adapted from: https://github.com/dotnet/aspnetcore/blob/404d81767784552b0a148cb8c437332ebe726ae9/src/Shared/CodeAnalysis/DynamicallyAccessedMemberTypes.cs
-#if !NET6_0_OR_GREATER
+#if !NET5_0_OR_GREATER
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
diff --git a/src/System.CommandLine/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMembersAttribute.cs b/src/Common/Compat/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs
similarity index 99%
rename from src/System.CommandLine/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMembersAttribute.cs
rename to src/Common/Compat/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs
index a39283b0ab..8a15e0106f 100644
--- a/src/System.CommandLine/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMembersAttribute.cs
+++ b/src/Common/Compat/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs
@@ -1,6 +1,6 @@
// adapted from: https://github.com/dotnet/aspnetcore/blob/404d81767784552b0a148cb8c437332ebe726ae9/src/Shared/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs#L29
-#if !NET6_0_OR_GREATER
+#if !NET5_0_OR_GREATER
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
diff --git a/src/Common/Compat/CodeAnalysis/NullabilityAttributes.cs b/src/Common/Compat/CodeAnalysis/NullabilityAttributes.cs
new file mode 100644
index 0000000000..eaa568afac
--- /dev/null
+++ b/src/Common/Compat/CodeAnalysis/NullabilityAttributes.cs
@@ -0,0 +1,149 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Diagnostics.CodeAnalysis
+{
+#if !NETCOREAPP3_0_OR_GREATER
+ /// Specifies that null is allowed as an input even if the corresponding type disallows it.
+ [AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
+ internal sealed class AllowNullAttribute : Attribute
+ { }
+
+ /// Specifies that null is disallowed as an input even if the corresponding type allows it.
+ [AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
+ internal sealed class DisallowNullAttribute : Attribute
+ { }
+
+ /// Specifies that an output may be null even if the corresponding type disallows it.
+ [AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
+ internal sealed class MaybeNullAttribute : Attribute
+ { }
+
+ /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns.
+ [AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
+ internal sealed class NotNullAttribute : Attribute
+ { }
+
+ /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it.
+ [AttributeUsage (AttributeTargets.Parameter, Inherited = false)]
+ internal sealed class MaybeNullWhenAttribute : Attribute
+ {
+ /// Initializes the attribute with the specified return value condition.
+ ///
+ /// The return value condition. If the method returns this value, the associated parameter may be null.
+ ///
+ public MaybeNullWhenAttribute (bool returnValue) => ReturnValue = returnValue;
+
+ /// Gets the return value condition.
+ public bool ReturnValue { get; }
+ }
+
+ /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it.
+ [AttributeUsage (AttributeTargets.Parameter, Inherited = false)]
+ internal sealed class NotNullWhenAttribute : Attribute
+ {
+ /// Initializes the attribute with the specified return value condition.
+ ///
+ /// The return value condition. If the method returns this value, the associated parameter will not be null.
+ ///
+ public NotNullWhenAttribute (bool returnValue) => ReturnValue = returnValue;
+
+ /// Gets the return value condition.
+ public bool ReturnValue { get; }
+ }
+
+ /// Specifies that the output will be non-null if the named parameter is non-null.
+ [AttributeUsage (AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
+ internal sealed class NotNullIfNotNullAttribute : Attribute
+ {
+ /// Initializes the attribute with the associated parameter name.
+ ///
+ /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
+ ///
+ public NotNullIfNotNullAttribute (string parameterName) => ParameterName = parameterName;
+
+ /// Gets the associated parameter name.
+ public string ParameterName { get; }
+ }
+
+ /// Applied to a method that will never return under any circumstance.
+ [AttributeUsage (AttributeTargets.Method, Inherited = false)]
+ internal sealed class DoesNotReturnAttribute : Attribute
+ { }
+
+ /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value.
+ [AttributeUsage (AttributeTargets.Parameter, Inherited = false)]
+ internal sealed class DoesNotReturnIfAttribute : Attribute
+ {
+ /// Initializes the attribute with the specified parameter value.
+ ///
+ /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
+ /// the associated parameter matches this value.
+ ///
+ public DoesNotReturnIfAttribute (bool parameterValue) => ParameterValue = parameterValue;
+
+ /// Gets the condition parameter value.
+ public bool ParameterValue { get; }
+ }
+
+#endif
+#if !NET5_0_OR_GREATER
+
+ /// Specifies that the method or property will ensure that the listed field and property members have not-null values.
+ [AttributeUsage (AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
+ internal sealed class MemberNotNullAttribute : Attribute
+ {
+ /// Initializes the attribute with a field or property member.
+ ///
+ /// The field or property member that is promised to be not-null.
+ ///
+ public MemberNotNullAttribute (string member) => Members = new[] { member };
+
+ /// Initializes the attribute with the list of field and property members.
+ ///
+ /// The list of field and property members that are promised to be not-null.
+ ///
+ public MemberNotNullAttribute (params string[] members) => Members = members;
+
+ /// Gets field or property member names.
+ public string[] Members { get; }
+ }
+
+ /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.
+ [AttributeUsage (AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
+ internal sealed class MemberNotNullWhenAttribute : Attribute
+ {
+ /// Initializes the attribute with the specified return value condition and a field or property member.
+ ///
+ /// The return value condition. If the method returns this value, the associated parameter will not be null.
+ ///
+ ///
+ /// The field or property member that is promised to be not-null.
+ ///
+ public MemberNotNullWhenAttribute (bool returnValue, string member)
+ {
+ ReturnValue = returnValue;
+ Members = new[] { member };
+ }
+
+ /// Initializes the attribute with the specified return value condition and list of field and property members.
+ ///
+ /// The return value condition. If the method returns this value, the associated parameter will not be null.
+ ///
+ ///
+ /// The list of field and property members that are promised to be not-null.
+ ///
+ public MemberNotNullWhenAttribute (bool returnValue, params string[] members)
+ {
+ ReturnValue = returnValue;
+ Members = members;
+ }
+
+ /// Gets the return value condition.
+ public bool ReturnValue { get; }
+
+ /// Gets field or property member names.
+ public string[] Members { get; }
+ }
+#endif
+}
\ No newline at end of file
diff --git a/src/Common/Compat/CodeAnalysis/StringSyntaxAttribute.cs b/src/Common/Compat/CodeAnalysis/StringSyntaxAttribute.cs
new file mode 100644
index 0000000000..ee4bfcf8c4
--- /dev/null
+++ b/src/Common/Compat/CodeAnalysis/StringSyntaxAttribute.cs
@@ -0,0 +1,71 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Diagnostics.CodeAnalysis
+{
+#if !NET7_0_OR_GREATER
+ /// Specifies the syntax used in a string.
+ [AttributeUsage (AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+ sealed class StringSyntaxAttribute : Attribute
+ {
+ /// Initializes the with the identifier of the syntax used.
+ /// The syntax identifier.
+ public StringSyntaxAttribute(string syntax)
+ {
+ Syntax = syntax;
+ Arguments = Array.Empty