Skip to content

Commit a47baa1

Browse files
committed
Made required behaviour for CliArgument and CliOption more sensible
If the decorated property has a default value (set via a property initializer), the argument/option is detected as "not required". If the decorated property does not have a default value, the argument/option is detected as "required". If you want to force an argument/option to be required, set this property to `true`. In that case, the default value for the decorated property will be ignored (if exists). If you want to force an argument/option to be not required, set this property to `false`.
1 parent 82138cd commit a47baa1

File tree

64 files changed

+233
-123
lines changed

Some content is hidden

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

64 files changed

+233
-123
lines changed

docs/README.md

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,21 @@ You see this result:
141141
```console
142142
Required argument missing for command: 'TestApp'.
143143
```
144-
This is because a `CliArgument` decorated property is required by default (`CliArgument.Required` property's default value is `true`).
145-
A `CliArgument` is a parameter for the command itself (for the root command - the exe in this case), that's why it's required by default.
144+
This is because a `CliArgument` or `CliOption` decorated property is required if the decorated property does not have a
145+
default value (set via a property initializer) or if it's not forced via attribute property `Required`.
146146

147-
If you want to make a `CliArgument` optional, set `CliArgument.Required` property to `false` and set a default value for the decorated property.
148-
In that case, the default value for the decorated property will be used when the user does not specify the argument on the command line.
147+
If you want to make a `CliArgument` or `CliOption` optional (not required), set a default value for the decorated property:
149148
```c#
150-
[CliArgument(Required = false)]
149+
[CliArgument]
151150
public string Argument1 { get; set; } = "DefaultForArgument1";
152151
```
152+
or set `Required` property to `false`, to force it:
153+
```c#
154+
[CliArgument(Required = false)]
155+
public string Argument1 { get; set; }
156+
```
157+
In that case, the default value for the decorated property will be used when the user does not specify the argument on the command line.
158+
153159
---
154160
When you run,
155161
```console
@@ -165,15 +171,21 @@ Handler for 'TestApp.Commands.RootCliCommand' is run:
165171
Value for Option1 property is 'DefaultForOption1'
166172
Value for Argument1 property is 'NewValueForArgument1'
167173
```
168-
This is because a `CliOption` decorated property is not required by default (`CliOption.Required` property's default value is `false`).
169-
A `CliOption` is optional, as the name implies, for the command itself (for the root command - the exe in this case), that's why it's not required by default.
174+
This is because a `CliArgument` or `CliOption` decorated property is not required if the decorated property has a
175+
default value (set via a property initializer) or if it's not forced via attribute property `Required`.
170176

171-
If you want to make a `CliOption` required, set `CliArgument.Required` property to `true`.
172-
In that case, the default value for the decorated property will be ignored (if exists) and the user has to specify the option on the command line.
177+
If you want to make a `CliArgument` or `CliOption` required, remove the default value for the decorated property:
173178
```c#
174-
[CliOption(Required = true)]
179+
[CliOption]
175180
public string Option1 { get; set; }
176181
```
182+
or set `Required` property to `true`, to force it:
183+
```c#
184+
[CliOption(Required = true)]
185+
public string Option1 { get; set; } = "DefaultForOption1";
186+
```
187+
In that case, the default value for the decorated property will be ignored (if exists) and the user has to specify the option on the command line.
188+
177189
---
178190
When you run,
179191
```console

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<!-- https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#generateassemblyinfo -->
55
<LangVersion>10.0</LangVersion>
6-
<VersionPrefix>1.5.6</VersionPrefix>
6+
<VersionPrefix>1.5.7</VersionPrefix>
77
<Product>DotMake Command-Line</Product>
88
<Company>DotMake</Company>
99
<!-- Copyright is also used for NuGet metadata -->

src/DotMake.CommandLine.Shared/Attributes/CliArgumentAttribute.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ namespace DotMake.CommandLine
44
{
55
/// <summary>
66
/// Specifies a class property that represents an argument which is a value that can be passed on the command line to a command or an option.
7-
/// <para>Note that arguments are required by default, see <see cref="Required"/> property for details.</para>
7+
/// <para>
8+
/// Note that an argument is required if the decorated property does not have a default value (set via a property initializer),
9+
/// see <see cref="Required"/> property for details.
10+
/// </para>
811
/// <para>
912
/// <b>Arguments:</b> An argument is a value passed to an option or a command. The following examples show an argument for the <c>verbosity</c> option and an argument for the <c>build</c> command.
1013
/// <code>
@@ -57,18 +60,23 @@ public class CliArgumentAttribute : Attribute
5760

5861
/// <summary>
5962
/// Gets or sets a value indicating whether the argument is required when its parent command is invoked.
60-
/// Default is <see langword="true" /> for arguments.
63+
/// Default is auto-detected.
64+
/// <para>
65+
/// If the decorated property has a default value (set via a property initializer), the argument is detected as "not required".
66+
/// If the decorated property does not have a default value, the argument is detected as "required".
67+
/// </para>
6168
/// <para>
62-
/// When an argument is required and its parent command is invoked without it,
63-
/// an error message is displayed and the command handler isn't called.
69+
/// If you want to force an argument to be required, set this property to <see langword="true"/>. In that case,
70+
/// the default value for the decorated property will be ignored (if exists).
71+
/// If you want to force an argument to be not required, set this property to <see langword="false"/>.
6472
/// </para>
6573
/// <para>
66-
/// If you want to make a CliArgument optional, set this property to <see langword="false"/> and set a default value
67-
/// for the decorated property.In that case, the default value for the decorated property will be used when the user
68-
/// does not specify the argument on the command line.
74+
/// When an argument is required, the argument has to be specified on the command line and if its parent command is invoked
75+
/// without it, an error message is displayed and the command handler isn't called.
76+
/// When an argument is not required, the argument doesn't have to be specified on the command line, the default value provides the argument value.
6977
/// </para>
7078
/// </summary>
71-
public bool Required { get; set; } = true;
79+
public bool Required { get; set; }
7280

7381
/// <summary>
7482
/// Gets or sets the arity of the argument. The arity refers to the number of values that can be passed on the command line.

src/DotMake.CommandLine.Shared/Attributes/CliOptionAttribute.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ namespace DotMake.CommandLine
44
{
55
/// <summary>
66
/// Specifies a class property that represents an option which is a named parameter and a value for that parameter, that is used on the command line.
7-
/// <para>Note that options are not required by default (optional as the name implies), see <see cref="Required"/> property for details.</para>
7+
/// <para>
8+
/// Note that an option is required if the decorated property does not have a default value (set via a property initializer),
9+
/// see <see cref="Required"/> property for details.
10+
/// </para>
811
/// <para>
912
/// <b>Options:</b> An option is a named parameter that can be passed to a command. The POSIX convention is to prefix the option name with two hyphens (<c>--</c>).
1013
/// The following example shows two options:
@@ -75,15 +78,20 @@ public class CliOptionAttribute : Attribute
7578

7679
/// <summary>
7780
/// Gets or sets a value indicating whether the option is required when its parent command is invoked.
78-
/// Default is <see langword="false" /> for options.
81+
/// Default is auto-detected.
82+
/// <para>
83+
/// If the decorated property has a default value (set via a property initializer), the option is detected as "not required".
84+
/// If the decorated property does not have a default value, the option is detected as "required".
85+
/// </para>
7986
/// <para>
80-
/// When an option is required and its parent command is invoked without it,
81-
/// an error message is displayed and the command handler isn't called.
87+
/// If you want to force an option to be required, set this property to <see langword="true"/>. In that case,
88+
/// the default value for the decorated property will be ignored (if exists).
89+
/// If you want to force an option to be not required, set this property to <see langword="false"/>.
8290
/// </para>
8391
/// <para>
84-
/// If you want to make a CliOption required, set this property to <see langword="true"/>. In that case,
85-
/// the default value for the decorated property will be ignored (if exists) and the user has to specify
86-
/// the option on the command line.
92+
/// When an option is required, the option has to be specified on the command line and if its parent command is invoked
93+
/// without it, an error message is displayed and the command handler isn't called.
94+
/// When an option is not required, the option doesn't have to be specified on the command line, the default value provides the option value.
8795
/// </para>
8896
/// </summary>
8997
public bool Required { get; set; }

src/DotMake.CommandLine.SourceGeneration/CliArgumentInfo.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using Microsoft.CodeAnalysis;
66
using Microsoft.CodeAnalysis.CSharp;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
78

89
namespace DotMake.CommandLine.SourceGeneration
910
{
@@ -43,6 +44,9 @@ public CliArgumentInfo(ISymbol symbol, SyntaxNode syntaxNode, AttributeData attr
4344
if (AttributeArguments.TryGetValue(AttributeRequiredProperty, out var requiredTypedConstant)
4445
&& requiredTypedConstant.Value != null)
4546
Required = (bool)requiredTypedConstant.Value;
47+
else
48+
Required = (SyntaxNode is PropertyDeclarationSyntax propertyDeclarationSyntax
49+
&& propertyDeclarationSyntax.Initializer == null);
4650
}
4751

4852
public CliArgumentInfo(GeneratorAttributeSyntaxContext attributeSyntaxContext)
@@ -60,7 +64,7 @@ public CliArgumentInfo(GeneratorAttributeSyntaxContext attributeSyntaxContext)
6064

6165
public CliCommandInfo Parent { get; }
6266

63-
public bool Required { get; } = CliArgumentAttribute.Default.Required;
67+
public bool Required { get; }
6468

6569
public CliArgumentParseInfo ParseInfo { get; set; }
6670

src/DotMake.CommandLine.SourceGeneration/CliOptionInfo.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using Microsoft.CodeAnalysis;
66
using Microsoft.CodeAnalysis.CSharp;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
78

89
namespace DotMake.CommandLine.SourceGeneration
910
{
@@ -23,8 +24,7 @@ public class CliOptionInfo : CliSymbolInfo, IEquatable<CliOptionInfo>
2324
public static readonly Dictionary<string, string> PropertyMappings = new Dictionary<string, string>
2425
{
2526
{ nameof(CliOptionAttribute.HelpName), "ArgumentHelpName"},
26-
{ nameof(CliOptionAttribute.Hidden), "IsHidden"},
27-
{ nameof(CliOptionAttribute.Required), "IsRequired"}
27+
{ nameof(CliOptionAttribute.Hidden), "IsHidden"}
2828
};
2929

3030
public CliOptionInfo(ISymbol symbol, SyntaxNode syntaxNode, AttributeData attributeData, SemanticModel semanticModel, CliCommandInfo parent)
@@ -49,6 +49,9 @@ public CliOptionInfo(ISymbol symbol, SyntaxNode syntaxNode, AttributeData attrib
4949
if (AttributeArguments.TryGetValue(AttributeRequiredProperty, out var requiredTypedConstant)
5050
&& requiredTypedConstant.Value != null)
5151
Required = (bool)requiredTypedConstant.Value;
52+
else
53+
Required = (SyntaxNode is PropertyDeclarationSyntax propertyDeclarationSyntax
54+
&& propertyDeclarationSyntax.Initializer == null);
5255
}
5356

5457
public CliOptionInfo(GeneratorAttributeSyntaxContext attributeSyntaxContext)
@@ -121,6 +124,7 @@ public void AppendCSharpCreateString(CodeStringBuilder sb, string varName, strin
121124
case AttributeAliasesProperty:
122125
case AttributeGlobalProperty:
123126
case AttributeAllowedValuesProperty:
127+
case AttributeRequiredProperty:
124128
continue;
125129
case AttributeArityProperty:
126130
var arity = kvp.Value.ToCSharpString().Split('.').Last();
@@ -134,6 +138,8 @@ public void AppendCSharpCreateString(CodeStringBuilder sb, string varName, strin
134138
break;
135139
}
136140
}
141+
142+
sb.AppendLine($"IsRequired = {Required.ToString().ToLowerInvariant()},");
137143
}
138144

139145
if (AttributeArguments.TryGetValue(AttributeAllowedValuesProperty, out var allowedValuesTypedConstant)

src/DotMake.CommandLine/DotMake.CommandLine.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@
1919
<Description>Declarative syntax for System.CommandLine via attributes for easy, fast, strongly-typed (no reflection) usage. Includes a source generator which automagically converts your classes to CLI commands and properties to CLI options or CLI arguments.</Description>
2020
<PackageTags>command-line CLI console System.CommandLine declarative attributes parsing command argument option generator</PackageTags>
2121
<PackageReleaseNotes>
22-
- Fixed net472 and netstandard2.0 support. ModuleInitializerAttribute will be generated for legacy projects (missing before net5.0).
22+
- Made required behaviour for `CliArgument` and `CliOption` more sensible:
23+
If the decorated property has a default value (set via a property initializer), the argument/option is detected as "not required".
24+
If the decorated property does not have a default value, the argument/option is detected as "required".
25+
26+
If you want to force an argument/option to be required, set this property to `true`. In that case,
27+
the default value for the decorated property will be ignored (if exists).
28+
If you want to force an argument/option to be not required, set this property to `false`.
2329
</PackageReleaseNotes>
2430
</PropertyGroup>
2531

src/TestApp/GeneratedFiles/DotMake.CommandLine.SourceGeneration/DotMake.CommandLine.SourceGeneration.CliCommandGenerator/ArgumentConverterCliCommandBuilder-f61jepm.g.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// <auto-generated />
2-
// Generated by DotMake.CommandLine.SourceGeneration v1.5.6.0
2+
// Generated by DotMake.CommandLine.SourceGeneration v1.5.7.0
33
// Roslyn (Microsoft.CodeAnalysis) v4.800.23.57201
44
// Generation: 1
55

@@ -36,8 +36,8 @@ public override System.CommandLine.Command Build()
3636
)
3737
)
3838
{
39+
IsRequired = true,
3940
};
40-
option0.SetDefaultValue(defaultClass.Opt);
4141
option0.AddAlias("-o");
4242
rootCommand.Add(option0);
4343

@@ -53,8 +53,8 @@ public override System.CommandLine.Command Build()
5353
)
5454
{
5555
AllowMultipleArgumentsPerToken = true,
56+
IsRequired = true,
5657
};
57-
option1.SetDefaultValue(defaultClass.OptArray);
5858
rootCommand.Add(option1);
5959

6060
// Option for 'OptNullable' property
@@ -67,8 +67,8 @@ public override System.CommandLine.Command Build()
6767
)
6868
)
6969
{
70+
IsRequired = true,
7071
};
71-
option2.SetDefaultValue(defaultClass.OptNullable);
7272
rootCommand.Add(option2);
7373

7474
// Option for 'OptEnumerable' property
@@ -82,8 +82,8 @@ public override System.CommandLine.Command Build()
8282
)
8383
)
8484
{
85+
IsRequired = true,
8586
};
86-
option3.SetDefaultValue(defaultClass.OptEnumerable);
8787
rootCommand.Add(option3);
8888

8989
// Option for 'OptList' property
@@ -97,8 +97,8 @@ public override System.CommandLine.Command Build()
9797
)
9898
)
9999
{
100+
IsRequired = true,
100101
};
101-
option4.SetDefaultValue(defaultClass.OptList);
102102
rootCommand.Add(option4);
103103

104104
// Option for 'OptCustomList' property
@@ -112,8 +112,8 @@ public override System.CommandLine.Command Build()
112112
)
113113
)
114114
{
115+
IsRequired = true,
115116
};
116-
option5.SetDefaultValue(defaultClass.OptCustomList);
117117
rootCommand.Add(option5);
118118

119119
// Argument for 'Arg' property

src/TestApp/GeneratedFiles/DotMake.CommandLine.SourceGeneration/DotMake.CommandLine.SourceGeneration.CliCommandGenerator/AsyncIntReturnCliCommandBuilder-7vdns44.g.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// <auto-generated />
2-
// Generated by DotMake.CommandLine.SourceGeneration v1.5.6.0
2+
// Generated by DotMake.CommandLine.SourceGeneration v1.5.7.0
33
// Roslyn (Microsoft.CodeAnalysis) v4.800.23.57201
44
// Generation: 1
55

@@ -38,6 +38,7 @@ public override System.CommandLine.Command Build()
3838
)
3939
{
4040
Description = "Description for Option1",
41+
IsRequired = false,
4142
};
4243
option0.SetDefaultValue(defaultClass.Option1);
4344
option0.AddAlias("-o");

src/TestApp/GeneratedFiles/DotMake.CommandLine.SourceGeneration/DotMake.CommandLine.SourceGeneration.CliCommandGenerator/AsyncVoidReturnCliCommandBuilder-7ha5x0r.g.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// <auto-generated />
2-
// Generated by DotMake.CommandLine.SourceGeneration v1.5.6.0
2+
// Generated by DotMake.CommandLine.SourceGeneration v1.5.7.0
33
// Roslyn (Microsoft.CodeAnalysis) v4.800.23.57201
44
// Generation: 1
55

@@ -38,6 +38,7 @@ public override System.CommandLine.Command Build()
3838
)
3939
{
4040
Description = "Description for Option1",
41+
IsRequired = false,
4142
};
4243
option0.SetDefaultValue(defaultClass.Option1);
4344
option0.AddAlias("-o");

0 commit comments

Comments
 (0)