1
0
mirror of https://github.com/gogf/gf.git synced 2025-04-05 11:18:50 +08:00

command object feature for package gcmd

This commit is contained in:
John Guo 2021-11-23 20:26:55 +08:00
parent 5415d6dc52
commit 0622b517c5
10 changed files with 288 additions and 142 deletions

View File

@ -281,7 +281,7 @@ type parseWithTagInFieldStructOutput struct {
Order string
}
func (m *Model) parseWithTagInFieldStruct(field *structs.Field) (output parseWithTagInFieldStructOutput) {
func (m *Model) parseWithTagInFieldStruct(field structs.Field) (output parseWithTagInFieldStructOutput) {
var (
match []string
ormTag = field.Tag(OrmTagForStruct)

View File

@ -29,7 +29,6 @@ func Init(args ...string) {
// GetOpt returns the option value named `name` as gvar.Var.
func GetOpt(name string, def ...string) *gvar.Var {
Init()
if v := command.GetOpt(name, def...); v != "" {
return gvar.New(v)
}
@ -38,19 +37,16 @@ func GetOpt(name string, def ...string) *gvar.Var {
// GetOptAll returns all parsed options.
func GetOptAll() map[string]string {
Init()
return command.GetOptAll()
}
// ContainsOpt checks whether option named `name` exist in the arguments.
func ContainsOpt(name string) bool {
Init()
return command.ContainsOpt(name)
}
// GetArg returns the argument at `index` as gvar.Var.
func GetArg(index int, def ...string) *gvar.Var {
Init()
if v := command.GetArg(index, def...); v != "" {
return gvar.New(v)
}
@ -59,7 +55,6 @@ func GetArg(index int, def ...string) *gvar.Var {
// GetArgAll returns all parsed arguments.
func GetArgAll() []string {
Init()
return command.GetArgAll()
}

View File

@ -16,22 +16,26 @@ import (
// Command holds the info about an argument that can handle custom logic.
type Command struct {
Name string // Command name(case-sensitive).
Usage string // A brief line description about its usage, eg: gf build main.go [OPTION]
Brief string // A brief info that describes what this command will do.
Description string // A detailed description.
Options []Option // Option array, configuring how this command act.
Func Function // Custom function.
HelpFunc Function // Custom help function
Examples string // Usage examples.
Additional string // Additional custom info about this command.
parent *Command // Parent command for internal usage.
commands []Command // Sub commands of this command.
Name string // Command name(case-sensitive).
Usage string // A brief line description about its usage, eg: gf build main.go [OPTION]
Brief string // A brief info that describes what this command will do.
Description string // A detailed description.
Options []Option // Option array, configuring how this command act.
Func Function // Custom function.
FuncWithValue FuncWithValue // Custom function with output parameters that can interact with command caller.
HelpFunc Function // Custom help function
Examples string // Usage examples.
Additional string // Additional custom info about this command.
parent *Command // Parent command for internal usage.
commands []Command // Sub commands of this command.
}
// Function is a custom command callback function that is bound to a certain argument.
type Function func(ctx context.Context, parser *Parser) (err error)
// FuncWithValue is similar like Func but with output parameters that can interact with command caller.
type FuncWithValue func(ctx context.Context, parser *Parser) (out interface{}, err error)
// Option is the command value that is specified by a name or shor name.
// An Option can have or have no value bound to it.
type Option struct {
@ -58,7 +62,7 @@ func (c *Command) AddCommand(commands ...Command) error {
if cmd.Name == "" {
return gerror.New("command name should not be empty")
}
if cmd.Func == nil {
if cmd.Func == nil && cmd.FuncWithValue == nil {
return gerror.New("command function should not be empty")
}
cmd.parent = c
@ -66,3 +70,18 @@ func (c *Command) AddCommand(commands ...Command) error {
}
return nil
}
// AddObject adds one or more sub-commands to current command using struct object.
func (c *Command) AddObject(objects ...interface{}) error {
var (
commands []Command
)
for _, object := range objects {
tempCommand, err := NewFromObject(object)
if err != nil {
return err
}
commands = append(commands, *tempCommand)
}
return c.AddCommand(commands...)
}

View File

@ -8,8 +8,10 @@
package gcmd
import (
"context"
"reflect"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/structs"
@ -18,14 +20,22 @@ import (
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gmeta"
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/util/gvalid"
)
const (
tagNameDc = `dc`
tagNameAd = `ad`
tagNameDc = `dc`
tagNameAd = `ad`
tagNameRoot = `root`
)
func CommandsFromObject(object interface{}) (commands []Command, err error) {
var (
// defaultValueTags is the struct tag names for default value storing.
defaultValueTags = []string{"d", "default"}
)
// NewFromObject creates and returns a root command object using given object.
func NewFromObject(object interface{}) (rootCmd *Command, err error) {
originValueAndKind := utils.OriginValueAndKind(object)
if originValueAndKind.OriginKind != reflect.Struct {
return nil, gerror.Newf(
@ -33,18 +43,52 @@ func CommandsFromObject(object interface{}) (commands []Command, err error) {
originValueAndKind.InputValue.Type().String(),
)
}
//for i := 0; i < originValueAndKind.InputValue.NumMethod(); i++ {
// method := originValueAndKind.InputValue.Method(i)
//}
//for _, field := range fields {
//
//}
var (
nameSet = gset.NewStrSet()
subCommands []Command
)
for i := 0; i < originValueAndKind.InputValue.NumMethod(); i++ {
var (
root bool
method = originValueAndKind.InputValue.Method(i)
methodCommand Command
)
methodCommand, root, err = newCommandFromMethod(object, method)
if err != nil {
return nil, err
}
if nameSet.Contains(methodCommand.Name) {
return nil, gerror.Newf(
`command name should be unique, found duplicated command name in method "%s"`,
method.Type().String(),
)
}
if root {
if rootCmd != nil {
return nil, gerror.Newf(
`there should be only one root command in object, found duplicated in method "%s"`,
method.Type().String(),
)
}
rootCmd = &methodCommand
} else {
subCommands = append(subCommands, methodCommand)
}
}
if rootCmd == nil {
return nil, gerror.Newf(
`there should be one root command in object when creating command from object, but found none in object "%s"`,
originValueAndKind.InputValue.Type().String(),
)
}
if len(subCommands) > 0 {
err = rootCmd.AddCommand(subCommands...)
}
return
}
func newCommandFromMethod(object, method reflect.Value) (*Command, error) {
func newCommandFromMethod(object interface{}, method reflect.Value) (command Command, root bool, err error) {
var (
err error
reflectType = method.Type()
)
// Necessary validation for input/output parameters and naming.
@ -52,33 +96,33 @@ func newCommandFromMethod(object, method reflect.Value) (*Command, error) {
if reflectType.PkgPath() != "" {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid handler: %s.%s.%s defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
reflectType.PkgPath(), object.Type().Name(), reflectType.Name(), reflectType.String(),
`invalid command: %s.%s.%s defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
reflectType.PkgPath(), reflect.TypeOf(object).Name(), reflectType.Name(), reflectType.String(),
)
} else {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid handler: defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
`invalid command: defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
reflectType.String(),
)
}
return nil, err
return
}
if reflectType.In(0).String() != "context.Context" {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid handler: defined as "%s", but the first input parameter should be type of "context.Context"`,
`invalid command: defined as "%s", but the first input parameter should be type of "context.Context"`,
reflectType.String(),
)
return nil, err
return
}
if reflectType.Out(1).String() != "error" {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid handler: defined as "%s", but the last output parameter should be type of "error"`,
`invalid command: defined as "%s", but the last output parameter should be type of "error"`,
reflectType.String(),
)
return nil, err
return
}
// The input struct should be named as `xxxInput`.
if !gstr.HasSuffix(reflectType.In(1).String(), `Input`) {
@ -87,7 +131,7 @@ func newCommandFromMethod(object, method reflect.Value) (*Command, error) {
`invalid struct naming for input: defined as "%s", but it should be named with "Input" suffix like "xxxInput"`,
reflectType.In(1).String(),
)
return nil, err
return
}
// The output struct should be named as `xxxOutput`.
if !gstr.HasSuffix(reflectType.Out(0).String(), `Output`) {
@ -96,12 +140,11 @@ func newCommandFromMethod(object, method reflect.Value) (*Command, error) {
`invalid struct naming for output: defined as "%s", but it should be named with "Output" suffix like "xxxOutput"`,
reflectType.Out(0).String(),
)
return nil, err
return
}
var (
inputObject reflect.Value
outputObject reflect.Value
inputObject reflect.Value
)
if method.Type().In(1).Kind() == reflect.Ptr {
inputObject = reflect.New(method.Type().In(1).Elem()).Elem()
@ -109,37 +152,112 @@ func newCommandFromMethod(object, method reflect.Value) (*Command, error) {
inputObject = reflect.New(method.Type().In(1)).Elem()
}
if method.Type().Out(1).Kind() == reflect.Ptr {
outputObject = reflect.New(method.Type().Out(0).Elem()).Elem()
} else {
outputObject = reflect.New(method.Type().Out(0)).Elem()
}
// Command creating.
var (
cmd = Command{}
metaData = gmeta.Data(inputObject.Interface())
)
if err = gconv.Scan(metaData, &cmd); err != nil {
return nil, err
if err = gconv.Scan(metaData, &command); err != nil {
return
}
root = gconv.Bool(metaData[tagNameRoot])
// Name filed is necessary.
if cmd.Name == "" {
return nil, gerror.Newf(
if command.Name == "" {
err = gerror.Newf(
`command name cannot be empty, "name" tag not found in struct "%s"`,
inputObject.Type().String(),
)
return
}
if cmd.Description == "" {
cmd.Description = metaData[tagNameDc]
if command.Description == "" {
command.Description = metaData[tagNameDc]
}
if cmd.Additional == "" {
cmd.Additional = metaData[tagNameAd]
if command.Additional == "" {
command.Additional = metaData[tagNameAd]
}
if cmd.Options, err = newOptionsFromInput(inputObject.Interface()); err != nil {
return nil, err
if command.Options, err = newOptionsFromInput(inputObject.Interface()); err != nil {
return
}
return &cmd, nil
// Create function that has value return.
command.FuncWithValue = func(ctx context.Context, parser *Parser) (out interface{}, err error) {
defer func() {
if exception := recover(); exception != nil {
if v, ok := exception.(error); ok && gerror.HasStack(v) {
err = v
} else {
err = gerror.New(`exception recovered:` + gconv.String(exception))
}
}
}()
var (
data = gconv.Map(parser.GetOptAll())
inputValues = []reflect.Value{reflect.ValueOf(ctx)}
)
if data == nil {
data = map[string]interface{}{}
}
err = mergeDefaultStructValue(data, inputObject.Interface())
if err != nil {
return nil, err
}
// Construct input parameters.
if len(data) > 0 {
}
if inputObject.Kind() == reflect.Ptr {
err = gconv.Scan(data, inputObject.Interface())
} else {
err = gconv.Struct(data, inputObject.Addr().Interface())
}
if err != nil {
return
}
// Parameters validation.
if err = gvalid.New().Bail().Data(inputObject.Interface()).Assoc(data).Run(ctx); err != nil {
err = gerror.Current(err)
return
}
inputValues = append(inputValues, inputObject)
// Call handler with dynamic created parameter values.
results := method.Call(inputValues)
out = results[0].Interface()
if !results[1].IsNil() {
if v, ok := results[1].Interface().(error); ok {
err = v
}
}
return
}
return
}
// mergeDefaultStructValue merges the request parameters with default values from struct tag definition.
func mergeDefaultStructValue(data map[string]interface{}, pointer interface{}) error {
tagFields, err := structs.TagFields(pointer, defaultValueTags)
if err != nil {
return err
}
if len(tagFields) > 0 {
var (
foundKey string
foundValue interface{}
)
for _, field := range tagFields {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
if foundKey == "" {
data[field.Name()] = field.TagValue
} else {
if utils.IsEmpty(foundValue) {
data[foundKey] = field.TagValue
}
}
}
}
return nil
}
func newOptionsFromInput(object interface{}) (options []Option, err error) {
@ -153,7 +271,7 @@ func newOptionsFromInput(object interface{}) (options []Option, err error) {
for _, field := range fields {
var (
option = Option{}
metaData = gmeta.Data(field.Value.Interface())
metaData = field.TagMap()
)
if err = gconv.Scan(metaData, &option); err != nil {
return nil, err

View File

@ -12,22 +12,29 @@ import (
"fmt"
"os"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gstr"
)
// Run calls custom function that bound to this command.
func (c *Command) Run(ctx context.Context) error {
_, err := c.RunWithValue(ctx)
return err
}
// RunWithValue calls custom function that bound to this command with value output.
func (c *Command) RunWithValue(ctx context.Context) (value interface{}, err error) {
// Parse command arguments and options using default algorithm.
parser, err := Parse(nil)
if err != nil {
return err
return nil, err
}
args := parser.GetArgAll()
if len(args) == 1 {
if c.HelpFunc != nil {
return c.HelpFunc(ctx, parser)
return nil, c.HelpFunc(ctx, parser)
}
return c.defaultHelpFunc(ctx, parser)
return nil, c.defaultHelpFunc(ctx, parser)
}
// Exclude the root binary name.
@ -46,26 +53,32 @@ func (c *Command) Run(ctx context.Context) error {
)
c.Print()
return nil
return nil, nil
}
func (c *Command) doRun(ctx context.Context, parser *Parser) (err error) {
func (c *Command) doRun(ctx context.Context, parser *Parser) (value interface{}, err error) {
// Add built-in help option, just for info only.
c.Options = append(c.Options, defaultHelpOption)
// Check built-in help command.
if parser.ContainsOpt(helpOptionName) || parser.ContainsOpt(helpOptionNameShort) {
if c.HelpFunc != nil {
return c.HelpFunc(ctx, parser)
return nil, c.HelpFunc(ctx, parser)
}
return c.defaultHelpFunc(ctx, parser)
return nil, c.defaultHelpFunc(ctx, parser)
}
// Reparse the arguments for current command configuration.
parser, err = c.reParse(ctx, parser)
if err != nil {
return err
return nil, err
}
// Registered command function calling.
return c.Func(ctx, parser)
if c.Func != nil {
return nil, c.Func(ctx, parser)
}
if c.FuncWithValue != nil {
return c.FuncWithValue(ctx, parser)
}
return nil, gerror.New(`no function registered for current command`)
}
// reParse re-parses the arguments using option configuration of current command.
@ -80,7 +93,7 @@ func (c *Command) reParse(ctx context.Context, parser *Parser) (*Parser, error)
)
for _, option := range c.Options {
if option.Short != "" {
optionKey = fmt.Sprintf(`%s.%s`, option.Name, option.Short)
optionKey = fmt.Sprintf(`%s,%s`, option.Name, option.Short)
} else {
optionKey = option.Name
}

View File

@ -10,7 +10,7 @@ package gcmd_test
import (
"context"
"fmt"
"os"
"testing"
"github.com/gogf/gf/v2/frame/g"
@ -22,21 +22,22 @@ import (
type TestCmdObject struct{}
type TestCmdObjectInput struct {
g.Meta `name:"gf" usage:"gf env/test" brief:"gf env command" dc:"description" ad:"ad"`
g.Meta `root:"true" name:"root" usage:"root env/test" brief:"root env command" dc:"description" ad:"ad"`
}
type TestCmdObjectOutput struct{}
type TestCmdObjectEnvInput struct {
g.Meta `name:"env" usage:"gf env/test" brief:"gf env command" dc:"description" ad:"ad"`
Name string `v:"required" short:"n" orphan:"false" brief:"name for command"`
g.Meta `name:"env" usage:"root env" brief:"root env command" dc:"root env command description" ad:"root env command ad"`
}
type TestCmdObjectEnvOutput struct{}
type TestCmdObjectTestInput struct {
g.Meta `name:"test" usage:"gf env/test" brief:"gf test command" dc:"description" ad:"ad"`
Name string `v:"required" short:"n" orphan:"false" brief:"name for command"`
g.Meta `name:"test" usage:"root test" brief:"root test command" dc:"root test command description" ad:"root test command ad"`
Name string `v:"required" short:"n" orphan:"false" brief:"name for test command"`
}
type TestCmdObjectTestOutput struct {
Content string
}
type TestCmdObjectTestOutput struct{}
func (TestCmdObject) Root(ctx context.Context, in TestCmdObjectInput) (out *TestCmdObjectOutput, err error) {
return
@ -47,67 +48,42 @@ func (TestCmdObject) Env(ctx context.Context, in TestCmdObjectEnvInput) (out *Te
}
func (TestCmdObject) Test(ctx context.Context, in TestCmdObjectTestInput) (out *TestCmdObjectTestOutput, err error) {
out = &TestCmdObjectTestOutput{
Content: in.Name,
}
return
}
func Test_Command_AddObject(t *testing.T) {
func Test_Command_NewFromObject(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
ctx = gctx.New()
err error
ctx = gctx.New()
cmd, err = gcmd.NewFromObject(&TestCmdObject{})
)
commandRoot := &gcmd.Command{
Name: "gf",
}
// env
commandEnv := gcmd.Command{
Name: "env",
Func: func(ctx context.Context, parser *gcmd.Parser) error {
fmt.Println("env")
return nil
},
}
// test
commandTest := gcmd.Command{
Name: "test",
Brief: "test brief",
Description: "test description current Golang environment variables",
Examples: `
gf get github.com/gogf/gf
gf get github.com/gogf/gf@latest
gf get github.com/gogf/gf@master
gf get golang.org/x/sys
`,
Options: []gcmd.Option{
{
Name: "my-option",
Short: "o",
Brief: "It's my custom option",
Orphan: false,
},
{
Name: "another",
Short: "a",
Brief: "It's my another custom option",
Orphan: false,
},
},
Func: func(ctx context.Context, parser *gcmd.Parser) error {
fmt.Println("test")
return nil
},
}
err = commandRoot.AddCommand(
commandEnv,
commandTest,
)
if err != nil {
g.Log().Fatal(ctx, err)
}
t.AssertNil(err)
t.Assert(cmd.Name, "root")
if err = commandRoot.Run(ctx); err != nil {
g.Log().Fatal(ctx, err)
}
os.Args = []string{"root", "test", "-n=john"}
value, err := cmd.RunWithValue(ctx)
t.AssertNil(err)
t.Assert(value, `{"Content":"john"}`)
})
}
func Test_Command_AddObject(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
ctx = gctx.New()
command = gcmd.Command{
Name: "start",
}
)
err := command.AddObject(&TestCmdObject{})
t.AssertNil(err)
os.Args = []string{"start", "root", "test", "-n=john"}
value, err := command.RunWithValue(ctx)
t.AssertNil(err)
t.Assert(value, `{"Content":"john"}`)
})
}

View File

@ -10,25 +10,33 @@ import (
"context"
"time"
"github.com/gogf/gf/v2/internal/command"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gfsnotify"
)
const (
defaultCacheExpire = time.Minute // defaultCacheExpire is the expire time for file content caching in seconds.
defaultCacheExpire = "1m" // defaultCacheExpire is the expire time for file content caching in seconds.
commandEnvKeyForCache = "gf.gfile.cache" // commandEnvKeyForCache is the configuration key for command argument or environment configuring cache expire duration.
)
var (
// Default expire time for file content caching.
cacheExpire = gcmd.GetOptWithEnv(commandEnvKeyForCache, defaultCacheExpire).Duration()
cacheExpire = getCacheExpire()
// internalCache is the memory cache for internal usage.
internalCache = gcache.New()
)
func getCacheExpire() time.Duration {
d, err := time.ParseDuration(command.GetOptWithEnv(commandEnvKeyForCache, defaultCacheExpire))
if err != nil {
panic(err)
}
return d
}
// GetContentsWithCache returns string content of given file by `path` from cache.
// If there's no content in the cache, it will read it from disk file specified by `path`.
// The parameter `expire` specifies the caching time for this file content in seconds.

View File

@ -20,11 +20,12 @@ package gtimer
import (
"context"
"strconv"
"sync"
"time"
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/internal/command"
)
// Timer is the timer manager, which uses ticks to calculate the timing interval.
@ -47,15 +48,23 @@ const (
StatusStopped = 2 // Job or Timer is stopped.
StatusClosed = -1 // Job or Timer is closed and waiting to be deleted.
panicExit = "exit" // panicExit is used for custom job exit with panic.
defaultTimerInterval = 100 // defaultTimerInterval is the default timer interval in milliseconds.
defaultTimerInterval = "100" // defaultTimerInterval is the default timer interval in milliseconds.
commandEnvKeyForInterval = "gf.gtimer.interval" // commandEnvKeyForInterval is the key for command argument or environment configuring default interval duration for timer.
)
var (
defaultInterval = getDefaultInterval()
defaultTimer = New()
defaultInterval = gcmd.GetOptWithEnv(commandEnvKeyForInterval, defaultTimerInterval).Duration() * time.Millisecond
)
func getDefaultInterval() time.Duration {
n, err := strconv.Atoi(command.GetOptWithEnv(commandEnvKeyForInterval, defaultTimerInterval))
if err != nil {
panic(err)
}
return time.Duration(n) * time.Millisecond
}
// DefaultOptions creates and returns a default options object for Timer creation.
func DefaultOptions() TimerOptions {
return TimerOptions{

View File

@ -151,7 +151,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
}
// Normal unmarshalling interfaces checks.
if err, ok := bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok {
if err, ok = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok {
return err
}
@ -166,7 +166,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
// return v.UnmarshalValue(params)
// }
// Note that it's `pointerElemReflectValue` here not `pointerReflectValue`.
if err, ok := bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
if err, ok = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
return err
}
// Retrieve its element, may be struct at last.
@ -177,7 +177,11 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
// DO NOT use MapDeep here.
paramsMap := Map(paramsInterface)
if paramsMap == nil {
return gerror.NewCodef(gcode.CodeInvalidParameter, "convert params to map failed: %v", params)
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`convert params "%#v" to map failed`,
params,
)
}
// It only performs one converting to the same attribute.

View File

@ -142,10 +142,14 @@ func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDum
doDumpNumber(exportInternalInput)
case reflect.Chan:
buffer.WriteString(`<chan>`)
buffer.WriteString(fmt.Sprintf(`<%s>`, reflect.TypeOf(value).String()))
case reflect.Func:
buffer.WriteString(`<func>`)
if reflectValue.IsNil() || !reflectValue.IsValid() {
buffer.WriteString(`<nil>`)
} else {
buffer.WriteString(fmt.Sprintf(`<%s>`, reflect.TypeOf(value).String()))
}
default:
doDumpDefault(exportInternalInput)