module Toys::Middleware

A middleware is an object that has the opportunity to alter the configuration and runtime behavior of each tool in a Toys CLI. A CLI contains an ordered list of middleware, known as the *middleware stack*, that together define the CLI's default behavior.

Specifically, a middleware can perform two functions.

First, it can modify the configuration of a tool. After tools are defined from configuration, the middleware stack can make modifications to each tool. A middleware can add flags and arguments to the tool, modify the description, or make any other changes to how the tool is set up.

Second, a middleware can intercept and change tool execution. Like a Rack middleware, a Toys middleware can wrap execution with its own code, replace it outright, or leave it unmodified.

Generally, a middleware is a class that implements one or more of the methods defined in this module: {Toys::Middleware#config}, and {Toys::Middleware#run}. This module provides default implementations that do nothing, but using them is not required. Middleware objects need respond only to methods they care about.

Public Class Methods

spec(middleware, *args, **kwargs, &block) click to toggle source

Create a middleware spec.

@overload spec(name, *args, **kwargs, &block)

Create a spec indicating a given middleware name should be
instantiated with the given arguments.

@param name [String,Symbol,Class] The middleware name or class
@param args [Array] The arguments to pass to the constructor
@param kwargs [Hash] The keyword arguments to pass to the constructor
@param block [Proc,nil] The block to pass to the constructor
@return [Toys::Middleware::Spec] A spec

@overload spec(array)

Create a middleware spec from an array specification.

The array must be 1-4 elements long. The first element must be the
middleware name or class. The other three arguments may include any
or all of the following optional elements, in any order:
 *  An array for the positional arguments to pass to the constructor
 *  A hash for the keyword arguments to pass to the constructor
 *  A proc for the block to pass to the constructor

@param array [Array] The array input
@return [Toys::Middleware::Spec] A spec

@overload spec(middleware_object)

Create a spec wrapping an existing middleware object

@param middleware_object [Toys::Middleware] The middleware object
@return [Toys::Middleware::Spec] A spec
# File lib/toys/middleware.rb, line 106
def spec(middleware, *args, **kwargs, &block)
  case middleware
  when ::Array
    spec_from_array(middleware)
  when ::String, ::Symbol, ::Class
    Spec.new(nil, middleware, args, kwargs, block)
  when Spec
    middleware
  else
    Spec.new(middleware, nil, nil, nil, nil)
  end
end
spec_from_array(array) click to toggle source

@private

# File lib/toys/middleware.rb, line 144
def spec_from_array(array)
  middleware = array.first
  if !middleware.is_a?(::String) && !middleware.is_a?(::Symbol) && !middleware.is_a?(::Class)
    raise ::ArgumentError, "Bad middleware name: #{middleware.inspect}"
  end
  args = []
  kwargs = {}
  block = nil
  array.slice(1..-1).each do |param|
    case param
    when ::Array
      args += param
    when ::Hash
      kwargs = kwargs.merge(param)
    when ::Proc
      block = param
    else
      raise ::ArgumentError, "Bad param: #{param.inspect}"
    end
  end
  Spec.new(nil, middleware, args, kwargs, block)
end
stack(input) click to toggle source

Create a {Toys::Middleware::Stack} from an array of middleware specs. Each element may be one of the following:

*  A {Toys::Middleware} object
*  A {Toys::Middleware::Spec}
*  An array whose first element is a middleware name or class, and the
   subsequent elements are params that define what to pass to the class
   constructor (see {Toys::Middleware.spec})

@param input [Array<Toys::Middleware,Toys::Middleware::Spec,Array>] @return [Toys::Middleware::Stack]

# File lib/toys/middleware.rb, line 132
def stack(input)
  case input
  when Stack
    input
  when ::Array
    Stack.new(default_specs: input.map { |spec| spec(spec) })
  else
    raise ::ArgumentError, "Illegal middleware stack: #{input.inspect}"
  end
end

Public Instance Methods

config(tool, loader) { || ... } click to toggle source

This method is called after a tool has been defined, and gives this middleware the opportunity to modify the tool definition. It is passed the tool definition object and the loader, and can make any changes to the tool definition. In most cases, this method should also call `yield`, which passes control to the next middleware in the stack. A middleware can disable modifications done by subsequent middleware by omitting the `yield` call, but this is uncommon.

This basic implementation does nothing and simply yields to the next middleware.

@param tool [Toys::ToolDefinition] The tool definition to modify. @param loader [Toys::Loader] The loader that loaded this tool. @return [void]

# File lib/toys/middleware.rb, line 44
def config(tool, loader) # rubocop:disable Lint/UnusedMethodArgument
  yield
end
run(context) { || ... } click to toggle source

This method is called when the tool is run. It gives the middleware an opportunity to modify the runtime behavior of the tool. It is passed the tool instance (i.e. the object that hosts a tool's `run` method), and you can use this object to access the tool's options and other context data. In most cases, this method should also call `yield`, which passes control to the next middleware in the stack. A middleware can “wrap” normal execution by calling `yield` somewhere in its implementation of this method, or it can completely replace the execution behavior by not calling `yield` at all.

Like a tool's `run` method, this method's return value is unused. If you want to output from a tool, write to stdout or stderr. If you want to set the exit status code, call {Toys::Context#exit} on the context.

This basic implementation does nothing and simply yields to the next middleware.

@param context [Toys::Context] The tool execution context. @return [void]

# File lib/toys/middleware.rb, line 69
def run(context) # rubocop:disable Lint/UnusedMethodArgument
  yield
end