Skip to content

Commit 0914d56

Browse files
committed
Pass additional context to annotation providers
This allows more powerful dynamic resolution of annotation values
1 parent bc885e7 commit 0914d56

File tree

7 files changed

+72
-27
lines changed

7 files changed

+72
-27
lines changed

src/System.CommandLine.Subsystems.Tests/AlternateSubsystems.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public VersionThatUsesHelpData(CliSymbol symbol)
3030

3131
public override void Execute(PipelineResult pipelineResult)
3232
{
33-
pipelineResult.Pipeline.Annotations.TryGet(Symbol, HelpAnnotations.Description, out string? description);
33+
pipelineResult.Annotations.TryGet(Symbol, HelpAnnotations.Description, out string? description);
3434
pipelineResult.ConsoleHack.WriteLine(description);
3535
pipelineResult.AlreadyHandled = true;
3636
pipelineResult.SetSuccess();

src/System.CommandLine.Subsystems/Pipeline.cs

-6
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ private Pipeline(IEnumerable<IAnnotationProvider>? annotationProviders = null)
7575
this.annotationProviders = annotationProviders is not null
7676
? [..annotationProviders]
7777
: [];
78-
Annotations = new(this.annotationProviders);
7978
}
8079

8180
/// <summary>
@@ -194,11 +193,6 @@ public ErrorReportingSubsystem? ErrorReporting
194193
/// </summary>
195194
public ResponseSubsystem Response { get; }
196195

197-
/// <summary>
198-
/// Gets the annotation resolver
199-
/// </summary>
200-
public AnnotationResolver Annotations { get; }
201-
202196
/// <summary>
203197
/// Gets the list of annotation providers registered with the pipeline
204198
/// </summary>

src/System.CommandLine.Subsystems/PipelineResult.cs

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.CommandLine.Parsing;
5+
using System.CommandLine.Subsystems.Annotations;
56

67
namespace System.CommandLine;
78

@@ -18,6 +19,7 @@ public PipelineResult(ParseResult parseResult, string rawInput, Pipeline? pipeli
1819
Pipeline = pipeline ?? Pipeline.CreateEmpty();
1920
ConsoleHack = consoleHack ?? new ConsoleHack();
2021
valueProvider = new ValueProvider(this);
22+
Annotations = new AnnotationResolver(this);
2123
}
2224

2325
public ParseResult ParseResult { get; }
@@ -27,6 +29,8 @@ public PipelineResult(ParseResult parseResult, string rawInput, Pipeline? pipeli
2729
public Pipeline Pipeline { get; }
2830
public ConsoleHack ConsoleHack { get; }
2931

32+
public AnnotationResolver Annotations { get; }
33+
3034
public bool AlreadyHandled { get; set; }
3135
public int ExitCode { get; set; }
3236

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.CommandLine.Parsing;
5+
6+
namespace System.CommandLine.Subsystems.Annotations;
7+
8+
/// <summary>
9+
/// Additional context that is passed to <see cref="IAnnotationProvider"/>.
10+
/// </summary>
11+
/// <remarks>
12+
/// This class exists so that additional context properties can be added without
13+
/// breaking existing <see cref="IAnnotationProvider"/> implementations.
14+
/// <para>
15+
/// This is intended to be usable independently of the pipeline. For example, a method could be
16+
/// implemented that takes a <see cref="CommandLine.ParseResult"/> and prints help output based on the help
17+
/// annotations in the <see cref="CliSymbol"/> tree, which would then be usable by developers who
18+
/// are using the <see cref="CliParser"/> API directly.
19+
/// </para>
20+
/// </remarks>
21+
public class AnnotationResolveContext(ParseResult parseResult)
22+
{
23+
public AnnotationResolveContext(PipelineResult pipelineResult)
24+
: this(pipelineResult.ParseResult)
25+
{
26+
}
27+
28+
/// <summary>
29+
/// The <see cref="ParseResult"/> for which annotations are being resolved.
30+
/// </summary>
31+
/// <remarks>
32+
/// This may be used to resolve different values for an annotation based on the parents of the symbol,
33+
/// or based on values of other symbols in the parse result.
34+
/// </remarks>
35+
public ParseResult ParseResult { get; } = parseResult;
36+
}

src/System.CommandLine.Subsystems/Subsystems/Annotations/AnnotationResolver.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ namespace System.CommandLine.Subsystems.Annotations;
1010
/// taking into account both the values stored directly on the symbol via extension methods
1111
/// and any values from <see cref="IAnnotationProvider"/> providers.
1212
/// </summary>
13+
/// <param name="providers">The providers from which annotation values will be resolved</param>
14+
/// <param name="context">The context for resolving annotation values</param>
1315
/// <remarks>
1416
/// The <paramref name="providers"/> will be enumerated each time an annotation value is requested.
1517
/// It may be modified after the resolver is created.
1618
/// </remarks>
17-
public class AnnotationResolver(ICollection<IAnnotationProvider> providers)
19+
public class AnnotationResolver(IEnumerable<IAnnotationProvider> providers, AnnotationResolveContext context)
1820
{
19-
private readonly IEnumerable<IAnnotationProvider> providers = providers ?? throw new ArgumentNullException(nameof(providers));
21+
public AnnotationResolver(PipelineResult pipelineResult)
22+
: this(pipelineResult.Pipeline.AnnotationProviders, new AnnotationResolveContext(pipelineResult))
23+
{
24+
}
2025

2126
/// <summary>
2227
/// Attempt to retrieve the <paramref name="symbol"/>'s value for the annotation <paramref name="id"/>. This will check any
@@ -48,7 +53,7 @@ public bool TryGet<TValue>(CliSymbol symbol, AnnotationId annotationId, [NotNull
4853
{
4954
foreach (var provider in providers)
5055
{
51-
if (provider.TryGet(symbol, annotationId, out object? rawValue))
56+
if (provider.TryGet(symbol, annotationId, context, out object? rawValue))
5257
{
5358
if (rawValue is TValue expectedTypeValue)
5459
{
@@ -87,7 +92,7 @@ public bool TryGet(CliSymbol symbol, AnnotationId annotationId, [NotNullWhen(tru
8792
{
8893
foreach (var provider in providers)
8994
{
90-
if (provider.TryGet(symbol, annotationId, out value))
95+
if (provider.TryGet(symbol, annotationId, context, out value))
9196
{
9297
return true;
9398
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
6+
namespace System.CommandLine.Subsystems.Annotations;
7+
8+
/// <summary>
9+
/// Alternative storage of annotations, enabling lazy loading and dynamic annotations.
10+
/// </summary>
11+
public interface IAnnotationProvider
12+
{
13+
/// <summary>
14+
/// Try to get the value of the annotation with the given <paramref name="id"/> for the <paramref name="symbol"/>.
15+
/// </summary>
16+
/// <param name="context">Additional context that may be used when resolving the annotation value.</param>
17+
/// <param name="symbol">The symbol</param>
18+
/// <param name="annotationId">The annotation identifier</param>
19+
/// <param name="value">The annotation value</param>
20+
/// <returns><see langword="true"> if the symbol was resolved, otherwise <see langword="false"></returns>
21+
bool TryGet(CliSymbol symbol, AnnotationId annotationId, AnnotationResolveContext context, [NotNullWhen(true)] out object? value);
22+
}

src/System.CommandLine.Subsystems/Subsystems/IAnnotationProvider.cs

-16
This file was deleted.

0 commit comments

Comments
 (0)