├── .trunk
├── .gitignore
├── config
│ ├── .hadolint.yaml
│ └── .markdownlint.yaml
└── trunk.yaml
├── .github
└── dependabot.yml
├── src
├── Samples
│ ├── MultilevelCommandApp
│ │ ├── CommandBase.cs
│ │ ├── StatusCommand.cs
│ │ ├── MultilevelCommandApp.csproj
│ │ ├── GetConfigurationCommand.cs
│ │ ├── SetConfigurationCommand.cs
│ │ ├── ProgramArguments.cs
│ │ ├── ConfigurationCommand.cs
│ │ └── Program.cs
│ ├── HelpApp
│ │ ├── Actions
│ │ │ ├── Amazing.cs
│ │ │ └── Mediocre.cs
│ │ ├── HelpApp.csproj
│ │ └── Program.cs
│ └── MergedCommandApp
│ │ ├── ExtraCommandsType.cs
│ │ ├── CommandType.cs
│ │ ├── ExtraCommand.cs
│ │ ├── SimpleCommand.cs
│ │ ├── ProgramArguments.cs
│ │ ├── EnumProvider.cs
│ │ ├── Program.cs
│ │ └── MergedCommandApp.csproj
├── NClap
│ ├── Utilities
│ │ ├── ColoredStringBuilder.cs
│ │ ├── None.cs
│ │ ├── Some.cs
│ │ ├── IDeepCloneable`1.cs
│ │ ├── CircularEnumerator.cs
│ │ ├── MaybeUtilities.cs
│ │ ├── TokenizerOptions.cs
│ │ ├── Windows
│ │ │ ├── NativeMethods.cs
│ │ │ └── InputUtilities.cs
│ │ ├── GenericCollectionFactory.cs
│ │ ├── IMutableMemberInfo.cs
│ │ └── FluentBuilder`1.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Help
│ │ ├── ArgumentSortOrder.cs
│ │ ├── ArgumentGroupingMode.cs
│ │ ├── ArgumentHelpLayout.cs
│ │ ├── ArgumentShortNameHelpMode.cs
│ │ ├── ArgumentDefaultValueHelpMode.cs
│ │ ├── OneColumnArgumentHelpLayout.cs
│ │ ├── ArgumentEnumValueHelpFlags.cs
│ │ ├── ArgumentSetHelpSectionType.cs
│ │ ├── ArgumentEnumValueHelpOptions.cs
│ │ ├── ArgumentSyntaxHelpOptions.cs
│ │ ├── ArgumentSyntaxFlags.cs
│ │ ├── TwoColumnArgumentHelpLayout.cs
│ │ └── ArgumentMetadataHelpOptions.cs
│ ├── Metadata
│ │ ├── ArgumentGroupAttribute.cs
│ │ ├── IArgumentSetWithHelp.cs
│ │ ├── ExitCommand.cs
│ │ ├── ICommand.cs
│ │ ├── UnimplementedCommand.cs
│ │ ├── HelpArgumentsBase.cs
│ │ ├── ArgumentTypeAttribute.cs
│ │ ├── Command.cs
│ │ ├── ArgumentValueFlags.cs
│ │ ├── HelpCommandAttribute.cs
│ │ ├── ArgumentValidationContext.cs
│ │ ├── CommandResult.cs
│ │ ├── IntegerComparisonValidationAttribute.cs
│ │ ├── ArgumentSetStyle.cs
│ │ ├── IArgumentProvider.cs
│ │ ├── ArgumentNameGenerationFlags.cs
│ │ ├── ICommandGroup.cs
│ │ ├── SynchronousCommand.cs
│ │ ├── ExtensibleEnumAttribute.cs
│ │ ├── HelpCommand.cs
│ │ ├── CommandGroupOptions.cs
│ │ ├── NumberOptions.cs
│ │ ├── FileSystemPathValidationAttribute.cs
│ │ ├── IntegerValidationAttribute.cs
│ │ ├── MustNotBeEmptyAttribute.cs
│ │ ├── HelpCommandArgumentCompleter.cs
│ │ ├── NamedArgumentAttribute.cs
│ │ ├── ArgumentValidationAttribute.cs
│ │ ├── CommandAttribute.cs
│ │ ├── PositionalArgumentAttribute.cs
│ │ ├── MustBeLessThanAttribute.cs
│ │ ├── MustBeGreaterThanAttribute.cs
│ │ ├── MustBeLessThanOrEqualToAttribute.cs
│ │ ├── MustNotExistAttribute.cs
│ │ ├── MustBeGreaterThanOrEqualToAttribute.cs
│ │ ├── StringValidationAttribute.cs
│ │ ├── ArgumentValueAttribute.cs
│ │ └── MustNotBeAttribute.cs
│ ├── Parser
│ │ ├── ArgumentNameType.cs
│ │ ├── ArgumentSetParseResultType.cs
│ │ ├── CommandGroupDefinition.cs
│ │ └── CommandDefinition.cs
│ ├── Types
│ │ ├── IEnumArgumentTypeProvider.cs
│ │ ├── IObjectFormatter.cs
│ │ ├── IStringParser.cs
│ │ ├── IStringCompleter.cs
│ │ ├── IEnumArgumentType.cs
│ │ ├── ArgumentCompletionContext.cs
│ │ ├── IArgumentType.cs
│ │ ├── ICollectionArgumentType.cs
│ │ ├── StringArgumentType.cs
│ │ ├── IArgumentValue.cs
│ │ └── MergedEnumArgumentType.cs
│ ├── ConsoleInput
│ │ ├── ConsoleUtilities.cs
│ │ ├── IReadOnlyConsoleKeyBindingSet.cs
│ │ ├── ConsoleInputOperationResult.cs
│ │ ├── IConsoleInput.cs
│ │ ├── ITokenCompleter.cs
│ │ ├── IConsoleReader.cs
│ │ └── IConsoleHistory.cs
│ ├── stylecop.json
│ ├── Expressions
│ │ ├── Operator.cs
│ │ ├── ExpressionEnvironment.cs
│ │ ├── Expression.cs
│ │ ├── ParenthesisExpression.cs
│ │ ├── StringLiteral.cs
│ │ ├── ConcatenationExpression.cs
│ │ └── OperatorExpression.cs
│ ├── Exceptions
│ │ └── InternalInvariantBrokenException.cs
│ ├── Repl
│ │ ├── LoopInputOutputParameters.cs
│ │ ├── ILoopClient.cs
│ │ └── LoopOptions.cs
│ └── IFileSystemReader.cs
├── Tests
│ ├── UnitTests
│ │ ├── Exceptions
│ │ │ ├── InternalInvariantBrokenExceptionTests.cs
│ │ │ ├── InvalidCommandExceptionTests.cs
│ │ │ └── InvalidArgumentSetExceptionTests.cs
│ │ ├── Help
│ │ │ ├── ArgumentSetHelpOptionsTests.cs
│ │ │ ├── ArgumentSetUsageInfoTests.cs
│ │ │ └── ArgumentSetHelpOptionsExtensionsTests.cs
│ │ ├── Types
│ │ │ ├── ArgumentParseContextTests.cs
│ │ │ ├── ArgumentTypeExtensionTests.cs
│ │ │ ├── KeyValuePairArgumentTypeTests.cs
│ │ │ ├── TupleArgumentTypeTests.cs
│ │ │ └── EnumArgumentValueTests.cs
│ │ ├── GlobalSuppressions.cs
│ │ ├── Metadata
│ │ │ ├── CommandTests.cs
│ │ │ ├── UnimplementedCommandTests.cs
│ │ │ ├── ArgumentTypeAttributeTests.cs
│ │ │ ├── ArgumentValueAttributeTests.cs
│ │ │ ├── StringValidationAttributeTests.cs
│ │ │ └── ArgumentAttributeTests.cs
│ │ ├── ConsoleInput
│ │ │ ├── TestTokenCompleter.cs
│ │ │ └── SimulatedConsoleInput.cs
│ │ ├── StringsTests.cs
│ │ ├── Utilities
│ │ │ ├── ColoredStringExtensionMethodsTests.cs
│ │ │ ├── DeepCloneTests.cs
│ │ │ ├── EnumerableUtilitiesTests.cs
│ │ │ ├── MutableFieldInfoTests.cs
│ │ │ ├── FluentBuilderTests.cs
│ │ │ ├── MutablePropertyInfoTests.cs
│ │ │ ├── MaybeTests.cs
│ │ │ ├── TypeUtilitiesTests.cs
│ │ │ └── StringWrapperTests.cs
│ │ ├── CommandLineParserOptionsTests.cs
│ │ ├── NClap.Tests.csproj
│ │ ├── Parser
│ │ │ └── ArgumentSetParserTests.cs
│ │ ├── Expressions
│ │ │ ├── ExpressionTests.cs
│ │ │ ├── TestEnvironment.cs
│ │ │ ├── StringLiteralTests.cs
│ │ │ ├── ParenthesisExpressionTests.cs
│ │ │ └── StringExpanderTests.cs
│ │ └── AssemblyTests.cs
│ └── TestApp
│ │ ├── CliHelp.cs
│ │ ├── SubCommandType.cs
│ │ ├── LogoCommand.cs
│ │ ├── NClap.TestApp.csproj
│ │ ├── CompleteCommand.cs
│ │ ├── ReadLineCommand.cs
│ │ ├── Program.cs
│ │ ├── ProgramArguments.cs
│ │ ├── MainCommandType.cs
│ │ └── ReplCommand.cs
└── Tools
│ └── Inspector
│ ├── Program.cs
│ ├── NClap.Inspector.csproj
│ ├── CompleteTokensCommand.cs
│ └── CompleteLineCommand.cs
├── .devcontainer
├── hashes.txt
├── devcontainer.json
└── Dockerfile
├── GitVersion.yml
├── .vscode
├── settings.json
├── launch.json
└── tasks.json
├── CODE_OF_CONDUCT.md
├── .editorconfig
├── LICENSE.txt
└── README.md
/.trunk/.gitignore:
--------------------------------------------------------------------------------
1 | *out
2 | *logs
3 | *actions
4 | *notifications
5 | plugins
6 | user_trunk.yaml
7 | user.yaml
8 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: nuget
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | open-pull-requests-limit: 10
8 |
--------------------------------------------------------------------------------
/src/Samples/MultilevelCommandApp/CommandBase.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace MultilevelCommandApp
4 | {
5 | internal abstract class CommandBase : SynchronousCommand
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/.devcontainer/hashes.txt:
--------------------------------------------------------------------------------
1 | 095fa945b775be944f660875cfe80218898c210615b6584faea31a4060ba3463 ./bat-musl_0.22.1_amd64.deb
2 | a65a87bd545e969979ae9388f6333167f041a1f09fa9d60b32fd3072348ff6ce ./exa-linux-x86_64-v0.10.1.zip
3 |
--------------------------------------------------------------------------------
/.trunk/config/.hadolint.yaml:
--------------------------------------------------------------------------------
1 | ignored:
2 | # Following source doesn't work in most setups
3 | - SC1090
4 | - SC1091
5 |
6 | # DL3004: Don't use sudo
7 | - DL3004
8 | # DL3008: Pin apt package versions
9 | - DL3008
10 |
--------------------------------------------------------------------------------
/src/Samples/HelpApp/Actions/Amazing.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace HelpApp.Actions
4 | {
5 | internal class Amazing : SynchronousCommand
6 | {
7 | public override CommandResult Execute() => CommandResult.Success;
8 | }
9 | }
--------------------------------------------------------------------------------
/GitVersion.yml:
--------------------------------------------------------------------------------
1 | next-version: 3.1.0
2 | branches:
3 | main:
4 | regex: ^main$
5 | mode: ContinuousDelivery
6 | tag: ""
7 | increment: Patch
8 | prevent-increment-of-merged-branch-version: true
9 | track-merge-target: false
10 |
--------------------------------------------------------------------------------
/src/NClap/Utilities/ColoredStringBuilder.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Utilities
2 | {
3 | ///
4 | /// String builder for constructing colored strings.
5 | ///
6 | public class ColoredStringBuilder
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "[csharp]": {
4 | "editor.defaultFormatter": "ms-dotnettools.csharp"
5 | },
6 | "dotnet-test-explorer.testProjectPath": "**/*.sln",
7 | "dotnet-test-explorer.enableTelemetry": false
8 | }
9 |
--------------------------------------------------------------------------------
/src/NClap/Utilities/None.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Utilities
2 | {
3 | ///
4 | /// Utility for constructing objects with no contained
5 | /// values.
6 | ///
7 | internal sealed class None
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Samples/MergedCommandApp/ExtraCommandsType.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace MergedCommandApp
4 | {
5 | internal enum ExtraCommandsType
6 | {
7 | [Command(typeof(ExtraCommand), Description = "Do something a bit extra")]
8 | Extra
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.trunk/config/.markdownlint.yaml:
--------------------------------------------------------------------------------
1 | # Autoformatter friendly markdownlint config (all formatting rules disabled)
2 | default: true
3 | blank_lines: false
4 | bullet: false
5 | html: false
6 | indentation: false
7 | line_length: false
8 | spaces: false
9 | url: false
10 | whitespace: false
11 |
--------------------------------------------------------------------------------
/src/Samples/MergedCommandApp/CommandType.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace MergedCommandApp
4 | {
5 | [ExtensibleEnum(typeof(EnumProvider))]
6 | internal enum CommandType
7 | {
8 | [Command(typeof(SimpleCommand), Description = "Keep it simple")]
9 | Foo
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/NClap/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: InternalsVisibleTo("NClap.Tests")]
6 |
7 | [assembly: CLSCompliant(true)]
8 | [assembly: ComVisible(false)]
9 | [assembly: Guid("ce1a820d-79f3-410d-b869-884dea01fbe6")]
10 |
--------------------------------------------------------------------------------
/src/NClap/Help/ArgumentSortOrder.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Help
2 | {
3 | ///
4 | /// Sort order type for arguments.
5 | ///
6 | public enum ArgumentSortOrder
7 | {
8 | ///
9 | /// Sort lexicographically.
10 | ///
11 | Lexicographic
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Exceptions/InternalInvariantBrokenExceptionTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using NClap.Exceptions;
3 |
4 | namespace NClap.Tests.Exceptions
5 | {
6 | [TestClass]
7 | public class InternalInvariantBrokenExceptionTests : ExceptionTests
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Exceptions/InvalidCommandExceptionTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Exceptions;
4 |
5 | namespace NClap.Tests.Exceptions
6 | {
7 | [TestClass]
8 | public class InvalidCommandExceptionTests : ExceptionTests
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
4 | [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/src/NClap/Help/ArgumentGroupingMode.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Help
2 | {
3 | ///
4 | /// Grouping mode for arguments.
5 | ///
6 | public enum ArgumentGroupingMode
7 | {
8 | ///
9 | /// Group required arguments vs. optional arguments.
10 | ///
11 | RequiredVersusOptional
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Samples/MergedCommandApp/ExtraCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Metadata;
3 |
4 | namespace MergedCommandApp
5 | {
6 | internal class ExtraCommand : SynchronousCommand
7 | {
8 | public override CommandResult Execute()
9 | {
10 | Console.WriteLine("Extra!");
11 | return CommandResult.Success;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Samples/MergedCommandApp/SimpleCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Metadata;
3 |
4 | namespace MergedCommandApp
5 | {
6 | internal class SimpleCommand : SynchronousCommand
7 | {
8 | public override CommandResult Execute()
9 | {
10 | Console.WriteLine("Simple.");
11 | return CommandResult.Success;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Samples/MergedCommandApp/ProgramArguments.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace MergedCommandApp
4 | {
5 | internal class ProgramArguments : IArgumentSetWithHelp
6 | {
7 | [PositionalArgument(ArgumentFlags.Required)]
8 | public CommandGroup Command { get; set; }
9 |
10 | [NamedArgument]
11 | public bool Help { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Samples/MergedCommandApp/EnumProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using NClap.Types;
3 |
4 | namespace MergedCommandApp
5 | {
6 | internal class EnumProvider : IEnumArgumentTypeProvider
7 | {
8 | public IEnumerable GetTypes()
9 | {
10 | return new[] { (IEnumArgumentType)ArgumentType.GetType(typeof(ExtraCommandsType)) };
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Tests/TestApp/CliHelp.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace NClap.TestApp
4 | {
5 | class CliHelp : SynchronousCommand
6 | {
7 | public override CommandResult Execute()
8 | {
9 | var info = CommandLineParser.GetUsageInfo(typeof(ProgramArguments));
10 | CommandLineParser.DefaultReporter(info);
11 | return CommandResult.Success;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ArgumentGroupAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Unused.
7 | ///
8 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
9 | [Obsolete("This attribute is not supported and will be removed from a future release.", true)]
10 | public sealed class ArgumentGroupAttribute : Attribute
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/NClap/Parser/ArgumentNameType.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Parser
2 | {
3 | ///
4 | /// Type of a name for a named argument.
5 | ///
6 | public enum ArgumentNameType
7 | {
8 | ///
9 | /// A short name.
10 | ///
11 | ShortName,
12 |
13 | ///
14 | /// A long name.
15 | ///
16 | LongName
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Tests/TestApp/SubCommandType.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace NClap.TestApp
4 | {
5 | [ArgumentType(DisplayName = "Sub-command")]
6 | enum SubCommandType
7 | {
8 | [Command(typeof(UnimplementedCommand))]
9 | Foo,
10 |
11 | [Command(typeof(UnimplementedCommand))]
12 | Bar,
13 |
14 | [Command(typeof(CommandGroup))]
15 | Main
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "NClap dev container",
3 | "dockerFile": "Dockerfile",
4 | "extensions": [
5 | "formulahendry.dotnet-test-explorer",
6 | "ms-dotnettools.csharp",
7 | "eamodio.gitlens"
8 | ],
9 | "features": {
10 | "ghcr.io/devcontainers/features/github-cli:1": {
11 | "version": "latest"
12 | }
13 | },
14 | "remoteUser": "vscode"
15 | }
16 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/IArgumentSetWithHelp.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Metadata
2 | {
3 | ///
4 | /// Interface to be implemented on argument set types that expose help options.
5 | ///
6 | public interface IArgumentSetWithHelp
7 | {
8 | ///
9 | /// True if the user wants to receive usage help information; false
10 | /// otherwise.
11 | ///
12 | bool Help { get; set; }
13 | }
14 | }
--------------------------------------------------------------------------------
/src/NClap/Types/IEnumArgumentTypeProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace NClap.Types
4 | {
5 | ///
6 | /// Enum argument type provider.
7 | ///
8 | public interface IEnumArgumentTypeProvider
9 | {
10 | ///
11 | /// Retrieves types being provided.
12 | ///
13 | /// Enumeration of types.
14 | IEnumerable GetTypes();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/NClap/ConsoleInput/ConsoleUtilities.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.ConsoleInput
2 | {
3 | ///
4 | /// Assorted console utilities exported for use outside this assembly.
5 | ///
6 | public static class ConsoleUtilities
7 | {
8 | ///
9 | /// Reads a line of input from the console.
10 | ///
11 | /// The read string.
12 | public static string ReadLine() => new ConsoleReader().ReadLine();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ExitCommand.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Metadata
2 | {
3 | ///
4 | /// Simple command implementation that only exits.
5 | ///
6 | public class ExitCommand : SynchronousCommand
7 | {
8 | ///
9 | /// Does nothing, but indicates to the caller that termination is desired.
10 | ///
11 | /// CommandResult.Terminate.
12 | public override CommandResult Execute() => CommandResult.Terminate;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Samples/MergedCommandApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap;
3 |
4 | namespace MergedCommandApp
5 | {
6 | class Program
7 | {
8 | static int Main(string[] args)
9 | {
10 | if (!CommandLineParser.TryParse(args, out ProgramArguments progArgs))
11 | {
12 | return 1;
13 | }
14 |
15 | var result = progArgs.Command.Execute();
16 | return result == NClap.Metadata.CommandResult.Success ? 0 : 1;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Samples/HelpApp/HelpApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | false
7 |
8 |
9 |
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/NClap/Types/IObjectFormatter.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Types
2 | {
3 | ///
4 | /// Interface implemented by objects that can convert objects into strings.
5 | ///
6 | public interface IObjectFormatter
7 | {
8 | ///
9 | /// Converts a value into a readable string form.
10 | ///
11 | /// The value to format into a string.
12 | /// The formatted string.
13 | string Format(object value);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Tools/Inspector/Program.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace NClap.Inspector
4 | {
5 | class Program
6 | {
7 | public static int Main(string[] args)
8 | {
9 | if (!CommandLineParser.TryParse(args, out ProgramArguments parsedArgs))
10 | {
11 | return 1;
12 | }
13 |
14 | if (parsedArgs.Execute() != CommandResult.Success)
15 | {
16 | return 1;
17 | }
18 |
19 | return 0;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Samples/MergedCommandApp/MergedCommandApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | false
7 |
8 |
9 |
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Tests/TestApp/LogoCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Metadata;
3 |
4 | namespace NClap.TestApp
5 | {
6 | class LogoCommand : SynchronousCommand
7 | {
8 | public override CommandResult Execute()
9 | {
10 | Console.WriteLine("Logo:");
11 | Console.WriteLine("---------------------------");
12 | Console.Write(CommandLineParser.GetLogo());
13 | Console.WriteLine("---------------------------");
14 |
15 | return CommandResult.Success;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ICommand.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace NClap.Metadata
5 | {
6 | ///
7 | /// Represents a command (a.k.a. verb).
8 | ///
9 | public interface ICommand
10 | {
11 | ///
12 | /// Executes the command.
13 | ///
14 | /// Cancellation token.
15 | /// Result of execution.
16 | Task ExecuteAsync(CancellationToken cancel);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/UnimplementedCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Simple stub command implementation that is not implemented.
7 | ///
8 | public class UnimplementedCommand : SynchronousCommand
9 | {
10 | ///
11 | /// Throws an exception.
12 | ///
13 | /// Does not return.
14 | public override CommandResult Execute() => throw new NotImplementedException("Executed unimplemented command.");
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/HelpArgumentsBase.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Metadata
2 | {
3 | ///
4 | /// Simple implementation of .
5 | ///
6 | public class HelpArgumentsBase : IArgumentSetWithHelp
7 | {
8 | ///
9 | /// True if the user wants to receive usage help information; false
10 | /// otherwise.
11 | ///
12 | [NamedArgument(ArgumentFlags.AtMostOnce, Description = "Display help information")]
13 | public bool Help { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/NClap/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "settings": {
3 | "documentationRules": {
4 | "xmlHeader": false,
5 | "documentInternalElements": true,
6 | "fileNamingConvention": "metadata"
7 | },
8 | "maintainabilityRules": {
9 | "topLevelTypes": ["class", "interface"]
10 | },
11 | "orderingRules": {
12 | "systemUsingDirectivesFirst": true,
13 | "usingDirectivesPlacement": "outsideNamespace",
14 | "blankLinesBetweenUsingGroups": "omit"
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Samples/MultilevelCommandApp/StatusCommand.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using NClap.Metadata;
3 |
4 | namespace MultilevelCommandApp
5 | {
6 | internal class StatusCommand : CommandBase
7 | {
8 | private readonly ILogger logger;
9 |
10 | public StatusCommand(ILogger logger)
11 | {
12 | this.logger = logger;
13 | }
14 |
15 | public override CommandResult Execute()
16 | {
17 | logger.LogWarning($"Status.");
18 | return CommandResult.Success;
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Help/ArgumentSetHelpOptionsTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using NClap.Help;
3 | using NClap.Tests.Utilities;
4 |
5 | namespace NClap.Tests.Help
6 | {
7 | [TestClass]
8 | public class ArgumentSetHelpOptionsTests
9 | {
10 | [TestMethod]
11 | public void TestThatDeepCloningDefaultOptionsWorksAsExpected()
12 | {
13 | var options = new ArgumentSetHelpOptions();
14 | DeepCloneTests.CloneShouldYieldADistinctButEquivalentObject(options);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/NClap/ConsoleInput/IReadOnlyConsoleKeyBindingSet.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | namespace NClap.ConsoleInput
6 | {
7 | ///
8 | /// Read-only abstract interface for querying a console key binding set.
9 | ///
10 | [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "[Legacy]")]
11 | public interface IReadOnlyConsoleKeyBindingSet : IReadOnlyDictionary
12 | {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/NClap/Expressions/Operator.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Expressions
2 | {
3 | ///
4 | /// Operator type.
5 | ///
6 | internal enum Operator
7 | {
8 | ///
9 | /// Sentinel invalid value.
10 | ///
11 | Unspecified = 0,
12 |
13 | ///
14 | /// Converts to lower-case.
15 | ///
16 | ConvertToLowerCase = 1,
17 |
18 | ///
19 | /// Converts to lower-case.
20 | ///
21 | ConvertToUpperCase = 2,
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/NClap/Utilities/Some.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Utilities
2 | {
3 | ///
4 | /// Convenience class for constructing values.
5 | ///
6 | internal static class Some
7 | {
8 | ///
9 | /// Constructs an object with a value present.
10 | ///
11 | /// Type of the value.
12 | /// The value.
13 | /// The constructed object.
14 | public static Maybe Of(T value) => new Maybe(value);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Tests/TestApp/NClap.TestApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | win10-x64;linux-x64
7 | false
8 |
9 |
10 |
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ArgumentTypeAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Attribute for annotating types that can be used as arguments.
7 | ///
8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
9 | public sealed class ArgumentTypeAttribute : Attribute
10 | {
11 | ///
12 | /// Optionally indicates how this type should be displayed in
13 | /// help/usage information.
14 | ///
15 | public string DisplayName { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/NClap/Utilities/IDeepCloneable`1.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Utilities
2 | {
3 | ///
4 | /// An interface representing an item that is deeply cloneable.
5 | ///
6 | /// The type of the clone.
7 | public interface IDeepCloneable
8 | {
9 | ///
10 | /// Creates a deep clone of the item, where no data references are shared.
11 | /// Changes made to the clone do not affect the original, and vice versa.
12 | ///
13 | /// The clone.
14 | T DeepClone();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Samples/HelpApp/Actions/Mediocre.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace HelpApp.Actions
4 | {
5 | internal enum MediocrityLevel
6 | {
7 | Mediocre,
8 | TrulyMediocre
9 | }
10 |
11 | internal class Mediocre : SynchronousCommand
12 | {
13 | [NamedArgument(
14 | ArgumentFlags.Required,
15 | LongName = "med-level",
16 | Description = "Level of mediocrity desired to be attained. Or barely passed.")]
17 | MediocrityLevel Level { get; set; }
18 |
19 | public override CommandResult Execute() => CommandResult.Success;
20 | }
21 | }
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Types/ArgumentParseContextTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using FluentAssertions;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using NClap.Types;
6 |
7 | namespace NClap.Tests.Types
8 | {
9 | [TestClass]
10 | public class ArgumentParseContextTests
11 | {
12 | [TestMethod]
13 | public void DefaultedReader()
14 | {
15 | var context = new ArgumentParseContext();
16 |
17 | Action setNull = () => context.FileSystemReader = null;
18 | setNull.Should().NotThrow();
19 |
20 | context.FileSystemReader.Should().NotBeNull();
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/NClap/Help/ArgumentHelpLayout.cs:
--------------------------------------------------------------------------------
1 | using NClap.Utilities;
2 |
3 | namespace NClap.Help
4 | {
5 | ///
6 | /// Abstract base class for an argument help layout.
7 | ///
8 | public abstract class ArgumentHelpLayout : IDeepCloneable
9 | {
10 | ///
11 | /// Default constructor.
12 | ///
13 | protected ArgumentHelpLayout()
14 | {
15 | }
16 |
17 | ///
18 | /// Create a separate clone of this object.
19 | ///
20 | /// Clone.
21 | public abstract ArgumentHelpLayout DeepClone();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | //
2 | // This file is used by Code Analysis to maintain SuppressMessage
3 | // attributes that are applied to this project.
4 | // Project-level suppressions either have no target or are given
5 | // a specific target and scoped to a namespace, type, member, etc.
6 | //
7 |
8 | using System.Diagnostics.CodeAnalysis;
9 | using System.Runtime.CompilerServices;
10 |
11 | [assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "[Legacy]")]
12 | [assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "[Legacy]")]
13 |
14 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
--------------------------------------------------------------------------------
/src/Samples/MultilevelCommandApp/MultilevelCommandApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | false
7 |
8 |
9 |
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/Command.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace NClap.Metadata
5 | {
6 | ///
7 | /// Base class for implementing commands.
8 | ///
9 | [ArgumentType(DisplayName = "command")]
10 | public abstract class Command : ICommand
11 | {
12 | ///
13 | /// Executes the command.
14 | ///
15 | /// Cancellation token.
16 | /// Result of execution.
17 | public virtual Task ExecuteAsync(CancellationToken cancel) =>
18 | Task.FromResult(CommandResult.UsageError);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/NClap/Help/ArgumentShortNameHelpMode.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Help
2 | {
3 | ///
4 | /// Mode for including short name info in help.
5 | ///
6 | public enum ArgumentShortNameHelpMode
7 | {
8 | ///
9 | /// Do not include short names.
10 | ///
11 | Omit,
12 |
13 | ///
14 | /// Include short names together with long names at the beginning of the
15 | /// description.
16 | ///
17 | IncludeWithLongName,
18 |
19 | ///
20 | /// Append short names to the end of the description.
21 | ///
22 | AppendToDescription
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Metadata/CommandTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Metadata;
4 | using System;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace NClap.Tests.Metadata
9 | {
10 | [TestClass]
11 | public class CommandTests
12 | {
13 | private class TestCommand : Command
14 | {
15 | }
16 |
17 | [TestMethod]
18 | public void TestThatBaseCommandImplementationReturnsUsageError()
19 | {
20 | var command = new TestCommand();
21 | command.ExecuteAsync(CancellationToken.None).Result.Should().Be(CommandResult.UsageError);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Tools/Inspector/NClap.Inspector.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | win10-x64;linux-x64
7 | false
8 |
9 |
10 |
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ArgumentValueFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Flags controlling the use of argument values.
7 | ///
8 | [Flags]
9 | public enum ArgumentValueFlags
10 | {
11 | ///
12 | /// Indicates default behavior is desired.
13 | ///
14 | None,
15 |
16 | ///
17 | /// Indicates that the related value should not be allowed.
18 | ///
19 | Disallowed,
20 |
21 | ///
22 | /// Indicates that the related value should not be displayed in help
23 | /// text.
24 | ///
25 | Hidden
26 | }
27 | }
--------------------------------------------------------------------------------
/src/NClap/Help/ArgumentDefaultValueHelpMode.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Help
2 | {
3 | ///
4 | /// Mode for including info about argument default values.
5 | ///
6 | public enum ArgumentDefaultValueHelpMode
7 | {
8 | ///
9 | /// Do not include default values.
10 | ///
11 | Omit,
12 |
13 | ///
14 | /// Prepend to the start of the argument's description (but after the
15 | /// argument's syntax).
16 | ///
17 | PrependToDescription,
18 |
19 | ///
20 | /// Append to the end of the argument's description.
21 | ///
22 | AppendToDescription
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/NClap/Expressions/ExpressionEnvironment.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Expressions
2 | {
3 | ///
4 | /// An expression environment.
5 | ///
6 | internal abstract class ExpressionEnvironment
7 | {
8 | ///
9 | /// Tries to retrieve the value associated with the given
10 | /// variable.
11 | ///
12 | /// Name of the variable.
13 | /// On success, receives the value associated
14 | /// with the variable.
15 | /// true if the variable was found; false otherwise.
16 | public abstract bool TryGetVariable(string variableName, out string value);
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Tests/TestApp/CompleteCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Metadata;
3 | using NClap.Types;
4 |
5 | namespace NClap.TestApp
6 | {
7 | class CompleteCommand : SynchronousCommand
8 | {
9 | [NamedArgument]
10 | public FileSystemPath Path { get; set; }
11 |
12 | [NamedArgument]
13 | public int Integer { get; set; }
14 |
15 | [NamedArgument]
16 | public string String { get; set; }
17 |
18 | [NamedArgument]
19 | public Guid Guid { get; set; }
20 |
21 | [NamedArgument]
22 | public bool Boolean { get; set; }
23 |
24 | public override CommandResult Execute()
25 | {
26 | return CommandResult.Success;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Metadata/UnimplementedCommandTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Metadata;
4 | using System;
5 | using System.Threading;
6 |
7 | namespace NClap.Tests.Metadata
8 | {
9 | [TestClass]
10 | public class UnimplementedCommandTests
11 | {
12 | [TestMethod]
13 | public void TestThatUnimplementedCommandAlwaysReturnsCorrectCode()
14 | {
15 | var command = new UnimplementedCommand();
16 | command.Awaiting(c => c.ExecuteAsync(CancellationToken.None)).Should().ThrowAsync();
17 | command.Invoking(c => c.Execute()).Should().Throw();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/NClap/ConsoleInput/ConsoleInputOperationResult.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.ConsoleInput
2 | {
3 | ///
4 | /// The result of processing a console input operation.
5 | ///
6 | internal enum ConsoleInputOperationResult
7 | {
8 | ///
9 | /// The operation was handled, but more input is available.
10 | ///
11 | Normal,
12 |
13 | ///
14 | /// The event was handled, and the end of the input line was reached.
15 | ///
16 | EndOfInputLine,
17 |
18 | ///
19 | /// The event was handled, and the end of the input stream was reached.
20 | ///
21 | EndOfInputStream
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/NClap/Expressions/Expression.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Expressions
2 | {
3 | ///
4 | /// Base class for all expressions.
5 | ///
6 | internal abstract class Expression
7 | {
8 | ///
9 | /// Tries to evaluate the given expression in the context of
10 | /// the given environment.
11 | ///
12 | /// Environment in which to evaluate the
13 | /// expression.
14 | /// On success, receives the evaluation
15 | /// result.
16 | /// true on success; false otherwise.
17 | public abstract bool TryEvaluate(ExpressionEnvironment env, out string value);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/HelpCommandAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Attribute for annotating help commands.
7 | ///
8 | [AttributeUsage(AttributeTargets.Field)]
9 | public sealed class HelpCommandAttribute : CommandAttribute
10 | {
11 | ///
12 | /// Gets the type that "implements" this command.
13 | ///
14 | /// The type of the command associated with this
15 | /// attribute.
16 | /// The type.
17 | public override Type GetImplementingType(Type commandType) =>
18 | typeof(HelpCommand<>).MakeGenericType(commandType);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Tests/TestApp/ReadLineCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.ConsoleInput;
3 | using NClap.Metadata;
4 |
5 | namespace NClap.TestApp
6 | {
7 | class ReadLineCommand : SynchronousCommand
8 | {
9 | [NamedArgument(ArgumentFlags.Optional, DefaultValue = true, Description = "Echo input back to screen.")]
10 | bool Echo { get; set; }
11 |
12 | public override CommandResult Execute()
13 | {
14 | Console.WriteLine("Reading input line...");
15 |
16 | var line = ConsoleUtilities.ReadLine();
17 |
18 | if (Echo)
19 | {
20 | Console.WriteLine($"Read: [{line}]");
21 | }
22 |
23 | return CommandResult.Success;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/NClap/Help/OneColumnArgumentHelpLayout.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Help
2 | {
3 | ///
4 | /// Describes a single-column argument help layout: name(s) followed
5 | /// by description (if applicable) in the second.
6 | ///
7 | public class OneColumnArgumentHelpLayout : ArgumentHelpLayout
8 | {
9 | ///
10 | /// Default constructor.
11 | ///
12 | public OneColumnArgumentHelpLayout()
13 | {
14 | }
15 |
16 | ///
17 | /// Create a separate clone of this object.
18 | ///
19 | /// Clone.
20 | public override ArgumentHelpLayout DeepClone() => new OneColumnArgumentHelpLayout();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ArgumentValidationContext.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Metadata
2 | {
3 | ///
4 | /// Context for argument validation.
5 | ///
6 | public class ArgumentValidationContext
7 | {
8 | ///
9 | /// Primary constructor.
10 | ///
11 | /// File system reader for context.
12 | ///
13 | public ArgumentValidationContext(IFileSystemReader fileSystemReader)
14 | {
15 | FileSystemReader = fileSystemReader;
16 | }
17 |
18 | ///
19 | /// The file-system reader to use in this context.
20 | ///
21 | public IFileSystemReader FileSystemReader { get; }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/NClap/Metadata/CommandResult.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Metadata
2 | {
3 | ///
4 | /// Result from a command's execution.
5 | ///
6 | public enum CommandResult
7 | {
8 | ///
9 | /// The command completed successfully.
10 | ///
11 | Success,
12 |
13 | ///
14 | /// The command requested the termination of the caller.
15 | ///
16 | Terminate,
17 |
18 | ///
19 | /// The command detected a usage or syntax error.
20 | ///
21 | UsageError,
22 |
23 | ///
24 | /// The command experienced a runtime failure.
25 | ///
26 | RuntimeFailure
27 | }
28 | }
--------------------------------------------------------------------------------
/src/NClap/Utilities/CircularEnumerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace NClap.Utilities
5 | {
6 | ///
7 | /// Utilities for interacting with circular enumerators.
8 | ///
9 | internal static class CircularEnumerator
10 | {
11 | ///
12 | /// Creates a new circular enumerator.
13 | ///
14 | /// Type of the item in the enumerated list.
15 | ///
16 | /// List to be enumerated.
17 | /// The enumerator.
18 | public static CircularEnumerator Create(IReadOnlyList values) =>
19 | new CircularEnumerator(values);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Samples/MultilevelCommandApp/GetConfigurationCommand.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using NClap.Metadata;
3 |
4 | namespace MultilevelCommandApp
5 | {
6 | internal class GetConfigurationCommand : CommandBase
7 | {
8 | private readonly ILogger logger;
9 | private readonly ConfigurationCommand configCommand;
10 |
11 | public GetConfigurationCommand(ILogger logger, ConfigurationCommand configCommand)
12 | {
13 | this.logger = logger;
14 | this.configCommand = configCommand;
15 | }
16 |
17 | public override CommandResult Execute()
18 | {
19 | logger.LogInformation($"Getting {configCommand.Policy} configuration");
20 | return CommandResult.Success;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Samples/MultilevelCommandApp/SetConfigurationCommand.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using NClap.Metadata;
3 |
4 | namespace MultilevelCommandApp
5 | {
6 | internal class SetConfigurationCommand : CommandBase
7 | {
8 | private readonly ILogger logger;
9 | private readonly ConfigurationCommand configCommand;
10 |
11 | public SetConfigurationCommand(ILogger logger, ConfigurationCommand configCommand)
12 | {
13 | this.logger = logger;
14 | this.configCommand = configCommand;
15 | }
16 |
17 | public override CommandResult Execute()
18 | {
19 | logger.LogInformation($"Setting {configCommand.Policy} configuration");
20 | return CommandResult.Success;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Tests/TestApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Help;
3 |
4 | namespace NClap.TestApp
5 | {
6 | class Program
7 | {
8 | private static int Main(string[] args)
9 | {
10 | var options = new CommandLineParserOptions
11 | {
12 | HelpOptions = new ArgumentSetHelpOptions()
13 | .With()
14 | .TwoColumnLayout()
15 | };
16 |
17 | if (!CommandLineParser.TryParse(args, options, out ProgramArguments programArgs))
18 | {
19 | return -1;
20 | }
21 |
22 | var result = programArgs.Command.Execute();
23 | Console.WriteLine($"Result: {result}");
24 |
25 | return 0;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.json]
2 | indent_style = space
3 | indent_size = 4
4 |
5 | [*.{cs,vb}]
6 | # CA1711: Identifiers should not have incorrect suffix
7 | dotnet_diagnostic.CA1711.severity = none
8 | # CA1008: Enums should have zero value
9 | dotnet_diagnostic.CA1008.severity = none
10 | # CA1069: Enums should not have duplicate values
11 | dotnet_diagnostic.CA1069.severity = none
12 | # TODO: CA5392: Use DefaultDllImportSearchPaths attribute for P/Invokes
13 | dotnet_diagnostic.CA5392.severity = none
14 | # TODO: CA2237: Mark ISerializable types with SerializableAttribute
15 | dotnet_diagnostic.CA2237.severity = none
16 | # TODO: CA2201: Do not raise reserved exception types
17 | dotnet_diagnostic.CA2201.severity = none
18 | # TODO: CA1309: Use ordinal StringComparison
19 | dotnet_diagnostic.CA1309.severity = none
--------------------------------------------------------------------------------
/src/Samples/MultilevelCommandApp/ProgramArguments.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace MultilevelCommandApp
4 | {
5 | class ProgramArguments
6 | {
7 | public enum ToplevelCommandType
8 | {
9 | [ArgumentValue(Flags = ArgumentValueFlags.Disallowed)] Invalid,
10 |
11 | [Command(typeof(ConfigurationCommand), LongName = "Config")] Configuration,
12 | [Command(typeof(StatusCommand))] Status,
13 |
14 | [Command(typeof(ExitCommand))] Exit,
15 | [HelpCommand] Help
16 | }
17 |
18 | [NamedArgument]
19 | public bool Verbose { get; set; }
20 |
21 | [PositionalArgument(ArgumentFlags.Optional)]
22 | public CommandGroup Command { get; set; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/ConsoleInput/TestTokenCompleter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using NClap.ConsoleInput;
4 |
5 | namespace NClap.Tests.ConsoleInput
6 | {
7 | internal class TestTokenCompleter : ITokenCompleter
8 | {
9 | Func, int, IEnumerable> _func;
10 |
11 | public TestTokenCompleter(Func, int, IEnumerable> func)
12 | {
13 | _func = func;
14 | }
15 |
16 | public TestTokenCompleter(IEnumerable results)
17 | {
18 | _func = (tokens, index) => results;
19 | }
20 |
21 | public IEnumerable GetCompletions(IEnumerable tokens, int tokenIndex) =>
22 | _func(tokens, tokenIndex);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Tests/TestApp/ProgramArguments.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace NClap.TestApp
4 | {
5 | enum LogLevel
6 | {
7 | Default,
8 | Heightened,
9 | Awesome,
10 | Subdued
11 | }
12 |
13 | [ArgumentSet(
14 | Logo = Logo,
15 | Style = ArgumentSetStyle.PowerShell,
16 | Description = "Some tool that is useful only for testing.")]
17 | class ProgramArguments : HelpArgumentsBase
18 | {
19 | public const string Logo = @"My Test Tool
20 | Version 1.0";
21 |
22 | [PositionalArgument(ArgumentFlags.Required, Position = 0)]
23 | public CommandGroup Command { get; set; }
24 |
25 | [NamedArgument(ArgumentFlags.Optional)]
26 | public LogLevel LogLevel { get; set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Metadata/ArgumentTypeAttributeTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Metadata;
4 | using NClap.Types;
5 |
6 | namespace NClap.Tests.Metadata
7 | {
8 | [TestClass]
9 | public class ArgumentTypeAttributeTests
10 | {
11 | private const string CustomDisplayName = "My custom display name";
12 |
13 | [ArgumentType(DisplayName = CustomDisplayName)]
14 | private enum MyCustomType
15 | {
16 | SomeValue
17 | }
18 |
19 | [TestMethod]
20 | public void TestThatCustomDisplayNameIsObserved()
21 | {
22 | var argType = ArgumentType.GetType(typeof(MyCustomType));
23 | argType.DisplayName.Should().Be(CustomDisplayName);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/IntegerComparisonValidationAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Metadata
2 | {
3 | ///
4 | /// Abstract base class for integer validation attributes that compare
5 | /// against a known value.
6 | ///
7 | public abstract class IntegerComparisonValidationAttribute : IntegerValidationAttribute
8 | {
9 | ///
10 | /// Constructor for derived classes to use.
11 | ///
12 | /// Value to compare against.
13 | protected IntegerComparisonValidationAttribute(object target)
14 | {
15 | Target = target;
16 | }
17 |
18 | ///
19 | /// Fixed comparison value for validation.
20 | ///
21 | public object Target { get; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ArgumentSetStyle.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Metadata
2 | {
3 | ///
4 | /// Overall argument parsing style.
5 | ///
6 | public enum ArgumentSetStyle
7 | {
8 | ///
9 | /// No specific style is desired or specified.
10 | ///
11 | Unspecified,
12 |
13 | ///
14 | /// The style of simple Windows command-line tools.
15 | ///
16 | WindowsCommandLine,
17 |
18 | ///
19 | /// The style of PowerShell cmdlets.
20 | ///
21 | PowerShell,
22 |
23 | ///
24 | /// The style of apps and scripts implemented using getopt and its
25 | /// default formatting.
26 | ///
27 | GetOpt
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/IArgumentProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Interface for an object to expose additional arguments that it does
7 | /// not directly contain.
8 | ///
9 | internal interface IArgumentProvider
10 | {
11 | ///
12 | /// Retrieve info for the object type that defines the arguments to be
13 | /// parsed.
14 | ///
15 | /// The defining type.
16 | Type GetTypeDefiningArguments();
17 |
18 | ///
19 | /// Retrieve a reference to the object into which parsed arguments
20 | /// should be stored.
21 | ///
22 | /// The object in question.
23 | object GetDestinationObject();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ArgumentNameGenerationFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Style flags for names generated automatically from code symbols.
7 | ///
8 | [Flags]
9 | public enum ArgumentNameGenerationFlags
10 | {
11 | ///
12 | /// Use the code symbol verbatim.
13 | ///
14 | UseOriginalCodeSymbol = 0x0,
15 |
16 | ///
17 | /// Make a best effort attempt to convert code symbols to hyphenated,
18 | /// lower-case symbols when generating long names.
19 | ///
20 | GenerateHyphenatedLowerCaseLongNames = 0x1,
21 |
22 | ///
23 | /// Prefer lower case short names.
24 | ///
25 | PreferLowerCaseForShortNames = 0x2
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Help/ArgumentSetUsageInfoTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Help;
4 | using NClap.Metadata;
5 | using NClap.Parser;
6 |
7 | namespace NClap.Tests.Help
8 | {
9 | [TestClass]
10 | public class ArgumentSetUsageInfoTests
11 | {
12 | [TestMethod]
13 | public void TestThatGetLogoYieldsEmptyStringLogoCannotBeExpanded()
14 | {
15 | var attrib = new ArgumentSetAttribute
16 | {
17 | Logo = "{",
18 | ExpandLogo = true
19 | };
20 |
21 | var argSet = new ArgumentSetDefinition(attrib);
22 | var usageInfo = new ArgumentSetUsageInfo(argSet, null);
23 |
24 | var logo = usageInfo.Logo;
25 | logo.Should().BeEmpty();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/NClap/ConsoleInput/IConsoleInput.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.ConsoleInput
4 | {
5 | ///
6 | /// Abstract interface for interacting with an input console.
7 | ///
8 | public interface IConsoleInput
9 | {
10 | ///
11 | /// True if Control-C is treated as a normal input character; false if
12 | /// it's specially handled.
13 | ///
14 | bool TreatControlCAsInput { get; set; }
15 |
16 | ///
17 | /// Reads a key press from the console.
18 | ///
19 | /// True to suppress auto-echoing the key's
20 | /// character; false to echo it as normal.
21 | /// Info about the press.
22 | ConsoleKeyInfo ReadKey(bool suppressEcho);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/StringsTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using FluentAssertions;
3 |
4 | namespace NClap.Tests
5 | {
6 | [TestClass]
7 | public class StringsTests
8 | {
9 | [TestMethod]
10 | public void Instantiate()
11 | {
12 | var s = new Strings();
13 | s.Should().NotBeNull();
14 | }
15 |
16 | [TestMethod]
17 | public void Culture()
18 | {
19 | // The culture is initially null.
20 | Strings.Culture.Should().BeNull();
21 |
22 | // Should be okay to set to null too.
23 | Strings.Culture = null;
24 | }
25 |
26 | [TestMethod]
27 | public void DefaultPrompt()
28 | {
29 | Strings.DefaultPrompt.Should().NotBeNullOrWhiteSpace();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/NClap/ConsoleInput/ITokenCompleter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace NClap.ConsoleInput
4 | {
5 | ///
6 | /// Abstract interface for an object that can generate completions for a console
7 | /// input token.
8 | ///
9 | public interface ITokenCompleter
10 | {
11 | ///
12 | /// Retrieves the completions for the given token, in the context of the given
13 | /// set of tokens.
14 | ///
15 | /// The current set of tokens.
16 | /// The 0-based index of the token to get completions
17 | /// for.
18 | /// The enumeration of completions for the token.
19 | IEnumerable GetCompletions(IEnumerable tokens, int tokenIndex);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/NClap/Types/IStringParser.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | namespace NClap.Types
4 | {
5 | ///
6 | /// Interface implemented by objects that can parse strings.
7 | ///
8 | public interface IStringParser
9 | {
10 | ///
11 | /// Tries to parse the provided string, extracting a value of the type
12 | /// described by this interface.
13 | ///
14 | /// Context for parsing.
15 | /// The string to parse.
16 | /// On success, receives the parsed value; null
17 | /// otherwise.
18 | /// True on success; false otherwise.
19 | bool TryParse(ArgumentParseContext context, string stringToParse, out object value);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ICommandGroup.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Metadata
2 | {
3 | ///
4 | /// Interface for interacting with any command group.
5 | ///
6 | public interface ICommandGroup : ICommand
7 | {
8 | ///
9 | /// True if the group has a selection, false if no selection was yet
10 | /// made.
11 | ///
12 | bool HasSelection { get; }
13 |
14 | ///
15 | /// The enum value corresponding with the selected command, or null if no
16 | /// selection has yet been made.
17 | ///
18 | object Selection { get; }
19 |
20 | ///
21 | /// The command presently selected from this group, or null if no
22 | /// selection has yet been made.
23 | ///
24 | ICommand InstantiatedCommand { get; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/SynchronousCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace NClap.Metadata
5 | {
6 | ///
7 | /// Base class for implementing synchronously executing commands.
8 | ///
9 | public abstract class SynchronousCommand : Command
10 | {
11 | ///
12 | /// Executes the command.
13 | ///
14 | /// Cancellation token.
15 | /// Result of execution.
16 | public override Task ExecuteAsync(CancellationToken cancel) =>
17 | Task.Run(() => Execute(), cancel);
18 |
19 | ///
20 | /// Executes the command.
21 | ///
22 | /// Result of execution.
23 | public abstract CommandResult Execute();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.trunk/trunk.yaml:
--------------------------------------------------------------------------------
1 | version: 0.1
2 | actions:
3 | disabled:
4 | - trunk-announce
5 | - trunk-check-pre-push
6 | - trunk-fmt-pre-commit
7 | enabled:
8 | - trunk-cache-prune
9 | - trunk-upgrade-available
10 | runtimes:
11 | enabled:
12 | - go@1.18.3
13 | - node@16.14.2
14 | cli:
15 | version: 0.18.0-beta
16 | sha256:
17 | darwin_arm64: ed797167515f28c22d5f7bd553f67fd94ce84d5f709963bfc2af1c2ecba10d6a
18 | darwin_x86_64: d40927a6b7a84d00103044c342ed240baab52eeb9e0f6d40e5d2adff299889ec
19 | linux_x86_64: 4da43299049fb1836960b72de4f6830f5e672ca876656836b85588d7a5723eab
20 | plugins:
21 | sources:
22 | - id: trunk
23 | ref: v0.0.4
24 | uri: https://github.com/trunk-io/plugins
25 | lint:
26 | enabled:
27 | - actionlint@1.6.19
28 | - git-diff-check@SYSTEM
29 | - gitleaks@8.13.0
30 | - hadolint@2.10.0
31 | - markdownlint@0.32.2
32 | - prettier@2.7.1
33 |
--------------------------------------------------------------------------------
/src/Samples/MultilevelCommandApp/ConfigurationCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NClap.Metadata;
4 |
5 | namespace MultilevelCommandApp
6 | {
7 | enum ConfigurationPolicy
8 | {
9 | Permanent,
10 | Ephemeral
11 | }
12 |
13 | internal class ConfigurationCommand : ICommand
14 | {
15 | internal enum Ty
16 | {
17 | [Command(typeof(GetConfigurationCommand))] Get,
18 | [Command(typeof(SetConfigurationCommand))] Set
19 | }
20 |
21 | [NamedArgument(ArgumentFlags.Optional)]
22 | public ConfigurationPolicy Policy { get; set; }
23 |
24 | [PositionalArgument(ArgumentFlags.Required, LongName = "ConfigAction")]
25 | public CommandGroup Command { get; set; }
26 |
27 | public Task ExecuteAsync(CancellationToken cancel) => Command.ExecuteAsync(cancel);
28 | }
29 | }
--------------------------------------------------------------------------------
/src/NClap/Utilities/MaybeUtilities.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace NClap.Utilities
5 | {
6 | ///
7 | /// Extension methods for objects.
8 | ///
9 | internal static class MaybeUtilities
10 | {
11 | ///
12 | /// From the given enumeration of values, yield an enumeration
13 | /// with only the values present (and unwrap them from their
14 | /// objects).
15 | ///
16 | /// Value type.
17 | /// Input enumeration.
18 | /// The resulting enumeration.
19 | public static IEnumerable WhereHasValue(this IEnumerable> values) =>
20 | values.Where(v => v.HasValue).Select(v => v.Value);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/NClap/Help/ArgumentEnumValueHelpFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Help
4 | {
5 | ///
6 | /// Mode for generating help for enum values.
7 | ///
8 | [Flags]
9 | public enum ArgumentEnumValueHelpFlags
10 | {
11 | ///
12 | /// Each enum is documented in the default format, at each use site.
13 | /// This summary will be duplicated if the enum type is used in multiple
14 | /// arguments.
15 | ///
16 | None = 0,
17 |
18 | ///
19 | /// Enum types with multiple references will be promoted to their own
20 | /// sections.
21 | ///
22 | SingleSummaryOfEnumsWithMultipleUses = 0x1,
23 |
24 | ///
25 | /// Command enums will be promoted to their own sections.
26 | ///
27 | SingleSummaryOfAllCommandEnums = 0x2
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Metadata/ArgumentValueAttributeTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Exceptions;
4 | using NClap.Metadata;
5 |
6 | namespace NClap.Tests.Metadata
7 | {
8 | [TestClass]
9 | public class ArgumentValueAttributeTests
10 | {
11 | [TestMethod]
12 | public void TestThatLongNameAcceptsNull()
13 | {
14 | var attrib = new ArgumentValueAttribute();
15 |
16 | attrib.Invoking(a => a.LongName = null)
17 | .Should().NotThrow();
18 | }
19 |
20 | [TestMethod]
21 | public void TestThatLongNameThrowsOnEmptyString()
22 | {
23 | var attrib = new ArgumentValueAttribute();
24 |
25 | attrib.Invoking(a => a.LongName = string.Empty)
26 | .Should().Throw();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ExtensibleEnumAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Types;
3 |
4 | namespace NClap.Metadata
5 | {
6 | // CA1019: Define accessors for attribute arguments
7 | #pragma warning disable CA1019
8 |
9 | ///
10 | /// Attribute that indicates the associated enum type is extensible.
11 | ///
12 | [AttributeUsage(AttributeTargets.Enum, AllowMultiple = true)]
13 | public sealed class ExtensibleEnumAttribute : Attribute
14 | {
15 | ///
16 | /// Constructor.
17 | ///
18 | /// Provider.
19 | public ExtensibleEnumAttribute(Type provider)
20 | {
21 | Provider = provider;
22 | }
23 |
24 | ///
25 | /// Implementation of .
26 | ///
27 | public Type Provider { get; set; }
28 | }
29 |
30 | #pragma warning restore CA1019
31 | }
32 |
--------------------------------------------------------------------------------
/src/NClap/Types/IStringCompleter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace NClap.Types
4 | {
5 | ///
6 | /// Interface implemented by objects that can generate completions for a
7 | /// given string.
8 | ///
9 | public interface IStringCompleter
10 | {
11 | ///
12 | /// Generates a set of valid strings--parseable to this type--that
13 | /// contain the provided string as a strict prefix.
14 | ///
15 | /// Context for parsing.
16 | /// The string to complete.
17 | /// An enumeration of a set of completion strings; if no such
18 | /// strings could be generated, or if the type doesn't support
19 | /// completion, then an empty enumeration is returned.
20 | IEnumerable GetCompletions(ArgumentCompletionContext context, string valueToComplete);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Exceptions/InvalidArgumentSetExceptionTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Exceptions;
4 | using NClap.Metadata;
5 | using NClap.Tests.Metadata;
6 |
7 | namespace NClap.Tests.Exceptions
8 | {
9 | [TestClass]
10 | public class InvalidArgumentSetExceptionTests : ExceptionTests
11 | {
12 | public class SampleArguments
13 | {
14 | [NamedArgument] public int Value;
15 | }
16 |
17 | [TestMethod]
18 | public void ArgumentConstructor()
19 | {
20 | const string innerMessage = "Something message-like.";
21 |
22 | var arg = ArgumentTests.GetArgument(typeof(SampleArguments), "Value");
23 | var exn = new InvalidArgumentSetException(arg, innerMessage);
24 | exn.Argument.Should().BeSameAs(arg);
25 | exn.InnerMessage.Should().Be(innerMessage);
26 | exn.Message.Should().Contain(innerMessage);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Tests/TestApp/MainCommandType.cs:
--------------------------------------------------------------------------------
1 | using NClap.Metadata;
2 |
3 | namespace NClap.TestApp
4 | {
5 | [ArgumentType(DisplayName = "REPL command")]
6 | enum MainCommandType
7 | {
8 | [HelpCommand(Description = "Displays command help")]
9 | Help,
10 |
11 | [Command(typeof(CliHelp), Description = "Displays toplevel help")]
12 | CliHelp,
13 |
14 | [Command(typeof(CompleteCommand), Description = "Useful only for completing")]
15 | Complete,
16 |
17 | [Command(typeof(LogoCommand), Description = "Display 'logo'")]
18 | Logo,
19 |
20 | [Command(typeof(ReadLineCommand), ShortName = "readl", Description = "Reads a line of input")]
21 | ReadLine,
22 |
23 | [Command(typeof(ReplCommand), Description = "Starts interactive loop")]
24 | Repl,
25 |
26 | [Command(typeof(CommandGroup), Description = "Test sub-command group")]
27 | SubCommand,
28 |
29 | [Command(typeof(ExitCommand), Description = "Exits the loop")]
30 | Exit
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/HelpCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Help;
3 | using NClap.Utilities;
4 |
5 | namespace NClap.Metadata
6 | {
7 | ///
8 | /// Static class useful for configuring the behavior of help commands.
9 | ///
10 | public static class HelpCommand
11 | {
12 | ///
13 | /// The default options to use for generate help.
14 | ///
15 | [Obsolete("Loop help may be customized with LoopOptions instead.")]
16 | public static ArgumentSetHelpOptions DefaultHelpOptions { get; set; } =
17 | new ArgumentSetHelpOptions
18 | {
19 | Logo = new ArgumentMetadataHelpOptions { Include = false },
20 | Name = string.Empty
21 | };
22 |
23 | ///
24 | /// The output handler function for this class.
25 | ///
26 | [Obsolete("Loop help may be customized with LoopOptions instead.")]
27 | public static Action OutputHandler { get; set; }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Utilities/ColoredStringExtensionMethodsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using NClap.Utilities;
5 |
6 | namespace NClap.Tests.Utilities
7 | {
8 | [TestClass]
9 | public class ColoredStringExtensionMethodsTests
10 | {
11 | [TestMethod]
12 | public void TestThatTransformThrowsOnNullFunc()
13 | {
14 | var anyCs = AnyColoredString();
15 | anyCs.Invoking(cs => cs.Transform(null)).Should().Throw();
16 | }
17 |
18 | [TestMethod]
19 | public void TestThatTransformPreservesColor()
20 | {
21 | var anyCs = AnyColoredString();
22 | const string anyString = "Something different";
23 | var updated = anyCs.Transform(_ => anyString);
24 |
25 | updated.IsSameColorAs(anyCs).Should().BeTrue();
26 | }
27 |
28 | private ColoredString AnyColoredString() =>
29 | new ColoredString("Some text", Any.Enum(), Any.Enum());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/NClap/Utilities/TokenizerOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Utilities
4 | {
5 | ///
6 | /// Options for tokenizing command lines.
7 | ///
8 | [Flags]
9 | internal enum TokenizerOptions
10 | {
11 | ///
12 | /// Do not apply other policy options.
13 | ///
14 | None,
15 |
16 | ///
17 | /// Allow tokenizing of partial (incomplete) input lines; this includes
18 | /// ignoring errors related to unmatched quotes around the last token.
19 | ///
20 | AllowPartialInput,
21 |
22 | ///
23 | /// Handle double quote character as a token delimiter (allowing embedded
24 | /// whitespace within the token.
25 | ///
26 | HandleDoubleQuoteAsTokenDelimiter,
27 |
28 | ///
29 | /// Handle single quote character as a token delimiter (allowing embedded
30 | /// whitespace within the token.
31 | ///
32 | HandleSingleQuoteAsTokenDelimiter,
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/NClap/Help/ArgumentSetHelpSectionType.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Help
2 | {
3 | ///
4 | /// Type of section in argument help output.
5 | ///
6 | internal enum ArgumentSetHelpSectionType
7 | {
8 | ///
9 | /// Argument set description.
10 | ///
11 | ArgumentSetDescription,
12 |
13 | ///
14 | /// Summary of separately documented enum values.
15 | ///
16 | EnumValues,
17 |
18 | ///
19 | /// Example usage information.
20 | ///
21 | Examples,
22 |
23 | ///
24 | /// Argument set logo.
25 | ///
26 | Logo,
27 |
28 | ///
29 | /// Optional parameters.
30 | ///
31 | OptionalParameters,
32 |
33 | ///
34 | /// Required parameters.
35 | ///
36 | RequiredParameters,
37 |
38 | ///
39 | /// Syntax summary.
40 | ///
41 | Syntax,
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Samples/HelpApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap;
3 | using NClap.Help;
4 |
5 | namespace HelpApp
6 | {
7 | class Program
8 | {
9 | static int Main(string[] args)
10 | {
11 | Console.WriteLine("Parsing...");
12 |
13 | // Set up help options the way we want them.
14 | var helpOptions = new ArgumentSetHelpOptions()
15 | .With()
16 | .BlankLinesBetweenArguments(1)
17 | .ShortNames(ArgumentShortNameHelpMode.IncludeWithLongName)
18 | .DefaultValues(ArgumentDefaultValueHelpMode.PrependToDescription)
19 | .TwoColumnLayout();
20 |
21 | // Wrap help options in general parsing options.
22 | var options = new CommandLineParserOptions { HelpOptions = helpOptions };
23 |
24 | // Try to parse.
25 | if (!CommandLineParser.TryParse(args, options, out ProgramArguments programArgs))
26 | {
27 | return 1;
28 | }
29 |
30 | Console.WriteLine("Successfully parsed.");
31 |
32 | return 0;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/CommandGroupOptions.cs:
--------------------------------------------------------------------------------
1 | using NClap.Utilities;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Options for command groups.
7 | ///
8 | public class CommandGroupOptions : IDeepCloneable
9 | {
10 | ///
11 | /// Default constructor.
12 | ///
13 | internal CommandGroupOptions()
14 | {
15 | }
16 |
17 | ///
18 | /// Deeply cloning constructor.
19 | ///
20 | /// Template for clone.
21 | private CommandGroupOptions(CommandGroupOptions other)
22 | {
23 | ServiceConfigurer = other.ServiceConfigurer;
24 | }
25 |
26 | ///
27 | /// Service configurer.
28 | ///
29 | internal ServiceConfigurer ServiceConfigurer { get; set; }
30 |
31 | ///
32 | /// Duplicates the options.
33 | ///
34 | /// The duplicate.
35 | public CommandGroupOptions DeepClone() => new CommandGroupOptions(this);
36 | }
37 | }
--------------------------------------------------------------------------------
/src/NClap/Metadata/NumberOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Options describing how to parse numeric values.
7 | ///
8 | [Flags]
9 | public enum NumberOptions
10 | {
11 | ///
12 | /// Default behavior.
13 | ///
14 | None,
15 |
16 | ///
17 | /// Allow use of metric unit suffixes (e.g. k to denote a multiplier of
18 | /// 1 thousand, M to denote a multiplier of 1 million). This option
19 | /// conflicts with AllowBinaryMetricUnitSuffix. If both flags are
20 | /// present, then AllowBinaryMetricUnitSuffix takes precedence.
21 | ///
22 | AllowMetricUnitSuffix,
23 |
24 | ///
25 | /// Allow use of binary metric unit suffixes (e.g. k to denote 1024,
26 | /// M to denote 1024 * 1024). This option conflicts with
27 | /// AllowMetricUnitSuffix. If both flags are present, then
28 | /// AllowBinaryMetricUnitSuffix takes precedence.
29 | ///
30 | AllowBinaryMetricUnitSuffix
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Microsoft Corporation
4 |
5 | All rights reserved.
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | // Use IntelliSense to find out which attributes exist for C# debugging
6 | // Use hover for the description of the existing attributes
7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/src/Tests/UnitTests/bin/Debug/net6.0/NClap.Tests.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}/src/Tests/UnitTests",
16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
17 | "console": "internalConsole",
18 | "stopAtEntry": false
19 | },
20 | {
21 | "name": ".NET Core Attach",
22 | "type": "coreclr",
23 | "request": "attach"
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/FileSystemPathValidationAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Types;
3 | using NClap.Utilities;
4 |
5 | namespace NClap.Metadata
6 | {
7 | ///
8 | /// Abstract base class for implementing argument validation attributes
9 | /// that inspect file-system paths.
10 | ///
11 | public abstract class FileSystemPathValidationAttribute : ArgumentValidationAttribute
12 | {
13 | ///
14 | /// Checks if this validation attributes accepts values of the specified
15 | /// type.
16 | ///
17 | /// Type to check.
18 | /// True if this attribute accepts values of the specified
19 | /// type; false if not.
20 | /// Thrown when
21 | /// is null.
22 | public sealed override bool AcceptsType(IArgumentType type)
23 | {
24 | if (type == null) throw new ArgumentNullException(nameof(type));
25 | return (type.Type == typeof(string)) || type.Type.IsEffectivelySameAs(typeof(FileSystemPath));
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/NClap/Types/IEnumArgumentType.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace NClap.Types
4 | {
5 | ///
6 | /// Interface for advertising a type as being parseable
7 | /// using this assembly. The implementation provides sufficient
8 | /// functionality for command-line parsing, generating usage help
9 | /// information, etc. This interface should only be implemented
10 | /// by objects that describe .NET enum objects.
11 | ///
12 | public interface IEnumArgumentType : IArgumentType
13 | {
14 | ///
15 | /// Enumerate the values allowed for this enum.
16 | ///
17 | /// The values.
18 | IEnumerable GetValues();
19 |
20 | ///
21 | /// Tries to look up the corresponding with
22 | /// the given object.
23 | ///
24 | /// Object to look up.
25 | /// On success, receives the object's value.
26 | /// true on success; false otherwise.
27 | bool TryGetValue(object value, out IArgumentValue argValue);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Utilities/DeepCloneTests.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Reflection;
3 | using FluentAssertions;
4 | using NClap.Utilities;
5 |
6 | namespace NClap.Tests.Utilities
7 | {
8 | public static class DeepCloneTests
9 | {
10 | public static void CloneShouldYieldADistinctButEquivalentObject(T instance)
11 | where T : IDeepCloneable
12 | {
13 | var type = instance.GetType();
14 | var clone = instance.DeepClone();
15 |
16 | clone.Should().NotBeNull();
17 | clone.Should().NotBeSameAs(instance);
18 | clone.Should().BeOfType(type);
19 | clone.Should().BeEquivalentTo(instance);
20 |
21 | var allProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
22 | foreach (var prop in allProps.Where(p => p.PropertyType.GetTypeInfo().IsByRef))
23 | {
24 | var instanceValue = prop.GetValue(instance);
25 | if (instanceValue != null)
26 | {
27 | var cloneValue = prop.GetValue(clone);
28 | instanceValue.Should().NotBeSameAs(cloneValue);
29 | }
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/IntegerValidationAttribute.cs:
--------------------------------------------------------------------------------
1 | using NClap.Types;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Abstract base class for implementing argument validation attributes
7 | /// that inspect integers.
8 | ///
9 | public abstract class IntegerValidationAttribute : ArgumentValidationAttribute
10 | {
11 | ///
12 | /// Checks if this validation attributes accepts values of the specified
13 | /// type.
14 | ///
15 | /// Type to check.
16 | /// True if this attribute accepts values of the specified
17 | /// type; false if not.
18 | public sealed override bool AcceptsType(IArgumentType type) =>
19 | type is IntegerArgumentType;
20 |
21 | ///
22 | /// Retrieves the the argument type associated with the provided integer
23 | /// value.
24 | ///
25 | /// The value.
26 | /// The argument type.
27 | internal static IntegerArgumentType GetArgumentType(object value) =>
28 | (IntegerArgumentType)ArgumentType.GetType(value.GetType());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Types/ArgumentTypeExtensionTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 |
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 |
5 | using NClap.Types;
6 |
7 | namespace NClap.Tests.Types
8 | {
9 | [TestClass]
10 | public class ArgumentTypeExtensionTests
11 | {
12 | [TestMethod]
13 | public void NonOverriddenExtensionConstructedFromType()
14 | {
15 | var argType = new ArgumentTypeExtension(typeof(int));
16 | argType.InnerType.Type.Should().Be(typeof(int));
17 | argType.DisplayName.Should().Be(argType.InnerType.DisplayName);
18 | argType.Type.Should().Be(argType.InnerType.Type);
19 | argType.SyntaxSummary.Should().Be(argType.InnerType.SyntaxSummary);
20 | }
21 |
22 | [TestMethod]
23 | public void NonOverriddenExtensionConstructedFromArgType()
24 | {
25 | var argType = new ArgumentTypeExtension(ArgumentType.Int);
26 | argType.InnerType.Should().Be(ArgumentType.Int);
27 | argType.DisplayName.Should().Be(argType.InnerType.DisplayName);
28 | argType.Type.Should().Be(argType.InnerType.Type);
29 | argType.SyntaxSummary.Should().Be(argType.InnerType.SyntaxSummary);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/NClap/Exceptions/InternalInvariantBrokenException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Exceptions
4 | {
5 | ///
6 | /// Exception thrown when an internal invariant is broken; if this is
7 | /// thrown, then there is a code defect in this library.
8 | ///
9 | public sealed class InternalInvariantBrokenException : Exception
10 | {
11 | ///
12 | /// Standard parameterless constructor.
13 | ///
14 | public InternalInvariantBrokenException()
15 | {
16 | }
17 |
18 | ///
19 | /// Standard constructor that takes a string message.
20 | ///
21 | /// Message.
22 | public InternalInvariantBrokenException(string message) : base(message)
23 | {
24 | }
25 |
26 | ///
27 | /// Standard constructor that wraps an inner exception.
28 | ///
29 | /// Message.
30 | /// Inner exception to wrap.
31 | public InternalInvariantBrokenException(string message, Exception innerException) : base(message, innerException)
32 | {
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/NClap/Parser/ArgumentSetParseResultType.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Parser
2 | {
3 | ///
4 | /// Describes a result state for parsing an argument set.
5 | ///
6 | internal enum ArgumentSetParseResultType
7 | {
8 | ///
9 | /// Parser is ready to parse more arguments.
10 | ///
11 | Ready,
12 |
13 | ///
14 | /// Parser has encountered an unknown named argument.
15 | ///
16 | UnknownNamedArgument,
17 |
18 | ///
19 | /// Parser has encountered an unknown positional argument.
20 | ///
21 | UnknownPositionalArgument,
22 |
23 | ///
24 | /// Parser has failed to parse an argument.
25 | ///
26 | FailedParsing,
27 |
28 | ///
29 | /// Parser has failed to finalize an argument set.
30 | ///
31 | FailedFinalizing,
32 |
33 | ///
34 | /// Parser has encountered an invalid answer file.
35 | ///
36 | InvalidAnswerFile,
37 |
38 | ///
39 | /// Parser is waiting for an option argument.
40 | ///
41 | RequiresOptionArgument
42 | }
43 | }
--------------------------------------------------------------------------------
/src/Tests/UnitTests/CommandLineParserOptionsTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Tests.Utilities;
4 |
5 | namespace NClap.Tests
6 | {
7 | [TestClass]
8 | public class CommandLineParserOptionsTests
9 | {
10 | [TestMethod]
11 | public void TestThatDefaultOptionsCloneCorrectly()
12 | {
13 | var options = new CommandLineParserOptions();
14 | DeepCloneTests.CloneShouldYieldADistinctButEquivalentObject(options);
15 | }
16 |
17 | [TestMethod]
18 | public void TestThatRequiredPropertiesArePresentInDefaultOptions()
19 | {
20 | var options = new CommandLineParserOptions();
21 | options.HelpOptions.Should().NotBeNull();
22 | options.Reporter.Should().NotBeNull();
23 | options.FileSystemReader.Should().NotBeNull();
24 | }
25 |
26 | [TestMethod]
27 | public void TestThatRequiredPropertiesArePresentInQuietOptions()
28 | {
29 | var options = CommandLineParserOptions.Quiet();
30 | options.HelpOptions.Should().NotBeNull();
31 | options.Reporter.Should().NotBeNull();
32 | options.FileSystemReader.Should().NotBeNull();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/NClap/ConsoleInput/IConsoleReader.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.ConsoleInput
2 | {
3 | ///
4 | /// An advanced console reader.
5 | ///
6 | public interface IConsoleReader
7 | {
8 | ///
9 | /// The beginning-of-line comment character.
10 | ///
11 | char? CommentCharacter { get; set; }
12 |
13 | ///
14 | /// The console being used for input.
15 | ///
16 | IConsoleInput ConsoleInput { get; }
17 |
18 | ///
19 | /// The console being used for output.
20 | ///
21 | IConsoleOutput ConsoleOutput { get; }
22 |
23 | ///
24 | /// The inner line input object.
25 | ///
26 | IConsoleLineInput LineInput { get; }
27 |
28 | ///
29 | /// The console key bindings used by this console reader.
30 | ///
31 | IReadOnlyConsoleKeyBindingSet KeyBindingSet { get; }
32 |
33 | ///
34 | /// Reads a line of input text from the underlying console.
35 | ///
36 | /// The line of text, or null if the end of input was
37 | /// encountered.
38 | string ReadLine();
39 | }
40 | }
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Utilities/EnumerableUtilitiesTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Utilities;
4 | using System.Linq;
5 |
6 | namespace NClap.Tests.Utilities
7 | {
8 | [TestClass]
9 | public class EnumerableUtilitiesTests
10 | {
11 | [TestMethod]
12 | public void TestThatInsertBetweenLeavesEmptyEnumerationUnmodified()
13 | {
14 | Enumerable.Empty().InsertBetween("x").Should().BeEmpty();
15 | }
16 |
17 | [TestMethod]
18 | public void TestThatInsertBetweenLeavesOneElementEnumerationUnmodified()
19 | {
20 | var input = new[] { "elt" };
21 | input.InsertBetween("x").Should().Equal(input);
22 | }
23 |
24 | [TestMethod]
25 | public void TestThatInsertBetweenCanInsertOnce()
26 | {
27 | var input = new[] { "first", "last" };
28 | input.InsertBetween("x").Should().Equal("first", "x", "last");
29 | }
30 |
31 | [TestMethod]
32 | public void TestThatInsertBetweenCanInsertMultipleTimes()
33 | {
34 | var input = new[] { "first", "second", "third" };
35 | input.InsertBetween("x").Should().Equal("first", "x", "second", "x", "third");
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Utilities/MutableFieldInfoTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using FluentAssertions;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using NClap.Utilities;
6 |
7 | namespace NClap.Tests.Utilities
8 | {
9 | [TestClass]
10 | public class MutableFieldInfoTests
11 | {
12 | class TestObject
13 | {
14 | #pragma warning disable 0649 // Field is never assigned to, and will always have its default value
15 | public T Value;
16 | #pragma warning restore 0649
17 | }
18 |
19 | [TestMethod]
20 | public void ConversionFails()
21 | {
22 | var prop = new MutableFieldInfo(typeof(TestObject).GetTypeInfo().GetField("Value"));
23 | var obj = new TestObject();
24 | Action setter = () => prop.SetValue(obj, 3.0);
25 | setter.Should().Throw();
26 | }
27 |
28 | [TestMethod]
29 | public void ConversionSucceeds()
30 | {
31 | var prop = new MutableFieldInfo(typeof(TestObject).GetTypeInfo().GetField("Value"));
32 | var obj = new TestObject();
33 | Action setter = () => prop.SetValue(obj, 3L);
34 | setter.Should().NotThrow();
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/NClap/Help/ArgumentEnumValueHelpOptions.cs:
--------------------------------------------------------------------------------
1 | namespace NClap.Help
2 | {
3 | ///
4 | /// Help options for enum value help content.
5 | ///
6 | public class ArgumentEnumValueHelpOptions : ArgumentMetadataHelpOptions
7 | {
8 | ///
9 | /// Default constructor.
10 | ///
11 | public ArgumentEnumValueHelpOptions()
12 | {
13 | }
14 |
15 | ///
16 | /// Deeply cloning constructor.
17 | ///
18 | /// Template for clone.
19 | private ArgumentEnumValueHelpOptions(ArgumentEnumValueHelpOptions other) : base(other)
20 | {
21 | Flags = other.Flags;
22 | }
23 |
24 | ///
25 | /// Whether or not enum value summaries should be fully promoted to their
26 | /// own section, etc.
27 | ///
28 | public ArgumentEnumValueHelpFlags Flags { get; set; } =
29 | ArgumentEnumValueHelpFlags.SingleSummaryOfEnumsWithMultipleUses;
30 |
31 | ///
32 | /// Create a separate clone of this object.
33 | ///
34 | /// Clone.
35 | public override ArgumentMetadataHelpOptions DeepClone() => new ArgumentEnumValueHelpOptions(this);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/src/Tests/UnitTests/NClap.Tests.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/src/Tests/UnitTests/NClap.Tests.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "--project",
36 | "${workspaceFolder}/src/Tests/UnitTests/NClap.Tests.csproj"
37 | ],
38 | "problemMatcher": "$msCompile"
39 | }
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/src/NClap/ConsoleInput/IConsoleHistory.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace NClap.ConsoleInput
4 | {
5 | ///
6 | /// Abstract interface for managing console history.
7 | ///
8 | public interface IConsoleHistory
9 | {
10 | ///
11 | /// The count of entries in the history.
12 | ///
13 | int EntryCount { get; }
14 |
15 | ///
16 | /// If the cursor is valid, the current entry in the history; null
17 | /// otherwise.
18 | ///
19 | string CurrentEntry { get; }
20 |
21 | ///
22 | /// Add a new entry to the end of the history, and reset the history's
23 | /// cursor to that new entry.
24 | ///
25 | /// Entry to add.
26 | void Add(string entry);
27 |
28 | ///
29 | /// Move the current history cursor by the specified offset.
30 | ///
31 | /// Reference for movement.
32 | /// Positive or negative offset to apply to the
33 | /// specified origin.
34 | /// True on success; false if the move could not be made.
35 | ///
36 | bool MoveCursor(SeekOrigin origin, int offset);
37 | }
38 | }
--------------------------------------------------------------------------------
/src/NClap/Expressions/ParenthesisExpression.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Expressions
4 | {
5 | ///
6 | /// Parenthesis expression.
7 | ///
8 | internal class ParenthesisExpression : Expression
9 | {
10 | ///
11 | /// Basic constructor.
12 | ///
13 | /// Inner expression.
14 | public ParenthesisExpression(Expression innerExpression)
15 | {
16 | InnerExpression = innerExpression ?? throw new ArgumentNullException(nameof(innerExpression));
17 | }
18 |
19 | ///
20 | /// Inner expression.
21 | ///
22 | public Expression InnerExpression { get; }
23 |
24 | ///
25 | /// Tries to evaluate the given expression in the context of
26 | /// the given environment.
27 | ///
28 | /// Environment in which to evaluate the
29 | /// expression.
30 | /// On success, receives the evaluation
31 | /// result.
32 | /// true on success; false otherwise.
33 | public override bool TryEvaluate(ExpressionEnvironment env, out string value) =>
34 | InnerExpression.TryEvaluate(env, out value);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/MustNotBeEmptyAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Attribute that indicates the associated string argument member cannot be
7 | /// empty.
8 | ///
9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
10 | public sealed class MustNotBeEmptyAttribute : StringValidationAttribute
11 | {
12 | ///
13 | /// Validate the provided value in accordance with the attribute's
14 | /// policy.
15 | ///
16 | /// Context for validation.
17 | /// The value to validate.
18 | /// On failure, receives a user-readable string
19 | /// message explaining why the value is not valid.
20 | /// True if the value passes validation; false otherwise.
21 | ///
22 | public override bool TryValidate(ArgumentValidationContext context, object value, out string reason)
23 | {
24 | if (!string.IsNullOrEmpty(GetString(value)))
25 | {
26 | reason = null;
27 | return true;
28 | }
29 |
30 | reason = Strings.StringIsEmpty;
31 | return false;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/NClap.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net461;netcoreapp3.1;net6.0
5 | win10-x64;linux-x64
6 | latest
7 | false
8 |
9 |
10 |
11 | true
12 |
13 | full
14 |
15 |
16 |
17 |
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 | all
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/NClap/Expressions/StringLiteral.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Expressions
4 | {
5 | ///
6 | /// A string literal expression.
7 | ///
8 | internal class StringLiteral : Expression
9 | {
10 | ///
11 | /// Basic constructor.
12 | ///
13 | /// Literal string value.
14 | public StringLiteral(string value)
15 | {
16 | Value = value ?? throw new ArgumentNullException(nameof(value));
17 | }
18 |
19 | ///
20 | /// Literal string.
21 | ///
22 | public string Value { get; }
23 |
24 | ///
25 | /// Tries to evaluate the given expression in the context of
26 | /// the given environment.
27 | ///
28 | /// Environment in which to evaluate the
29 | /// expression.
30 | /// On success, receives the evaluation
31 | /// result.
32 | /// true on success; false otherwise.
33 | public override bool TryEvaluate(ExpressionEnvironment env, out string value)
34 | {
35 | if (env == null) throw new ArgumentNullException(nameof(env));
36 |
37 | value = Value;
38 | return true;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Parser/ArgumentSetParserTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using NClap.Parser;
5 |
6 | namespace NClap.Tests.Parser
7 | {
8 | [TestClass]
9 | public class ArgumentSetParserTests
10 | {
11 | [TestMethod]
12 | public void TestThatConstructorThrowsOnNullArgumentSet()
13 | {
14 | Action a = () => new ArgumentSetParser(null, new CommandLineParserOptions());
15 | a.Should().Throw();
16 | }
17 |
18 | [TestMethod]
19 | public void TestThatConstructorAllowNullOptions()
20 | {
21 | var argSet = new ArgumentSetDefinition();
22 | Action a = () => new ArgumentSetParser(argSet, null);
23 | a.Should().NotThrow();
24 | }
25 |
26 | [TestMethod]
27 | public void TestThatConstructorAllowsOptionsWithNullProperties()
28 | {
29 | var argSet = new ArgumentSetDefinition();
30 | var options = new CommandLineParserOptions
31 | {
32 | Context = null,
33 | FileSystemReader = null,
34 | HelpOptions = null,
35 | Reporter = null
36 | };
37 |
38 | Action a = () => new ArgumentSetParser(argSet, options);
39 | a.Should().NotThrow();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/NClap/Types/ArgumentCompletionContext.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace NClap.Types
4 | {
5 | ///
6 | /// Context for generating argument completions.
7 | ///
8 | public class ArgumentCompletionContext
9 | {
10 | ///
11 | /// The context that should be used if completion requires parsing.
12 | ///
13 | public ArgumentParseContext ParseContext { get; set; }
14 |
15 | ///
16 | /// The current tokenized state of the input.
17 | ///
18 | public IReadOnlyList Tokens { get; set; }
19 |
20 | ///
21 | /// The zero-based index of the token being completed.
22 | ///
23 | public int TokenIndex { get; set; }
24 |
25 | ///
26 | /// If this completion is being generated for command-line arguments
27 | /// being parsed, and if this object is non-null, then it is the
28 | /// object that results from parsing and processing the tokens *before*
29 | /// the one being completed.
30 | ///
31 | public object InProgressParsedObject { get; set; }
32 |
33 | ///
34 | /// True for completion to be case-sensitive; false for case-insensitive.
35 | ///
36 | public bool CaseSensitive { get; set; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Help/ArgumentSetHelpOptionsExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using NClap.Help;
5 |
6 | namespace NClap.Tests.Help
7 | {
8 | [TestClass]
9 | public class ArgumentSetHelpOptionsExtensionsTests
10 | {
11 | [TestMethod]
12 | public void TestThatColumnWidthsThrowsWithTooManyWidths()
13 | {
14 | var options = new ArgumentSetHelpOptions()
15 | .With().TwoColumnLayout();
16 |
17 | options.Invoking(o => o.ColumnWidths(Any.Int(), Any.Int(), Any.Int()).Apply())
18 | .Should().Throw();
19 | }
20 |
21 | [TestMethod]
22 | public void TestThatColumnWidthsThrowsWithWrongLayout()
23 | {
24 | var options = new ArgumentSetHelpOptions()
25 | .With().OneColumnLayout();
26 |
27 | options.Invoking(o => o.ColumnWidths(10).Apply())
28 | .Should().Throw();
29 | }
30 |
31 | [TestMethod]
32 | public void TestThatColumnSeparatorThrowsWithWrongLayout()
33 | {
34 | var options = new ArgumentSetHelpOptions()
35 | .With().OneColumnLayout();
36 |
37 | options.Invoking(o => o.ColumnSeparator(" ").Apply())
38 | .Should().Throw();
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/NClap/Types/IArgumentType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | namespace NClap.Types
6 | {
7 | ///
8 | /// Interface for advertising a type as being parseable using this
9 | /// assembly. The implementation provides sufficient functionality
10 | /// for command-line parsing, generating usage help information,
11 | /// etc.
12 | ///
13 | public interface IArgumentType : IObjectFormatter, IStringParser, IStringCompleter
14 | {
15 | ///
16 | /// The type's human-readable (display) name.
17 | ///
18 | string DisplayName { get; }
19 |
20 | ///
21 | /// The Type object associated with values described by this interface.
22 | ///
23 | [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "[Legacy]")]
24 | Type Type { get; }
25 |
26 | ///
27 | /// A summary of the concrete syntax required to indicate a value of
28 | /// the type described by this interface (e.g. ">Int32<").
29 | ///
30 | string SyntaxSummary { get; }
31 |
32 | ///
33 | /// Enumeration of all types that this type depends on / includes.
34 | ///
35 | IEnumerable DependentTypes { get; }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/HelpCommandArgumentCompleter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using NClap.Repl;
5 | using NClap.Types;
6 |
7 | namespace NClap.Metadata
8 | {
9 | ///
10 | /// Helper class used by .
11 | ///
12 | internal class HelpCommandArgumentCompleter : IStringCompleter
13 | {
14 | private readonly Loop _loop;
15 |
16 | ///
17 | /// Constructs a new completer for the given loop.
18 | ///
19 | /// Loop to generate completions for.
20 | public HelpCommandArgumentCompleter(Loop loop)
21 | {
22 | _loop = loop ?? throw new ArgumentNullException(nameof(loop));
23 | }
24 |
25 | ///
26 | public IEnumerable GetCompletions(ArgumentCompletionContext context, string valueToComplete)
27 | {
28 | // We get the entire command line here, including the token that triggered
29 | // the help command to be invoked. Any options to the help command would
30 | // also be present, which would pose a problem in the future if we add
31 | // more options to the help command.
32 | const int tokensToSkip = 1;
33 | return _loop.GetCompletions(context.Tokens.Skip(tokensToSkip), context.TokenIndex - tokensToSkip);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/NamedArgumentAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Indicates that this argument is a named argument. Attach this attribute
7 | /// to instance fields (or properties) of types used as the destination
8 | /// of command-line argument parsing.
9 | ///
10 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
11 | public sealed class NamedArgumentAttribute : ArgumentBaseAttribute
12 | {
13 | ///
14 | /// Default constructor, which may be used to indicate an optional
15 | /// named argument that may appear at most once.
16 | ///
17 | public NamedArgumentAttribute() : this(ArgumentFlags.Optional)
18 | {
19 | }
20 |
21 | ///
22 | /// Constructor that requires specifying flags.
23 | ///
24 | /// Specifies the error checking to be done on the
25 | /// argument.
26 | public NamedArgumentAttribute(ArgumentFlags flags) : base(flags)
27 | {
28 | }
29 |
30 | ///
31 | /// The short name of the argument. Set to null means use the default
32 | /// short name if it does not conflict with any other parameter name.
33 | /// Set to string.Empty for no short name.
34 | ///
35 | public string ShortName { get; set; }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Tests/TestApp/ReplCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.ConsoleInput;
3 | using NClap.Metadata;
4 | using NClap.Repl;
5 | using NClap.Utilities;
6 |
7 | namespace NClap.TestApp
8 | {
9 | [ArgumentSet(Style = ArgumentSetStyle.PowerShell)]
10 | class ReplCommand : SynchronousCommand
11 | {
12 | [NamedArgument(ArgumentFlags.Optional)]
13 | public LogLevel ReplLevel { get; set; }
14 |
15 | private static int _count = 0;
16 |
17 | public override CommandResult Execute()
18 | {
19 | Console.WriteLine("Entering loop.");
20 |
21 | var keyBindingSet = ConsoleKeyBindingSet.CreateDefaultSet();
22 | keyBindingSet.Bind('c', ConsoleModifiers.Control, ConsoleInputOperation.Abort);
23 |
24 | ++_count;
25 |
26 | var parameters = new LoopInputOutputParameters
27 | {
28 | Prompt = new ColoredString($"Loop{new string('>', _count)} ", ConsoleColor.Cyan),
29 | KeyBindingSet = keyBindingSet,
30 | EndOfLineCommentCharacter = '#'
31 | };
32 |
33 | var attrib = new ArgumentSetAttribute
34 | {
35 | Style = ArgumentSetStyle.GetOpt
36 | };
37 |
38 | new Loop(typeof(MainCommandType), parameters, attrib).Execute();
39 |
40 | --_count;
41 |
42 | Console.WriteLine("Exited loop.");
43 |
44 | return CommandResult.Success;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/ArgumentValidationAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Types;
3 |
4 | namespace NClap.Metadata
5 | {
6 | ///
7 | /// Abstract base class for implementing argument validation attributes.
8 | ///
9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
10 | public abstract class ArgumentValidationAttribute : Attribute
11 | {
12 | ///
13 | /// Checks if this validation attributes accepts values of the specified
14 | /// type.
15 | ///
16 | /// Type to check.
17 | /// True if this attribute accepts values of the specified
18 | /// type; false if not.
19 | public abstract bool AcceptsType(IArgumentType type);
20 |
21 | ///
22 | /// Validate the provided value in accordance with the attribute's
23 | /// policy.
24 | ///
25 | /// Context for validation.
26 | /// The value to validate.
27 | /// On failure, receives a user-readable string
28 | /// message explaining why the value is not valid.
29 | /// True if the value passes validation; false otherwise.
30 | ///
31 | public abstract bool TryValidate(ArgumentValidationContext context, object value, out string reason);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Expressions/ExpressionTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using NClap.Expressions;
5 |
6 | namespace NClap.Tests.Expressions
7 | {
8 | public abstract class ExpressionTests
9 | {
10 | private const string AnyString = "test";
11 |
12 | internal readonly TestEnvironment EmptyEnvironment =
13 | new TestEnvironment();
14 |
15 | internal static readonly Expression AnyExpression = new StringLiteral(AnyString);
16 | internal static readonly string EvaluatedAnyExpression = AnyString;
17 |
18 | internal static readonly Expression AnyUnevaluatableExpr = new UnevaluatableExpression();
19 | internal static readonly Expression AnyEvaluatableExpr = new StringLiteral("foo");
20 |
21 | internal abstract Expression CreateInstance();
22 |
23 | class UnevaluatableExpression : Expression
24 | {
25 | public override bool TryEvaluate(NClap.Expressions.ExpressionEnvironment env, out string value)
26 | {
27 | value = null;
28 | return false;
29 | }
30 | }
31 |
32 | [TestMethod]
33 | public void TestThatEvaluationThrowsOnNullEnvironment()
34 | {
35 | var expr = CreateInstance();
36 |
37 | Action a = () => expr.TryEvaluate(null, out string value);
38 | a.Should().Throw();
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/NClap/Utilities/Windows/NativeMethods.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace NClap.Utilities.Windows
5 | {
6 | #pragma warning disable PC003 // TODO: Native API not available in UWP
7 |
8 | ///
9 | /// Wrapper for native methods only available on traditional Windows platforms.
10 | ///
11 | internal static class NativeMethods
12 | {
13 | ///
14 | /// Extracts a Unicode character from a key press.
15 | ///
16 | /// Virtual key.
17 | /// Key scan code.
18 | /// Current modifiers key state.
19 | /// On success receives extracted characters.
20 | /// Maximum number of characters to retrieve.
21 | /// Flags.
22 | /// On success, returns number of characters extracted; otherwise zero
23 | /// or a negative number.
24 | [DllImport("user32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ThrowOnUnmappableChar = true)]
25 | public static extern int ToUnicode(
26 | uint wVirtKey,
27 | uint wScanCode,
28 | byte[] lpKeyState,
29 | [MarshalAs(UnmanagedType.LPArray)] [Out] char[] pwszBuff,
30 | int cchBuff,
31 | uint wFlags);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Expressions/TestEnvironment.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace NClap.Expressions
4 | {
5 | ///
6 | /// An expression environment.
7 | ///
8 | internal class TestEnvironment : NClap.Expressions.ExpressionEnvironment
9 | {
10 | private Dictionary _variables = new Dictionary();
11 |
12 | ///
13 | /// Associates the given variable with the given value. If the
14 | /// variable is already defined, then the existing association
15 | /// will be replaced with this new one.
16 | ///
17 | /// Name of the variable.
18 | /// Value to associate with the variable.
19 | public void Define(string variableName, string value)
20 | {
21 | _variables[variableName] = value;
22 | }
23 |
24 | ///
25 | /// Tries to retrieve the value associated with the given
26 | /// variable.
27 | ///
28 | /// Name of the variable.
29 | /// On success, receives the value associated
30 | /// with the variable.
31 | /// true if the variable was found; false otherwise.
32 | public override bool TryGetVariable(string variableName, out string value) =>
33 | _variables.TryGetValue(variableName, out value);
34 | }
35 | }
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Expressions/StringLiteralTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using NClap.Expressions;
5 |
6 | namespace NClap.Tests.Expressions
7 | {
8 | [TestClass]
9 | public class StringLiteralTests : ExpressionTests
10 | {
11 | [TestMethod]
12 | public void TestThatConstructorThrowsOnNull()
13 | {
14 | Action a = () => new StringLiteral(null);
15 | a.Should().Throw();
16 | }
17 |
18 | [TestMethod]
19 | public void TestThatLiteralIsCorrect()
20 | {
21 | const string anyString = "foo";
22 | var literal = new StringLiteral(anyString);
23 | literal.Value.Should().Be(anyString);
24 | }
25 |
26 | [TestMethod]
27 | public void TestThatEmptyStringIsValidLiteral()
28 | {
29 | var literal = new StringLiteral(string.Empty);
30 | literal.Value.Should().Be(string.Empty);
31 | }
32 |
33 | [TestMethod]
34 | public void TestThatLiteralEvaluatesToInnerString()
35 | {
36 | const string anyString = "foo";
37 | var literal = new StringLiteral(anyString);
38 | literal.TryEvaluate(EmptyEnvironment, out string value).Should().BeTrue();
39 | value.Should().Be(anyString);
40 | }
41 |
42 | internal override Expression CreateInstance() =>
43 | new StringLiteral("foo");
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/NClap/Help/ArgumentSyntaxHelpOptions.cs:
--------------------------------------------------------------------------------
1 | using NClap.Utilities;
2 |
3 | namespace NClap.Help
4 | {
5 | ///
6 | /// Help options for argument syntax summaries.
7 | ///
8 | public class ArgumentSyntaxHelpOptions : ArgumentMetadataHelpOptions
9 | {
10 | ///
11 | /// Default constructor.
12 | ///
13 | public ArgumentSyntaxHelpOptions()
14 | {
15 | }
16 |
17 | ///
18 | /// Deeply cloning constructor.
19 | ///
20 | /// Template for clone.
21 | private ArgumentSyntaxHelpOptions(ArgumentSyntaxHelpOptions other) : base(other)
22 | {
23 | CommandNameColor = other.CommandNameColor;
24 | IncludeOptionalArguments = other.IncludeOptionalArguments;
25 | }
26 |
27 | ///
28 | /// Color of the command name.
29 | ///
30 | public TextColor CommandNameColor { get; set; }
31 |
32 | ///
33 | /// True to include optional arguments in syntax summary; false to
34 | /// exclude them.
35 | ///
36 | internal bool IncludeOptionalArguments { get; set; } = true;
37 |
38 | ///
39 | /// Create a separate clone of this object.
40 | ///
41 | /// Clone.
42 | public override ArgumentMetadataHelpOptions DeepClone() => new ArgumentSyntaxHelpOptions(this);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/NClap/Types/ICollectionArgumentType.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 |
3 | namespace NClap.Types
4 | {
5 | ///
6 | /// Interface for advertising a collection type as being parseable
7 | /// using this assembly. The implementation provides sufficient
8 | /// functionality for command-line parsing, generating usage help
9 | /// information, etc. This interface should only be implemented
10 | /// by objects that describe .NET collection objects.
11 | ///
12 | public interface ICollectionArgumentType : IArgumentType
13 | {
14 | ///
15 | /// Argument type of elements in the collection described by this
16 | /// object.
17 | ///
18 | IArgumentType ElementType { get; }
19 |
20 | ///
21 | /// Constructs a collection of the type described by this object,
22 | /// populated with objects from the provided input collection.
23 | ///
24 | /// Objects to add to the collection.
25 | /// Constructed collection.
26 | object ToCollection(IEnumerable objects);
27 |
28 | ///
29 | /// Enumerates the items in the collection. The input collection
30 | /// should be of the type described by this object.
31 | ///
32 | /// Collection to enumerate.
33 | /// The enumeration.
34 | IEnumerable ToEnumerable(object collection);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/CommandAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | // CA1813: Avoid unsealed attributes
6 | #pragma warning disable CA1813
7 |
8 | // CA1019: Define accessors for attribute arguments
9 | #pragma warning disable CA1019
10 |
11 | ///
12 | /// Attribute class used to denote commands.
13 | ///
14 | [AttributeUsage(AttributeTargets.Field)]
15 | public class CommandAttribute : ArgumentValueAttribute
16 | {
17 | private readonly Type _implementingType;
18 |
19 | ///
20 | /// Default, parameterless constructor.
21 | ///
22 | protected CommandAttribute()
23 | {
24 | }
25 |
26 | ///
27 | /// Constructor that allows specifying the type that "implements"
28 | /// this command.
29 | ///
30 | /// The implementing type.
31 | public CommandAttribute(Type implementingType)
32 | {
33 | _implementingType = implementingType;
34 | }
35 |
36 | ///
37 | /// Gets the type that "implements" this command.
38 | ///
39 | /// The type of the command associated with this
40 | /// attribute.
41 | /// The type.
42 | public virtual Type GetImplementingType(Type commandType) => _implementingType;
43 | }
44 |
45 | #pragma warning restore CA1019
46 | #pragma warning restore CA1813
47 | }
48 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/PositionalArgumentAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Metadata
4 | {
5 | ///
6 | /// Indicates that this argument is an (unnamed) positional argument. The
7 | /// LongName property is used for usage text only and does not affect the
8 | /// usage of the argument.
9 | ///
10 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
11 | public sealed class PositionalArgumentAttribute : ArgumentBaseAttribute
12 | {
13 | ///
14 | /// Default constructor, which may be used to indicate an optional
15 | /// positional argument that may appear at most once.
16 | ///
17 | public PositionalArgumentAttribute() : this(ArgumentFlags.Optional)
18 | {
19 | }
20 |
21 | ///
22 | /// Indicates that this argument is a default, positional argument.
23 | ///
24 | /// Specifies the error checking to be done on the
25 | /// argument.
26 | public PositionalArgumentAttribute(ArgumentFlags flags) : base(flags)
27 | {
28 | }
29 |
30 | ///
31 | /// The zero-based index of this argument amongst all (positional)
32 | /// default arguments. Each default argument present within an
33 | /// object must have a unique position value, and they must be
34 | /// consecutive, with the smallest being zero.
35 | ///
36 | public int Position { get; set; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/ConsoleInput/SimulatedConsoleInput.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using NClap.ConsoleInput;
6 |
7 | namespace NClap.Tests.ConsoleInput
8 | {
9 | class SimulatedConsoleInput : IConsoleInput
10 | {
11 | private int _keyIndex;
12 |
13 | public SimulatedConsoleInput(IEnumerable keyStream = null)
14 | {
15 | KeyStream = keyStream?.ToList() ?? new List();
16 | }
17 |
18 | ///
19 | /// True if Control-C is treated as a normal input character; false if
20 | /// it's specially handled.
21 | ///
22 | public bool TreatControlCAsInput { get; set; }
23 |
24 | ///
25 | /// The key stream surfaced from this console.
26 | ///
27 | public IReadOnlyList KeyStream { get; }
28 |
29 | ///
30 | /// Reads a key press from the console.
31 | ///
32 | /// True to suppress auto-echoing the key's
33 | /// character; false to echo it as normal.
34 | /// Info about the press.
35 | public ConsoleKeyInfo ReadKey(bool suppressEcho)
36 | {
37 | if (_keyIndex >= KeyStream.Count)
38 | {
39 | throw new InvalidOperationException("There are no more key events to read.");
40 | }
41 |
42 | return KeyStream[_keyIndex++];
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/NClap/Repl/LoopInputOutputParameters.cs:
--------------------------------------------------------------------------------
1 | using NClap.ConsoleInput;
2 | using NClap.Utilities;
3 |
4 | namespace NClap.Repl
5 | {
6 | ///
7 | /// Parameters for constructing a loop with advanced line input. The
8 | /// parameters indicate how the loop's textual input and output should
9 | /// be implemented.
10 | ///
11 | public class LoopInputOutputParameters
12 | {
13 | ///
14 | /// Line input object to use, or null for a default one to be
15 | /// constructed.
16 | ///
17 | public IConsoleLineInput LineInput { get; set; }
18 |
19 | ///
20 | /// The console input interface to use, or null to use the default one.
21 | ///
22 | public IConsoleInput ConsoleInput { get; set; }
23 |
24 | ///
25 | /// The console output interface to use, or null to use the default one.
26 | ///
27 | public IConsoleOutput ConsoleOutput { get; set; }
28 |
29 | ///
30 | /// The console key binding set to use, or null to use the default one.
31 | ///
32 | public IReadOnlyConsoleKeyBindingSet KeyBindingSet { get; set; }
33 |
34 | ///
35 | /// Input prompt, or null to use the default one.
36 | ///
37 | public ColoredString? Prompt { get; set; }
38 |
39 | ///
40 | /// The character that starts a comment.
41 | ///
42 | public char? EndOfLineCommentCharacter { get; set; }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Expressions/ParenthesisExpressionTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using NClap.Expressions;
5 |
6 | namespace NClap.Tests.Expressions
7 | {
8 | [TestClass]
9 | public class ParenthesisExpressionTests : ExpressionTests
10 | {
11 | internal override Expression CreateInstance() =>
12 | new ParenthesisExpression(AnyExpression);
13 |
14 | [TestMethod]
15 | public void TestThatConstructorThrowsOnNullInnerExpression()
16 | {
17 | Action a = () => new ParenthesisExpression(null);
18 | a.Should().Throw();
19 | }
20 |
21 | [TestMethod]
22 | public void TestThatExpressionWrapsCorrectInnerExpression()
23 | {
24 | var expr = new ParenthesisExpression(AnyExpression);
25 | expr.InnerExpression.Should().BeSameAs(AnyExpression);
26 | }
27 |
28 | [TestMethod]
29 | public void TestThatExpressionEvaluatesToEvaluatedInnerExpression()
30 | {
31 | var expr = new ParenthesisExpression(AnyExpression);
32 | expr.TryEvaluate(EmptyEnvironment, out string value).Should().BeTrue();
33 | value.Should().Be(EvaluatedAnyExpression);
34 | }
35 |
36 | [TestMethod]
37 | public void TestThatEvalFailsWhenInnerExprCannotBeEvaluated()
38 | {
39 | var expr = new ParenthesisExpression(AnyUnevaluatableExpr);
40 | expr.TryEvaluate(EmptyEnvironment, out string value).Should().BeFalse();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Types/KeyValuePairArgumentTypeTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using FluentAssertions;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using NClap.Types;
6 |
7 | namespace NClap.Tests.Types
8 | {
9 | [TestClass]
10 | public class KeyValuePairArgumentTypeTests
11 | {
12 | [TestMethod]
13 | public void InvalidUseOfGetCompletions()
14 | {
15 | var type = (KeyValuePairArgumentType)ArgumentType.GetType(typeof(KeyValuePair));
16 | var c = new ArgumentCompletionContext { ParseContext = ArgumentParseContext.Default };
17 |
18 | type.Invoking(t => t.GetCompletions(null, "Tr"))
19 | .Should().Throw();
20 |
21 | type.Invoking(t => t.GetCompletions(c, null))
22 | .Should().Throw();
23 | }
24 |
25 | [TestMethod]
26 | public void GetCompletions()
27 | {
28 | var type = (KeyValuePairArgumentType)ArgumentType.GetType(typeof(KeyValuePair));
29 | var c = new ArgumentCompletionContext { ParseContext = ArgumentParseContext.Default };
30 |
31 | type.GetCompletions(c, "Tr").Should().Equal("True");
32 | type.GetCompletions(c, string.Empty).Should().Equal("False", "True");
33 | type.GetCompletions(c, "False=f").Should().Equal("False=False");
34 | type.GetCompletions(c, "33=f").Should().Equal("33=False");
35 | type.GetCompletions(c, "True=").Should().Equal("True=False", "True=True");
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/NClap/Types/StringArgumentType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Types
4 | {
5 | ///
6 | /// Implementation to describe System.Stringean.
7 | ///
8 | internal class StringArgumentType : ArgumentTypeBase
9 | {
10 | private static readonly StringArgumentType Instance = new StringArgumentType();
11 |
12 | ///
13 | /// Primary constructor.
14 | ///
15 | private StringArgumentType() : base(typeof(string))
16 | {
17 | }
18 |
19 | ///
20 | /// Display name.
21 | ///
22 | public override string DisplayName => "Str";
23 |
24 | ///
25 | /// Public factory method.
26 | ///
27 | /// A constructed object.
28 | public static StringArgumentType Create() => Instance;
29 |
30 | ///
31 | /// Parses the provided string. Throws an exception if the string
32 | /// cannot be parsed.
33 | ///
34 | /// Context for parsing.
35 | /// String to parse.
36 | /// The parsed object.
37 | protected override object Parse(ArgumentParseContext context, string stringToParse)
38 | {
39 | if (string.IsNullOrEmpty(stringToParse) && !context.AllowEmpty)
40 | {
41 | throw new ArgumentOutOfRangeException(nameof(stringToParse));
42 | }
43 |
44 | return stringToParse;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Tools/Inspector/CompleteTokensCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using NClap.Metadata;
5 |
6 | namespace NClap.Inspector
7 | {
8 | class CompleteTokensCommand : SynchronousCommand
9 | {
10 | private readonly ProgramArguments _programArgs;
11 |
12 | public CompleteTokensCommand(ProgramArguments programArgs)
13 | {
14 | _programArgs = programArgs;
15 | }
16 |
17 | [NamedArgument(ArgumentFlags.Required, LongName = "TokenIndex",
18 | Description = "0-based index of token to complete")]
19 | public int IndexOfTokenToComplete { get; set; }
20 |
21 | [NamedArgument(ArgumentFlags.RestOfLine, LongName = "Tokens",
22 | Description = "Command-line tokens")]
23 | public List Tokens { get; set; } = new List();
24 |
25 | public override CommandResult Execute()
26 | {
27 | if (_programArgs.Verbose)
28 | {
29 | Console.WriteLine($"Completing token {IndexOfTokenToComplete} of args: [{string.Join(" ", Tokens.Select(a => "\"" + a + "\""))}]");
30 | }
31 |
32 | // Swallow bogus requests.
33 | if (IndexOfTokenToComplete > Tokens.Count)
34 | {
35 | return CommandResult.Success;
36 | }
37 |
38 | foreach (var completion in CommandLineParser.GetCompletions(_programArgs.LoadedType, Tokens, IndexOfTokenToComplete))
39 | {
40 | Console.WriteLine(completion);
41 | }
42 |
43 | return CommandResult.Success;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/NClap/Repl/ILoopClient.cs:
--------------------------------------------------------------------------------
1 | using NClap.ConsoleInput;
2 | using NClap.Utilities;
3 |
4 | namespace NClap.Repl
5 | {
6 | ///
7 | /// Interface provided by REPL view.
8 | ///
9 | public interface ILoopClient
10 | {
11 | ///
12 | /// The loop prompt. If you wish to use a as your
13 | /// prompt, you should use the property instead.
14 | ///
15 | string Prompt { get; set; }
16 |
17 | ///
18 | /// The loop prompt (with color).
19 | ///
20 | ColoredString? PromptWithColor { get; set; }
21 |
22 | ///
23 | /// The character that starts a comment.
24 | ///
25 | char? EndOfLineCommentCharacter { get; }
26 |
27 | ///
28 | /// Optionally provides a token completer that the loop client may choose to use.
29 | ///
30 | ITokenCompleter TokenCompleter { get; set; }
31 |
32 | ///
33 | /// Displays the loop prompt.
34 | ///
35 | void DisplayPrompt();
36 |
37 | ///
38 | /// Reads a line of text input.
39 | ///
40 | /// The read line.
41 | string ReadLine();
42 |
43 | ///
44 | /// Notifies the client of a continuable error.
45 | ///
46 | /// The message if one is available, or null if
47 | /// there is no more input.
48 | void OnError(string message);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # .NET Command Line Argument Parser (NClap)
2 |
3 | [](https://github.com/reubeno/NClap/actions/workflows/build.yaml)
4 | [](https://www.nuget.org/packages/NClap)
5 | [](https://reubeno.github.io/NClap/LICENSE.txt)
6 | [](https://gitter.im/NClap/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7 |
8 | NClap is a .NET library for parsing command-line arguments and building interactive command shells. It's driven by a declarative attribute syntax, and easy to extend. It's like a lightweight serializer for command-line arguments.
9 |
10 | ## Getting NClap
11 |
12 | The easiest way to consume NClap is by adding its [NuGet package](https://www.nuget.org/packages/NClap) to your project. It is built for use with .NET 4.6.1+ and .NET Core 2.0+.
13 |
14 | NClap is shared under the MIT license, as described in [LICENSE.txt](https://reubeno.github.io/NClap/LICENSE.txt).
15 |
16 | ## The details
17 |
18 | - [Basic usage](docs/Usage.md)
19 | - [Mostly complete feature list](docs/Features.md)
20 | - [Commands (a.k.a. verbs)](docs/Commands.md)
21 |
22 | ## Contributing
23 |
24 | We welcome contributions of all kinds, whether it be submitting pull requests or even just filing issues or feature requests!
25 |
26 | For code contributions, we follow the standard [Github workflow](https://guides.github.com/introduction/flow/).
27 |
28 | Please also see our [Code of Conduct](CODE_OF_CONDUCT.md)
29 |
--------------------------------------------------------------------------------
/src/NClap/Repl/LoopOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Help;
3 | using NClap.Utilities;
4 |
5 | namespace NClap.Repl
6 | {
7 | ///
8 | /// Options for executing loops.
9 | ///
10 | public class LoopOptions : IDeepCloneable
11 | {
12 | ///
13 | /// Default constructor.
14 | ///
15 | public LoopOptions()
16 | {
17 | }
18 |
19 | ///
20 | /// Deeply cloning constructor.
21 | ///
22 | /// Template for clone.
23 | private LoopOptions(LoopOptions other)
24 | {
25 | ParserOptions = other.ParserOptions?.DeepClone();
26 | HelpOutputHandler = other.HelpOutputHandler;
27 | }
28 |
29 | ///
30 | /// Parser options; initialized with defaults.
31 | ///
32 | public CommandLineParserOptions ParserOptions { get; set; } = new CommandLineParserOptions
33 | {
34 | HelpOptions = new ArgumentSetHelpOptions
35 | {
36 | Logo = new ArgumentMetadataHelpOptions { Include = false },
37 | Name = string.Empty
38 | }
39 | };
40 |
41 | ///
42 | /// The output handler for help/usage information.
43 | ///
44 | public Action HelpOutputHandler { get; set; }
45 |
46 | ///
47 | /// Creates a separate clone of this object.
48 | ///
49 | /// Clone.
50 | public LoopOptions DeepClone() => new LoopOptions(this);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/NClap/Utilities/GenericCollectionFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 |
5 | namespace NClap.Utilities
6 | {
7 | ///
8 | /// Factory for creating typed collections when types are not
9 | /// known at compile time.
10 | ///
11 | internal static class GenericCollectionFactory
12 | {
13 | ///
14 | /// Creates an instance of that can
15 | /// hold instance of the given type .
16 | ///
17 | /// Type of instance.
18 | /// Constructed list.
19 | public static IList CreateList(Type t)
20 | {
21 | var listType = typeof(List<>).MakeGenericType(new[] { t });
22 | return (IList)Activator.CreateInstance(listType);
23 | }
24 |
25 | ///
26 | /// Creates an array that may hold values of type
27 | /// with the values in given enumeration .
28 | ///
29 | /// Values to store.
30 | /// Type for the array's elements.
31 | /// The constructed and initialized array.
32 | public static Array ToArray(this IEnumerable values, Type t)
33 | {
34 | var list = CreateList(t);
35 | foreach (var value in values)
36 | {
37 | list.Add(value);
38 | }
39 |
40 | var array = Array.CreateInstance(t, list.Count);
41 | list.CopyTo(array, 0);
42 |
43 | return array;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/NClap/Help/ArgumentSyntaxFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Help
4 | {
5 | ///
6 | /// Flags describing the format of argument syntax summaries.
7 | ///
8 | [Flags]
9 | internal enum ArgumentSyntaxFlags
10 | {
11 | ///
12 | /// No flags.
13 | ///
14 | None = 0x0,
15 |
16 | ///
17 | /// Visibly distinguish optional arguments.
18 | ///
19 | DistinguishOptionalArguments = 0x1,
20 |
21 | ///
22 | /// Visibly indicate how many times the argument may occur.
23 | ///
24 | IndicateCardinality = 0x2,
25 |
26 | ///
27 | /// Whether or not to indicate the type of positional arguments.
28 | ///
29 | IndicatePositionalArgumentType = 0x4,
30 |
31 | ///
32 | /// Whether or not to indicate if the argument accepts an empty string.
33 | ///
34 | IndicateArgumentsThatAcceptEmptyString = 0x8,
35 |
36 | ///
37 | /// Whether or not to include the syntax of specifying the value associated with
38 | /// the argument.
39 | ///
40 | IncludeValueSyntax = 0x10,
41 |
42 | ///
43 | /// Defaults.
44 | ///
45 | Default =
46 | DistinguishOptionalArguments |
47 | IndicateCardinality |
48 | IndicateArgumentsThatAcceptEmptyString |
49 | IncludeValueSyntax,
50 |
51 | ///
52 | /// All flags.
53 | ///
54 | All = Default | IndicatePositionalArgumentType,
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Tools/Inspector/CompleteLineCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NClap.Metadata;
3 |
4 | namespace NClap.Inspector
5 | {
6 | class CompleteLineCommand : SynchronousCommand
7 | {
8 | private readonly ProgramArguments _programArgs;
9 |
10 | public CompleteLineCommand(ProgramArguments programArgs)
11 | {
12 | _programArgs = programArgs;
13 | }
14 |
15 | [NamedArgument(ArgumentFlags.Optional, LongName = "Skip",
16 | Description = "Tokens to skip",
17 | DefaultValue = 1)]
18 | public int TokensToSkip { get; set; }
19 |
20 | [NamedArgument(ArgumentFlags.Required, LongName = "Cursor",
21 | Description = "0-based index of cursor")]
22 | public int CursorIndex { get; set; }
23 |
24 | [NamedArgument(ArgumentFlags.Required, LongName = "CommandLine",
25 | Description = "Command line")]
26 | public string CommandLine { get; set; }
27 |
28 | public override CommandResult Execute()
29 | {
30 | var verboseMessage = $"{Guid.NewGuid()}: Completing with skip={TokensToSkip} cursor={CursorIndex} of command line: [{CommandLine}]";
31 | if (_programArgs.Verbose)
32 | {
33 | Console.WriteLine(verboseMessage);
34 | }
35 |
36 | foreach (var completion in CommandLineParser.GetCompletions(
37 | _programArgs.LoadedType,
38 | CommandLine,
39 | CursorIndex,
40 | tokensToSkip: this.TokensToSkip,
41 | options: null))
42 | {
43 | Console.WriteLine(completion);
44 | }
45 |
46 | return CommandResult.Success;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/MustBeLessThanAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 |
4 | namespace NClap.Metadata
5 | {
6 | ///
7 | /// Attribute that indicates the associated integer argument member must
8 | /// be less than a given value.
9 | ///
10 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
11 | public sealed class MustBeLessThanAttribute : IntegerComparisonValidationAttribute
12 | {
13 | ///
14 | /// Primary constructor.
15 | ///
16 | /// Value to compare against.
17 | public MustBeLessThanAttribute(object target) : base(target)
18 | {
19 | }
20 |
21 | ///
22 | /// Validate the provided value in accordance with the attribute's
23 | /// policy.
24 | ///
25 | /// Context for validation.
26 | /// The value to validate.
27 | /// On failure, receives a user-readable string
28 | /// message explaining why the value is not valid.
29 | /// True if the value passes validation; false otherwise.
30 | ///
31 | public override bool TryValidate(ArgumentValidationContext context, object value, out string reason)
32 | {
33 | if (GetArgumentType(value).IsLessThan(value, Target))
34 | {
35 | reason = null;
36 | return true;
37 | }
38 |
39 | reason = string.Format(CultureInfo.CurrentCulture, Strings.ValueIsNotLessThan, Target);
40 | return false;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Utilities/FluentBuilderTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Utilities;
4 |
5 | namespace NClap.Tests.Utilities
6 | {
7 | [TestClass]
8 | public class FluentBuilderTests
9 | {
10 | private class Boxed
11 | {
12 | public T Value { get; set; }
13 | }
14 |
15 | [TestMethod]
16 | public void TestBuilderWithNoTransformationsAppliesToStartingState()
17 | {
18 | var startingValue = Any.Int();
19 | var builder = new FluentBuilder(startingValue);
20 | builder.Apply().Should().Be(startingValue);
21 | }
22 |
23 | [TestMethod]
24 | public void TestBuilderImplicitlyCoercesFromStartingState()
25 | {
26 | var startingValue = Any.Int();
27 | FluentBuilder builder = startingValue;
28 | builder.Apply().Should().Be(startingValue);
29 | }
30 |
31 | [TestMethod]
32 | public void TestBuilderImplicitlyCoercesToAppliedResult()
33 | {
34 | var startingValue = Any.Int();
35 | var builder = new FluentBuilder(startingValue);
36 | int coerced = builder;
37 | builder.Apply().Should().Be(coerced);
38 | }
39 |
40 | [TestMethod]
41 | public void TestBuilderAppliesTransformationsInCorrectOrder()
42 | {
43 | var startingValue = 20;
44 | FluentBuilder> builder = new Boxed { Value = startingValue };
45 | builder.AddTransformer(v => v.Value /= 10);
46 | builder.AddTransformer(v => v.Value -= 2);
47 | builder.Apply().Value.Should().Be(0);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/MustBeGreaterThanAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 |
4 | namespace NClap.Metadata
5 | {
6 | ///
7 | /// Attribute that indicates the associated integer argument member must
8 | /// be greater than a given value.
9 | ///
10 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
11 | public sealed class MustBeGreaterThanAttribute : IntegerComparisonValidationAttribute
12 | {
13 | ///
14 | /// Primary constructor.
15 | ///
16 | /// Value to compare against.
17 | public MustBeGreaterThanAttribute(object target) : base(target)
18 | {
19 | }
20 |
21 | ///
22 | /// Validate the provided value in accordance with the attribute's
23 | /// policy.
24 | ///
25 | /// Context for validation.
26 | /// The value to validate.
27 | /// On failure, receives a user-readable string
28 | /// message explaining why the value is not valid.
29 | /// True if the value passes validation; false otherwise.
30 | ///
31 | public override bool TryValidate(ArgumentValidationContext context, object value, out string reason)
32 | {
33 | if (GetArgumentType(value).IsGreaterThan(value, Target))
34 | {
35 | reason = null;
36 | return true;
37 | }
38 |
39 | reason = string.Format(CultureInfo.CurrentCulture, Strings.ValueIsNotGreaterThan, Target);
40 | return false;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/NClap/Utilities/IMutableMemberInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 |
4 | namespace NClap.Utilities
5 | {
6 | ///
7 | /// Abstract interface for interacting with mutable members of types
8 | /// (e.g. fields and properties).
9 | ///
10 | public interface IMutableMemberInfo
11 | {
12 | ///
13 | /// Retrieve the member's base member info.
14 | ///
15 | MemberInfo MemberInfo { get; }
16 |
17 | ///
18 | /// True if the member can be read at arbitrary points during execution;
19 | /// false otherwise.
20 | ///
21 | bool IsReadable { get; }
22 |
23 | ///
24 | /// True if the member can be written to at arbitrary points during
25 | /// execution; false otherwise.
26 | ///
27 | bool IsWritable { get; }
28 |
29 | ///
30 | /// The type of the member.
31 | ///
32 | Type MemberType { get; }
33 |
34 | ///
35 | /// Retrieve the value associated with this field in the specified
36 | /// containing object.
37 | ///
38 | /// Object to look in.
39 | /// The field's value.
40 | object GetValue(object containingObject);
41 |
42 | ///
43 | /// Sets the value associated with this field in the specified
44 | /// containing object.
45 | ///
46 | /// Object to look in.
47 | /// Value to set.
48 | void SetValue(object containingObject, object value);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/MustBeLessThanOrEqualToAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 |
4 | namespace NClap.Metadata
5 | {
6 | ///
7 | /// Attribute that indicates the associated integer argument member must
8 | /// be less than or equal to a given value.
9 | ///
10 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
11 | public sealed class MustBeLessThanOrEqualToAttribute : IntegerComparisonValidationAttribute
12 | {
13 | ///
14 | /// Primary constructor.
15 | ///
16 | /// Value to compare against.
17 | public MustBeLessThanOrEqualToAttribute(object target) : base(target)
18 | {
19 | }
20 |
21 | ///
22 | /// Validate the provided value in accordance with the attribute's
23 | /// policy.
24 | ///
25 | /// Context for validation.
26 | /// The value to validate.
27 | /// On failure, receives a user-readable string
28 | /// message explaining why the value is not valid.
29 | /// True if the value passes validation; false otherwise.
30 | ///
31 | public override bool TryValidate(ArgumentValidationContext context, object value, out string reason)
32 | {
33 | if (GetArgumentType(value).IsLessThanOrEqualTo(value, Target))
34 | {
35 | reason = null;
36 | return true;
37 | }
38 |
39 | reason = string.Format(CultureInfo.CurrentCulture, Strings.ValueIsNotLessThanOrEqualTo, Target);
40 | return false;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Types/TupleArgumentTypeTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using FluentAssertions;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using NClap.Types;
6 |
7 | namespace NClap.Tests.Types
8 | {
9 | [TestClass]
10 | public class TupleArgumentTypeTests
11 | {
12 | [TestMethod]
13 | public void InvalidUseOfGetCompletions()
14 | {
15 | var type = (TupleArgumentType)ArgumentType.GetType(typeof(Tuple));
16 | var c = new ArgumentCompletionContext { ParseContext = ArgumentParseContext.Default };
17 |
18 | type.Invoking(t => t.GetCompletions(null, "Tr")).Should().Throw();
19 | type.Invoking(t => t.GetCompletions(c, null)).Should().Throw();
20 | }
21 |
22 | [TestMethod]
23 | public void GetCompletions()
24 | {
25 | var type = (TupleArgumentType)ArgumentType.GetType(typeof(Tuple));
26 | var c = new ArgumentCompletionContext { ParseContext = ArgumentParseContext.Default };
27 |
28 | type.GetCompletions(c, "Tr").Should().Equal("True");
29 | type.GetCompletions(c, string.Empty).Should().Equal("False", "True");
30 | type.GetCompletions(c, "False,3").Should().BeEmpty();
31 | type.GetCompletions(c, "False,3,").Should().Equal("False,3,False", "False,3,True");
32 | }
33 |
34 | [TestMethod]
35 | public void TestThatDependentTypesListIsCorrect()
36 | {
37 | var type = (TupleArgumentType)ArgumentType.GetType(typeof(Tuple));
38 | type.DependentTypes.Should().BeEquivalentTo(
39 | new[] { typeof(bool), typeof(int) }.Select(ArgumentType.GetType));
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/MustNotExistAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using NClap.Types;
4 |
5 | namespace NClap.Metadata
6 | {
7 | ///
8 | /// Attribute that indicates the associated file-system path argument
9 | /// member must name a directory that exists.
10 | ///
11 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
12 | public sealed class MustNotExistAttribute : FileSystemPathValidationAttribute
13 | {
14 | ///
15 | /// Validate the provided value in accordance with the attribute's
16 | /// policy.
17 | ///
18 | /// Context for validation.
19 | /// The value to validate.
20 | /// On failure, receives a user-readable string
21 | /// message explaining why the value is not valid.
22 | /// True if the value passes validation; false otherwise.
23 | ///
24 | /// Thrown when
25 | /// is null.
26 | public override bool TryValidate(ArgumentValidationContext context, object value, out string reason)
27 | {
28 | if (context == null) throw new ArgumentNullException(nameof(context));
29 |
30 | var path = (FileSystemPath)value;
31 | if (!context.FileSystemReader.FileExists(path) && !context.FileSystemReader.DirectoryExists(path))
32 | {
33 | reason = null;
34 | return true;
35 | }
36 |
37 | reason = string.Format(CultureInfo.CurrentCulture, Strings.PathExists);
38 | return false;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/NClap/Metadata/MustBeGreaterThanOrEqualToAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 |
4 | namespace NClap.Metadata
5 | {
6 | ///
7 | /// Attribute that indicates the associated integer argument member must
8 | /// be greater than or equal to a given value.
9 | ///
10 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
11 | public sealed class MustBeGreaterThanOrEqualToAttribute : IntegerComparisonValidationAttribute
12 | {
13 | ///
14 | /// Primary constructor.
15 | ///
16 | /// Value to compare against.
17 | public MustBeGreaterThanOrEqualToAttribute(object target) : base(target)
18 | {
19 | }
20 |
21 | ///
22 | /// Validate the provided value in accordance with the attribute's
23 | /// policy.
24 | ///
25 | /// Context for validation.
26 | /// The value to validate.
27 | /// On failure, receives a user-readable string
28 | /// message explaining why the value is not valid.
29 | /// True if the value passes validation; false otherwise.
30 | ///
31 | public override bool TryValidate(ArgumentValidationContext context, object value, out string reason)
32 | {
33 | if (GetArgumentType(value).IsGreaterThanOrEqualTo(value, Target))
34 | {
35 | reason = null;
36 | return true;
37 | }
38 |
39 | reason = string.Format(CultureInfo.CurrentCulture, Strings.ValueIsNotGreaterThanOrEqualTo, Target);
40 | return false;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/NClap/Types/IArgumentValue.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace NClap.Types
5 | {
6 | ///
7 | /// Interface for advertising a value as being parseable using this
8 | /// assembly.
9 | ///
10 | public interface IArgumentValue
11 | {
12 | ///
13 | /// The value.
14 | ///
15 | object Value { get; }
16 |
17 | ///
18 | /// True if the value has been disallowed from parsing use; false
19 | /// otherwise.
20 | ///
21 | bool Disallowed { get; }
22 |
23 | ///
24 | /// True if the value has been marked to be hidden from help and usage
25 | /// information; false otherwise.
26 | ///
27 | bool Hidden { get; }
28 |
29 | ///
30 | /// Display name for this value.
31 | ///
32 | string DisplayName { get; }
33 |
34 | ///
35 | /// Long name of this value.
36 | ///
37 | string LongName { get; }
38 |
39 | ///
40 | /// Short name of this value, if it has one; null if it has none.
41 | ///
42 | string ShortName { get; }
43 |
44 | ///
45 | /// Description of this value, if it has one; null if it has none.
46 | ///
47 | string Description { get; }
48 |
49 | ///
50 | /// Get any attributes of the given type associated with the value.
51 | ///
52 | /// Type of attribute to look for.
53 | /// The attributes.
54 | IEnumerable GetAttributes()
55 | where T : Attribute;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/NClap/Utilities/Windows/InputUtilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NClap.Utilities.Windows
4 | {
5 | ///
6 | /// Windows-specific input utilities.
7 | ///
8 | internal static class InputUtilities
9 | {
10 | ///
11 | /// Converts the indicated key (with modifiers) to the generated
12 | /// characters, in accordance with the currently active keyboard
13 | /// layout.
14 | ///
15 | /// The key to translate.
16 | /// Key modifiers.
17 | /// The characters.
18 | public static char[] GetChars(ConsoleKey key, ConsoleModifiers modifiers)
19 | {
20 | var virtKey = (uint)key;
21 | var output = new char[32];
22 |
23 | var result = NativeMethods.ToUnicode(virtKey, 0, GetKeyState(modifiers), output, output.Length, 0 /* flags */);
24 | if (result < 0) result = 0;
25 | if (result == 1 && output[0] == '\0') result = 0;
26 |
27 | var relevantOutput = new char[result];
28 | Array.Copy(output, relevantOutput, result);
29 |
30 | return relevantOutput;
31 | }
32 |
33 | private static byte[] GetKeyState(ConsoleModifiers modifiers)
34 | {
35 | const byte keyDownFlag = 0x80;
36 |
37 | var keyState = new byte[256];
38 |
39 | if (modifiers.HasFlag(ConsoleModifiers.Alt)) keyState[(int)ConsoleModifierKeys.Alt] |= keyDownFlag;
40 | if (modifiers.HasFlag(ConsoleModifiers.Control)) keyState[(int)ConsoleModifierKeys.Control] |= keyDownFlag;
41 | if (modifiers.HasFlag(ConsoleModifiers.Shift)) keyState[(int)ConsoleModifierKeys.Shift] |= keyDownFlag;
42 |
43 | return keyState;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Utilities/MutablePropertyInfoTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using FluentAssertions;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using NClap.Utilities;
6 |
7 | namespace NClap.Tests.Utilities
8 | {
9 | [TestClass]
10 | public class MutablePropertyInfoTests
11 | {
12 | class TestObject
13 | {
14 | public T Value { get; set; }
15 | }
16 |
17 | class TestThrowingObject
18 | {
19 | public T Value
20 | {
21 | get => default;
22 | set => throw new ArgumentOutOfRangeException(nameof(value));
23 | }
24 | }
25 |
26 | [TestMethod]
27 | public void ConversionFails()
28 | {
29 | var prop = new MutablePropertyInfo(typeof(TestObject).GetTypeInfo().GetProperty("Value"));
30 | var obj = new TestObject();
31 | Action setter = () => prop.SetValue(obj, 3.0);
32 | setter.Should().Throw();
33 | }
34 |
35 | [TestMethod]
36 | public void ConversionSucceeds()
37 | {
38 | var prop = new MutablePropertyInfo(typeof(TestObject).GetTypeInfo().GetProperty("Value"));
39 | var obj = new TestObject();
40 | Action setter = () => prop.SetValue(obj, 3L);
41 | setter.Should().NotThrow();
42 | }
43 |
44 | [TestMethod]
45 | public void ConversionSucceedsButSettingFails()
46 | {
47 | var prop = new MutablePropertyInfo(typeof(TestThrowingObject).GetTypeInfo().GetProperty("Value"));
48 | var obj = new TestThrowingObject();
49 | Action setter = () => prop.SetValue(obj, 3L);
50 | setter.Should().Throw();
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Utilities/MaybeTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using NClap.Utilities;
5 |
6 | namespace NClap.Tests.Utilities
7 | {
8 | [TestClass]
9 | public class MaybeTests
10 | {
11 | private const string AnyString = "Any string";
12 |
13 | [TestMethod]
14 | public void TestThatNoneValueAsMaybeYieldsNoValue()
15 | {
16 | Maybe value = new None();
17 | value.HasValue.Should().BeFalse();
18 | value.IsNone.Should().BeTrue();
19 | value.Invoking(v => { var _ = v.Value; }).Should().Throw();
20 | value.GetValueOrDefault(AnyString).Should().Be(AnyString);
21 | value.GetValueOrDefault().Should().BeNull();
22 | }
23 |
24 | [TestMethod]
25 | public void TestThatSomeNullValueAsMaybeYieldsNullValue()
26 | {
27 | var value = new Maybe(null);
28 | value.HasValue.Should().BeTrue();
29 | value.IsNone.Should().BeFalse();
30 | value.Value.Should().BeNull();
31 | value.GetValueOrDefault(AnyString).Should().BeNull();
32 | value.GetValueOrDefault().Should().BeNull();
33 | }
34 |
35 | [TestMethod]
36 | public void TestThatSomeNonNulValueAsMaybeYieldsCorrectValue()
37 | {
38 | const string anyOtherString = "Any other string";
39 |
40 | var value = new Maybe(AnyString);
41 | value.HasValue.Should().BeTrue();
42 | value.IsNone.Should().BeFalse();
43 | value.Value.Should().BeSameAs(AnyString);
44 | value.GetValueOrDefault(anyOtherString).Should().BeSameAs(AnyString);
45 | value.GetValueOrDefault().Should().BeSameAs(AnyString);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Samples/MultilevelCommandApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Logging;
4 | using NClap;
5 | using NClap.Metadata;
6 | using NClap.Repl;
7 |
8 | namespace MultilevelCommandApp
9 | {
10 | class Program
11 | {
12 | static int Main(string[] args)
13 | {
14 | Console.WriteLine("Setting up logging...");
15 |
16 | var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug));
17 |
18 | var logger = loggerFactory.CreateLogger();
19 |
20 | Console.WriteLine("Parsing...");
21 |
22 | var options = new CommandLineParserOptions().With()
23 | .ConfigureServices(s => s.AddSingleton(logger));
24 |
25 | if (!CommandLineParser.TryParse(args, options, out ProgramArguments programArgs))
26 | {
27 | return 1;
28 | }
29 |
30 | Console.WriteLine("Successfully parsed; reserialized:");
31 | Console.WriteLine(" " + string.Join(" ", CommandLineParser.Format(programArgs)));
32 |
33 | Console.WriteLine("Executing...");
34 |
35 | CommandResult result;
36 | if (programArgs.Command != null)
37 | {
38 | result = programArgs.Command.Execute();
39 | }
40 | else
41 | {
42 | var loopOptions = new LoopOptions { ParserOptions = options };
43 | var loop = new Loop(typeof(ProgramArguments.ToplevelCommandType), options: loopOptions);
44 | loop.Execute();
45 |
46 | result = CommandResult.Success;
47 | }
48 |
49 | loggerFactory.Dispose();
50 |
51 | Console.WriteLine($"Result: {result}");
52 |
53 | return 0;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/NClap/IFileSystemReader.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace NClap
4 | {
5 | ///
6 | /// Abstract interface for reading/querying file system contents.
7 | ///
8 | public interface IFileSystemReader
9 | {
10 | ///
11 | /// Checks if the path exists as a file.
12 | ///
13 | /// Path to check.
14 | /// True if the path exists and references a non-directory
15 | /// file; false otherwise.
16 | bool FileExists(string path);
17 |
18 | ///
19 | /// Checks if the path exists as a directory.
20 | ///
21 | /// Path to check.
22 | /// True if the path exists and references a directory; false
23 | /// otherwise.
24 | bool DirectoryExists(string path);
25 |
26 | ///
27 | /// Enumerates the names of the files and directories that exist in the
28 | /// indicated directory, and which match the provided file pattern.
29 | ///
30 | /// Path to the containing directory.
31 | ///
32 | /// The file pattern to match.
33 | /// An enumeration of the names of the files.
34 | IEnumerable EnumerateFileSystemEntries(string directoryPath, string filePattern);
35 |
36 | ///
37 | /// Enumerate the textual lines in the specified file. Throws an
38 | /// IOException if I/O errors occur while accessing the file.
39 | ///
40 | /// Path to the file.
41 | /// The line enumeration.
42 | IEnumerable GetLines(string filePath);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Tests/UnitTests/Types/EnumArgumentValueTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using NClap.Types;
4 | using NSubstitute;
5 | using System;
6 | using System.Reflection;
7 |
8 | namespace NClap.Tests.Types
9 | {
10 | [TestClass]
11 | public class EnumArgumentValueTests
12 | {
13 | [TestMethod]
14 | public void TestThatConstructorSucceedsEvenIfFieldValueIsOnlyAvailableAsRawConstant()
15 | {
16 | var anyInt = Any.PositiveInt();
17 |
18 | var fieldInfo = Substitute.For();
19 | fieldInfo.GetValue(Arg.Any