Skip to content

Commit 382abcf

Browse files
committed
Ensure options on parent command are validated and cleanup ReflectionAppBuilder
1 parent 6bf07d3 commit 382abcf

14 files changed

+221
-211
lines changed

src/CommandLineUtils/CommandLineApplication.Execute.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static int Execute<TApp>(IConsole console, params string[] args)
5353

5454
if (bindResult.ValidationResult != ValidationResult.Success)
5555
{
56-
return HandleValidationError<TApp>(console, bindResult);
56+
return HandleValidationError(console, bindResult);
5757
}
5858

5959
var invoker = ExecuteMethodInvoker.Create(bindResult.Target.GetType());
@@ -106,7 +106,7 @@ public static async Task<int> ExecuteAsync<TApp>(IConsole console, params string
106106

107107
if (bindResult.ValidationResult != ValidationResult.Success)
108108
{
109-
return HandleValidationError<TApp>(console, bindResult);
109+
return HandleValidationError(console, bindResult);
110110
}
111111

112112
var invoker = ExecuteMethodInvoker.Create(bindResult.Target.GetType());
@@ -121,7 +121,7 @@ public static async Task<int> ExecuteAsync<TApp>(IConsole console, params string
121121
}
122122
}
123123

124-
private static int HandleValidationError<TApp>(IConsole console, BindContext bindResult)
124+
private static int HandleValidationError(IConsole console, BindResult bindResult)
125125
{
126126
const BindingFlags MethodFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
127127

@@ -132,7 +132,7 @@ private static int HandleValidationError<TApp>(IConsole console, BindContext bin
132132

133133
if (method == null)
134134
{
135-
return bindResult.RootApp.DefaultValidationErrorHandler(bindResult.ValidationResult);
135+
return bindResult.Command.DefaultValidationErrorHandler(bindResult.ValidationResult);
136136
}
137137

138138
var arguments = ReflectionHelper.BindParameters(method, console, bindResult);
@@ -145,27 +145,27 @@ private static int HandleValidationError<TApp>(IConsole console, BindContext bin
145145
return 1;
146146
}
147147

148-
private static BindContext Bind<TApp>(IConsole console, string[] args) where TApp : class, new()
148+
private static BindResult Bind<TApp>(IConsole console, string[] args) where TApp : class, new()
149149
{
150150
if (console == null)
151151
{
152152
throw new ArgumentNullException(nameof(console));
153153
}
154154

155155
var applicationBuilder = new ReflectionAppBuilder<TApp>();
156-
return applicationBuilder.Bind(console, args).GetBottomContext();
156+
return applicationBuilder.Bind(console, args);
157157
}
158158

159-
private static bool IsShowingInfo(BindContext bindResult)
159+
private static bool IsShowingInfo(BindResult bindResult)
160160
{
161-
if (bindResult.RootApp.IsShowingInformation)
161+
if (bindResult.Command.IsShowingInformation)
162162
{
163-
if (bindResult.RootApp.OptionHelp?.HasValue() == true && bindResult.RootApp.StopParsingAfterHelpOption)
163+
if (bindResult.Command.OptionHelp?.HasValue() == true && bindResult.Command.StopParsingAfterHelpOption)
164164
{
165165
return true;
166166
}
167167

168-
if (bindResult.RootApp.OptionVersion?.HasValue() == true && bindResult.RootApp.StopParsingAfterVersionOption)
168+
if (bindResult.Command.OptionVersion?.HasValue() == true && bindResult.Command.StopParsingAfterVersionOption)
169169
{
170170
return true;
171171
}

src/CommandLineUtils/CommandLineApplication.Parser.cs

Lines changed: 0 additions & 52 deletions
This file was deleted.

src/CommandLineUtils/CommandLineApplication.Validation.cs

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace McMaster.Extensions.CommandLineUtils
1515
{
16-
partial class CommandLineApplication : IServiceProvider
16+
partial class CommandLineApplication
1717
{
1818
private Func<ValidationResult, int> _validationErrorHandler;
1919

@@ -33,9 +33,19 @@ public Func<ValidationResult, int> ValidationErrorHandler
3333
/// <returns>The first validation result that is not <see cref="ValidationResult.Success"/> if there is an error.</returns>
3434
internal ValidationResult GetValidationResult()
3535
{
36+
if (Parent != null)
37+
{
38+
var result = Parent.GetValidationResult();
39+
40+
if (result != ValidationResult.Success)
41+
{
42+
return result;
43+
}
44+
}
45+
3646
foreach (var argument in Arguments)
3747
{
38-
var context = new ValidationContext(argument, this, null);
48+
var context = new ValidationContext(argument, new ServiceProvider(this), null);
3949

4050
if (!string.IsNullOrEmpty(argument.Name))
4151
{
@@ -55,7 +65,7 @@ internal ValidationResult GetValidationResult()
5565

5666
foreach (var option in GetOptions())
5767
{
58-
var context = new ValidationContext(option, this, null);
68+
var context = new ValidationContext(option, new ServiceProvider(this), null);
5969

6070
string name = null;
6171
if (option.LongName != null)
@@ -101,39 +111,39 @@ private int DefaultValidationErrorHandler(ValidationResult result)
101111
return 1;
102112
}
103113

104-
/// <summary>
105-
/// Returns a service provider for some commonly used types associated with a CommandLineApplication.
106-
/// </summary>
107-
/// <param name="serviceType">The service type</param>
108-
/// <returns>An instance of <paramref name="serviceType"/>, or <c>null</c> if it is not available.</returns>
109-
object IServiceProvider.GetService(Type serviceType)
114+
private sealed class ServiceProvider : IServiceProvider
110115
{
111-
if (serviceType == typeof(CommandLineApplication))
112-
{
113-
return this;
114-
}
116+
private readonly CommandLineApplication _parent;
115117

116-
if (serviceType == typeof(IConsole))
118+
public ServiceProvider(CommandLineApplication parent)
117119
{
118-
return _console;
120+
_parent = parent;
119121
}
120122

121-
if (State != null && serviceType == State.GetType())
123+
public object GetService(Type serviceType)
122124
{
123-
return State;
124-
}
125+
if (serviceType == typeof(CommandLineApplication))
126+
{
127+
return _parent;
128+
}
125129

126-
if (serviceType == typeof(IEnumerable<CommandOption>))
127-
{
128-
return GetOptions();
129-
}
130+
if (serviceType == typeof(IConsole))
131+
{
132+
return _parent._console;
133+
}
130134

131-
if (serviceType == typeof(IEnumerable<CommandArgument>))
132-
{
133-
return Arguments;
134-
}
135+
if (serviceType == typeof(IEnumerable<CommandOption>))
136+
{
137+
return _parent.GetOptions();
138+
}
139+
140+
if (serviceType == typeof(IEnumerable<CommandArgument>))
141+
{
142+
return _parent.Arguments;
143+
}
135144

136-
return null;
145+
return null;
146+
}
137147
}
138148
}
139149
}

src/CommandLineUtils/CommandLineApplication.cs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,6 @@ private CommandLineApplication(CommandLineApplication parent, string name, bool
7676
StopParsingAfterVersionOption = parent.StopParsingAfterVersionOption;
7777
}
7878

79-
/// <summary>
80-
/// An arbitrary object that can be used to set state or context.
81-
/// </summary>
82-
public object State { get; set; }
83-
8479
/// <summary>
8580
/// Defaults to null. A link to the parent command if this is instance is a subcommand.
8681
/// </summary>
@@ -370,14 +365,19 @@ public void OnExecute(Func<Task<int>> invoke)
370365
/// <returns>The return code from <see cref="Invoke"/>.</returns>
371366
public int Execute(params string[] args)
372367
{
373-
var arguments = new List<string>();
368+
args = args ?? new string[0];
369+
370+
var processor = new CommandLineProcessor(this, args);
371+
var command = processor.Process();
374372

375-
if (args != null)
373+
var result = command.GetValidationResult();
374+
375+
if (result != ValidationResult.Success)
376376
{
377-
arguments.AddRange(args);
377+
return command.ValidationErrorHandler(result);
378378
}
379379

380-
return Execute(arguments);
380+
return command.Invoke();
381381
}
382382

383383
/// <summary>
@@ -614,5 +614,28 @@ public void ShowRootCommandFullNameAndVersion()
614614
Out.WriteLine(rootCmd.GetFullNameAndVersion());
615615
Out.WriteLine();
616616
}
617+
618+
private bool _settingConsole;
619+
620+
internal void SetConsole(IConsole console)
621+
{
622+
if (_settingConsole)
623+
{
624+
// prevent stack overflow in the event someone has looping command line apps
625+
return;
626+
}
627+
628+
_settingConsole = true;
629+
_console = console;
630+
Out = console.Out;
631+
Error = console.Error;
632+
633+
foreach (var cmd in Commands)
634+
{
635+
cmd.SetConsole(console);
636+
}
637+
638+
_settingConsole = false;
639+
}
617640
}
618641
}

src/CommandLineUtils/Internal/AsyncMethodInvoker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public AsyncMethodInvoker(MethodInfo method) : base(method)
1212
{
1313
}
1414

15-
public async Task<int> ExecuteAsync(IConsole console, BindContext bindResult)
15+
public async Task<int> ExecuteAsync(IConsole console, BindResult bindResult)
1616
{
1717
var arguments = ReflectionHelper.BindParameters(Method, console, bindResult);
1818

src/CommandLineUtils/Internal/BindContext.cs

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Nate McMaster.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.ComponentModel.DataAnnotations;
5+
6+
namespace McMaster.Extensions.CommandLineUtils
7+
{
8+
internal class BindResult
9+
{
10+
/// <summary>
11+
/// The command selected based on input args.
12+
/// </summary>
13+
public CommandLineApplication Command { get; set; }
14+
15+
/// <summary>
16+
/// An instance of the object that matches <see cref="Command" />.
17+
/// </summary>
18+
public object Target { get; set; }
19+
20+
/// <summary>
21+
/// The top-level target type. The same as <see cref="Target" /> on single-level apps.
22+
/// </summary>
23+
public object ParentTarget { get; set; }
24+
25+
/// <summary>
26+
/// Validation result, if any.
27+
/// </summary>
28+
public ValidationResult ValidationResult { get; set; }
29+
}
30+
}

0 commit comments

Comments
 (0)