ExampleDiscovery.cs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Reflection;
  3. namespace Terminal.Gui.Examples;
  4. /// <summary>
  5. /// Provides methods for discovering example applications by scanning assemblies for example metadata attributes.
  6. /// </summary>
  7. public static class ExampleDiscovery
  8. {
  9. /// <summary>
  10. /// Discovers examples from the specified assembly file paths.
  11. /// </summary>
  12. /// <param name="assemblyPaths">The paths to assembly files to scan for examples.</param>
  13. /// <returns>An enumerable of <see cref="ExampleInfo"/> objects for each discovered example.</returns>
  14. [RequiresUnreferencedCode ("Calls System.Reflection.Assembly.LoadFrom")]
  15. [RequiresDynamicCode ("Calls System.Reflection.Assembly.LoadFrom")]
  16. public static IEnumerable<ExampleInfo> DiscoverFromFiles (params string [] assemblyPaths)
  17. {
  18. foreach (string path in assemblyPaths)
  19. {
  20. if (!File.Exists (path))
  21. {
  22. continue;
  23. }
  24. Assembly? asm = null;
  25. try
  26. {
  27. asm = Assembly.LoadFrom (path);
  28. }
  29. catch
  30. {
  31. // Skip assemblies that can't be loaded
  32. continue;
  33. }
  34. ExampleMetadataAttribute? metadata = asm.GetCustomAttribute<ExampleMetadataAttribute> ();
  35. if (metadata is null)
  36. {
  37. continue;
  38. }
  39. ExampleInfo info = new ()
  40. {
  41. Name = metadata.Name,
  42. Description = metadata.Description,
  43. AssemblyPath = path,
  44. Categories = asm.GetCustomAttributes<ExampleCategoryAttribute> ()
  45. .Select (c => c.Category)
  46. .ToList (),
  47. DemoKeyStrokes = ParseDemoKeyStrokes (asm)
  48. };
  49. yield return info;
  50. }
  51. }
  52. /// <summary>
  53. /// Discovers examples from assemblies in the specified directory.
  54. /// </summary>
  55. /// <param name="directory">The directory to search for assembly files.</param>
  56. /// <param name="searchPattern">The search pattern for assembly files (default is "*.dll").</param>
  57. /// <param name="searchOption">The search option for traversing subdirectories.</param>
  58. /// <returns>An enumerable of <see cref="ExampleInfo"/> objects for each discovered example.</returns>
  59. [RequiresUnreferencedCode ("Calls System.Reflection.Assembly.LoadFrom")]
  60. [RequiresDynamicCode ("Calls System.Reflection.Assembly.LoadFrom")]
  61. public static IEnumerable<ExampleInfo> DiscoverFromDirectory (
  62. string directory,
  63. string searchPattern = "*.dll",
  64. SearchOption searchOption = SearchOption.AllDirectories
  65. )
  66. {
  67. if (!Directory.Exists (directory))
  68. {
  69. return [];
  70. }
  71. string [] assemblyPaths = Directory.GetFiles (directory, searchPattern, searchOption);
  72. return DiscoverFromFiles (assemblyPaths);
  73. }
  74. private static List<DemoKeyStrokeSequence> ParseDemoKeyStrokes (Assembly assembly)
  75. {
  76. List<DemoKeyStrokeSequence> sequences = new ();
  77. foreach (ExampleDemoKeyStrokesAttribute attr in assembly.GetCustomAttributes<ExampleDemoKeyStrokesAttribute> ())
  78. {
  79. List<string> keys = new ();
  80. if (attr.KeyStrokes is { Length: > 0 })
  81. {
  82. keys.AddRange (attr.KeyStrokes);
  83. }
  84. if (!string.IsNullOrEmpty (attr.RepeatKey))
  85. {
  86. for (var i = 0; i < attr.RepeatCount; i++)
  87. {
  88. keys.Add (attr.RepeatKey);
  89. }
  90. }
  91. if (keys.Count > 0)
  92. {
  93. sequences.Add (
  94. new ()
  95. {
  96. KeyStrokes = keys.ToArray (),
  97. Order = attr.Order
  98. });
  99. }
  100. }
  101. return sequences.OrderBy (s => s.Order).ToList ();
  102. }
  103. }