CWPWorkflowHelper.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. namespace Terminal.Gui.App;
  2. using System;
  3. /// <summary>
  4. /// Provides helper methods for executing single-phase and result-producing workflows in the Cancellable Work Pattern (CWP).
  5. /// </summary>
  6. /// <remarks>
  7. /// <para>
  8. /// Used for workflows that allow customization or cancellation, such as command execution
  9. /// (e.g., <see cref="View.RaiseAccepting"/>) or scheme resolution (e.g., <see cref="View.GetScheme"/>).
  10. /// The <see cref="Execute{T}"/> method handles workflows without results, while
  11. /// <see cref="ExecuteWithResult{TResult}"/> handles workflows producing results.
  12. /// </para>
  13. /// </remarks>
  14. /// <seealso cref="ResultEventArgs{T}"/>
  15. public static class CWPWorkflowHelper
  16. {
  17. /// <summary>
  18. /// Executes a single-phase CWP workflow with a virtual method, event, and optional default action.
  19. /// </summary>
  20. /// <typeparam name="T">The type of the result in the event arguments.</typeparam>
  21. /// <param name="onMethod">The virtual method invoked first, returning true to mark the workflow as handled.</param>
  22. /// <param name="eventHandler">The event handler to invoke, or null if no handlers are subscribed.</param>
  23. /// <param name="args">The event arguments containing a result and handled status.</param>
  24. /// <param name="defaultAction">The default action to execute if the workflow is not handled, or null if none.</param>
  25. /// <returns>True if the workflow was handled, false if not, or null if no event handlers are subscribed.</returns>
  26. /// <exception cref="ArgumentNullException">
  27. /// Thrown if <paramref name="onMethod"/> or <paramref name="args"/> is null.
  28. /// </exception>
  29. /// <example>
  30. /// <code>
  31. /// ResultEventArgs&lt;bool&gt; args = new();
  32. /// Func&lt;ResultEventArgs&lt;bool&gt;, bool&gt; onAccepting = _ =&gt; false;
  33. /// EventHandler&lt;ResultEventArgs&lt;bool&gt;&gt;? acceptingHandler = null;
  34. /// Action? defaultAction = () =&gt; args.Result = true;
  35. /// bool? handled = CWPWorkflowHelper.Execute(onAccepting, acceptingHandler, args, defaultAction);
  36. /// </code>
  37. /// </example>
  38. public static bool? Execute<T> (
  39. Func<ResultEventArgs<T>, bool> onMethod,
  40. EventHandler<ResultEventArgs<T>>? eventHandler,
  41. ResultEventArgs<T> args,
  42. Action? defaultAction = null)
  43. {
  44. ArgumentNullException.ThrowIfNull (onMethod);
  45. ArgumentNullException.ThrowIfNull (args);
  46. bool handled = onMethod (args) || args.Handled;
  47. if (handled)
  48. {
  49. return true;
  50. }
  51. // BUGBUG: This should pass this not null; need to test
  52. eventHandler?.Invoke (null, args);
  53. if (args.Handled)
  54. {
  55. return true;
  56. }
  57. if (defaultAction is {})
  58. {
  59. defaultAction ();
  60. return true;
  61. }
  62. return eventHandler is null ? null : false;
  63. }
  64. /// <summary>
  65. /// Executes a CWP workflow that produces a result, suitable for methods like <see cref="View.GetScheme"/>.
  66. /// </summary>
  67. /// <typeparam name="TResult">The type of the result, which may be a nullable reference type (e.g., <see cref="Scheme"/>?).</typeparam>
  68. /// <param name="onMethod">The virtual method invoked first, returning true to mark the workflow as handled.</param>
  69. /// <param name="eventHandler">The event handler to invoke, or null if no handlers are subscribed.</param>
  70. /// <param name="args">The event arguments containing a result and handled status.</param>
  71. /// <param name="defaultAction">The default action that produces the result if the workflow is not handled.</param>
  72. /// <returns>The result from the event arguments or the default action.</returns>
  73. /// <exception cref="ArgumentNullException">
  74. /// Thrown if <paramref name="onMethod"/>, <paramref name="args"/>, or <paramref name="defaultAction"/> is null.
  75. /// </exception>
  76. /// <exception cref="InvalidOperationException">
  77. /// Thrown if <see cref="ResultEventArgs{T}.Result"/> is null for non-nullable reference types when <see cref="ResultEventArgs{T}.Handled"/> is true.
  78. /// </exception>
  79. /// <example>
  80. /// <code>
  81. /// ResultEventArgs&lt;Scheme?&gt; args = new();
  82. /// Func&lt;ResultEventArgs&lt;Scheme?&gt;, bool&gt; onGettingScheme = _ =&gt; false;
  83. /// EventHandler&lt;ResultEventArgs&lt;Scheme?&gt;&gt;? gettingSchemeHandler = null;
  84. /// Func&lt;Scheme&gt; defaultAction = () =&gt; SchemeManager.GetScheme("Base");
  85. /// Scheme scheme = CWPWorkflowHelper.ExecuteWithResult(onGettingScheme, gettingSchemeHandler, args, defaultAction);
  86. /// </code>
  87. /// </example>
  88. public static TResult ExecuteWithResult<TResult> (
  89. Func<ResultEventArgs<TResult>, bool> onMethod,
  90. EventHandler<ResultEventArgs<TResult>>? eventHandler,
  91. ResultEventArgs<TResult> args,
  92. Func<TResult> defaultAction)
  93. {
  94. ArgumentNullException.ThrowIfNull (onMethod);
  95. ArgumentNullException.ThrowIfNull (args);
  96. ArgumentNullException.ThrowIfNull (defaultAction);
  97. bool handled = onMethod (args) || args.Handled;
  98. if (handled)
  99. {
  100. if (args.Result is null && !typeof (TResult).IsValueType && !Nullable.GetUnderlyingType (typeof (TResult))?.IsValueType == true)
  101. {
  102. throw new InvalidOperationException ("Result cannot be null for non-nullable reference types when Handled is true.");
  103. }
  104. return args.Result!;
  105. }
  106. // BUGBUG: This should pass this not null; need to test
  107. eventHandler?.Invoke (null, args);
  108. if (!args.Handled)
  109. {
  110. return defaultAction ();
  111. }
  112. if (args.Result is null && !typeof (TResult).IsValueType && !Nullable.GetUnderlyingType (typeof (TResult))?.IsValueType == true)
  113. {
  114. throw new InvalidOperationException ("Result cannot be null for non-nullable reference types when Handled is true.");
  115. }
  116. return args.Result!;
  117. }
  118. }