Skip to content

Commit debaeb8

Browse files
committed
Implement input parsing feature v2 fixes #89
1 parent 8cc771c commit debaeb8

File tree

8 files changed

+121
-62
lines changed

8 files changed

+121
-62
lines changed

CommandLineParser/Abstractions/Parsing/IArgumentManager.cs

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace MatthiWare.CommandLine.Abstractions.Parsing
77
/// </summary>
88
public interface IArgumentManager
99
{
10+
11+
1012
/// <summary>
1113
/// Tries to get the arguments associated to the current option
1214
/// </summary>

CommandLineParser/CommandLineParser`TOption.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ public IParserResult<TOption> Parse(string[] args)
195195

196196
ParseOptions(errors, result, argumentManager);
197197

198-
//CheckForExtraHelpArguments(result, argumentManager);
198+
CheckForExtraHelpArguments(result, argumentManager);
199199

200200
Validate(m_option, errors);
201201

@@ -226,7 +226,7 @@ public async Task<IParserResult<TOption>> ParseAsync(string[] args, Cancellation
226226

227227
ParseOptions(errors, result, argumentManager);
228228

229-
CheckForExtraHelpArguments(result, argumentManager);
229+
//CheckForExtraHelpArguments(result, argumentManager);
230230

231231
await ValidateAsync(m_option, errors, cancellationToken);
232232

@@ -280,18 +280,18 @@ private async Task ValidateAsync<T>(T @object, List<Exception> errors, Cancellat
280280
}
281281
}
282282

283-
private void CheckForExtraHelpArguments(ParseResult<TOption> result, ArgumentManager argumentManager)
283+
private void CheckForExtraHelpArguments(ParseResult<TOption> result, ArgumentManager2 argumentManager)
284284
{
285285
var unusedArg = argumentManager.UnusedArguments
286-
.Where(a => a.Argument.EqualsIgnoreCase(m_helpOptionName) || a.Argument.EqualsIgnoreCase(m_helpOptionNameLong))
286+
.Where(a => a.key.EqualsIgnoreCase(m_helpOptionName) || a.key.EqualsIgnoreCase(m_helpOptionNameLong))
287287
.FirstOrDefault();
288288

289-
if (unusedArg == null)
289+
if (unusedArg.argument == null)
290290
{
291291
return;
292292
}
293293

294-
result.HelpRequestedFor = unusedArg.ArgModel ?? this;
294+
result.HelpRequestedFor = unusedArg.argument ?? this;
295295
}
296296

297297
private void AutoPrintUsageAndErrors(ParseResult<TOption> result, bool noArgsSupplied)

CommandLineParser/Core/CommandLineOptionBase.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,6 @@ private bool ShouldUseDefaultWhenParsingFails(ArgumentModel model)
7878
=> !CanParse(model) && HasDefault;
7979

8080
private bool ShouldUseDefaultWhenNoValueProvidedButDefaultValueIsSpecified(ArgumentModel model)
81-
=> !model.HasValue && HasDefault;
81+
=> (model is null || !model.HasValue) && HasDefault;
8282
}
8383
}

CommandLineParser/Core/Parsing/ArgumentManager2.cs

+76-50
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@
22
using MatthiWare.CommandLine.Abstractions.Command;
33
using MatthiWare.CommandLine.Abstractions.Models;
44
using MatthiWare.CommandLine.Abstractions.Parsing;
5-
using MatthiWare.CommandLine.Core.Command;
65
using MatthiWare.CommandLine.Core.Utils;
76
using System;
87
using System.Collections;
98
using System.Collections.Generic;
10-
using System.Linq;
11-
using System.Text;
129

1310
namespace MatthiWare.CommandLine.Core.Parsing
1411
{
@@ -21,6 +18,8 @@ public class ArgumentManager2 : IArgumentManager
2118

2219
private ProcessingContext CurrentContext { get; set; }
2320

21+
public List<(string key, IArgument argument)> UnusedArguments { get; } = new List<(string key, IArgument argument)>();
22+
2423
public bool TryGetValue(IArgument argument, out ArgumentModel model) => results.TryGetValue(argument, out model);
2524

2625
public ArgumentManager2(CommandLineParserOptions options, ICommandLineCommandContainer commandContainer)
@@ -37,43 +36,76 @@ public void Process(IReadOnlyList<string> arguments)
3736

3837
while (enumerator.MoveNext())
3938
{
40-
ProcessNext();
39+
var processed = ProcessNext();
40+
41+
if (!processed)
42+
{
43+
var item = (enumerator.Current.RawData, CurrentContext.CurrentOption != null ? (IArgument)CurrentContext.CurrentOption : (IArgument)CurrentContext.CurrentCommand);
44+
UnusedArguments.Add(item);
45+
}
4146
}
4247
}
4348

44-
private void ProcessNext()
49+
private bool ProcessNext()
4550
{
4651
switch (enumerator.Current)
4752
{
4853
case OptionRecord option:
49-
ProcessOption(option);
50-
break;
54+
return ProcessOption(option);
5155
case CommandOrOptionValueRecord commandOrValue:
52-
ProcessCommandOrOptionValue(commandOrValue);
53-
break;
56+
return ProcessCommandOrOptionValue(commandOrValue);
57+
default:
58+
return false;
59+
}
60+
}
61+
62+
private bool ProcessOption(OptionRecord rec)
63+
{
64+
var foundOption = FindOption(rec);
65+
66+
if (foundOption == null)
67+
{
68+
// In case we have an option named "-1" and int value -1. This causes confusion.
69+
return ProcessCommandOrOptionValue(rec);
5470
}
71+
72+
var argumentModel = new ArgumentModel(rec.Name, rec.Value);
73+
74+
results.Add(foundOption, argumentModel);
75+
76+
return true;
5577
}
5678

57-
private void ProcessOption(OptionRecord rec)
79+
private ICommandLineOption FindOption(OptionRecord rec)
5880
{
59-
foreach (var option in CurrentContext.CurrentCommand.Options)
81+
var context = CurrentContext;
82+
83+
while (context != null)
6084
{
61-
if (!rec.Name.EqualsIgnoreCase(rec.IsLongOption ? option.LongName : option.ShortName))
85+
foreach (var option in context.CurrentCommand.Options)
6286
{
63-
continue;
64-
}
87+
if (!rec.Name.EqualsIgnoreCase(rec.IsLongOption ? option.LongName : option.ShortName))
88+
{
89+
continue;
90+
}
6591

66-
var argumentModel = new ArgumentModel(rec.Name, rec.Value);
92+
if (results.ContainsKey(option))
93+
{
94+
continue;
95+
}
6796

68-
results.Add(option, argumentModel);
97+
context.CurrentOption = option;
6998

70-
CurrentContext.CurrentOption = option;
99+
return option;
100+
}
71101

72-
break;
102+
context = context.Parent;
73103
}
104+
105+
return null;
74106
}
75107

76-
private void ProcessCommandOrOptionValue(CommandOrOptionValueRecord rec)
108+
private bool ProcessCommandOrOptionValue(ArgumentRecord rec)
77109
{
78110
foreach (var cmd in CurrentContext.CurrentCommand.Commands)
79111
{
@@ -84,50 +116,44 @@ private void ProcessCommandOrOptionValue(CommandOrOptionValueRecord rec)
84116

85117
results.Add(cmd, new ArgumentModel(cmd.Name, null));
86118

87-
CurrentContext.CurrentCommand = (ICommandLineCommandContainer)cmd;
119+
CurrentContext = new ProcessingContext(CurrentContext, (ICommandLineCommandContainer)cmd);
88120

89-
return;
90-
}
91-
92-
if (CurrentContext.CurrentOption == null)
93-
{
94-
return;
121+
return true;
95122
}
96123

97-
if (!TryGetValue(CurrentContext.CurrentOption, out var model))
98-
{
99-
// not sure yet what to do here..
100-
// no option yet and not matching command => unknown item
101-
return;
102-
}
124+
var context = CurrentContext;
103125

104-
if (model.HasValue)
126+
while (context != null)
105127
{
106-
throw new ArgumentException("model already has a value????");
107-
}
108-
109-
model.Value = rec.RawData;
110-
}
128+
if (context.CurrentOption == null)
129+
{
130+
context = context.Parent;
131+
continue;
132+
}
111133

112-
private IEnumerable<ICommandLineOption> GetOptions(IEnumerable<ICommandLineOption> options, IEnumerable<CommandLineCommandBase> commands)
113-
{
114-
foreach (var option in options)
115-
{
116-
yield return option;
117-
}
134+
if (!TryGetValue(context.CurrentOption, out var model))
135+
{
136+
// not sure yet what to do here..
137+
// no option yet and not matching command => unknown item
138+
context = context.Parent;
139+
continue;
140+
}
118141

119-
foreach (var command in commands)
120-
{
121-
foreach (var cmdOption in GetOptions(command.Options, command.Commands.Cast<CommandLineCommandBase>()))
142+
if (model.HasValue)
122143
{
123-
yield return cmdOption;
144+
context = context.Parent;
145+
continue;
124146
}
147+
148+
model.Value = rec.RawData;
149+
return true;
125150
}
151+
152+
return false;
126153
}
127154

128155
private class ProcessingContext
129156
{
130-
131157
public ICommandLineOption CurrentOption { get; set; }
132158
public ProcessingContext Parent { get; set; }
133159
public ICommandLineCommandContainer CurrentCommand { get; set; }
@@ -232,7 +258,7 @@ public OptionRecord(string data, string postfix, bool isLongOption)
232258
Name = tokens[0];
233259
}
234260

235-
public bool IsLongOption { get; }
261+
public bool IsLongOption { get; }
236262
public string Name { get; }
237263
public string Value { get; }
238264
}

CommandLineParser/Core/Parsing/Resolvers/DefaultResolver.cs

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using MatthiWare.CommandLine.Abstractions.Models;
22
using MatthiWare.CommandLine.Abstractions.Parsing;
3+
using Microsoft.Extensions.Logging;
34
using System;
45
using System.Globalization;
56
using System.Linq;
@@ -13,10 +14,12 @@ internal class DefaultResolver<T> : BaseArgumentResolver<T>
1314
private static readonly string ParseName = "Parse";
1415

1516
private readonly Type genericType;
17+
private readonly ILogger<CommandLineParser> logger;
1618

17-
public DefaultResolver()
19+
public DefaultResolver(ILogger<CommandLineParser> logger)
1820
{
1921
genericType = typeof(T);
22+
this.logger = logger;
2023
}
2124

2225
public override bool CanResolve(ArgumentModel model)
@@ -33,14 +36,23 @@ public override T Resolve(ArgumentModel model)
3336

3437
private bool TryResolve(ArgumentModel model, out T result)
3538
{
39+
if (model is null)
40+
{
41+
logger.LogDebug("DefaultResolver input is null");
42+
result = default;
43+
return false;
44+
}
45+
3646
if (!model.HasValue)
3747
{
48+
logger.LogDebug("DefaultResolver model does not have a value", model.Value);
3849
result = default;
3950
return false;
4051
}
4152

4253
if (genericType.IsEnum && TryParseEnum(model, out result))
4354
{
55+
logger.LogDebug("DefaultResolver {input} resolved to {result}", model.Value, result);
4456
return true;
4557
}
4658

@@ -66,13 +78,17 @@ private bool TryResolve(ArgumentModel model, out T result)
6678
{
6779
var tryParse = (CustomTryParseWithFormat)tryParseMethod.CreateDelegate(typeof(CustomTryParseWithFormat));
6880

69-
return tryParse(model.Value, CultureInfo.InvariantCulture, out result);
81+
var returnResult = tryParse(model.Value, CultureInfo.InvariantCulture, out result);
82+
logger.LogDebug("DefaultResolver {input} resolved to {result}", model.Value, result);
83+
return returnResult;
7084
}
7185
else if (amountOfParams == 2)
7286
{
7387
var tryParse = (CustomTryParse)tryParseMethod.CreateDelegate(typeof(CustomTryParse));
7488

75-
return tryParse(model.Value, out result);
89+
var returnResult = tryParse(model.Value, out result);
90+
logger.LogDebug("DefaultResolver {input} resolved to {result}", model.Value, result);
91+
return returnResult;
7692
}
7793
}
7894

@@ -89,17 +105,20 @@ private bool TryResolve(ArgumentModel model, out T result)
89105
var parse = (CustomParseWithFormat)parseMethod.CreateDelegate(typeof(CustomParseWithFormat));
90106

91107
result = parse(model.Value, CultureInfo.InvariantCulture);
108+
logger.LogDebug("DefaultResolver {input} resolved to {result}", model.Value, result);
92109
return true;
93110
}
94111
else if (amountOfParams == 1)
95112
{
96113
var parse = (CustomParse)parseMethod.CreateDelegate(typeof(CustomParse));
97114

98115
result = parse(model.Value);
116+
logger.LogDebug("DefaultResolver {input} resolved to {result}", model.Value, result);
99117
return true;
100118
}
101119
}
102120

121+
logger.LogDebug("DefaultResolver unable to resolve {input}", model.Value);
103122
result = default;
104123
return false;
105124
}

CommandLineParser/Core/Parsing/Resolvers/DoubleResolver.cs

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public override double Resolve(ArgumentModel model)
2626

2727
private bool TryResolve(ArgumentModel model, out double result)
2828
{
29+
if (model is null)
30+
{
31+
result = 0;
32+
return false;
33+
}
34+
2935
if (!model.HasValue)
3036
{
3137
result = 0;

CommandLineParser/Core/Parsing/Resolvers/IntResolver.cs

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public override int Resolve(ArgumentModel model)
2424

2525
private bool TryResolve(ArgumentModel model, out int result)
2626
{
27+
if (model is null)
28+
{
29+
result = 0;
30+
return false;
31+
}
32+
2733
if (!model.HasValue)
2834
{
2935
result = 0;

CommandLineParser/Core/Parsing/Resolvers/StringResolver.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ namespace MatthiWare.CommandLine.Core.Parsing.Resolvers
55
{
66
internal class StringResolver : BaseArgumentResolver<string>
77
{
8-
public override bool CanResolve(ArgumentModel model) => model.HasValue;
8+
public override bool CanResolve(ArgumentModel model) => model != null && model.HasValue;
99

10-
public override string Resolve(ArgumentModel model) => model.HasValue ? model.Value : null;
10+
public override string Resolve(ArgumentModel model) => model != null && model.HasValue ? model.Value : null;
1111
}
1212
}

0 commit comments

Comments
 (0)