Skip to content

Defining custom directives

Adam Bajguz edited this page Apr 5, 2021 · 12 revisions

To define a custom directive, just create a new class that implements the IDirective or IPipelinedDirective interface and annotate it with [Directive] attribute:

[Directive(BuiltInDirectives.Debug, Description = "Starts a debugging mode. Application will wait for debugger to be attached before proceeding.")]
public sealed class DebugDirective : IPipelinedDirective
{
    /// <inheritdoc/>
    public ValueTask OnInitializedAsync(CancellationToken cancellationToken)
    {
        return default;
    }

    /// <inheritdoc/>
    public async ValueTask HandleAsync(ICliContext context, CommandPipelineHandlerDelegate next, CancellationToken cancellationToken)
    {
#if NET5_0
        int processId = Environment.ProcessId;
#else
        int processId = Process.GetCurrentProcess().Id;
#endif

        IConsole console = context.Console;

        console.Output.WithForegroundColor(ConsoleColor.Green, (output) => output.WriteLine($"Attach debugger to PID {processId} to continue."));

        Debugger.Launch();

        while (!Debugger.IsAttached)
            await Task.Delay(100, cancellationToken);

        await next();
    }
}

To facilitate both asynchronous and synchronous execution, OnInitializedAsync and HandleAsync methods return a ValueTask. In synchronous implementation, we can just put return default at the end, while in an asynchronous we can use the async/await keywords instead.

Similarly to commands, in every directive it is possible to define a description and a manual with [Directive] attribute. [Directive] attribute provides also an easy way for excluding a command from execution in normal mode through InteractiveModeOnly property.

Difference between IDirective and IPipelinedDirective

IDirective is a base interface for all directives, including IPipelinedDirective, and it contains a single method ValueTask OnInitializedAsync(CancellationToken cancellationToken) method that can be used to initialize some data in the directive. Thus, directives implementing IDirective interface can be treated as simple directives that only purpose is to provide some extra data that can be accessed by every command in the application using ICliContext DI-injectable service.

If you want to stop the execution of the command, simply throw DirectiveException, and DefaultExceptionHandler will catch it and print to console.

On the other hand, directives that implement IPipelinedDirective are the most advanced directives available in Typin. As the name suggest they use pipeline - are executed in Typin's pipeline. In Typin 3.0 core middleware execution order has changed: ResolveCommandSchemaAndInstance -> InitializeDirectives -> ExecuteDirectivesSubpipeline -> [Directives subpipeline] -> HandleSpecialOptions -> BindInput -> [User middlewares] -> ExecuteCommand.

Pipelined directives can also be called dynamic middlewares.

Clone this wiki locally