-
Notifications
You must be signed in to change notification settings - Fork 2
Fix global option descriptions in help output #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1ce66c9
e7d16a5
f044b58
5bbfac5
8f9f94b
366d780
9a40cc9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,7 +101,25 @@ internal bool TryGetRouteConstraint(string name, out Func<string, bool> predicat | |
| /// <param name="aliases">Optional aliases. Values without prefix are normalized to <c>--alias</c>.</param> | ||
| /// <param name="defaultValue">Optional default value metadata.</param> | ||
| public void AddGlobalOption<T>(string name, string[]? aliases = null, T? defaultValue = default) => | ||
| AddGlobalOptionCore(name, typeof(T), aliases, defaultValue?.ToString()); | ||
| AddGlobalOptionCore(name, typeof(T), aliases, FormatDefaultValue(defaultValue, typeof(T))); | ||
|
|
||
| /// <summary> | ||
| /// Registers a custom global option with an explicit help description. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <paramref name="description"/> is the trailing required parameter so this overload never collides with | ||
| /// <see cref="AddGlobalOption{T}(string, string[], T)"/> during overload resolution: a positional | ||
| /// <c>null</c> second argument (for example <c>AddGlobalOption<string>("tenant", null)</c>) binds | ||
| /// unambiguously to the aliases-only overload. The typed <see cref="System.ComponentModel.DescriptionAttribute"/> | ||
| /// path (<c>UseGlobalOptions<T>()</c>) remains the primary way to attach descriptions. | ||
| /// </remarks> | ||
| /// <typeparam name="T">Declared value type.</typeparam> | ||
| /// <param name="name">Canonical name without prefix (for example: "tenant").</param> | ||
| /// <param name="aliases">Aliases (pass <c>null</c> for none). Values without prefix are normalized to <c>--alias</c>.</param> | ||
| /// <param name="defaultValue">Default value metadata (pass <c>default</c> for none).</param> | ||
| /// <param name="description">Description shown in help output.</param> | ||
| public void AddGlobalOption<T>(string name, string[]? aliases, T? defaultValue, string description) => | ||
| AddGlobalOptionCore(name, typeof(T), aliases, FormatDefaultValue(defaultValue, typeof(T)), description); | ||
|
|
||
| /// <summary> | ||
| /// Registers a custom global option using a type or constraint name | ||
|
|
@@ -117,7 +135,27 @@ public void AddGlobalOption<T>(string name, string[]? aliases = null, T? default | |
| public void AddGlobalOption(string name, string constraintOrTypeName, string[]? aliases = null, string? defaultValue = null) => | ||
| AddGlobalOptionCore(name, ResolveConstraintOrTypeName(constraintOrTypeName, _customRouteConstraints), aliases, defaultValue); | ||
|
|
||
| internal void AddGlobalOptionCore(string name, Type valueType, string[]? aliases, string? defaultValue, Type? ownerType = null) | ||
| /// <summary> | ||
| /// Registers a custom global option (by type or constraint name) with an explicit help description. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <paramref name="description"/> is the trailing required parameter so this overload never collides with | ||
| /// <see cref="AddGlobalOption(string, string, string[], string)"/> during overload resolution: a positional | ||
| /// <c>null</c> third argument (for example <c>AddGlobalOption("port", "int", null)</c>) binds unambiguously | ||
| /// to the aliases-only overload. | ||
| /// </remarks> | ||
| /// <param name="name">Canonical name without prefix (for example: "tenant").</param> | ||
| /// <param name="constraintOrTypeName"> | ||
| /// Built-in type name ("string", "int", "long", "bool", "guid", "uri", "date", "datetime", "timespan") | ||
| /// or a registered custom route constraint name. Custom constraints resolve to <c>string</c>. | ||
| /// </param> | ||
| /// <param name="aliases">Aliases (pass <c>null</c> for none). Values without prefix are normalized to <c>--alias</c>.</param> | ||
| /// <param name="defaultValue">Default value as string (pass <c>null</c> for none).</param> | ||
| /// <param name="description">Description shown in help output.</param> | ||
| public void AddGlobalOption(string name, string constraintOrTypeName, string[]? aliases, string? defaultValue, string description) => | ||
| AddGlobalOptionCore(name, ResolveConstraintOrTypeName(constraintOrTypeName, _customRouteConstraints), aliases, defaultValue, description); | ||
|
|
||
| internal void AddGlobalOptionCore(string name, Type valueType, string[]? aliases, string? defaultValue, string? description = null, Type? ownerType = null) | ||
| { | ||
| name = string.IsNullOrWhiteSpace(name) | ||
| ? throw new ArgumentException("Global option name cannot be empty.", nameof(name)) | ||
|
|
@@ -141,6 +179,7 @@ internal void AddGlobalOptionCore(string name, Type valueType, string[]? aliases | |
| CanonicalToken: normalizedCanonical, | ||
| Aliases: normalizedAliases, | ||
| DefaultValue: defaultValue, | ||
| Description: description, | ||
| ValueType: valueType, | ||
| OwnerType: ownerType); | ||
| } | ||
|
|
@@ -161,6 +200,37 @@ private static string BuildDuplicateGlobalOptionMessage(string name, Type? exist | |
| return $"A global option named '{name}' is already registered by {existingSource} and cannot also be registered by {newSource}."; | ||
| } | ||
|
|
||
| internal static string? FormatDefaultValue(object? value, Type type) => | ||
| value is not null && !IsDefaultForType(value, type) | ||
| ? value.ToString() | ||
| : null; | ||
|
Comment on lines
+203
to
+206
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When callers explicitly pass a registration default equal to the CLR default, for example Useful? React with 👍 / 👎. |
||
|
|
||
| internal static bool IsDefaultForType(object value, Type type) | ||
| { | ||
| var effectiveType = Nullable.GetUnderlyingType(type) ?? type; | ||
| if (effectiveType == typeof(bool)) | ||
| { | ||
| return value is false; | ||
| } | ||
|
|
||
| if (effectiveType == typeof(int)) | ||
| { | ||
| return value is 0; | ||
| } | ||
|
|
||
| if (effectiveType == typeof(long)) | ||
| { | ||
| return value is 0L; | ||
| } | ||
|
|
||
| if (effectiveType == typeof(double)) | ||
| { | ||
| return value is 0.0d; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| private static Type ResolveConstraintOrTypeName( | ||
| string constraintOrTypeName, | ||
| Dictionary<string, Func<string, bool>> customConstraints) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.