Skip to content

Commit 270d6cc

Browse files
committed
Fixed defaults not working
1 parent 0baf8d7 commit 270d6cc

10 files changed

Lines changed: 124 additions & 83 deletions

File tree

CommandLineParser.Tests/CommandLineParserTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ public void ParseWithDefaults(string[] args, string result1, string result2, str
4242
.Default(result1)
4343
.Required();
4444

45-
parser.Configure(opt => opt.Option1)
45+
parser.Configure(opt => opt.Option2)
4646
.ShortName("-2")
4747
.Default(result2)
4848
.Required();
4949

50-
parser.Configure(opt => opt.Option1)
50+
parser.Configure(opt => opt.Option3)
5151
.ShortName("-3")
5252
.Default(result3)
5353
.Required();

CommandLineParser/Abstractions/Command/ICommandLineCommandParser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ namespace MatthiWare.CommandLine.Abstractions.Command
99
public interface ICommandLineCommandParser
1010
{
1111
IReadOnlyList<ICommandLineOption> Options { get; }
12-
ICommandParserResult Parse(List<string> args, int startIndex);
12+
ICommandParserResult Parse(IArgumentManager argumentManager);
1313
}
1414
}

CommandLineParser/Abstractions/Parsing/IArgumentGrouper.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using MatthiWare.CommandLine.Abstractions.Command;
5+
using MatthiWare.CommandLine.Abstractions.Models;
6+
7+
namespace MatthiWare.CommandLine.Abstractions.Parsing
8+
{
9+
public interface IArgumentManager
10+
{
11+
bool TryGetValue(ICommandLineCommand cmd, out ArgumentModel model);
12+
}
13+
}

CommandLineParser/CommandLineParser.cs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,22 @@ private IOptionBuilder<TProperty> ConfigureInternal<TProperty>(Expression<Func<T
5252

5353
public IParserResult<TSource> Parse(string[] args)
5454
{
55-
var lstArgs = new List<string>(args);
5655
var errors = new List<Exception>();
5756

5857
var result = new ParseResult<TSource>();
5958

59+
var argumentManager = new ArgumentManager(args, m_commands, m_options);
60+
6061
foreach (var cmd in m_commands)
6162
{
62-
63-
64-
if (idx < 0 || idx > lstArgs.Count)
63+
if (!argumentManager.TryGetValue(cmd, out ArgumentModel model) && cmd.IsRequired)
6564
{
66-
if (cmd.IsRequired)
67-
errors.Add(new CommandNotFoundException(cmd));
65+
errors.Add(new CommandNotFoundException(cmd));
6866

6967
continue;
7068
}
7169

72-
var cmdParseResult = cmd.Parse(lstArgs, idx);
70+
var cmdParseResult = cmd.Parse(argumentManager);
7371

7472
if (cmdParseResult.HasErrors)
7573
errors.Add(new CommandParseException(cmd, cmdParseResult.Error));
@@ -79,31 +77,31 @@ public IParserResult<TSource> Parse(string[] args)
7977

8078
foreach (var option in m_options)
8179
{
82-
83-
84-
if (idx < 0 || idx > lstArgs.Count)
80+
if (!argumentManager.TryGetValue(option, out ArgumentModel model) && option.IsRequired)
8581
{
86-
if (option.IsRequired)
87-
errors.Add(new OptionNotFoundException(option));
82+
errors.Add(new OptionNotFoundException(option));
8883

8984
continue;
9085
}
86+
else if (!model.HasValue && option.HasDefault)
87+
{
88+
option.UseDefault();
9189

92-
var key = lstArgs.GetAndRemove(idx);
93-
var value = lstArgs.GetAndRemove(idx);
94-
95-
var argModel = new ArgumentModel(key, value);
96-
97-
if (!option.CanParse(argModel))
90+
continue;
91+
}
92+
else if (!option.CanParse(model))
9893
{
99-
errors.Add(new OptionParseException(option, argModel));
94+
errors.Add(new OptionParseException(option, model));
10095

10196
continue;
10297
}
10398

104-
option.Parse(argModel);
99+
option.Parse(model);
105100
}
106101

102+
if (errors.Any())
103+
result.MergeResult(errors);
104+
107105
result.MergeResult(m_option);
108106

109107
return result;

CommandLineParser/Core/Command/CommandLineCommand.cs

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using MatthiWare.CommandLine.Abstractions.Models;
88
using MatthiWare.CommandLine.Abstractions.Parsing;
99
using MatthiWare.CommandLine.Abstractions.Parsing.Command;
10+
using MatthiWare.CommandLine.Core.Exceptions;
1011

1112
namespace MatthiWare.CommandLine.Core.Command
1213
{
@@ -28,47 +29,40 @@ public IOptionBuilder<TProperty> Configure<TProperty>(Expression<Func<TSource, T
2829
{
2930
var option = new CommandLineOption<TSource, TProperty>(source, selector, resolverFactory.CreateResolver<TProperty>());
3031

31-
options.Add(option);
32+
m_options.Add(option);
3233

3334
return option;
3435
}
3536

3637
public override void Execute() => execution(source);
3738

38-
public override ICommandParserResult Parse(List<string> lstArgs, int startIndex)
39+
public override ICommandParserResult Parse(IArgumentManager argumentManager)
3940
{
4041
var result = new CommandParserResult(this);
4142
var errors = new List<Exception>();
4243

43-
foreach (var option in Options)
44+
foreach (var option in m_options)
4445
{
45-
int idx = lstArgs.FindIndex(startIndex, arg =>
46-
(option.HasShortName && string.Equals(option.ShortName, arg, StringComparison.InvariantCultureIgnoreCase)) ||
47-
(option.HasLongName && string.Equals(option.LongName, arg, StringComparison.InvariantCultureIgnoreCase)));
48-
49-
if (idx < 0 || idx > lstArgs.Count)
46+
if (!argumentManager.TryGetValue(option, out ArgumentModel model) && option.IsRequired)
5047
{
51-
if (option.IsRequired)
52-
errors.Add(new KeyNotFoundException($"Required argument '{option.HasShortName}' or '{option.LongName}' not found!"));
48+
errors.Add(new OptionNotFoundException(option));
5349

5450
continue;
5551
}
52+
else if (!model.HasValue && option.HasDefault)
53+
{
54+
option.UseDefault();
5655

57-
var key = lstArgs.GetAndRemove(idx);
58-
var value = lstArgs.GetAndRemove(idx);
59-
60-
var argModel = new ArgumentModel(key, value);
61-
62-
var parser = option as IParser;
63-
64-
if (!parser.CanParse(argModel))
56+
continue;
57+
}
58+
else if (!option.CanParse(model))
6559
{
66-
errors.Add(new ArgumentException($"Cannot parse option '{argModel.Key}:{argModel.Value ?? "NULL"}'."));
60+
errors.Add(new OptionParseException(option, model));
6761

6862
continue;
6963
}
7064

71-
parser.Parse(argModel);
65+
option.Parse(model);
7266
}
7367

7468
result.MergeResult(errors);

CommandLineParser/Core/Command/CommandLineCommandBase.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Text;
44
using MatthiWare.CommandLine.Abstractions;
55
using MatthiWare.CommandLine.Abstractions.Command;
6+
using MatthiWare.CommandLine.Abstractions.Parsing;
67
using MatthiWare.CommandLine.Abstractions.Parsing.Command;
78

89
namespace MatthiWare.CommandLine.Core.Command
@@ -11,9 +12,9 @@ internal abstract class CommandLineCommandBase :
1112
ICommandLineCommandParser,
1213
ICommandLineCommand
1314
{
14-
protected readonly List<ICommandLineOption> options = new List<ICommandLineOption>();
15+
protected readonly List<CommandLineOptionBase> m_options = new List<CommandLineOptionBase>();
1516

16-
public IReadOnlyList<ICommandLineOption> Options => options.AsReadOnly();
17+
public IReadOnlyList<ICommandLineOption> Options => m_options.AsReadOnly();
1718

1819
public string ShortName { get; protected set; }
1920
public string LongName { get; protected set; }
@@ -25,6 +26,6 @@ internal abstract class CommandLineCommandBase :
2526

2627
public abstract void Execute();
2728

28-
public abstract ICommandParserResult Parse(List<string> args, int startIndex);
29+
public abstract ICommandParserResult Parse(IArgumentManager argumentManager);
2930
}
3031
}

CommandLineParser/Core/CommandLineOption.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public TProperty DefaultValue
3434
}
3535
}
3636

37-
37+
public override void UseDefault()
38+
=> AssignValue(DefaultValue);
3839

3940
public override bool CanParse(ArgumentModel model)
4041
=> resolver.CanResolve(model);

CommandLineParser/Core/CommandLineOptionBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ internal abstract class CommandLineOptionBase : IParser, ICommandLineOption
2222
public abstract bool CanParse(ArgumentModel model);
2323

2424
public abstract void Parse(ArgumentModel model);
25+
26+
public abstract void UseDefault();
2527
}
2628
}
Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,112 @@
1-
using MatthiWare.CommandLine.Abstractions.Command;
2-
using MatthiWare.CommandLine.Abstractions.Parsing;
3-
using MatthiWare.CommandLine.Core.Command;
4-
using System;
5-
using System.Linq;
1+
using System;
62
using System.Collections.Generic;
3+
using System.Linq;
74
using System.Text;
85
using MatthiWare.CommandLine.Abstractions;
6+
using MatthiWare.CommandLine.Abstractions.Command;
7+
using MatthiWare.CommandLine.Abstractions.Models;
8+
using MatthiWare.CommandLine.Abstractions.Parsing;
9+
using MatthiWare.CommandLine.Core.Command;
910

1011
namespace MatthiWare.CommandLine.Core.Parsing
1112
{
12-
internal class ArgumentManager : IArgumentGrouper, IDisposable
13+
internal class ArgumentManager : IArgumentManager, IDisposable
1314
{
14-
private IDictionary<ICommandLineCommand, string> arguments;
15+
private readonly IDictionary<ICommandLineCommand, ArgumentModel> arguments;
16+
private readonly List<ArgumentValueHolder> args;
1517

16-
public ArgumentManager(string[] args, List<CommandLineCommandBase> commands, List<CommandLineOptionBase> options)
18+
public ArgumentManager(string[] args, ICollection<CommandLineCommandBase> commands, ICollection<CommandLineOptionBase> options)
1719
{
18-
arguments = new Dictionary<ICommandLineCommand, string>(commands.Count + options.Count);
20+
arguments = new Dictionary<ICommandLineCommand, ArgumentModel>(commands.Count + options.Count);
1921

20-
var lstArgs = new List<ArgumentValueHolder>(args.Select(arg => new ArgumentValueHolder
22+
this.args = new List<ArgumentValueHolder>(args.Select(arg => new ArgumentValueHolder
2123
{
2224
Argument = arg,
2325
Used = false
2426
}));
2527

26-
Parse(lstArgs, commands);
28+
ParseCommands(commands);
2729

28-
Parse(lstArgs, options);
30+
Parse(options);
2931

30-
foreach (var item in lstArgs)
32+
foreach (var item in this.args)
3133
{
32-
if (item.Used) continue;
34+
if (item.Command == null) continue;
35+
36+
int nextIndex = item.Index + 1;
37+
38+
var argValue = nextIndex < this.args.Count ? this.args[nextIndex] : null;
3339

34-
item.Used = true;
40+
var argModel = new ArgumentModel
41+
{
42+
Key = item.Argument,
43+
Value = (argValue?.Used ?? true) ? null : argValue.Argument
44+
};
3545

36-
arguments.Add(item.Command, item.Argument);
46+
arguments.Add(item.Command, argModel);
3747
}
3848
}
3949

40-
private void Parse(List<ArgumentValueHolder> args, IEnumerable<ICommandLineCommand> list)
50+
private void Parse(IEnumerable<ICommandLineCommand> list)
4151
{
4252
foreach (var item in list)
4353
{
44-
int idx = args.FindIndex(arg => !arg.Used &&
45-
((item.HasShortName && string.Equals(item.ShortName, arg.Argument, StringComparison.InvariantCultureIgnoreCase)) ||
46-
(item.HasLongName && string.Equals(item.LongName, arg.Argument, StringComparison.InvariantCultureIgnoreCase))));
54+
int idx = FindIndex(item);
55+
56+
SetArgumentUsed(idx, item);
57+
}
58+
}
59+
60+
private void ParseCommands(IEnumerable<CommandLineCommandBase> cmds)
61+
{
62+
foreach (var cmd in cmds)
63+
{
64+
int idx = FindIndex(cmd);
65+
66+
SetArgumentUsed(idx, cmd);
4767

48-
args[idx].Used = true;
49-
args[idx].Command = item;
68+
foreach (var option in cmd.Options)
69+
{
70+
// find the option index starting at the command index
71+
int optionIdx = FindIndex(option, idx);
72+
73+
SetArgumentUsed(optionIdx, option);
74+
}
5075
}
5176
}
5277

78+
private void SetArgumentUsed(int idx, ICommandLineCommand cmd)
79+
{
80+
args[idx].Used = true;
81+
args[idx].Command = cmd;
82+
args[idx].Index = idx;
83+
}
84+
85+
/// <summary>
86+
/// Finds the index of the first unused argument
87+
/// </summary>
88+
/// <param name="args">List of arguments to search</param>
89+
/// <param name="cmd">Option to find</param>
90+
/// <param name="startOffset">Search offset</param>
91+
/// <returns></returns>
92+
private int FindIndex(ICommandLineCommand cmd, int startOffset = 0)
93+
=> args.FindIndex(startOffset, arg => !arg.Used &&
94+
((cmd.HasShortName && string.Equals(cmd.ShortName, arg.Argument, StringComparison.InvariantCultureIgnoreCase)) ||
95+
(cmd.HasLongName && string.Equals(cmd.LongName, arg.Argument, StringComparison.InvariantCultureIgnoreCase))));
96+
5397
public void Dispose()
5498
{
5599
arguments.Clear();
56100
}
57101

58-
public bool TryGetValue(ICommandLineCommand cmd, out string value) => arguments.TryGetValue(cmd, out value);
102+
public bool TryGetValue(ICommandLineCommand cmd, out ArgumentModel model) => arguments.TryGetValue(cmd, out model);
59103

60104
private class ArgumentValueHolder
61105
{
62106
public string Argument { get; set; }
63107
public bool Used { get; set; }
64108
public ICommandLineCommand Command { get; set; }
109+
public int Index { get; set; }
65110
}
66111
}
67112
}

0 commit comments

Comments
 (0)