You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
4.6 KiB

package console
import (
"context"
"fmt"
"gitoa.ru/go-4devs/console/input"
"gitoa.ru/go-4devs/console/output"
)
type (
Action func(ctx context.Context, input input.Input, output output.Output) error
Handle func(ctx context.Context, in input.Input, out output.Output, n Action) error
Configure func(ctx context.Context, cfg *input.Definition) error
Prepare func(ctx context.Context, cfg *input.Definition, n Configure) error
Option func(*Command)
)
// WithPrepare append middleware for configuration command.
func WithPrepare(prepares ...Prepare) Option {
return func(c *Command) {
if c.Prepare != nil {
prepares = append([]Prepare{c.Prepare}, prepares...)
}
c.Prepare = ChainPrepare(prepares...)
}
}
// WithHandle append middleware for executed command.
func WithHandle(handles ...Handle) Option {
return func(c *Command) {
if c.Handle != nil {
handles = append([]Handle{c.Handle}, handles...)
}
c.Handle = ChainHandle(handles...)
}
}
// WithHidden sets hidden command.
func WithHidden(hidden bool) Option {
return func(c *Command) {
c.Hidden = hidden
}
}
// WithName sets name command.
func WithName(name string) Option {
return func(c *Command) {
c.Name = name
}
}
type Command struct {
// The name of the command.
Name string
// A short description of the usage of this command.
Description string
// A longer explanation of how the command works.
Help string
// Vervion command.
Version string
// Boolean to hide this command from help or completion.
Hidden bool
// Configures the current command.
Configure Configure
// The middleware for configures current command.
Prepare Prepare
// The function to call when this command is invoked.
Execute Action
// The middleware for executes current command.
Handle Handle
}
func (c *Command) String() string {
return fmt.Sprintf("name: %s, version: %s", c.Name, c.Version)
}
// With creates new command by parent and options.
func (c *Command) With(opts ...Option) *Command {
cmd := &Command{
Name: c.Name,
Description: c.Description,
Help: c.Help,
Version: c.Version,
Hidden: c.Hidden,
Configure: c.Configure,
Prepare: c.Prepare,
Execute: c.Execute,
Handle: c.Handle,
}
for _, opt := range opts {
opt(cmd)
}
return cmd
}
// Run run command with input and output.
func (c *Command) Run(ctx context.Context, in input.Input, out output.Output) error {
if c.Execute == nil {
return fmt.Errorf("%w", ErrExecuteNil)
}
if c.Handle != nil {
return c.Handle(ctx, in, out, c.Execute)
}
return c.Execute(ctx, in, out)
}
// Init configures command.
func (c *Command) Init(ctx context.Context, cfg *input.Definition) error {
switch {
case c.Prepare != nil && c.Configure != nil:
return c.Prepare(ctx, cfg, c.Configure)
case c.Prepare != nil:
return c.Prepare(ctx, cfg, func(_ context.Context, _ *input.Definition) error {
return nil
})
case c.Configure != nil:
return c.Configure(ctx, cfg)
default:
return nil
}
}
// ChainPrepare creates middleware for configures command.
func ChainPrepare(prepare ...Prepare) Prepare {
num := len(prepare)
if num == 1 {
return prepare[0]
}
if num > 1 {
lastI := num - 1
return func(ctx context.Context, def *input.Definition, next Configure) error {
var (
chainHandler func(context.Context, *input.Definition) error
curI int
)
chainHandler = func(currentCtx context.Context, currentDef *input.Definition) error {
if curI == lastI {
return next(currentCtx, currentDef)
}
curI++
err := prepare[curI](currentCtx, currentDef, chainHandler)
curI--
return err
}
return prepare[0](ctx, def, chainHandler)
}
}
return func(ctx context.Context, cfg *input.Definition, next Configure) error {
return next(ctx, cfg)
}
}
// ChainHandle creates middleware for executes command.
func ChainHandle(handlers ...Handle) Handle {
num := len(handlers)
if num == 1 {
return handlers[0]
}
if num > 1 {
lastI := num - 1
return func(ctx context.Context, in input.Input, out output.Output, next Action) error {
var (
chainHandler func(context.Context, input.Input, output.Output) error
curI int
)
chainHandler = func(currentCtx context.Context, currentIn input.Input, currentOut output.Output) error {
if curI == lastI {
return next(currentCtx, currentIn, currentOut)
}
curI++
err := handlers[curI](currentCtx, currentIn, currentOut, chainHandler)
curI--
return err
}
return handlers[0](ctx, in, out, chainHandler)
}
}
return func(ctx context.Context, in input.Input, out output.Output, next Action) error {
return next(ctx, in, out)
}
}