1 #region Copyright & License Information 2 /* 3 * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) 4 * This file is part of OpenRA, which is free software. It is made 5 * available to you under the terms of the GNU General Public License 6 * as published by the Free Software Foundation, either version 3 of 7 * the License, or (at your option) any later version. For more 8 * information, see COPYING. 9 */ 10 #endregion 11 12 using System; 13 using System.Collections.Generic; 14 using System.ComponentModel; 15 using System.Globalization; 16 using System.IO; 17 using System.Linq; 18 using System.Reflection; 19 using System.Runtime.Serialization; 20 using System.Text.RegularExpressions; 21 using OpenRA.Graphics; 22 using OpenRA.Primitives; 23 using OpenRA.Support; 24 25 namespace OpenRA 26 { 27 public static class FieldLoader 28 { 29 [Serializable] 30 public class MissingFieldsException : YamlException 31 { 32 public readonly string[] Missing; 33 public readonly string Header; 34 public override string Message 35 { 36 get 37 { 38 return (string.IsNullOrEmpty(Header) ? "" : Header + ": ") + Missing[0] 39 + string.Concat(Missing.Skip(1).Select(m => ", " + m)); 40 } 41 } 42 MissingFieldsException(string[] missing, string header = null, string headerSingle = null)43 public MissingFieldsException(string[] missing, string header = null, string headerSingle = null) 44 : base(null) 45 { 46 Header = missing.Length > 1 ? header : headerSingle ?? header; 47 Missing = missing; 48 } 49 GetObjectData(SerializationInfo info, StreamingContext context)50 public override void GetObjectData(SerializationInfo info, StreamingContext context) 51 { 52 base.GetObjectData(info, context); 53 info.AddValue("Missing", Missing); 54 info.AddValue("Header", Header); 55 } 56 } 57 58 public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) => 59 { 60 throw new YamlException("FieldLoader: Cannot parse `{0}` into `{1}.{2}` ".F(s, f, t)); 61 }; 62 63 public static Action<string, Type> UnknownFieldAction = (s, f) => 64 { 65 throw new NotImplementedException("FieldLoader: Missing field `{0}` on `{1}`".F(s, f.Name)); 66 }; 67 68 static readonly ConcurrentCache<Type, FieldLoadInfo[]> TypeLoadInfo = 69 new ConcurrentCache<Type, FieldLoadInfo[]>(BuildTypeLoadInfo); 70 static readonly ConcurrentCache<MemberInfo, bool> MemberHasTranslateAttribute = 71 new ConcurrentCache<MemberInfo, bool>(member => member.HasAttribute<TranslateAttribute>()); 72 73 static readonly ConcurrentCache<string, BooleanExpression> BooleanExpressionCache = 74 new ConcurrentCache<string, BooleanExpression>(expression => new BooleanExpression(expression)); 75 static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache = 76 new ConcurrentCache<string, IntegerExpression>(expression => new IntegerExpression(expression)); 77 78 static readonly object TranslationsLock = new object(); 79 static Dictionary<string, string> translations; 80 Load(object self, MiniYaml my)81 public static void Load(object self, MiniYaml my) 82 { 83 var loadInfo = TypeLoadInfo[self.GetType()]; 84 var missing = new List<string>(); 85 86 Dictionary<string, MiniYaml> md = null; 87 88 foreach (var fli in loadInfo) 89 { 90 object val; 91 92 if (md == null) 93 md = my.ToDictionary(); 94 if (fli.Loader != null) 95 { 96 if (!fli.Attribute.Required || md.ContainsKey(fli.YamlName)) 97 val = fli.Loader(my); 98 else 99 { 100 missing.Add(fli.YamlName); 101 continue; 102 } 103 } 104 else 105 { 106 if (!TryGetValueFromYaml(fli.YamlName, fli.Field, md, out val)) 107 { 108 if (fli.Attribute.Required) 109 missing.Add(fli.YamlName); 110 continue; 111 } 112 } 113 114 fli.Field.SetValue(self, val); 115 } 116 117 if (missing.Any()) 118 throw new MissingFieldsException(missing.ToArray()); 119 } 120 TryGetValueFromYaml(string yamlName, FieldInfo field, Dictionary<string, MiniYaml> md, out object ret)121 static bool TryGetValueFromYaml(string yamlName, FieldInfo field, Dictionary<string, MiniYaml> md, out object ret) 122 { 123 ret = null; 124 125 MiniYaml yaml; 126 if (!md.TryGetValue(yamlName, out yaml)) 127 return false; 128 129 ret = GetValue(field.Name, field.FieldType, yaml, field); 130 return true; 131 } 132 133 public static T Load<T>(MiniYaml y) where T : new() 134 { 135 var t = new T(); 136 Load(t, y); 137 return t; 138 } 139 LoadField(object target, string key, string value)140 public static void LoadField(object target, string key, string value) 141 { 142 const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; 143 144 key = key.Trim(); 145 146 var field = target.GetType().GetField(key, Flags); 147 if (field != null) 148 { 149 var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First(); 150 if (!sa.FromYamlKey) 151 field.SetValue(target, GetValue(field.Name, field.FieldType, value, field)); 152 return; 153 } 154 155 var prop = target.GetType().GetProperty(key, Flags); 156 if (prop != null) 157 { 158 var sa = prop.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First(); 159 if (!sa.FromYamlKey) 160 prop.SetValue(target, GetValue(prop.Name, prop.PropertyType, value, prop), null); 161 return; 162 } 163 164 UnknownFieldAction(key, target.GetType()); 165 } 166 GetValue(string field, string value)167 public static T GetValue<T>(string field, string value) 168 { 169 return (T)GetValue(field, typeof(T), value, null); 170 } 171 GetValue(string fieldName, Type fieldType, string value)172 public static object GetValue(string fieldName, Type fieldType, string value) 173 { 174 return GetValue(fieldName, fieldType, value, null); 175 } 176 GetValue(string fieldName, Type fieldType, string value, MemberInfo field)177 public static object GetValue(string fieldName, Type fieldType, string value, MemberInfo field) 178 { 179 return GetValue(fieldName, fieldType, new MiniYaml(value), field); 180 } 181 GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)182 public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field) 183 { 184 var value = yaml.Value; 185 if (value != null) value = value.Trim(); 186 187 if (fieldType == typeof(int)) 188 { 189 int res; 190 if (Exts.TryParseIntegerInvariant(value, out res)) 191 return res; 192 return InvalidValueAction(value, fieldType, fieldName); 193 } 194 else if (fieldType == typeof(ushort)) 195 { 196 ushort res; 197 if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out res)) 198 return res; 199 return InvalidValueAction(value, fieldType, fieldName); 200 } 201 202 if (fieldType == typeof(long)) 203 { 204 long res; 205 if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out res)) 206 return res; 207 return InvalidValueAction(value, fieldType, fieldName); 208 } 209 else if (fieldType == typeof(float)) 210 { 211 float res; 212 if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res)) 213 return res * (value.Contains('%') ? 0.01f : 1f); 214 return InvalidValueAction(value, fieldType, fieldName); 215 } 216 else if (fieldType == typeof(decimal)) 217 { 218 decimal res; 219 if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res)) 220 return res * (value.Contains('%') ? 0.01m : 1m); 221 return InvalidValueAction(value, fieldType, fieldName); 222 } 223 else if (fieldType == typeof(string)) 224 { 225 if (field != null && MemberHasTranslateAttribute[field] && value != null) 226 return Regex.Replace(value, "@[^@]+@", m => Translate(m.Value.Substring(1, m.Value.Length - 2)), RegexOptions.Compiled); 227 return value; 228 } 229 else if (fieldType == typeof(Color)) 230 { 231 Color color; 232 if (value != null && Color.TryParse(value, out color)) 233 return color; 234 235 return InvalidValueAction(value, fieldType, fieldName); 236 } 237 else if (fieldType == typeof(Hotkey)) 238 { 239 Hotkey res; 240 if (Hotkey.TryParse(value, out res)) 241 return res; 242 243 return InvalidValueAction(value, fieldType, fieldName); 244 } 245 else if (fieldType == typeof(HotkeyReference)) 246 { 247 return Game.ModData.Hotkeys[value]; 248 } 249 else if (fieldType == typeof(WDist)) 250 { 251 WDist res; 252 if (WDist.TryParse(value, out res)) 253 return res; 254 255 return InvalidValueAction(value, fieldType, fieldName); 256 } 257 else if (fieldType == typeof(WVec)) 258 { 259 if (value != null) 260 { 261 var parts = value.Split(','); 262 if (parts.Length == 3) 263 { 264 WDist rx, ry, rz; 265 if (WDist.TryParse(parts[0], out rx) && WDist.TryParse(parts[1], out ry) && WDist.TryParse(parts[2], out rz)) 266 return new WVec(rx, ry, rz); 267 } 268 } 269 270 return InvalidValueAction(value, fieldType, fieldName); 271 } 272 else if (fieldType == typeof(WVec[])) 273 { 274 if (value != null) 275 { 276 var parts = value.Split(','); 277 278 if (parts.Length % 3 != 0) 279 return InvalidValueAction(value, fieldType, fieldName); 280 281 var vecs = new WVec[parts.Length / 3]; 282 283 for (var i = 0; i < vecs.Length; ++i) 284 { 285 WDist rx, ry, rz; 286 if (WDist.TryParse(parts[3 * i], out rx) && WDist.TryParse(parts[3 * i + 1], out ry) && WDist.TryParse(parts[3 * i + 2], out rz)) 287 vecs[i] = new WVec(rx, ry, rz); 288 } 289 290 return vecs; 291 } 292 293 return InvalidValueAction(value, fieldType, fieldName); 294 } 295 else if (fieldType == typeof(WPos)) 296 { 297 if (value != null) 298 { 299 var parts = value.Split(','); 300 if (parts.Length == 3) 301 { 302 WDist rx, ry, rz; 303 if (WDist.TryParse(parts[0], out rx) && WDist.TryParse(parts[1], out ry) && WDist.TryParse(parts[2], out rz)) 304 return new WPos(rx, ry, rz); 305 } 306 } 307 308 return InvalidValueAction(value, fieldType, fieldName); 309 } 310 else if (fieldType == typeof(WAngle)) 311 { 312 int res; 313 if (Exts.TryParseIntegerInvariant(value, out res)) 314 return new WAngle(res); 315 return InvalidValueAction(value, fieldType, fieldName); 316 } 317 else if (fieldType == typeof(WRot)) 318 { 319 if (value != null) 320 { 321 var parts = value.Split(','); 322 if (parts.Length == 3) 323 { 324 int rr, rp, ry; 325 if (Exts.TryParseIntegerInvariant(parts[0], out rr) && Exts.TryParseIntegerInvariant(parts[1], out rp) && Exts.TryParseIntegerInvariant(parts[2], out ry)) 326 return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry)); 327 } 328 } 329 330 return InvalidValueAction(value, fieldType, fieldName); 331 } 332 else if (fieldType == typeof(CPos)) 333 { 334 if (value != null) 335 { 336 var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 337 return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1])); 338 } 339 340 return InvalidValueAction(value, fieldType, fieldName); 341 } 342 else if (fieldType == typeof(CVec)) 343 { 344 if (value != null) 345 { 346 var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 347 return new CVec(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1])); 348 } 349 350 return InvalidValueAction(value, fieldType, fieldName); 351 } 352 else if (fieldType == typeof(CVec[])) 353 { 354 if (value != null) 355 { 356 var parts = value.Split(','); 357 358 if (parts.Length % 2 != 0) 359 return InvalidValueAction(value, fieldType, fieldName); 360 361 var vecs = new CVec[parts.Length / 2]; 362 for (var i = 0; i < vecs.Length; i++) 363 { 364 int rx, ry; 365 if (int.TryParse(parts[2 * i], out rx) && int.TryParse(parts[2 * i + 1], out ry)) 366 vecs[i] = new CVec(rx, ry); 367 } 368 369 return vecs; 370 } 371 372 return InvalidValueAction(value, fieldType, fieldName); 373 } 374 else if (fieldType == typeof(BooleanExpression)) 375 { 376 if (value != null) 377 { 378 try 379 { 380 return BooleanExpressionCache[value]; 381 } 382 catch (InvalidDataException e) 383 { 384 throw new YamlException(e.Message); 385 } 386 } 387 388 return InvalidValueAction(value, fieldType, fieldName); 389 } 390 else if (fieldType == typeof(IntegerExpression)) 391 { 392 if (value != null) 393 { 394 try 395 { 396 return IntegerExpressionCache[value]; 397 } 398 catch (InvalidDataException e) 399 { 400 throw new YamlException(e.Message); 401 } 402 } 403 404 return InvalidValueAction(value, fieldType, fieldName); 405 } 406 else if (fieldType.IsEnum) 407 { 408 try 409 { 410 return Enum.Parse(fieldType, value, true); 411 } 412 catch (ArgumentException) 413 { 414 return InvalidValueAction(value, fieldType, fieldName); 415 } 416 } 417 else if (fieldType == typeof(bool)) 418 { 419 bool result; 420 if (bool.TryParse(value.ToLowerInvariant(), out result)) 421 return result; 422 423 return InvalidValueAction(value, fieldType, fieldName); 424 } 425 else if (fieldType == typeof(int2[])) 426 { 427 if (value != null) 428 { 429 var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 430 if (parts.Length % 2 != 0) 431 return InvalidValueAction(value, fieldType, fieldName); 432 433 var ints = new int2[parts.Length / 2]; 434 for (var i = 0; i < ints.Length; i++) 435 ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1])); 436 437 return ints; 438 } 439 440 return InvalidValueAction(value, fieldType, fieldName); 441 } 442 else if (fieldType.IsArray && fieldType.GetArrayRank() == 1) 443 { 444 if (value == null) 445 return Array.CreateInstance(fieldType.GetElementType(), 0); 446 447 var options = field != null && field.HasAttribute<AllowEmptyEntriesAttribute>() ? 448 StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries; 449 var parts = value.Split(new char[] { ',' }, options); 450 451 var ret = Array.CreateInstance(fieldType.GetElementType(), parts.Length); 452 for (var i = 0; i < parts.Length; i++) 453 ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i); 454 return ret; 455 } 456 else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(HashSet<>)) 457 { 458 var set = Activator.CreateInstance(fieldType); 459 if (value == null) 460 return set; 461 462 var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 463 var addMethod = fieldType.GetMethod("Add", fieldType.GetGenericArguments()); 464 for (var i = 0; i < parts.Length; i++) 465 addMethod.Invoke(set, new[] { GetValue(fieldName, fieldType.GetGenericArguments()[0], parts[i].Trim(), field) }); 466 return set; 467 } 468 else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) 469 { 470 var dict = Activator.CreateInstance(fieldType); 471 var arguments = fieldType.GetGenericArguments(); 472 var addMethod = fieldType.GetMethod("Add", arguments); 473 474 foreach (var node in yaml.Nodes) 475 { 476 var key = GetValue(fieldName, arguments[0], node.Key, field); 477 var val = GetValue(fieldName, arguments[1], node.Value, field); 478 addMethod.Invoke(dict, new[] { key, val }); 479 } 480 481 return dict; 482 } 483 else if (fieldType == typeof(Size)) 484 { 485 if (value != null) 486 { 487 var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 488 return new Size(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1])); 489 } 490 491 return InvalidValueAction(value, fieldType, fieldName); 492 } 493 else if (fieldType == typeof(int2)) 494 { 495 if (value != null) 496 { 497 var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 498 if (parts.Length != 2) 499 return InvalidValueAction(value, fieldType, fieldName); 500 501 return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1])); 502 } 503 504 return InvalidValueAction(value, fieldType, fieldName); 505 } 506 else if (fieldType == typeof(float2)) 507 { 508 if (value != null) 509 { 510 var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 511 float xx = 0; 512 float yy = 0; 513 float res; 514 if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res)) 515 xx = res * (parts[0].Contains('%') ? 0.01f : 1f); 516 if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res)) 517 yy = res * (parts[1].Contains('%') ? 0.01f : 1f); 518 return new float2(xx, yy); 519 } 520 521 return InvalidValueAction(value, fieldType, fieldName); 522 } 523 else if (fieldType == typeof(float3)) 524 { 525 if (value != null) 526 { 527 var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 528 float x = 0; 529 float y = 0; 530 float z = 0; 531 float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out x); 532 float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out y); 533 534 // z component is optional for compatibility with older float2 definitions 535 if (parts.Length > 2) 536 float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z); 537 538 return new float3(x, y, z); 539 } 540 541 return InvalidValueAction(value, fieldType, fieldName); 542 } 543 else if (fieldType == typeof(Rectangle)) 544 { 545 if (value != null) 546 { 547 var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 548 return new Rectangle( 549 Exts.ParseIntegerInvariant(parts[0]), 550 Exts.ParseIntegerInvariant(parts[1]), 551 Exts.ParseIntegerInvariant(parts[2]), 552 Exts.ParseIntegerInvariant(parts[3])); 553 } 554 555 return InvalidValueAction(value, fieldType, fieldName); 556 } 557 else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(BitSet<>)) 558 { 559 if (value != null) 560 { 561 var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 562 var ctor = fieldType.GetConstructor(new[] { typeof(string[]) }); 563 return ctor.Invoke(new object[] { parts.Select(p => p.Trim()).ToArray() }); 564 } 565 566 return InvalidValueAction(value, fieldType, fieldName); 567 } 568 else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>)) 569 { 570 var innerType = fieldType.GetGenericArguments().First(); 571 var innerValue = GetValue("Nullable<T>", innerType, value, field); 572 return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue }); 573 } 574 else if (fieldType == typeof(DateTime)) 575 { 576 DateTime dt; 577 if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out dt)) 578 return dt; 579 return InvalidValueAction(value, fieldType, fieldName); 580 } 581 else 582 { 583 var conv = TypeDescriptor.GetConverter(fieldType); 584 if (conv.CanConvertFrom(typeof(string))) 585 { 586 try 587 { 588 return conv.ConvertFromInvariantString(value); 589 } 590 catch 591 { 592 return InvalidValueAction(value, fieldType, fieldName); 593 } 594 } 595 } 596 597 UnknownFieldAction("[Type] {0}".F(value), fieldType); 598 return null; 599 } 600 601 public sealed class FieldLoadInfo 602 { 603 public readonly FieldInfo Field; 604 public readonly SerializeAttribute Attribute; 605 public readonly string YamlName; 606 public readonly Func<MiniYaml, object> Loader; 607 FieldLoadInfo(FieldInfo field, SerializeAttribute attr, string yamlName, Func<MiniYaml, object> loader = null)608 internal FieldLoadInfo(FieldInfo field, SerializeAttribute attr, string yamlName, Func<MiniYaml, object> loader = null) 609 { 610 Field = field; 611 Attribute = attr; 612 YamlName = yamlName; 613 Loader = loader; 614 } 615 } 616 GetTypeLoadInfo(Type type, bool includePrivateByDefault = false)617 public static IEnumerable<FieldLoadInfo> GetTypeLoadInfo(Type type, bool includePrivateByDefault = false) 618 { 619 return TypeLoadInfo[type].Where(fli => includePrivateByDefault || fli.Field.IsPublic || (fli.Attribute.Serialize && !fli.Attribute.IsDefault)); 620 } 621 BuildTypeLoadInfo(Type type)622 static FieldLoadInfo[] BuildTypeLoadInfo(Type type) 623 { 624 var ret = new List<FieldLoadInfo>(); 625 626 foreach (var ff in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) 627 { 628 var field = ff; 629 630 var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First(); 631 if (!sa.Serialize) 632 continue; 633 634 var yamlName = string.IsNullOrEmpty(sa.YamlName) ? field.Name : sa.YamlName; 635 636 var loader = sa.GetLoader(type); 637 if (loader == null && sa.FromYamlKey) 638 loader = yaml => GetValue(yamlName, field.FieldType, yaml, field); 639 640 var fli = new FieldLoadInfo(field, sa, yamlName, loader); 641 ret.Add(fli); 642 } 643 644 return ret.ToArray(); 645 } 646 647 [AttributeUsage(AttributeTargets.Field)] 648 public sealed class IgnoreAttribute : SerializeAttribute 649 { IgnoreAttribute()650 public IgnoreAttribute() 651 : base(false) { } 652 } 653 654 [AttributeUsage(AttributeTargets.Field)] 655 public sealed class RequireAttribute : SerializeAttribute 656 { RequireAttribute()657 public RequireAttribute() 658 : base(true, true) { } 659 } 660 661 [AttributeUsage(AttributeTargets.Field)] 662 public sealed class AllowEmptyEntriesAttribute : SerializeAttribute 663 { AllowEmptyEntriesAttribute()664 public AllowEmptyEntriesAttribute() 665 : base(allowEmptyEntries: true) { } 666 } 667 668 [AttributeUsage(AttributeTargets.Field)] 669 public sealed class LoadUsingAttribute : SerializeAttribute 670 { LoadUsingAttribute(string loader, bool required = false)671 public LoadUsingAttribute(string loader, bool required = false) 672 { 673 Loader = loader; 674 Required = required; 675 } 676 } 677 678 [AttributeUsage(AttributeTargets.Field)] 679 public class SerializeAttribute : Attribute 680 { 681 public static readonly SerializeAttribute Default = new SerializeAttribute(true); 682 683 public bool IsDefault { get { return this == Default; } } 684 685 public readonly bool Serialize; 686 public string YamlName; 687 public string Loader; 688 public bool FromYamlKey; 689 public bool DictionaryFromYamlKey; 690 public bool Required; 691 public bool AllowEmptyEntries; 692 SerializeAttribute(bool serialize = true, bool required = false, bool allowEmptyEntries = false)693 public SerializeAttribute(bool serialize = true, bool required = false, bool allowEmptyEntries = false) 694 { 695 Serialize = serialize; 696 Required = required; 697 AllowEmptyEntries = allowEmptyEntries; 698 } 699 GetLoader(Type type)700 internal Func<MiniYaml, object> GetLoader(Type type) 701 { 702 const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy; 703 704 if (!string.IsNullOrEmpty(Loader)) 705 { 706 var method = type.GetMethod(Loader, Flags); 707 if (method == null) 708 throw new InvalidOperationException("{0} does not specify a loader function '{1}'".F(type.Name, Loader)); 709 710 return (Func<MiniYaml, object>)Delegate.CreateDelegate(typeof(Func<MiniYaml, object>), method); 711 } 712 713 return null; 714 } 715 } 716 Translate(string key)717 public static string Translate(string key) 718 { 719 if (string.IsNullOrEmpty(key)) 720 return key; 721 722 lock (TranslationsLock) 723 { 724 if (translations == null) 725 return key; 726 727 string value; 728 if (!translations.TryGetValue(key, out value)) 729 return key; 730 731 return value; 732 } 733 } 734 SetTranslations(IDictionary<string, string> translations)735 public static void SetTranslations(IDictionary<string, string> translations) 736 { 737 lock (TranslationsLock) 738 FieldLoader.translations = new Dictionary<string, string>(translations); 739 } 740 } 741 742 [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 743 public sealed class TranslateAttribute : Attribute { } 744 745 [AttributeUsage(AttributeTargets.Field)] 746 public sealed class FieldFromYamlKeyAttribute : FieldLoader.SerializeAttribute 747 { FieldFromYamlKeyAttribute()748 public FieldFromYamlKeyAttribute() 749 { 750 FromYamlKey = true; 751 } 752 } 753 754 // Special-cases FieldFromYamlKeyAttribute for use with Dictionary<K,V>. 755 [AttributeUsage(AttributeTargets.Field)] 756 public sealed class DictionaryFromYamlKeyAttribute : FieldLoader.SerializeAttribute 757 { DictionaryFromYamlKeyAttribute()758 public DictionaryFromYamlKeyAttribute() 759 { 760 FromYamlKey = true; 761 DictionaryFromYamlKey = true; 762 } 763 } 764 765 // Mirrors DescriptionAttribute from System.ComponentModel but we don't want to have to use that everywhere. 766 [AttributeUsage(AttributeTargets.All)] 767 public sealed class DescAttribute : Attribute 768 { 769 public readonly string[] Lines; DescAttribute(params string[] lines)770 public DescAttribute(params string[] lines) { Lines = lines; } 771 } 772 } 773