From 8750c941521801e57241aebf87b1f76e80afef87 Mon Sep 17 00:00:00 2001 From: "christian.vogt@hiquaro.com" Date: Sat, 5 Sep 2026 13:18:34 +0200 Subject: [PATCH 1/4] Add CI and trusted publishing workflows, version from git tags via MinVer - ci.yml: build, test and pack on ubuntu and windows for pushes to main and pull requests - publish.yml: on v* tags pack, verify the package version matches the tag, log in to nuget.org via OIDC (NuGet/login, environment release) and push both packages - MinVer replaces the hardcoded version; tag prefix v, minimum 5.0, untagged builds are 5.0.x-preview.0.N - README: CI and NuGet badges Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 51 ++++++++++++++++++++++++++++++ .github/workflows/publish.yml | 59 +++++++++++++++++++++++++++++++++++ Directory.Build.props | 12 ++++++- Directory.Packages.props | 4 +++ README.md | 3 ++ 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..848c3d2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 # MinVer reads the version from git tags + + - name: Setup .NET + uses: actions/setup-dotnet@v6 + with: + dotnet-version: | + 8.0.x + 9.0.x + 10.0.x + + - name: Restore + run: dotnet restore holonsoft.CmdLineParser.slnx + + - name: Build + run: dotnet build holonsoft.CmdLineParser.slnx -c Release --no-restore + + - name: Test + run: dotnet test holonsoft.CmdLineParser.Tests/holonsoft.CmdLineParser.Tests.csproj -c Release --no-build + + - name: Pack + run: dotnet pack holonsoft.CmdLineParser.slnx -c Release --no-build -o artifacts + + - name: Upload packages + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v7 + with: + name: packages + path: artifacts/*.nupkg diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..f240cd8 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,59 @@ +name: Publish to NuGet + +# Triggered by version tags such as v5.0.0 or v5.1.0-preview.1. +# Authenticates against nuget.org with trusted publishing (OIDC), no API key is stored anywhere. +# The nuget.org policy matches this file name (publish.yml) and the environment name (release). + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + publish: + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write # required for the OIDC token that NuGet/login exchanges + contents: read + + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 # MinVer reads the version from git tags + + - name: Setup .NET + uses: actions/setup-dotnet@v6 + with: + dotnet-version: | + 8.0.x + 9.0.x + 10.0.x + + - name: Build + run: dotnet build holonsoft.CmdLineParser.slnx -c Release + + - name: Test + run: dotnet test holonsoft.CmdLineParser.Tests/holonsoft.CmdLineParser.Tests.csproj -c Release --no-build + + - name: Pack + run: dotnet pack holonsoft.CmdLineParser.slnx -c Release --no-build -o artifacts + + - name: Verify package version matches the tag + run: | + expected="${GITHUB_REF_NAME#v}" + ls -l artifacts + test -f "artifacts/holonsoft.CmdLineParser.${expected}.nupkg" + test -f "artifacts/holonsoft.CmdLineParser.Abstractions.${expected}.nupkg" + + - name: NuGet login (OIDC) + id: login + uses: NuGet/login@v1 + with: + user: ${{ secrets.NUGET_USER }} # nuget.org profile name, not the email + + - name: Push to nuget.org + run: dotnet nuget push "artifacts/*.nupkg" --api-key ${{ steps.login.outputs.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/Directory.Build.props b/Directory.Build.props index 7f72ceb..cabc943 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,7 +10,6 @@ - 5.0.0 holonsoft Christian Vogt Christian Vogt, holonsoft @@ -36,4 +35,15 @@ + + + v + 5.0 + preview.0 + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index f6b722a..fce6adb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,6 +5,10 @@ false + + + + diff --git a/README.md b/README.md index 55d5be3..ec35006 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # CmdLineParser +[![CI](https://github.com/holonsoft/CmdLineParser/actions/workflows/ci.yml/badge.svg)](https://github.com/holonsoft/CmdLineParser/actions/workflows/ci.yml) +[![NuGet](https://img.shields.io/nuget/v/holonsoft.CmdLineParser.svg)](https://www.nuget.org/packages/holonsoft.CmdLineParser/) + Reflection based fast command line parser (`args[]` -> POCO). Define a class, put attributes on its public fields or properties, call `Parse`. You get a filled object, a list of structured errors and a help text. No builder DSL, no handlers, no ceremony. From 7fe3b49cb6aae18ca97ce4b2d3d28b2b2eac7368 Mon Sep 17 00:00:00 2001 From: "christian.vogt@hiquaro.com" Date: Sun, 6 Sep 2026 09:51:30 +0200 Subject: [PATCH 2/4] Ignore editor-local .vscode folder Co-Authored-By: Claude Fable 5.1 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b21e17b..55b9ca8 100644 --- a/.gitignore +++ b/.gitignore @@ -344,3 +344,6 @@ healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ /Acs.BackendHost/app.config + +# Editor-local settings +.vscode/ From e2c9fa791c875f51699e0ad93ee5d6049547d24b Mon Sep 17 00:00:00 2001 From: "christian.vogt@hiquaro.com" Date: Sun, 6 Sep 2026 09:57:49 +0200 Subject: [PATCH 3/4] Fix MustExist test on Unix: absolute paths must not be read as slash options Path.GetTempFileName() returns /tmp/... on Linux, which the default AllowSlashPrefix=true option turned into an option token. The test now disables the slash prefix explicitly. Co-Authored-By: Claude Fable 5.1 --- holonsoft.CmdLineParser.Tests/ValidationTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/holonsoft.CmdLineParser.Tests/ValidationTests.cs b/holonsoft.CmdLineParser.Tests/ValidationTests.cs index 6c2617d..7007b88 100644 --- a/holonsoft.CmdLineParser.Tests/ValidationTests.cs +++ b/holonsoft.CmdLineParser.Tests/ValidationTests.cs @@ -242,7 +242,8 @@ public void MustExistChecksTheFileSystem() { var missing = Path.Combine(directory, Guid.NewGuid().ToString("N")); try { - var parser = new CommandLineParser(); + // Absolute paths start with a slash on Unix, which the default options would read as an option prefix. + var parser = new CommandLineParser(new CommandLineParserOptions { AllowSlashPrefix = false }); Assert.Empty(parser.ParseArguments(["-File", file, "-Directory", directory, "-Any", file]).Errors); From 3a4d43911c0ddc311510b72ae54e2bbf8f43253a Mon Sep 17 00:00:00 2001 From: "christian.vogt@hiquaro.com" Date: Sun, 6 Sep 2026 12:01:11 +0200 Subject: [PATCH 4/4] Enable the slash prefix by default on Windows only Absolute paths start with '/' on Unix-like systems, so a default of AllowSlashPrefix=true turned every path value into an option token there. The default is now OperatingSystem.IsWindows(). Tests that use the classic /name syntax enable the prefix explicitly, the lexer tests cover the default, the enabled and the disabled behavior, and the fuzz test exercises both settings on every OS. Co-Authored-By: Claude Fable 5.1 --- README.md | 5 ++- .../ArgumentLexerTests.cs | 30 ++++++++++--- .../CommandLineParserTests.cs | 44 +++++++++++-------- .../EdgeCaseTests.cs | 2 +- holonsoft.CmdLineParser.Tests/FuzzTests.cs | 3 ++ holonsoft.CmdLineParser.Tests/HelpTests.cs | 2 +- .../ValidationTests.cs | 3 +- .../VerbParserTests.cs | 2 +- .../CommandLineParserOptions.cs | 7 +-- 9 files changed, 63 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index ec35006..90f644d 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ public class Options : IValidatableArguments { ## Parsing rules -* `-name`, `--name` and `/name` are equivalent. `/` can be disabled in the options for Unix style tools. +* `-name` and `--name` are equivalent. `/name` is accepted too when `AllowSlashPrefix` is on. That is the default on Windows only, because absolute paths start with `/` on Unix-like systems. * A value can follow as separate token or inline: `-name value`, `-name:value`, `-name=value`. Only the first separator counts, so `-url:http://x` works. * Values wrapped in double quotes are unquoted. * Negative numbers do not need quotes: `-Value -5`, `-Points -1.5 -.5 2`. @@ -234,7 +234,7 @@ switch (result.Value) { ```csharp var options = new CommandLineParserOptions { IgnoreCase = true, // default false - AllowSlashPrefix = false, // default true + AllowSlashPrefix = true, // default: true on Windows, false elsewhere ValueSeparators = [':', '='], // default RecognizeEndOfOptionsMarker = true, // default AutoHelp = true, // default @@ -278,6 +278,7 @@ Breaking changes in 5.0: * Empty `args` applies defaults and checks required arguments. * Built-in help names `help`, `h` and `?` are recognized unless your class defines them. * `--name=value`, unquoted negative numbers and `@file` response files are recognized. A bare `--` ends the options. +* `/name` is recognized by default on Windows only. Set `AllowSlashPrefix = true` to keep it on Linux and macOS. * `Uri`, `Version`, `FileInfo`, `DirectoryInfo` and many more types are supported. Byte overflow is an error instead of silent truncation. * The `Argument` class was removed from the Abstractions package. `ArgumentAttribute` string properties are nullable. Missing required arguments are reported once, not once per alias. * `GetHelpTexts()` returns empty strings instead of null for missing parts and sorts case-insensitively. diff --git a/holonsoft.CmdLineParser.Tests/ArgumentLexerTests.cs b/holonsoft.CmdLineParser.Tests/ArgumentLexerTests.cs index 30126ba..9bbd451 100644 --- a/holonsoft.CmdLineParser.Tests/ArgumentLexerTests.cs +++ b/holonsoft.CmdLineParser.Tests/ArgumentLexerTests.cs @@ -13,9 +13,7 @@ private static ArgumentLexer Create(Action? configure [Theory] [InlineData("-name", "name")] [InlineData("--name", "name")] - [InlineData("/name", "name")] [InlineData("-n", "n")] - [InlineData("/?", "?")] [InlineData("- name ", "name")] public void OptionPrefixes(string raw, string expectedName) { var token = Create().Classify(raw, afterEndOfOptions: false); @@ -28,7 +26,6 @@ public void OptionPrefixes(string raw, string expectedName) { [Theory] [InlineData("-name:value", "name", "value")] [InlineData("--name=value", "name", "value")] - [InlineData("/name:\"quoted value\"", "name", "quoted value")] [InlineData("-url:http://host:8080/x", "url", "http://host:8080/x")] [InlineData("-path=C:\\temp", "path", "C:\\temp")] [InlineData("-x:a=b", "x", "a=b")] @@ -82,11 +79,32 @@ public void EndOfOptionsMarkerCanBeDisabled() { } [Fact] - public void SlashPrefixCanBeDisabled() { - var token = Create(o => o.AllowSlashPrefix = false).Classify("/etc/passwd", afterEndOfOptions: false); + public void SlashPrefixDefaultFollowsTheOperatingSystem() { + Assert.Equal(OperatingSystem.IsWindows(), new CommandLineParserOptions().AllowSlashPrefix); + } + + [Theory] + [InlineData("/name", "name", null)] + [InlineData("/?", "?", null)] + [InlineData("/name:\"quoted value\"", "name", "quoted value")] + [InlineData("/etc/passwd", "etc/passwd", null)] + public void SlashPrefixWhenEnabled(string raw, string expectedName, string? expectedValue) { + var token = Create(o => o.AllowSlashPrefix = true).Classify(raw, afterEndOfOptions: false); + + Assert.Equal(LexedTokenKind.Option, token.Kind); + Assert.Equal(expectedName, token.Name); + Assert.Equal(expectedValue, token.Value); + } + + [Theory] + [InlineData("/name")] + [InlineData("/?")] + [InlineData("/etc/passwd")] + public void SlashPrefixWhenDisabled(string raw) { + var token = Create(o => o.AllowSlashPrefix = false).Classify(raw, afterEndOfOptions: false); Assert.Equal(LexedTokenKind.Value, token.Kind); - Assert.Equal("/etc/passwd", token.Value); + Assert.Equal(raw, token.Value); } [Fact] diff --git a/holonsoft.CmdLineParser.Tests/CommandLineParserTests.cs b/holonsoft.CmdLineParser.Tests/CommandLineParserTests.cs index 158547c..f675a00 100644 --- a/holonsoft.CmdLineParser.Tests/CommandLineParserTests.cs +++ b/holonsoft.CmdLineParser.Tests/CommandLineParserTests.cs @@ -11,6 +11,12 @@ namespace holonsoft.CmdLineParser.Tests; /// The scenarios of the original test suite, kept as regression guard for the 5.0 rewrite. /// public class CommandLineParserTests { + /// + /// The original suite mixes '-' and '/' prefixes, so the slash prefix is enabled explicitly. It is off by default on + /// Unix-like systems. + /// + private static CommandLineParserOptions WithSlashPrefix => new() { AllowSlashPrefix = true }; + public static readonly string[] Args1 = ["-s", "--help", "-ka", "\"was das soll\""]; public static readonly string[] Args2 = ["-c", "huhu", "-d", "-ef", "--h", "/?", "-f", "dummy", "-g", "\"Long text with spaces\"", "/t:test", "dd"]; public static readonly string[] Args3 = ["-StartConnections", "3", "-MaxConnections:5"]; @@ -56,7 +62,7 @@ public class CommandLineParserTests { [Fact] public void UnknownArgumentsAndHelpDoNotThrow() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.ParseArguments(Args1); @@ -67,7 +73,7 @@ public void UnknownArgumentsAndHelpDoNotThrow() { [Fact] public void GarbageInputProducesOnlyUnknownArgumentErrors() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.ParseArguments(Args2); @@ -78,7 +84,7 @@ public void GarbageInputProducesOnlyUnknownArgumentErrors() { [Fact] public void EmptyArgumentsReturnInstanceWithDefaults() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.Parse([]); @@ -89,7 +95,7 @@ public void EmptyArgumentsReturnInstanceWithDefaults() { [Fact] public void SimpleValuesAndDefaults() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var reported = new List<(ParserErrorKinds Kind, string Hint)>(); var result = parser.Parse(Args3, (kind, hint) => reported.Add((kind, hint))); @@ -107,7 +113,7 @@ public void SimpleValuesAndDefaults() { [Fact] public void MultiValueCollections() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.Parse(Args4); @@ -119,7 +125,7 @@ public void MultiValueCollections() { [Fact] public void MultipleUniqueCollectionRejectsDuplicates() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var kindOfError = ParserErrorKinds.None; var result = parser.Parse(Args7, (kind, _) => kindOfError = kind); @@ -131,7 +137,7 @@ public void MultipleUniqueCollectionRejectsDuplicates() { [Fact] public void UnknownOptionIsReported() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var kindOfError = ParserErrorKinds.None; parser.Parse(Args8, (kind, _) => kindOfError = kind); @@ -142,7 +148,7 @@ public void UnknownOptionIsReported() { [Fact] public void FlagWithoutValueIsTrue() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.Parse(Args9); @@ -152,7 +158,7 @@ public void FlagWithoutValueIsTrue() { [Fact] public void GuidWithInlineQuotedValue() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.Parse(Args5); @@ -162,7 +168,7 @@ public void GuidWithInlineQuotedValue() { [Fact] public void EnumByName() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.Parse(Args6); @@ -172,7 +178,7 @@ public void EnumByName() { [Fact] public void MissingRequiredFieldsAreReported() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); parser.Parse(Args6); @@ -183,7 +189,7 @@ public void MissingRequiredFieldsAreReported() { [Fact] public void AllSupportedTypes() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.ParseArguments(Args99); @@ -214,7 +220,7 @@ public void AllSupportedTypes() { [Fact] public void HelpTextsListEveryArgument() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var helpTexts = parser.GetHelpTexts().ToList(); @@ -223,14 +229,14 @@ public void HelpTextsListEveryArgument() { [Fact] public void UnsupportedMemberTypeThrowsOnFirstUse() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); Assert.Throws(() => parser.Parse(["/Anything", "x"])); } [Fact] public void ShortNameWithEveryPrefix() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); Assert.True(parser.Parse(Args10).FlagWhenFound); Assert.True(parser.Parse(Args11).FlagWhenFound); @@ -239,7 +245,7 @@ public void ShortNameWithEveryPrefix() { [Fact] public void MemberNameLongNameAndShortNameAddressTheSameField() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); Assert.Equal("Special_Out1.txt", parser.Parse(Args13).OutFileName); Assert.Equal("Special_Out1.txt", parser.Parse(Args14).OutFileName); @@ -248,7 +254,7 @@ public void MemberNameLongNameAndShortNameAddressTheSameField() { [Fact] public void DefaultValueAppliesWhenFieldNotGiven() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.Parse(Args16); @@ -258,7 +264,7 @@ public void DefaultValueAppliesWhenFieldNotGiven() { [Fact] public void RepeatedCollectionOption() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.Parse(Args17); @@ -270,7 +276,7 @@ public void RepeatedCollectionOption() { [Fact] public void CollectionWithSingleOptionAndSeveralValues() { - var parser = new CommandLineParser(); + var parser = new CommandLineParser(WithSlashPrefix); var result = parser.Parse(Args18); diff --git a/holonsoft.CmdLineParser.Tests/EdgeCaseTests.cs b/holonsoft.CmdLineParser.Tests/EdgeCaseTests.cs index 6731a91..766f2a1 100644 --- a/holonsoft.CmdLineParser.Tests/EdgeCaseTests.cs +++ b/holonsoft.CmdLineParser.Tests/EdgeCaseTests.cs @@ -114,7 +114,7 @@ public class FormattedDefaults { [Theory] [InlineData("-Verbose:false", false)] [InlineData("-Verbose=yes", true)] - [InlineData("/v:0", false)] + [InlineData("-v:0", false)] public void InlineValueOnBool(string argument, bool expected) { var result = Parse(argument); diff --git a/holonsoft.CmdLineParser.Tests/FuzzTests.cs b/holonsoft.CmdLineParser.Tests/FuzzTests.cs index 6a57b16..c21f264 100644 --- a/holonsoft.CmdLineParser.Tests/FuzzTests.cs +++ b/holonsoft.CmdLineParser.Tests/FuzzTests.cs @@ -35,6 +35,9 @@ private static IEnumerable> Parsers() { var relaxed = new CommandLineParser(new CommandLineParserOptions { IgnoreCase = true, AllowSlashPrefix = false, ValueSeparators = ['='] }); yield return args => relaxed.ParseArguments(args); + var classic = new CommandLineParser(new CommandLineParserOptions { AllowSlashPrefix = true }); + yield return args => classic.ParseArguments(args); + var verbs = new VerbParser().Add().Add().Add("run", isDefault: true); yield return args => verbs.Parse(args); } diff --git a/holonsoft.CmdLineParser.Tests/HelpTests.cs b/holonsoft.CmdLineParser.Tests/HelpTests.cs index ca90213..358ad8b 100644 --- a/holonsoft.CmdLineParser.Tests/HelpTests.cs +++ b/holonsoft.CmdLineParser.Tests/HelpTests.cs @@ -121,7 +121,7 @@ public void WrapSplitsAtWordBoundaries() { [InlineData("/?")] [InlineData("-help")] public void BuiltInHelpSuppressesMissingRequiredErrors(string helpArgument) { - var result = new CommandLineParser().ParseArguments([helpArgument]); + var result = new CommandLineParser(new CommandLineParserOptions { AllowSlashPrefix = true }).ParseArguments([helpArgument]); Assert.True(result.HelpRequested); Assert.Empty(result.Errors); diff --git a/holonsoft.CmdLineParser.Tests/ValidationTests.cs b/holonsoft.CmdLineParser.Tests/ValidationTests.cs index 7007b88..6c2617d 100644 --- a/holonsoft.CmdLineParser.Tests/ValidationTests.cs +++ b/holonsoft.CmdLineParser.Tests/ValidationTests.cs @@ -242,8 +242,7 @@ public void MustExistChecksTheFileSystem() { var missing = Path.Combine(directory, Guid.NewGuid().ToString("N")); try { - // Absolute paths start with a slash on Unix, which the default options would read as an option prefix. - var parser = new CommandLineParser(new CommandLineParserOptions { AllowSlashPrefix = false }); + var parser = new CommandLineParser(); Assert.Empty(parser.ParseArguments(["-File", file, "-Directory", directory, "-Any", file]).Errors); diff --git a/holonsoft.CmdLineParser.Tests/VerbParserTests.cs b/holonsoft.CmdLineParser.Tests/VerbParserTests.cs index e0dbb2e..019aadd 100644 --- a/holonsoft.CmdLineParser.Tests/VerbParserTests.cs +++ b/holonsoft.CmdLineParser.Tests/VerbParserTests.cs @@ -93,7 +93,7 @@ public void DefaultVerbHandlesCommandLinesWithoutVerb() { [InlineData("-h")] [InlineData("/?")] public void HelpWithoutVerbAsksForTheOverview(string help) { - var result = Create().Parse([help]); + var result = new VerbParser(new CommandLineParserOptions { AllowSlashPrefix = true }).Add().Add().Parse([help]); Assert.True(result.HelpRequested); Assert.Null(result.Verb); diff --git a/holonsoft.CmdLineParser/CommandLineParserOptions.cs b/holonsoft.CmdLineParser/CommandLineParserOptions.cs index 42b8dfb..7530e18 100644 --- a/holonsoft.CmdLineParser/CommandLineParserOptions.cs +++ b/holonsoft.CmdLineParser/CommandLineParserOptions.cs @@ -15,10 +15,11 @@ public sealed class CommandLineParserOptions { public bool IgnoreCase { get; set; } /// - /// Accept '/' as argument prefix in addition to '-' and '--'. Default: true. - /// Consider disabling this on Unix-like systems when values may be absolute paths. + /// Accept '/' as argument prefix in addition to '-' and '--', as in /name or /?. + /// Default: true on Windows, false elsewhere, because absolute paths start with '/' on Unix-like + /// systems and would otherwise be read as options. Set it explicitly when the behavior must not depend on the OS. /// - public bool AllowSlashPrefix { get; set; } = true; + public bool AllowSlashPrefix { get; set; } = OperatingSystem.IsWindows(); /// /// Characters that separate an argument name from an inline value, as in -name:value or --name=value.