-
Notifications
You must be signed in to change notification settings - Fork 0
/
OptionMetadata.cs
250 lines (209 loc) · 9.39 KB
/
OptionMetadata.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using TokenReader = CShargs.ListReader<string>;
namespace CShargs
{
internal abstract class OptionMetadata
{
protected readonly ParserMetadata parserMeta_;
protected IOptionAttribute attribute_ { get; init; }
protected MemberInfo member_ { get; init; }
public string LongName => attribute_.LongName;
public bool Required => attribute_.Required;
public OptionMetadata UseWith { get; set; }
public string UseWithName => attribute_.UseWith;
public char ShortName => attribute_.ShortName;
public string HelpText => attribute_.HelpText;
public OptionMetadata(ParserMetadata parserMeta, MemberInfo member, IOptionAttribute attribute)
{
ThrowIf.ArgumentNull(nameof(parserMeta), parserMeta);
ThrowIf.ArgumentNull(nameof(attribute), attribute);
member_ = member;
attribute_ = attribute;
parserMeta_ = parserMeta;
}
public abstract void Parse(Parser parser, string value, TokenReader tokens);
protected abstract void SetValue(object instance, object value);
public string GetName() => GetName(out _);
public virtual string GetName(out bool shortGiven, bool preferShort = true)
{
if ((preferShort || LongName == null) && ShortName != '\0') {
shortGiven = true;
return ShortName.ToString();
}
shortGiven = false;
return LongName;
}
public string GetRawName() => GetRawName(out _);
public virtual string GetRawName(out bool shortGiven, bool preferShort = true)
{
string name = GetName(out shortGiven, preferShort);
return (shortGiven ? parserMeta_.Config.ShortOptionSymbol : parserMeta_.Config.LongOptionSymbol) + name;
}
public string GetRawNameWithMetavar() => GetRawNameWithMetavar(out _);
public virtual string GetRawNameWithMetavar(out bool shortGiven, bool preferShort = true)
{
return GetRawName(out shortGiven, preferShort);
}
public IEnumerable<IRule> ExtractRules()
{
var rules = new List<IRule>();
if (Required) {
rules.Add(new RequiredRule(this));
}
if (UseWith != null) {
rules.Add(new DependencyRule(this));
}
return rules;
}
}
internal sealed class FlagOption : OptionMetadata
{
public PropertyInfo Property => (PropertyInfo)member_;
public FlagOption(ParserMetadata parserMeta, PropertyInfo prop, FlagOptionAttribute attribute)
: base(parserMeta, prop, attribute) { }
public override void Parse(Parser userParser, string value, TokenReader reader)
{
SetValue(userParser, true);
}
protected override void SetValue(object instance, object value)
=> Property.SetValue(instance, value);
}
internal sealed class ValueOption : OptionMetadata
{
public PropertyInfo Property => (PropertyInfo)member_;
private Delegate staticParse_;
private new ValueOptionAttribute attribute_ => (ValueOptionAttribute)base.attribute_;
public string MetaVar => attribute_.MetaVar ?? Property.Name.ToUpper();
public ValueOption(ParserMetadata parserMeta, PropertyInfo prop, ValueOptionAttribute attribute)
: base(parserMeta, prop, attribute) { }
public override void Parse(Parser parser, string valueStr, TokenReader tokens)
{
if (valueStr == null) {
if (tokens.EndOfList) {
throw new MissingOptionValueException(tokens.Peek(-1));
}
valueStr = tokens.Read();
}
object value;
if (Property.PropertyType == typeof(string)) {
value = valueStr;
} else {
// ex: int.Parse(...)
value = InvokeStaticParseMethod(valueStr);
}
SetValue(parser, value);
}
protected override void SetValue(object userObject, object value)
=> Property.SetValue(userObject, value);
public override string GetRawNameWithMetavar(out bool shortGiven, bool preferShort = true)
{
string name = base.GetRawNameWithMetavar(out shortGiven, preferShort);
if (shortGiven) {
if (!parserMeta_.Config.OptionFlags.HasFlag(OptionFlags.ForbidShortEquals)) {
name += parserMeta_.Config.EqualsSymbol + MetaVar;
} else if (!parserMeta_.Config.OptionFlags.HasFlag(OptionFlags.ForbidShortSpace)) {
name += ' ' + MetaVar;
} else if (!parserMeta_.Config.OptionFlags.HasFlag(OptionFlags.ForbidShortNoSpace)) {
name += MetaVar;
}
return name;
} else {
if (!parserMeta_.Config.OptionFlags.HasFlag(OptionFlags.ForbidLongEquals)) {
name += parserMeta_.Config.EqualsSymbol + MetaVar;
} else if (!parserMeta_.Config.OptionFlags.HasFlag(OptionFlags.ForbidLongSpace)) {
name += ' ' + MetaVar;
}
return name;
}
}
private object InvokeStaticParseMethod(string value)
{
if (staticParse_ == null) {
if (member_.MemberType != MemberTypes.Property) {
throw new InvalidOperationException("Member must be a property");
}
var propType = Property.PropertyType;
IEnumerable<MethodInfo> methods;
if (Property.PropertyType.IsEnum) {
try {
return Enum.Parse(Property.PropertyType, value, parserMeta_.Config.OptionFlags.HasFlag(OptionFlags.EnumCaseInsensitive));
} catch (ArgumentException ex) {
throw new FormatException(ex.Message);
}
} else {
var allMethods = propType.GetMethods(BindingFlags.Public | BindingFlags.Static);
var parseMethods = allMethods.Where(m => m.Name == "Parse");
var typedMethods = parseMethods.Where(m => m.ReturnType == propType);
methods = typedMethods.Where(m => {
var param = m.GetParameters().ToArray();
return param.Length == 1 && param[0].ParameterType == typeof(string);
});
}
if (!methods.Any()) {
throw new ConfigurationException(
$"Option {LongName}: {propType} must have public static method Parse(string) which returns {propType}");
}
var ParseMethod = methods.First();
var delegateType = Expression.GetDelegateType(typeof(string), propType);
staticParse_ = Delegate.CreateDelegate(delegateType, ParseMethod);
}
try {
return staticParse_.DynamicInvoke(value);
} catch (TargetInvocationException ex) {
throw ex.InnerException;
}
}
}
internal class CustomOption : OptionMetadata
{
public MethodInfo Method => (MethodInfo)member_;
public CustomOption(ParserMetadata parserMeta, MethodInfo method, CustomOptionAttribute attribute)
: base(parserMeta, method, attribute) { }
public override void Parse(Parser parser, string value, TokenReader tokens)
=> SetValue(parser, tokens.Peek(-1));
protected override void SetValue(object instance, object value)
{
try {
Method.Invoke(instance, new[] { value });
} catch (TargetInvocationException e) {
throw e.InnerException;
}
}
}
internal class VerbOption : OptionMetadata
{
public PropertyInfo Property => (PropertyInfo)member_;
public VerbOption(ParserMetadata parserMeta, PropertyInfo prop, VerbOptionAttribute attribute)
: base(parserMeta, prop, attribute) { }
public override void Parse(Parser parser, string value, TokenReader tokens)
{
var subparser = (Parser)Activator.CreateInstance(Property.PropertyType);
string[] rest = tokens.ReadToEnd().ToArray();
subparser.Parse(rest);
SetValue(parser, subparser);
}
protected override void SetValue(object instance, object value)
=> Property.SetValue(instance, value);
}
internal class AliasOption : OptionMetadata
{
public IEnumerable<OptionMetadata> Targets { get; private set; }
public AliasOption(ParserMetadata parserMeta, AliasOptionAttribute attribute, IEnumerable<OptionMetadata> targets)
: base(parserMeta, null, attribute)
{
Targets = targets.ToArray();
}
public override void Parse(Parser parser, string value, TokenReader tokens)
{
foreach (var target in Targets) {
parser.ParseOption(target, null);
}
}
protected override void SetValue(object instance, object value)
=> throw new InvalidOperationException();
}
}