From e5cbc8d63157aa4fc5d1e14193caf8b65ae0d769 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 9 Jan 2021 11:06:59 -0700 Subject: [PATCH] Promote hooks to fully-fledge types Hook types include a source, label, and context delegate. --- src/spectator/events.cr | 29 +++++++++++++++++------- src/spectator/example_group_hook.cr | 32 +++++++++++++++++++++++++++ src/spectator/example_hook.cr | 34 +++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 src/spectator/example_group_hook.cr create mode 100644 src/spectator/example_hook.cr diff --git a/src/spectator/events.cr b/src/spectator/events.cr index 33857ac..42c6bbd 100644 --- a/src/spectator/events.cr +++ b/src/spectator/events.cr @@ -1,4 +1,5 @@ -require "./example_context_delegate" +require "./example_group_hook" +require "./example_hook" module Spectator # Mix-in for managing events and hooks. @@ -10,7 +11,7 @@ module Spectator # # The *name* defines the name of the event. # This must be unique across all events, not just group events. - # Three public methods are defined - one to add a hook and the others to trigger the event which calls every hook. + # Four public methods are defined - two to add a hook and the others to trigger the event which calls every hook. # One trigger method, prefixed with *call_* will always call the event hooks. # The other trigger method, prefixed with *call_once_* will only call the event hooks on the first invocation. # @@ -24,13 +25,19 @@ module Spectator # end # ``` private macro group_event(name, &block) - @%hooks = Deque(->).new + @%hooks = [] of ExampleGroupHook @%called = Atomic::Flag.new + # Adds a hook to be invoked when the *{{name.id}}* event occurs. + def add_{{name.id}}_hook(hook : ExampleGroupHook) : Nil + @%hooks << hook + end + # Defines a hook for the *{{name.id}}* event. # The block of code given to this method is invoked when the event occurs. - def {{name.id}}(&block) : Nil - @%hooks << block + def {{name.id}}(&block : -> _) : Nil + hook = ExampleGroupHook.new(label: {{name.stringify}}, &block) + add_{{name.id}}_hook(hook) end # Signals that the *{{name.id}}* event has occurred. @@ -60,7 +67,7 @@ module Spectator # # The *name* defines the name of the event. # This must be unique across all events. - # Two public methods are defined - one to add a hook and the other to trigger the event which calls every hook. + # Three public methods are defined - two to add a hook and the other to trigger the event which calls every hook. # # A block must be provided to this macro. # The block defines the logic for invoking all of the hooks. @@ -72,13 +79,19 @@ module Spectator # end # ``` private macro example_event(name, &block) - @%hooks = Deque(Example ->).new + @%hooks = [] of ExampleHook + + # Adds a hook to be invoked when the *{{name.id}}* event occurs. + def add_{{name.id}}_hook(hook : ExampleHook) : Nil + @%hooks << hook + end # Defines a hook for the *{{name.id}}* event. # The block of code given to this method is invoked when the event occurs. # The current example is provided as a block argument. def {{name.id}}(&block : Example ->) : Nil - @%hooks << block + hook = ExampleHook.new(label: {{name.stringify}}, &block) + add_{{name.id}}_hook(hook) end # Signals that the *{{name.id}}* event has occurred. diff --git a/src/spectator/example_group_hook.cr b/src/spectator/example_group_hook.cr new file mode 100644 index 0000000..4748b2a --- /dev/null +++ b/src/spectator/example_group_hook.cr @@ -0,0 +1,32 @@ +require "./context_delegate" +require "./label" +require "./source" + +module Spectator + # Information about a hook tied to an example group and a delegate to invoke it. + class ExampleGroupHook + # Location of the hook in source code. + getter! source : Source + + # User-defined description of the hook. + getter! label : Label + + # Creates the hook with a context delegate. + # The *delegate* will be called when the hook is invoked. + # A *source* and *label* can be provided for debugging. + def initialize(@delegate : ContextDelegate, *, @source : Source? = nil, @label : Label = nil) + end + + # Creates the hook with a block. + # The block will be executed when the hook is invoked. + # A *source* and *label* can be provided for debugging. + def initialize(*, @source : Source? = nil, @label : Label = nil, &block : -> _) + @delegate = ContextDelegate.null(&block) + end + + # Invokes the hook. + def call : Nil + @delegate.call + end + end +end diff --git a/src/spectator/example_hook.cr b/src/spectator/example_hook.cr new file mode 100644 index 0000000..b169b9d --- /dev/null +++ b/src/spectator/example_hook.cr @@ -0,0 +1,34 @@ +require "./example_context_delegate" +require "./label" +require "./source" + +module Spectator + # Information about a hook tied to an example and a delegate to invoke it. + class ExampleHook + # Location of the hook in source code. + getter! source : Source + + # User-defined description of the hook. + getter! label : Label + + # Creates the hook with an example context delegate. + # The *delegate* will be called when the hook is invoked. + # A *source* and *label* can be provided for debugging. + def initialize(@delegate : ExampleContextDelegate, *, @source : Source? = nil, @label : Label = nil) + end + + # Creates the hook with a block. + # The block must take a single argument - the current example. + # The block will be executed when the hook is invoked. + # A *source* and *label* can be provided for debugging. + def initialize(*, @source : Source? = nil, @label : Label = nil, &block : Example -> _) + @delegate = ExampleContextDelegate.null(&block) + end + + # Invokes the hook. + # The *example* refers to the current example. + def call(example : Example) : Nil + @delegate.call(example) + end + end +end