-
Notifications
You must be signed in to change notification settings - Fork 11
/
Parser.cs
157 lines (141 loc) · 4.66 KB
/
Parser.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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FrEee.Utility;
using FrEee.Extensions;
using FrEee.Extensions;
namespace FrEee.Extensions;
/// <summary>
/// Parses various things from strings.
/// </summary>
public static class Parser
{
private static readonly MethodInfo enumParser = typeof(Parser).GetMethods().Single(m => m.Name == "ParseEnum" && m.ContainsGenericParameters);
private static SafeDictionary<Type, IDictionary<MemberInfo, object>> enumMemberCache = new SafeDictionary<Type, IDictionary<MemberInfo, object>>();
private static SafeDictionary<Type, SafeDictionary<string, object>> enumValues = new SafeDictionary<Type, SafeDictionary<string, object>>(true);
/// <summary>
/// Parses a string as an enum, using custom names.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="s"></param>
/// <returns></returns>
public static T ParseEnum<T>(this string s)
{
if (typeof(T).HasAttribute<FlagsAttribute>())
return s.ParseFlagsEnum<T>();
else
return s.ParseSingleEnum<T>();
}
/// <summary>
/// Parses a string as an enum, using custom names.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static object ParseEnum(this string s, Type type)
{
var v = enumValues[type][s];
if (v == null)
{
var parser = enumParser.MakeGenericMethod(type);
v = enumValues[type][s] = parser.Invoke(null, new object[] { s.Trim() });
}
return v;
}
/// <summary>
/// The inverse of ToUnitString. Parses a number with units.
/// </summary>
/// <param name="s"></param>
/// <returns>The parsed number, or null if the string could not be parsed.</returns>
/// <param name="allowMilli">Should lowercase m be treated as milli or mega?</param>
public static double? ParseUnits(this string s, bool allowMilli = false)
{
var last = s.Last();
if (char.IsNumber(last))
return double.Parse(s); // no unit
s = s.Substring(0, s.Length - 1);
double num;
if (!double.TryParse(s, out num))
return null; // can't parse the number
if (last == 'k' || last == 'K')
return num * 1e3;
if (last == 'M')
return num * 1e6;
if (last == 'G' || last == 'B' || last == 'g' || last == 'b') // giga or billions
return num * 1e9;
if (last == 'T' || last == 't')
return num * 1e12;
if (last == 'm')
return num * (allowMilli ? 1e-3 : 1e6); // treat as mega if milli isn't allowed
return null; // can't parse the units
}
private static IDictionary<MemberInfo, object> GetEnumValues<T>()
{
var names = Enum.GetNames(typeof(T));
var mems = names.SelectMany(n => typeof(T).GetMember(n));
var dict = new Dictionary<MemberInfo, object>();
foreach (var mem in mems)
dict.Add(mem, (T)Enum.Parse(typeof(T), mem.Name));
return dict;
}
/// <summary>
/// Parses a string as a flags enum.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="s"></param>
/// <returns></returns>
private static T ParseFlagsEnum<T>(this string s)
{
var spl = s.Split(',', '\\', '/');
return spl.ParseFlagsEnum<T>();
}
/// <summary>
/// Parses some strings as flags enum values.
/// If the type is not a flags enum, just par
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="ss"></param>
/// <returns></returns>
private static T ParseFlagsEnum<T>(this IEnumerable<string> ss)
{
if (!(typeof(T).IsEnum && typeof(T).HasAttribute<FlagsAttribute>()))
throw new InvalidCastException("{0} is not a flags enum type.".F(typeof(T)));
// TODO - non-integer enums
int result = 0;
var dict = GetEnumValues<T>();
foreach (var x in ss.Select(s => s.ParseSingleEnum<T>()))
{
// why can't I cast x to int?
var n = Enum.GetName(typeof(T), x);
var i = (int)Enum.Parse(typeof(T), n);
result |= i;
}
return (T)(object)result;
}
private static T ParseSingleEnum<T>(this string s)
{
s = s.Trim();
if (!typeof(T).IsEnum)
throw new InvalidCastException("{0} is not an enum type.".F(typeof(T)));
if (enumMemberCache[typeof(T)] == null)
enumMemberCache[typeof(T)] = GetEnumValues<T>();
var dict = enumMemberCache[typeof(T)];
var mems = dict.Keys;
var matches = mems.Where(m => m.GetNames().Contains(s)).ToArray();
MemberInfo match;
if (matches.Count() == 1)
match = matches.Single();
else if (matches.Count() == 0)
throw new ArgumentException("{0} is not a valid value or alias for enum type {1}.".F(s, typeof(T)));
else
{
// find by canonical name
var canonicalMatches = matches.Where(m => m.GetCanonicalName() == s);
if (canonicalMatches.Count() == 1)
match = canonicalMatches.Single();
else
throw new ArgumentException("{0} is an ambiguous match for {1}.".F(s, typeof(T)));
}
return (T)dict[match];
}
}