/
YesNo.cs
243 lines (212 loc) · 8.87 KB
/
YesNo.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
#pragma warning disable S1210
// "Equals" and the comparison operators should be overridden when implementing "IComparable"
// See README.md => Sortable
using Qowaiv.Globalization;
namespace Qowaiv;
/// <summary>Represents a yes-no.</summary>
/// <remarks>
/// A yes-no is a (bi-)polar that obviously has the values "yes" and "no". It also
/// has an "empty"(unset) and "unknown" value.It maps easily with a <see cref="bool"/>, but
/// Supports all kind of formatting(and both empty and unknown) that can not be
/// achieved when modeling a property as <see cref="bool"/> instead of an <see cref="YesNo"/>.
/// </remarks>
[DebuggerDisplay("{DebuggerDisplay}")]
[Serializable]
[SingleValueObject(SingleValueStaticOptions.All, typeof(byte))]
[OpenApiDataType(description: "Yes-No notation.", example: "yes", type: "string", format: "yes-no", nullable: true, @enum: "yes,no,?")]
[TypeConverter(typeof(YesNoTypeConverter))]
#if NET6_0_OR_GREATER
[System.Text.Json.Serialization.JsonConverter(typeof(Json.YesNoJsonConverter))]
#endif
public readonly partial struct YesNo : IXmlSerializable, IFormattable, IEquatable<YesNo>, IComparable, IComparable<YesNo>
{
/// <summary>Represents an unknown (but set) yes-no.</summary>
public static YesNo No => new(1);
/// <summary>Represents an unknown (but set) yes-no.</summary>
public static YesNo Yes => new(2);
/// <summary>Represents an unknown (but set) yes-no.</summary>
public static YesNo Unknown => new(3);
/// <summary>Contains yes and no.</summary>
public static readonly IReadOnlyCollection<YesNo> YesAndNo = [Yes, No];
/// <summary>Returns true if the yes-no value represents no, otherwise false.</summary>
[Pure]
public bool IsNo() => m_Value == No.m_Value;
/// <summary>Returns true if the yes-no value represents yes, otherwise false.</summary>
[Pure]
public bool IsYes() => m_Value == Yes.m_Value;
/// <summary>Returns true if the yes-no value represents yes or no.</summary>
[Pure]
public bool IsYesOrNo() => IsYes() || IsNo();
/// <summary>Deserializes the gender from a JSON number.</summary>
/// <param name="json">
/// The JSON number to deserialize.
/// </param>
/// <returns>
/// The deserialized gender.
/// </returns>
[Pure]
public static YesNo FromJson(double json) => FromJson<double>((int)json);
/// <summary>Deserializes the yes-no from a JSON number.</summary>
/// <param name="json">
/// The JSON number to deserialize.
/// </param>
/// <returns>
/// The deserialized yes-no.
/// </returns>
[Pure]
public static YesNo FromJson(long json) => FromJson<long>(json);
/// <summary>Deserializes the yes-no from a JSON boolean.</summary>
/// <param name="json">
/// The JSON boolean to deserialize.
/// </param>
/// <returns>
/// The deserialized yes-no.
/// </returns>
[Pure]
public static YesNo FromJson(bool json) => json ? Yes : No;
[Pure]
private static YesNo FromJson<TFrom>(long val)
=> val switch
{
0 => No,
1 => Yes,
byte.MaxValue => Unknown,
short.MaxValue => Unknown,
int.MaxValue => Unknown,
long.MaxValue => Unknown,
_ => throw Exceptions.InvalidCast<TFrom, YesNo>(),
};
/// <summary>Serializes the yes-no to a JSON node.</summary>
/// <returns>
/// The serialized JSON string.
/// </returns>
[Pure]
public string? ToJson() => SerializationValues[m_Value];
/// <summary>Returns a <see cref="string"/> that represents the current yes-no for debug purposes.</summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => this.DebuggerDisplay("{0:f}");
/// <summary>Returns a formatted <see cref="string"/> that represents the current yes-no.</summary>
/// <param name="format">
/// The format that describes the formatting.
/// </param>
/// <param name="formatProvider">
/// The format provider.
/// </param>
/// <remarks>
/// The formats:
///
/// i: as integer (note, unknown is a question mark sign).
/// c/C: as single character (y/n/?) (Upper cased).
/// f/F: as formatted string (Title cased).
/// b/B: as boolean (true/false) (Title cased).
/// </remarks>
[Pure]
public string ToString(string? format, IFormatProvider? formatProvider)
=> StringFormatter.TryApplyCustomFormatter(format, this, formatProvider, out string formatted)
? formatted
: StringFormatter.Apply(this, format.WithDefault("f"), formatProvider, FormatTokens);
/// <summary>The format token instructions.</summary>
private static readonly Dictionary<char, Func<YesNo, IFormatProvider, string>> FormatTokens = new()
{
{ 'c', (svo, provider) => svo.GetResourceString("ch_", provider) },
{ 'C', (svo, provider) => svo.GetResourceString("ch_", provider).ToUpper(provider) },
{ 'i', (svo, provider) => svo.GetResourceString("int_", provider) },
{ 'f', (svo, provider) => svo.GetResourceString("f_", provider) },
{ 'F', (svo, provider) => svo.GetResourceString("f_", provider).ToTitleCase(provider) },
{ 'b', (svo, provider) => svo.GetResourceString("b_", provider) },
{ 'B', (svo, provider) => svo.GetResourceString("b_", provider).ToTitleCase(provider) },
};
/// <summary>Gets an XML string representation of the yes-no.</summary>
[Pure]
private string ToXmlString() => ToString(CultureInfo.InvariantCulture);
/// <summary>Casts a yes-no to a nullable <see cref="bool"/>.</summary>
public static explicit operator bool?(YesNo val) => BooleanValues[val.m_Value];
/// <summary>Casts a yes-no to a <see cref="bool"/>.</summary>
public static implicit operator bool(YesNo val) => val.IsYes();
/// <summary>Casts a nullable <see cref="bool"/> to a yes-no.</summary>
public static explicit operator YesNo(bool? val)
{
if (val.HasValue)
{
return val.Value ? Yes : No;
}
else return Empty;
}
/// <summary>Casts a <see cref="bool"/> to a yes-no.</summary>
public static explicit operator YesNo(bool val) => val ? Yes : No;
private static readonly bool?[] BooleanValues = [null, false, true, null];
/// <summary>Converts the string to a yes-no.
/// A return value indicates whether the conversion succeeded.
/// </summary>
/// <param name="s">
/// A string containing a yes-no to convert.
/// </param>
/// <param name="provider">
/// The specified format provider.
/// </param>
/// <param name="result">
/// The result of the parsing.
/// </param>
/// <returns>
/// True if the string was converted successfully, otherwise false.
/// </returns>
public static bool TryParse(string? s, IFormatProvider? provider, out YesNo result)
{
result = Empty;
var str = s.Unify();
if (str.IsEmpty())
{
return true;
}
else if (str.IsUnknown(provider))
{
result = Unknown;
return true;
}
else if (ParseValues.TryGetValue(provider, str, out var val))
{
result = new YesNo(val);
return true;
}
else return false;
}
private static readonly YesNoValues ParseValues = new();
/// <summary>Gets the yes-no labels.</summary>
/// <remarks>
/// Used for both serialization and resource lookups.
/// </remarks>
private static readonly string?[] LookupSuffix = [null, "No", "Yes", "Unknown"];
private static readonly string?[] SerializationValues = [null, "no", "yes", "?"];
private sealed class YesNoValues : LocalizedValues<byte>
{
public YesNoValues() : base(new()
{
[string.Empty] = 0,
["0"] = 1, ["N"] = 1, ["NO"] = 1, ["FALSE"] = 1,
["1"] = 2, ["Y"] = 2, ["YES"] = 2, ["TRUE"] = 2,
}) { }
protected override void AddCulture(CultureInfo culture)
{
foreach (var value in YesAndNo)
{
var label = value.ToString(string.Empty, culture).Unify();
var @char = value.ToString("c", culture).Unify();
var @bool = value.ToString("b", culture).Unify();
this[culture][label] = value.m_Value;
this[culture][@char] = value.m_Value;
this[culture][@bool] = value.m_Value;
}
}
}
private static readonly ResourceManager ResourceManager = new("Qowaiv.YesNoLabels", typeof(YesNo).Assembly);
/// <summary>Get resource string.</summary>
/// <param name="prefix">
/// The prefix of the resource key.
/// </param>
/// <param name="formatProvider">
/// The format provider.
/// </param>
[Pure]
private string GetResourceString(string prefix, IFormatProvider formatProvider)
=> ResourceManager.Localized(formatProvider, prefix, LookupSuffix[m_Value]);
}