From a139470d844af09ebe67e4a2cd25920c874cc979 Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Wed, 10 Jul 2024 13:43:25 +0800 Subject: [PATCH 01/18] init --- doc.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc.go b/doc.go index 4c1e5af..76acbb1 100644 --- a/doc.go +++ b/doc.go @@ -9,6 +9,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-10 // Package console // provides a console manager. -- Gitee From ca860130952409507e5e73cd4905e2a11c097efc Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Sat, 20 Jul 2024 14:59:03 +0800 Subject: [PATCH 02/18] init --- alias.go | 36 +++++++ config/console.yaml | 7 ++ examples/simple/commands/example.go | 35 ++++++ examples/simple/main.go | 27 +++++ go.mod | 4 + go.sum | 6 ++ src/argument.go | 161 ++++++++++++++++++++++++++++ src/command.go | 146 +++++++++++++++++++++++++ src/config.go | 69 ++++++++++++ src/config_command.go | 35 ++++++ src/container.go | 129 ++++++++++++++++++++++ src/init.go | 32 ++++++ src/kind.go | 26 +++++ src/option.go | 133 +++++++++++++++++++++++ src/output.go | 72 +++++++++++++ src/provider.go | 30 ++++++ src/provider_help.go | 122 +++++++++++++++++++++ 17 files changed, 1070 insertions(+) create mode 100644 alias.go create mode 100644 config/console.yaml create mode 100644 examples/simple/commands/example.go create mode 100644 examples/simple/main.go create mode 100644 go.sum create mode 100644 src/argument.go create mode 100644 src/command.go create mode 100644 src/config.go create mode 100644 src/config_command.go create mode 100644 src/container.go create mode 100644 src/init.go create mode 100644 src/kind.go create mode 100644 src/option.go create mode 100644 src/output.go create mode 100644 src/provider.go create mode 100644 src/provider_help.go diff --git a/alias.go b/alias.go new file mode 100644 index 0000000..5fe6dc7 --- /dev/null +++ b/alias.go @@ -0,0 +1,36 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-18 + +package console + +import "gitee.com/go-libs/console/src" + +type ( + Argument = src.Argument + Command = src.Command + Container = src.Container + Option = src.Option + Output = src.Output +) + +var ( + Config = src.Config + + NewArgument = src.NewArgument + NewCommand = src.NewCommand + NewContainer = src.NewContainer + NewOption = src.NewOption + NewOutput = src.NewOutput +) diff --git a/config/console.yaml b/config/console.yaml new file mode 100644 index 0000000..53eb71c --- /dev/null +++ b/config/console.yaml @@ -0,0 +1,7 @@ +# Console configurations. +# +# commands: +# name: +# options: +# key: value +# key-2: value 2 \ No newline at end of file diff --git a/examples/simple/commands/example.go b/examples/simple/commands/example.go new file mode 100644 index 0000000..3dbc71e --- /dev/null +++ b/examples/simple/commands/example.go @@ -0,0 +1,35 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-19 + +package commands + +import "gitee.com/go-libs/console" + +type Example struct { +} + +func (o *Example) Run(container *console.Container, command *console.Command) (err error) { + return +} + +func (o *Example) init() *Example { + return o +} + +func NewExample() *console.Command { + command := console.NewCommand("example") + provider := (&Example{}).init() + return command.SetProvider(provider) +} diff --git a/examples/simple/main.go b/examples/simple/main.go new file mode 100644 index 0000000..659a00a --- /dev/null +++ b/examples/simple/main.go @@ -0,0 +1,27 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-19 + +package main + +import ( + "gitee.com/go-libs/console" + "gitee.com/go-libs/console/examples/simple/commands" +) + +func main() { + container := console.NewContainer() + container.Add(commands.NewExample()) + container.Run() +} diff --git a/go.mod b/go.mod index 70ef810..b718134 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,7 @@ module gitee.com/go-libs/console go 1.18 + +require gitee.com/go-libs/config v1.0.1 + +require gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b6c8720 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +gitee.com/go-libs/config v1.0.1 h1:9q+pfk7g9xX/t87foZ81inNcyysDLCBqhbX78mh76vA= +gitee.com/go-libs/config v1.0.1/go.mod h1:azzGgnpQ4cTP7EXl7uctzfZ0L+y+v7JGYf65qfSwsXs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/argument.go b/src/argument.go new file mode 100644 index 0000000..37b15b4 --- /dev/null +++ b/src/argument.go @@ -0,0 +1,161 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-18 + +package src + +import ( + "regexp" + "strings" +) + +var ( + RegexArgumentMatchCommand = regexp.MustCompile(`^[a-zA-Z][-_a-zA-Z0-9:.]*$`) + RegexArgumentMatchOption = regexp.MustCompile(`^-`) + RegexArgumentMatchOptionWithFull = regexp.MustCompile(`^(-+)([a-zA-Z0-9][-_a-zA-Z0-9.]*)=(.+)`) + RegexArgumentMatchOptionWithName = regexp.MustCompile(`^(-+)([a-zA-Z0-9][-_a-zA-Z0-9.]*)$`) + + singletonArgument *Argument +) + +// Argument +// is a component used to parse command-line arguments. +type Argument struct { + commandName, scriptPath string + optionMapper map[string]string +} + +// NewArgument +// returns a singleton instance. +func NewArgument() *Argument { + return singletonArgument +} + +// GetCommandName +// returns the command name in argument. +func (o *Argument) GetCommandName() string { + return o.commandName +} + +// GetOption +// returns the option value with given name if exists. +func (o *Argument) GetOption(name string) (str string, has bool) { + str, has = o.optionMapper[name] + return +} + +// GetOptions +// returns all options mapper. +func (o *Argument) GetOptions() map[string]string { + return o.optionMapper +} + +// GetScriptPath +// returns the script path in argument. +func (o *Argument) GetScriptPath() string { + return o.scriptPath +} + +func (o *Argument) GetScriptExecutor() string { + return "go run main.go" +} + +// Parse +// with given args. +func (o *Argument) Parse(args []string) { + var last = "" + + // Range args from the parameters list. + for i, s := range args { + // Ignore empty string. + if s = strings.TrimSpace(s); s == "" { + continue + } + // Script path from the first arg. + if i == 0 { + o.scriptPath = s + continue + } + // Value of the option. + if !RegexArgumentMatchOption.MatchString(s) { + // Bind on the option. + if last != "" { + o.optionMapper[last] = s + last = "" + continue + } + // Generate as command name. + if o.commandName == "" && RegexArgumentMatchCommand.MatchString(s) { + o.commandName = s + } + continue + } + // Matched option definition as key/value pair. + if m := RegexArgumentMatchOptionWithFull.FindStringSubmatch(s); len(m) == 4 { + ck := m[2] + cv := m[3] + + if c1 := m[1]; c1 != "-" { + // Example as follows: + // --key="value" + o.optionMapper[ck] = cv + } else { + // Example as follows: + // -k="value" + // -key="value" + cl := len(ck) + for ci, c := range ck { + if (ci + 1) == cl { + o.optionMapper[string(c)] = cv + } else { + o.optionMapper[string(c)] = "" + } + } + } + continue + } + // Matched option definition as single. + if m := RegexArgumentMatchOptionWithName.FindStringSubmatch(s); len(m) == 3 { + ck := m[2] + if c1 := m[1]; c1 != "-" { + // Example as follows: + // --key + o.optionMapper[ck] = "" + last = ck + } else { + // Example as follows: + // -k + // -key + cl := len(ck) + for ci, c := range ck { + cs := string(c) + o.optionMapper[cs] = "" + if (ci + 1) == cl { + last = cs + } + } + } + continue + } + } +} + +// +---------------------------------------------------------------------------+ +// | Access methods | +// +---------------------------------------------------------------------------+ + +func (o *Argument) init() *Argument { + o.optionMapper = make(map[string]string) + return o +} diff --git a/src/command.go b/src/command.go new file mode 100644 index 0000000..a4aa722 --- /dev/null +++ b/src/command.go @@ -0,0 +1,146 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-18 + +package src + +// Command +// is a component used to schedule in container. +type Command struct { + defaulter bool + keys map[string]string + name, description string + options map[string]*Option + provider Provider +} + +// NewCommand +// creates a new command instance. +func NewCommand(name string) *Command { + return &Command{ + name: name, + keys: make(map[string]string), + options: make(map[string]*Option), + } +} + +// Add +// adds an options list into command. +func (o *Command) Add(options ...*Option) { + for _, opt := range options { + if opt == nil { + continue + } + + o.options[opt.name] = opt + + o.keys[opt.name] = opt.name + + if opt.shortName != "" { + o.keys[opt.shortName] = opt.name + } + } +} + +// GetName +// returns the command name. +func (o *Command) GetName() string { + return o.name +} + +// GetOption +// returns the option in command if exists. +func (o *Command) GetOption(key string) (opt *Option, has bool) { + if name, ok := o.keys[key]; ok { + opt, has = o.options[name] + } + return +} + +func (o *Command) GetOptions() map[string]*Option { + return o.options +} + +func (o *Command) SetDescription(description string) *Command { + o.description = description + return o +} + +func (o *Command) SetProvider(provider Provider) *Command { + o.provider = provider + return o +} + +// +---------------------------------------------------------------------------+ +// | Access methods | +// +---------------------------------------------------------------------------+ + +func (o *Command) run(container *Container) { + // Before handler. + if v, ok := o.provider.(ProviderBefore); ok { + if err := v.Before(container, o); err != nil { + container.GetOutput().Error(`%v`, err) + return + } + } + + // Runner handler. + err := o.provider.Run(container, o) + + // After handler. + if v, ok := o.provider.(ProviderAfter); ok { + v.After(container, o, err) + } +} + +func (o *Command) verify(container *Container) (success bool) { + // Iterate + // configuration in config file then assign it. + if c := Config.GetCommand(o.name); c != nil { + for k, v := range c.Options { + if opt, has := o.GetOption(k); has { + opt.assign(v) + } else { + container.GetOutput().Error(`unsupported config option: %s`, k) + return false + } + } + } + + // Iterate + // options in command-line arguments then assign it. + for k, v := range container.argument.GetOptions() { + if opt, has := o.GetOption(k); has { + opt.assign(v) + } else { + if len(k) == 1 { + container.GetOutput().Error(`unsupported option: -%s`, k) + } else { + container.GetOutput().Error(`unsupported option: --%s`, k) + } + return false + } + } + + // Validate + // command options. + for _, opt := range o.options { + if err := opt.validate(); err != nil { + container.GetOutput().Error(`%v`, err) + return false + } + } + + return true +} diff --git a/src/config.go b/src/config.go new file mode 100644 index 0000000..c2c4ec1 --- /dev/null +++ b/src/config.go @@ -0,0 +1,69 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-18 + +package src + +import ( + "gitee.com/go-libs/config" +) + +// Config +// is a singleton instance for console configuration. +var Config *Configuration + +type Configuration struct { + err error + Commands map[string]*CommandConfig `yaml:"commands"` +} + +func (o *Configuration) GetCommand(name string) *CommandConfig { + if cmd, ok := o.Commands[name]; ok { + return cmd + } + return nil +} + +// +---------------------------------------------------------------------------+ +// | Event methods | +// +---------------------------------------------------------------------------+ + +func (o *Configuration) After() (err error) { + if o.Commands == nil { + o.Commands = make(map[string]*CommandConfig) + } + + for _, cmd := range o.Commands { + if err = cmd.After(); err != nil { + break + } + } + + return +} + +// +---------------------------------------------------------------------------+ +// | Access methods | +// +---------------------------------------------------------------------------+ + +func (o *Configuration) init() *Configuration { + config.Location.SetFolders("./", "../config", "../../config") + + // Scan + // config fields from yaml file. + if src := config.Seek("console.yml", "console.yaml"); !src.Notfound() { + o.err = src.ScanYaml(o) + } + return o +} diff --git a/src/config_command.go b/src/config_command.go new file mode 100644 index 0000000..b295553 --- /dev/null +++ b/src/config_command.go @@ -0,0 +1,35 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-19 + +package src + +type CommandConfig struct { + Options map[string]string `yaml:"options"` +} + +func (o *CommandConfig) GetOption(name string) any { + if v, ok := o.Options[name]; ok { + return v + } + return nil +} + +func (o *CommandConfig) GetOptions() map[string]string { + return o.Options +} + +func (o *CommandConfig) After() error { + return nil +} diff --git a/src/container.go b/src/container.go new file mode 100644 index 0000000..3a9c1c6 --- /dev/null +++ b/src/container.go @@ -0,0 +1,129 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-18 + +package src + +import "os" + +var singletonContainer *Container + +// Container +// is a component used to manage all components for console. +type Container struct { + argument *Argument + commands map[string]*Command + defaultCommand *Command + output *Output +} + +// NewContainer +// returns a singleton instance of package. +func NewContainer() *Container { + return singletonContainer +} + +// Add +// a commands list into container. +func (o *Container) Add(commands ...*Command) { + for _, command := range commands { + if _, ok := o.commands[command.name]; ok { + continue + } + o.commands[command.name] = command + + if command.defaulter { + o.defaultCommand = command + } + } +} + +// GetArgument +// returns the singleton instance of Argument. +func (o *Container) GetArgument() *Argument { + return o.argument +} + +// GetCommand +// returns the singleton instance of Command if exists. +func (o *Container) GetCommand(name string) (cmd *Command, has bool) { + cmd, has = o.commands[name] + return +} + +// GetCommands +// returns a commands mapper in container. +func (o *Container) GetCommands() map[string]*Command { + return o.commands +} + +// GetOutput +// returns the Output component. +func (o *Container) GetOutput() *Output { + return o.output +} + +// Run +// the container +func (o *Container) Run() { + o.RunWith(os.Args) +} + +// RunWith +// run the container with given args. +func (o *Container) RunWith(args []string) { + var ( + command = o.defaultCommand + has bool + ) + + // Parse + // args on to fields. + o.argument.Parse(args) + + // Use + // command with given name by Argument. + if name := o.argument.GetCommandName(); name != "" { + if command, has = o.commands[name]; !has { + o.output.Error(`not recognize command: %s`, name) + return + } + } + + // Return + // if default command not specified. + if command == nil { + o.output.Error(`command not specified`) + return + } + + // Verify + // option and run it. + if command.verify(o) { + command.run(o) + } +} + +// +---------------------------------------------------------------------------+ +// | Access methods | +// +---------------------------------------------------------------------------+ + +func (o *Container) init() *Container { + o.argument = NewArgument() + o.commands = make(map[string]*Command) + o.output = NewOutput() + return o +} + +func (o *Container) run() {} diff --git a/src/init.go b/src/init.go new file mode 100644 index 0000000..00cf658 --- /dev/null +++ b/src/init.go @@ -0,0 +1,32 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-18 + +package src + +import "sync" + +var once = new(sync.Once) + +func init() { + once.Do(func() { + Config = (&Configuration{}).init() + + singletonOutput = (&Output{}).init() + singletonArgument = (&Argument{}).init() + + singletonContainer = (&Container{}).init() + singletonContainer.Add(NewHelp()) + }) +} diff --git a/src/kind.go b/src/kind.go new file mode 100644 index 0000000..79f3292 --- /dev/null +++ b/src/kind.go @@ -0,0 +1,26 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-19 + +package src + +type Kind int + +const ( + KindString Kind = iota + KindBool + KindFloat + KindInt + KindNull +) diff --git a/src/option.go b/src/option.go new file mode 100644 index 0000000..172875d --- /dev/null +++ b/src/option.go @@ -0,0 +1,133 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-18 + +package src + +import ( + "fmt" + "strconv" + "strings" +) + +// Option +// is an interface used to store command-line argument. +type Option struct { + assigned, required bool + defaultValue any + kind Kind + name, shortName, description string + value string + + vb bool + vf float64 + vi int64 + vs string +} + +// NewOption +// creates an option instance. +func NewOption(name string) *Option { + return &Option{name: name} +} + +func (o *Option) SetDefaultValue(dv any) *Option { + o.defaultValue = dv + return o +} + +func (o *Option) SetDescription(description string) *Option { + o.description = description + return o +} + +func (o *Option) SetKind(kind Kind) *Option { + o.kind = kind + return o +} + +func (o *Option) SetRequired(required bool) *Option { + o.required = required + return o +} + +func (o *Option) SetShortName(c byte) *Option { + o.shortName = string(c) + return o +} + +func (o *Option) Specified() bool { return o.assigned } + +func (o *Option) ToBool() bool { return o.vb } + +func (o *Option) ToFloat() float64 { return o.vf } + +func (o *Option) ToInt() int64 { return o.vi } + +func (o *Option) ToString() string { return o.vs } + +// +---------------------------------------------------------------------------+ +// | Access methods | +// +---------------------------------------------------------------------------+ + +// Assign +// user value to option. It's from config file or command line argument. +func (o *Option) assign(value string) { + o.assigned = true + o.value = strings.TrimSpace(value) +} + +// Validate option. +func (o *Option) validate() (err error) { + // Use user value that from config file or command-line argument. + value := o.value + + // Use default value. + if value == "" && o.defaultValue != nil { + value = fmt.Sprintf("%v", o.defaultValue) + } + + // Required. + if o.value == "" { + if o.required { + err = fmt.Errorf(`option is required: --%s`, o.name) + } + return + } + + // Kind is null. + if o.kind == KindNull { + err = fmt.Errorf(`option not accept any value: --%s`, o.name) + return + } + + // Parse with kind + switch o.kind { + case KindBool: + o.vb, err = strconv.ParseBool(value) + case KindFloat: + o.vf, err = strconv.ParseFloat(value, 64) + case KindInt: + o.vi, err = strconv.ParseInt(value, 10, 64) + case KindString: + o.vs = value + default: + return + } + + if err != nil { + err = fmt.Errorf(`invalid option value type: --%s`, o.name) + } + return +} diff --git a/src/output.go b/src/output.go new file mode 100644 index 0000000..7726a95 --- /dev/null +++ b/src/output.go @@ -0,0 +1,72 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-19 + +package src + +import ( + "fmt" + "io" + "os" +) + +var singletonOutput *Output + +// Output +// is a component used to send message to terminal. +type Output struct{} + +// NewOutput +// creates a new Output instance. +func NewOutput() *Output { + return singletonOutput +} + +// Info +// send info message to terminal. +func (o *Output) Info(format string, args ...any) { + o.send(os.Stdout, format, args...) +} + +// Error +// send error message to terminal. +func (o *Output) Error(format string, args ...any) { + o.send(os.Stderr, format, args...) +} + +// Fatal +// send fatal message to terminal. +func (o *Output) Fatal(format string, args ...any) { + o.send(os.Stderr, format, args...) +} + +// Render +// send list of message with info to terminal. +func (o *Output) Render(list []string) { + for _, s := range list { + o.Info(s) + } +} + +// +---------------------------------------------------------------------------+ +// | Access methods | +// +---------------------------------------------------------------------------+ + +func (o *Output) init() *Output { + return o +} + +func (o *Output) send(device io.Writer, format string, args ...any) { + _, _ = fmt.Fprintf(device, fmt.Sprintf(format, args...)+"\n") +} diff --git a/src/provider.go b/src/provider.go new file mode 100644 index 0000000..06f8ba1 --- /dev/null +++ b/src/provider.go @@ -0,0 +1,30 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-19 + +package src + +type ( + Provider interface { + Run(container *Container, command *Command) error + } + + ProviderAfter interface { + After(container *Container, command *Command, err error) + } + + ProviderBefore interface { + Before(container *Container, command *Command) error + } +) diff --git a/src/provider_help.go b/src/provider_help.go new file mode 100644 index 0000000..ebcc022 --- /dev/null +++ b/src/provider_help.go @@ -0,0 +1,122 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-19 + +package src + +import "sort" + +const ( + builtinHelpName = "help" + builtinHelpOptCommand = "command" + builtinHelpOptCommandShort = 'c' + builtinHelpOptCommandDescription = "Which command to be showed" +) + +// Help +// is a built-in command provider used to print usages about console and command. +type Help struct { + self bool + show *Command +} + +func (o *Help) After(container *Container, command *Command, err error) { + if err == nil && o.self { + container.GetOutput().Info(`Run 'go run main.go -c ' for more information on a command.`) + } +} + +func (o *Help) Before(container *Container, command *Command) (err error) { + // Which command to be showed. + if opt, has := command.GetOption(builtinHelpOptCommand); has { + if name := opt.ToString(); name != "" { + if cmd, ok := container.GetCommand(name); ok { + o.show = cmd + } + } + } + + // Set current command to be showed. + if o.show == nil { + o.show = command + } + + // Set self state. + o.self = o.show.name == builtinHelpName + + // Show usages. + if o.self { + container.GetOutput().Info("\nUsage: %s [OPTIONS]\n", container.argument.GetScriptExecutor()) + } else { + container.GetOutput().Info("\nUsage: %s %s [OPTIONS]\n", container.argument.GetScriptExecutor(), o.show.name) + } + return +} + +func (o *Help) Run(container *Container, command *Command) (err error) { + o.printOptions(container, command) + + if o.self { + o.printCommands(container, command) + } + return +} + +func (o *Help) printCommands(container *Container, command *Command) { +} + +func (o *Help) printOptions(container *Container, command *Command) { + var ( + list = make([]string, 0) + mapper = o.show.GetOptions() + ) + + for key, _ := range mapper { + list = append(list, key) + } + + sort.Strings(list) + + for _, key := range list { + container.GetOutput().Info(` --%s`, key) + } + +} + +// NewHelp +// creates a built-in provider. +func NewHelp() *Command { + var ( + cmd *Command + help = &Help{} + ) + + cmd = NewCommand(builtinHelpName) + cmd.defaulter = true + + // Add options. + cmd.Add( + // With command. + // + // -c name + // --command name + // --command=name + // --command="name" + NewOption(builtinHelpOptCommand). + SetShortName(builtinHelpOptCommandShort). + SetDescription(builtinHelpOptCommandDescription), + ) + + return cmd.SetProvider(help) +} -- Gitee From fc24a78e32a914eab22b8d524e85543e7d402af9 Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Sat, 20 Jul 2024 16:00:26 +0800 Subject: [PATCH 03/18] init --- alias.go | 9 ++ examples/simple/commands/example.go | 13 +++ src/command.go | 9 ++ src/kind.go | 15 +++ src/option.go | 47 ++++++++- src/provider_help.go | 144 ++++++++++++++++++++++++---- 6 files changed, 218 insertions(+), 19 deletions(-) diff --git a/alias.go b/alias.go index 5fe6dc7..17c2cde 100644 --- a/alias.go +++ b/alias.go @@ -21,10 +21,19 @@ type ( Argument = src.Argument Command = src.Command Container = src.Container + Kind = src.Kind Option = src.Option Output = src.Output ) +const ( + KindBool = src.KindBool + KindFloat = src.KindFloat + KindInt = src.KindInt + KindNull = src.KindNull + KindString = src.KindString +) + var ( Config = src.Config diff --git a/examples/simple/commands/example.go b/examples/simple/commands/example.go index 3dbc71e..36fa65c 100644 --- a/examples/simple/commands/example.go +++ b/examples/simple/commands/example.go @@ -30,6 +30,19 @@ func (o *Example) init() *Example { func NewExample() *console.Command { command := console.NewCommand("example") + command.SetDescription("Example command in main") provider := (&Example{}).init() + + command.Add( + console.NewOption("bool"). + SetDescription("Boolean value accepted"). + SetKind(console.KindBool). + SetShortName('b'), + console.NewOption("int").SetKind(console.KindInt).SetDefaultValue(3), + console.NewOption("float").SetKind(console.KindFloat).SetRequired(true).SetDescription("Float value accepted\nExamples like"), + console.NewOption("string").SetKind(console.KindString), + console.NewOption("null").SetKind(console.KindNull), + ) + return command.SetProvider(provider) } diff --git a/src/command.go b/src/command.go index a4aa722..a98ef49 100644 --- a/src/command.go +++ b/src/command.go @@ -15,6 +15,8 @@ package src +import "strings" + // Command // is a component used to schedule in container. type Command struct { @@ -86,6 +88,13 @@ func (o *Command) SetProvider(provider Provider) *Command { // | Access methods | // +---------------------------------------------------------------------------+ +func (o *Command) formatDescription() []string { + if o.description == "" { + return []string{} + } + return strings.Split(o.description, "\n") +} + func (o *Command) run(container *Container) { // Before handler. if v, ok := o.provider.(ProviderBefore); ok { diff --git a/src/kind.go b/src/kind.go index 79f3292..59d1ff7 100644 --- a/src/kind.go +++ b/src/kind.go @@ -24,3 +24,18 @@ const ( KindInt KindNull ) + +func (o Kind) Value() string { + switch o { + case KindBool: + return "bool" + case KindFloat: + return "float" + case KindInt: + return "int" + case KindString: + return "str" + default: + return "" + } +} diff --git a/src/option.go b/src/option.go index 172875d..397ace9 100644 --- a/src/option.go +++ b/src/option.go @@ -48,7 +48,7 @@ func (o *Option) SetDefaultValue(dv any) *Option { } func (o *Option) SetDescription(description string) *Option { - o.description = description + o.description = strings.TrimSpace(description) return o } @@ -88,6 +88,51 @@ func (o *Option) assign(value string) { o.value = strings.TrimSpace(value) } +func (o *Option) formatDescription() []string { + if o.description == "" { + return []string{} + } + return strings.Split(o.description, "\n") +} + +func (o *Option) formatOption() (str string) { + // Ahead with short name. + if o.shortName == "" { + // No short name. + str = " " + } else { + // With short name. + str = fmt.Sprintf(`-%s, `, o.shortName) + } + + // Mixed with name. + str += fmt.Sprintf(`--%s`, o.name) + + // Null value. + if o.kind == KindNull { + return + } + + // Option value. + val := "" + + // Default value. + if o.defaultValue != nil { + val = fmt.Sprintf(`%v`, o.defaultValue) + } + + // Use type value. + if val == "" { + val = o.kind.Value() + } + + // Append on column. + if val != "" { + str += fmt.Sprintf(`=%s`, val) + } + return +} + // Validate option. func (o *Option) validate() (err error) { // Use user value that from config file or command-line argument. diff --git a/src/provider_help.go b/src/provider_help.go index ebcc022..d5fb48a 100644 --- a/src/provider_help.go +++ b/src/provider_help.go @@ -15,13 +15,17 @@ package src -import "sort" +import ( + "fmt" + "sort" +) const ( builtinHelpName = "help" builtinHelpOptCommand = "command" builtinHelpOptCommandShort = 'c' builtinHelpOptCommandDescription = "Which command to be showed" + builtinHelpDescription = "A self-sufficient runtime for console" ) // Help @@ -31,14 +35,25 @@ type Help struct { show *Command } -func (o *Help) After(container *Container, command *Command, err error) { +// +---------------------------------------------------------------------------+ +// | Implements methods | +// +---------------------------------------------------------------------------+ + +// After +// is a handler used to call in Command. +func (o *Help) After(container *Container, _ *Command, err error) { if err == nil && o.self { - container.GetOutput().Info(`Run 'go run main.go -c ' for more information on a command.`) + container.GetOutput().Info("\nRun 'go run main.go -c ' for more information on a command.") } } +// Before +// is a handler used to call in Command. func (o *Help) Before(container *Container, command *Command) (err error) { - // Which command to be showed. + // Use current command. + o.show = command + + // Use specified command. if opt, has := command.GetOption(builtinHelpOptCommand); has { if name := opt.ToString(); name != "" { if cmd, ok := container.GetCommand(name); ok { @@ -47,51 +62,143 @@ func (o *Help) Before(container *Container, command *Command) (err error) { } } - // Set current command to be showed. - if o.show == nil { - o.show = command - } - // Set self state. o.self = o.show.name == builtinHelpName // Show usages. if o.self { - container.GetOutput().Info("\nUsage: %s [OPTIONS]\n", container.argument.GetScriptExecutor()) + container.GetOutput().Info("\nUsage: %s [OPTIONS]", container.argument.GetScriptExecutor()) } else { - container.GetOutput().Info("\nUsage: %s %s [OPTIONS]\n", container.argument.GetScriptExecutor(), o.show.name) + container.GetOutput().Info("\nUsage: %s %s [OPTIONS]", container.argument.GetScriptExecutor(), o.show.name) } return } +// Run +// is a handler used to call in Command. func (o *Help) Run(container *Container, command *Command) (err error) { + // Print option. o.printOptions(container, command) + // Print commands. if o.self { o.printCommands(container, command) } return } -func (o *Help) printCommands(container *Container, command *Command) { -} +// +---------------------------------------------------------------------------+ +// | Access methods | +// +---------------------------------------------------------------------------+ -func (o *Help) printOptions(container *Container, command *Command) { +// PrintCommands +// print command list and guides. +func (o *Help) printCommands(container *Container, _ *Command) { var ( + desc = make(map[string][]string) + format string list = make([]string, 0) - mapper = o.show.GetOptions() + mapper = container.GetCommands() + width int ) - for key, _ := range mapper { + for key, cmd := range mapper { list = append(list, key) + desc[key] = cmd.formatDescription() + + if w := len(key); width < w { + width = w + } } - sort.Strings(list) + if len(list) == 0 { + return + } + + format = fmt.Sprintf(` %%-%ds %%s`, width) + container.GetOutput().Info("\nCommands:") + // Iterate + // option with name sorted. + sort.Strings(list) for _, key := range list { - container.GetOutput().Info(` --%s`, key) + i := 0 + // Description with mul-line. + for _, s := range desc[key] { + if i++; i == 1 { + // With option. + container.GetOutput().Info(format, key, s) + } else { + // Without option. + container.GetOutput().Info(format, " ", s) + } + } + // Description not specified. + if i == 0 { + container.GetOutput().Info(format, key, "") + } } +} +// PrintOptions +// print option list and guides. +func (o *Help) printOptions(container *Container, _ *Command) { + var ( + desc = make(map[string][]string) + format string + keys = make(map[string]string, 0) + list = make([]string, 0) + mapper = o.show.GetOptions() + required = make(map[string]string, 0) + width int + ) + + // Iterate mapper for options. + for key, opt := range mapper { + list = append(list, key) + desc[key] = opt.formatDescription() + keys[key] = opt.formatOption() + + if opt.required { + required[key] = "*" + } else { + required[key] = " " + } + + // Options width. + if w := len(keys[key]); width < w { + width = w + } + } + + // No option in command. + if len(list) == 0 { + return + } + + format = fmt.Sprintf(` %%-%ds %%s %%s`, width) + container.GetOutput().Info("\nOptions:") + + // Iterate + // option with name sorted. + sort.Strings(list) + for _, key := range list { + i := 0 + // Description with mul-line. + for _, s := range desc[key] { + if i++; i == 1 { + // With option. + container.GetOutput().Info(format, keys[key], required[key], s) + } else { + // Without option. + container.GetOutput().Info(format, " ", " ", s) + } + } + // Description not specified. + if i == 0 { + container.GetOutput().Info(format, keys[key], required[key], "") + } + } } // NewHelp @@ -104,6 +211,7 @@ func NewHelp() *Command { cmd = NewCommand(builtinHelpName) cmd.defaulter = true + cmd.description = builtinHelpDescription // Add options. cmd.Add( -- Gitee From c95ecb9de3a71e423fcda8399b3bd8a18c9a0e5e Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 11:35:54 +0800 Subject: [PATCH 04/18] update comment --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index e9c9580..dab3a2e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,46 @@ # Console Provide a console for the application bootstrap. + +```go +import "gitee.com/go-libs/console" +``` + +## Provider + +Create example provider for command runner like follows. + +- `Run`(container `*console.Container`, command `*console.Command`) - `Required` +- `After`(container `*console.Container`, command `*console.Command`, err `error`) +- `Before`(container `*console.Container`, command `*console.Command`) `error` + +``` +type Example struct{} + +func (o *Example) Run(container *console.Container, command *console.Command) (err error) { + return +} +``` + +## Provider + +```text +func main(){ + command := console.NewCommand("example") + command.SetDescription("Example command in main") + command.SetProvider(&Example{}) + + container := console.NewContainer() + container.add(command) + container.Run() +} +``` + +## Run console + +You can run follow script on terminal. + +```shell +cd /data/sketch +go run main.go example +``` -- Gitee From 0f1d10331d49c4af2c1ebfddb3b9533b594ec589 Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 11:36:12 +0800 Subject: [PATCH 05/18] update comment --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dab3a2e..b08c04a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Provide a console for the application bootstrap. -```go +``` import "gitee.com/go-libs/console" ``` -- Gitee From 9596d0967eaf1c4bada98e47e12421e5338a405f Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 12:11:06 +0800 Subject: [PATCH 06/18] update comment --- src/argument.go | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/argument.go b/src/argument.go index 37b15b4..c674eec 100644 --- a/src/argument.go +++ b/src/argument.go @@ -43,13 +43,28 @@ func NewArgument() *Argument { } // GetCommandName -// returns the command name in argument. +// returns a command name that defined in argument. +// +// Usage: +// go run main.go example +// +// Output: +// example func (o *Argument) GetCommandName() string { return o.commandName } // GetOption -// returns the option value with given name if exists. +// returns an option value with given name if exists. +// +// Usage: +// go run main.go example --key=value +// +// Code: +// key, has := console.GetOption("key") +// +// Output: +// "value", true func (o *Argument) GetOption(name string) (str string, has bool) { str, has = o.optionMapper[name] return @@ -57,12 +72,23 @@ func (o *Argument) GetOption(name string) (str string, has bool) { // GetOptions // returns all options mapper. +// +// Output: +// { +// "key": "value" +// } func (o *Argument) GetOptions() map[string]string { return o.optionMapper } // GetScriptPath -// returns the script path in argument. +// returns a real script path that called in argument. +// +// Usage: (compiled) +// /data/sketch/app example +// +// Output: +// /data/sketch/app func (o *Argument) GetScriptPath() string { return o.scriptPath } @@ -72,7 +98,7 @@ func (o *Argument) GetScriptExecutor() string { } // Parse -// with given args. +// parses specified argument list given args. func (o *Argument) Parse(args []string) { var last = "" -- Gitee From 9bd7252d0545f73bdae1ad4c0735323a90fdc1be Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 12:17:49 +0800 Subject: [PATCH 07/18] update comment --- src/command.go | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/command.go b/src/command.go index a98ef49..40117ee 100644 --- a/src/command.go +++ b/src/command.go @@ -40,15 +40,25 @@ func NewCommand(name string) *Command { // Add // adds an options list into command. func (o *Command) Add(options ...*Option) { + // Iterate options in parameters. for _, opt := range options { + // Ignore nil option. if opt == nil { continue } + // Ignore duplicated option. + if _, ok := o.options[opt.name]; ok { + continue + } + + // Set mapping with full name. o.options[opt.name] = opt + // Set keys mapping with full name. o.keys[opt.name] = opt.name + // Set keys mapping with short name. if opt.shortName != "" { o.keys[opt.shortName] = opt.name } @@ -56,13 +66,25 @@ func (o *Command) Add(options ...*Option) { } // GetName -// returns the command name. +// returns a command name. +// +// Code: +// println(cmd.GetName()) +// +// Output: +// example func (o *Command) GetName() string { return o.name } // GetOption -// returns the option in command if exists. +// returns an option in command if exists. +// +// Code: +// opt, has := cmd.GetOption("key") +// +// Output: +// &Option{}, true func (o *Command) GetOption(key string) (opt *Option, has bool) { if name, ok := o.keys[key]; ok { opt, has = o.options[name] @@ -70,15 +92,26 @@ func (o *Command) GetOption(key string) (opt *Option, has bool) { return } +// GetOptions +// returns an option mapping. +// +// Output: +// { +// "key": &Option{...}, +// } func (o *Command) GetOptions() map[string]*Option { return o.options } +// SetDescription +// set description for command. func (o *Command) SetDescription(description string) *Command { - o.description = description + o.description = strings.TrimSpace(description) return o } +// SetProvider +// set command provider. Method's in provider will be called when scheduler fired. func (o *Command) SetProvider(provider Provider) *Command { o.provider = provider return o -- Gitee From f42939a5e8c5577261fac99b17c551e2ff7a1d6b Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 14:49:05 +0800 Subject: [PATCH 08/18] update comment --- src/command.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/command.go b/src/command.go index 40117ee..ca97bf1 100644 --- a/src/command.go +++ b/src/command.go @@ -15,7 +15,9 @@ package src -import "strings" +import ( + "strings" +) // Command // is a component used to schedule in container. @@ -121,6 +123,8 @@ func (o *Command) SetProvider(provider Provider) *Command { // | Access methods | // +---------------------------------------------------------------------------+ +// FormatDescription +// returns a string list for description if set and not empty. func (o *Command) formatDescription() []string { if o.description == "" { return []string{} @@ -128,6 +132,8 @@ func (o *Command) formatDescription() []string { return strings.Split(o.description, "\n") } +// Run +// runs the command by schedule provider methods. func (o *Command) run(container *Container) { // Before handler. if v, ok := o.provider.(ProviderBefore); ok { @@ -146,6 +152,8 @@ func (o *Command) run(container *Container) { } } +// Verify +// verifies the command access. func (o *Command) verify(container *Container) (success bool) { // Iterate // configuration in config file then assign it. @@ -153,26 +161,29 @@ func (o *Command) verify(container *Container) (success bool) { for k, v := range c.Options { if opt, has := o.GetOption(k); has { opt.assign(v) - } else { - container.GetOutput().Error(`unsupported config option: %s`, k) - return false + continue } + container.GetOutput().Error(`unsupported config option: %s`, k) + return false } } // Iterate // options in command-line arguments then assign it. for k, v := range container.argument.GetOptions() { + // Defined in command-line arguments. if opt, has := o.GetOption(k); has { opt.assign(v) + continue + } + + // Not defined in console. + if len(k) == 1 { + container.GetOutput().Error(`unsupported option: -%s`, k) } else { - if len(k) == 1 { - container.GetOutput().Error(`unsupported option: -%s`, k) - } else { - container.GetOutput().Error(`unsupported option: --%s`, k) - } - return false + container.GetOutput().Error(`unsupported option: --%s`, k) } + return false } // Validate @@ -183,6 +194,5 @@ func (o *Command) verify(container *Container) (success bool) { return false } } - return true } -- Gitee From 42e05beabe38f996ef33d68dcc615ce39e8fa5e9 Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 14:52:29 +0800 Subject: [PATCH 09/18] update comment --- src/command.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/command.go b/src/command.go index ca97bf1..ce070fc 100644 --- a/src/command.go +++ b/src/command.go @@ -67,6 +67,12 @@ func (o *Command) Add(options ...*Option) { } } +// GetDescription +// returns a command description. +func (o *Command) GetDescription() string { + return o.description +} + // GetName // returns a command name. // -- Gitee From d5de039496307ba14926e56ca363e58b52bb9adf Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 14:58:15 +0800 Subject: [PATCH 10/18] update comment --- src/config.go | 11 ++++++++--- src/config_command.go | 8 ++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/config.go b/src/config.go index c2c4ec1..3cebb33 100644 --- a/src/config.go +++ b/src/config.go @@ -23,11 +23,15 @@ import ( // is a singleton instance for console configuration. var Config *Configuration +// Configuration +// is a component used to configure console variables. type Configuration struct { err error Commands map[string]*CommandConfig `yaml:"commands"` } +// GetCommand +// returns a command configuration if define in config file. func (o *Configuration) GetCommand(name string) *CommandConfig { if cmd, ok := o.Commands[name]; ok { return cmd @@ -39,17 +43,20 @@ func (o *Configuration) GetCommand(name string) *CommandConfig { // | Event methods | // +---------------------------------------------------------------------------+ +// After +// is an event handler used to be called in config-file manager. func (o *Configuration) After() (err error) { + // Build init commands. if o.Commands == nil { o.Commands = make(map[string]*CommandConfig) } + // Depth initializer. for _, cmd := range o.Commands { if err = cmd.After(); err != nil { break } } - return } @@ -58,8 +65,6 @@ func (o *Configuration) After() (err error) { // +---------------------------------------------------------------------------+ func (o *Configuration) init() *Configuration { - config.Location.SetFolders("./", "../config", "../../config") - // Scan // config fields from yaml file. if src := config.Seek("console.yml", "console.yaml"); !src.Notfound() { diff --git a/src/config_command.go b/src/config_command.go index b295553..81b29b2 100644 --- a/src/config_command.go +++ b/src/config_command.go @@ -15,10 +15,14 @@ package src +// CommandConfig +// is a component used to define options. type CommandConfig struct { Options map[string]string `yaml:"options"` } +// GetOption +// returns an option definition if defined in config file. func (o *CommandConfig) GetOption(name string) any { if v, ok := o.Options[name]; ok { return v @@ -26,10 +30,14 @@ func (o *CommandConfig) GetOption(name string) any { return nil } +// GetOptions +// returns an option mapper of a command. func (o *CommandConfig) GetOptions() map[string]string { return o.Options } +// After +// is an event handler that called by parent. func (o *CommandConfig) After() error { return nil } -- Gitee From 3bdf996683755cadbf95aad333055e10d6ff5b73 Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 15:04:30 +0800 Subject: [PATCH 11/18] update comment --- src/container.go | 58 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/src/container.go b/src/container.go index 3a9c1c6..d830dca 100644 --- a/src/container.go +++ b/src/container.go @@ -37,18 +37,46 @@ func NewContainer() *Container { // Add // a commands list into container. func (o *Container) Add(commands ...*Command) { + // Range commands in parameters. for _, command := range commands { + // Ignore nil command. + if command == nil { + continue + } + + // Ignore duplicated command. if _, ok := o.commands[command.name]; ok { continue } + + // Update command mapping. o.commands[command.name] = command + // Set as default. if command.defaulter { o.defaultCommand = command } } } +// Del +// deletes a commands list from container. +func (o *Container) Del(commands ...*Command) { + for _, cmd := range commands { + if _, ok := o.commands[cmd.name]; ok { + o.DelByName(cmd.name) + } + } +} + +// DelByName +// deletes a command from container. +func (o *Container) DelByName(name string) { + if _, ok := o.commands[name]; ok { + delete(o.commands, name) + } +} + // GetArgument // returns the singleton instance of Argument. func (o *Container) GetArgument() *Argument { @@ -77,12 +105,27 @@ func (o *Container) GetOutput() *Output { // Run // the container func (o *Container) Run() { - o.RunWith(os.Args) + o.run(os.Args) } // RunWith // run the container with given args. func (o *Container) RunWith(args []string) { + o.run(args) +} + +// +---------------------------------------------------------------------------+ +// | Access methods | +// +---------------------------------------------------------------------------+ + +func (o *Container) init() *Container { + o.argument = NewArgument() + o.commands = make(map[string]*Command) + o.output = NewOutput() + return o +} + +func (o *Container) run(args []string) { var ( command = o.defaultCommand has bool @@ -114,16 +157,3 @@ func (o *Container) RunWith(args []string) { command.run(o) } } - -// +---------------------------------------------------------------------------+ -// | Access methods | -// +---------------------------------------------------------------------------+ - -func (o *Container) init() *Container { - o.argument = NewArgument() - o.commands = make(map[string]*Command) - o.output = NewOutput() - return o -} - -func (o *Container) run() {} -- Gitee From 347683e98474e363be531a0fe45170ee99af81e0 Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 15:45:24 +0800 Subject: [PATCH 12/18] update comment --- alias.go | 12 +++++-- src/config.go | 2 +- src/config_command.go | 2 +- src/option.go | 24 ++++++++++++-- src/provider_help.go | 3 ++ tests/argument_test.go | 33 +++++++++++++++++++ tests/config_test.go | 27 +++++++++++++++ tests/option_test.go | 75 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 tests/argument_test.go create mode 100644 tests/config_test.go create mode 100644 tests/option_test.go diff --git a/alias.go b/alias.go index 17c2cde..bcab73d 100644 --- a/alias.go +++ b/alias.go @@ -35,11 +35,17 @@ const ( ) var ( - Config = src.Config - + Config = src.Config NewArgument = src.NewArgument NewCommand = src.NewCommand NewContainer = src.NewContainer - NewOption = src.NewOption NewOutput = src.NewOutput ) + +var ( + NewOption = src.NewOption + NewBoolOption = src.NewBoolOption + NewFloatOption = src.NewFloatOption + NewIntOption = src.NewIntOption + NewNullOption = src.NewNullOption +) diff --git a/src/config.go b/src/config.go index 3cebb33..0423550 100644 --- a/src/config.go +++ b/src/config.go @@ -27,7 +27,7 @@ var Config *Configuration // is a component used to configure console variables. type Configuration struct { err error - Commands map[string]*CommandConfig `yaml:"commands"` + Commands map[string]*CommandConfig `yaml:"commands" json:"commands"` } // GetCommand diff --git a/src/config_command.go b/src/config_command.go index 81b29b2..81719f6 100644 --- a/src/config_command.go +++ b/src/config_command.go @@ -18,7 +18,7 @@ package src // CommandConfig // is a component used to define options. type CommandConfig struct { - Options map[string]string `yaml:"options"` + Options map[string]string `yaml:"options" json:"options"` } // GetOption diff --git a/src/option.go b/src/option.go index 397ace9..5c9c30f 100644 --- a/src/option.go +++ b/src/option.go @@ -22,7 +22,7 @@ import ( ) // Option -// is an interface used to store command-line argument. +// is an interface used to store command options. type Option struct { assigned, required bool defaultValue any @@ -39,7 +39,27 @@ type Option struct { // NewOption // creates an option instance. func NewOption(name string) *Option { - return &Option{name: name} + return &Option{name: name, kind: KindString} +} + +func NewBoolOption(name string) *Option { + return &Option{name: name, kind: KindBool} +} + +func NewFloatOption(name string) *Option { + return &Option{name: name, kind: KindFloat} +} + +func NewIntOption(name string) *Option { + return &Option{name: name, kind: KindInt} +} + +func NewNullOption(name string) *Option { + return &Option{name: name, kind: KindNull} +} + +func (o *Option) Generate() string { + return o.formatOption() } func (o *Option) SetDefaultValue(dv any) *Option { diff --git a/src/provider_help.go b/src/provider_help.go index d5fb48a..cdd682b 100644 --- a/src/provider_help.go +++ b/src/provider_help.go @@ -103,6 +103,9 @@ func (o *Help) printCommands(container *Container, _ *Command) { ) for key, cmd := range mapper { + if cmd.name == builtinHelpName { + continue + } list = append(list, key) desc[key] = cmd.formatDescription() diff --git a/tests/argument_test.go b/tests/argument_test.go new file mode 100644 index 0000000..574991d --- /dev/null +++ b/tests/argument_test.go @@ -0,0 +1,33 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-22 + +package tests + +import ( + "gitee.com/go-libs/console" +) + +func ExampleArgument_GetCommandName() { + argument := console.NewArgument() + argument.Parse([]string{ + "/data/sketch/example", // compiled binary source file + `-k`, "value-1", + `--key1`, "value-2", + `--key2="value"`, + }) + + res := argument.GetCommandName() + console.NewOutput().Info("%v", res) +} diff --git a/tests/config_test.go b/tests/config_test.go new file mode 100644 index 0000000..eb8bc8a --- /dev/null +++ b/tests/config_test.go @@ -0,0 +1,27 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-22 + +package tests + +import ( + "encoding/json" + "gitee.com/go-libs/console" + "testing" +) + +func TestConfig_Get(t *testing.T) { + buf, _ := json.Marshal(console.Config) + t.Logf(`%s`, buf) +} diff --git a/tests/option_test.go b/tests/option_test.go new file mode 100644 index 0000000..2291539 --- /dev/null +++ b/tests/option_test.go @@ -0,0 +1,75 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: wsfuyibing <682805@qq.com> +// Date: 2024-07-22 + +package tests + +import ( + "gitee.com/go-libs/console" +) + +func ExampleNewOption() { + opt := console.NewOption("host") + opt.SetRequired(true) + opt.SetDescription("Connect to host address, format as IPv4") + opt.SetShortName('h') + opt.SetDefaultValue("127.0.0.1") + + // Output: + // -h, --host=127.0.0.1 + console.NewOutput().Info(opt.Generate()) +} + +func ExampleNewBoolOption() { + opt := console.NewBoolOption("ssl") + opt.SetRequired(true) + opt.SetDescription("Use ssl or not") + opt.SetShortName('s') + opt.SetDefaultValue("true") + + // Output: + // -s, --ssl=true + console.NewOutput().Info(opt.Generate()) +} + +func ExampleNewFloatOption() { + opt := console.NewFloatOption("key") + opt.SetDescription("About key") + opt.SetShortName('k') + + // Output: + // -k, --key=float + console.NewOutput().Info(opt.Generate()) +} + +func ExampleNewIntOption() { + opt := console.NewIntOption("key") + opt.SetDescription("About key") + opt.SetShortName('k') + opt.SetDefaultValue(0) + + // Output: + // -k, --key=0 + console.NewOutput().Info(opt.Generate()) +} + +func ExampleNewNullOption() { + opt := console.NewNullOption("key") + opt.SetDescription("About key") + opt.SetShortName('k') + + // Output: + // -k, --key + console.NewOutput().Info(opt.Generate()) +} -- Gitee From 5b0ea76e13cc2b78f641c5f8b26b02f98d45f942 Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 15:47:22 +0800 Subject: [PATCH 13/18] update comment --- src/kind.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/kind.go b/src/kind.go index 59d1ff7..4bbfcc6 100644 --- a/src/kind.go +++ b/src/kind.go @@ -18,13 +18,15 @@ package src type Kind int const ( - KindString Kind = iota - KindBool - KindFloat - KindInt - KindNull + KindString Kind = iota // Value of option accept any string. + KindBool // Value of option accept any boolean values + KindFloat // Value of option accept any float values + KindInt // Value of option accept any integer values + KindNull // Value of option do not accept any value. ) +// Value +// returns a type mapping string. func (o Kind) Value() string { switch o { case KindBool: -- Gitee From 7cd65e0b4c25a5eda1146a57713d637551806cb8 Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 15:51:08 +0800 Subject: [PATCH 14/18] update comment --- src/option.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/option.go b/src/option.go index 5c9c30f..235846b 100644 --- a/src/option.go +++ b/src/option.go @@ -62,6 +62,20 @@ func (o *Option) Generate() string { return o.formatOption() } +func (o *Option) GetDescription() string { return o.description } + +func (o *Option) GetDefaultValue() any { return o.defaultValue } + +func (o *Option) GetKind() Kind { return o.kind } + +func (o *Option) GetName() string { return o.name } + +func (o *Option) GetRequired() bool { return o.required } + +func (o *Option) GetShortName() string { return o.shortName } + +func (o *Option) GetSpecified() bool { return o.assigned } + func (o *Option) SetDefaultValue(dv any) *Option { o.defaultValue = dv return o @@ -87,8 +101,6 @@ func (o *Option) SetShortName(c byte) *Option { return o } -func (o *Option) Specified() bool { return o.assigned } - func (o *Option) ToBool() bool { return o.vb } func (o *Option) ToFloat() float64 { return o.vf } -- Gitee From e43e898e0751776231c52800f7ac916a3a054aa6 Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 15:53:30 +0800 Subject: [PATCH 15/18] update comment --- src/provider.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/provider.go b/src/provider.go index 06f8ba1..6ca87bb 100644 --- a/src/provider.go +++ b/src/provider.go @@ -16,14 +16,22 @@ package src type ( + // Provider + // is a required handler. It's called when command scheduled and Before + // returned nil. Provider interface { Run(container *Container, command *Command) error } + // ProviderAfter + // is an optional handler. It's called when required handler done. ProviderAfter interface { After(container *Container, command *Command, err error) } + // ProviderBefore + // is an optional handler. It's called when command scheduled, Ignore other + // handlers if error returned ProviderBefore interface { Before(container *Container, command *Command) error } -- Gitee From 2bb73416632fd8022e6532166c5c16f7e3fd0770 Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Mon, 22 Jul 2024 16:01:53 +0800 Subject: [PATCH 16/18] update comment --- src/provider_help.go | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/provider_help.go b/src/provider_help.go index cdd682b..ed72f65 100644 --- a/src/provider_help.go +++ b/src/provider_help.go @@ -18,6 +18,7 @@ package src import ( "fmt" "sort" + "strings" ) const ( @@ -50,37 +51,58 @@ func (o *Help) After(container *Container, _ *Command, err error) { // Before // is a handler used to call in Command. func (o *Help) Before(container *Container, command *Command) (err error) { - // Use current command. + // Use + // current command. o.show = command - // Use specified command. + // Use + // specified command. if opt, has := command.GetOption(builtinHelpOptCommand); has { if name := opt.ToString(); name != "" { if cmd, ok := container.GetCommand(name); ok { o.show = cmd + } else { + err = fmt.Errorf("command not found: %s", name) + return } } } - // Set self state. + // Self + // state compared with. o.self = o.show.name == builtinHelpName - // Show usages. + // Print + // usage of a command. if o.self { + // Any command. container.GetOutput().Info("\nUsage: %s [OPTIONS]", container.argument.GetScriptExecutor()) } else { + // Current command. container.GetOutput().Info("\nUsage: %s %s [OPTIONS]", container.argument.GetScriptExecutor(), o.show.name) } + + // Print + // description of a command. + if desc := o.show.description; desc != "" { + for _, s := range strings.Split(desc, "\n") { + if s = strings.TrimSpace(s); s != "" { + container.GetOutput().Info("\n%s", s) + } + } + } return } // Run // is a handler used to call in Command. func (o *Help) Run(container *Container, command *Command) (err error) { - // Print option. + // Print + // options list of a command. o.printOptions(container, command) - // Print commands. + // Print + // commands list of the container. if o.self { o.printCommands(container, command) } @@ -108,7 +130,6 @@ func (o *Help) printCommands(container *Container, _ *Command) { } list = append(list, key) desc[key] = cmd.formatDescription() - if w := len(key); width < w { width = w } -- Gitee From 2b212bce04c5085bb9c5e23d22e42a9c76785ecb Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Tue, 23 Jul 2024 18:34:38 +0800 Subject: [PATCH 17/18] append context on event handler --- examples/simple/commands/example.go | 7 +++++-- src/command.go | 9 +++++---- src/container.go | 15 +++++++++++++-- src/provider.go | 8 +++++--- src/provider_help.go | 7 ++++--- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/examples/simple/commands/example.go b/examples/simple/commands/example.go index 36fa65c..a789245 100644 --- a/examples/simple/commands/example.go +++ b/examples/simple/commands/example.go @@ -15,12 +15,15 @@ package commands -import "gitee.com/go-libs/console" +import ( + "context" + "gitee.com/go-libs/console" +) type Example struct { } -func (o *Example) Run(container *console.Container, command *console.Command) (err error) { +func (o *Example) Run(ctx context.Context, container *console.Container, command *console.Command) (err error) { return } diff --git a/src/command.go b/src/command.go index ce070fc..d1e3e6e 100644 --- a/src/command.go +++ b/src/command.go @@ -16,6 +16,7 @@ package src import ( + "context" "strings" ) @@ -140,21 +141,21 @@ func (o *Command) formatDescription() []string { // Run // runs the command by schedule provider methods. -func (o *Command) run(container *Container) { +func (o *Command) run(ctx context.Context, container *Container) { // Before handler. if v, ok := o.provider.(ProviderBefore); ok { - if err := v.Before(container, o); err != nil { + if err := v.Before(ctx, container, o); err != nil { container.GetOutput().Error(`%v`, err) return } } // Runner handler. - err := o.provider.Run(container, o) + err := o.provider.Run(ctx, container, o) // After handler. if v, ok := o.provider.(ProviderAfter); ok { - v.After(container, o, err) + v.After(ctx, container, o, err) } } diff --git a/src/container.go b/src/container.go index d830dca..22509fd 100644 --- a/src/container.go +++ b/src/container.go @@ -15,7 +15,10 @@ package src -import "os" +import ( + "context" + "os" +) var singletonContainer *Container @@ -24,6 +27,7 @@ var singletonContainer *Container type Container struct { argument *Argument commands map[string]*Command + ctx context.Context defaultCommand *Command output *Output } @@ -59,6 +63,12 @@ func (o *Container) Add(commands ...*Command) { } } +// Context +// returns a root context. +func (o *Container) Context() context.Context { + return o.ctx +} + // Del // deletes a commands list from container. func (o *Container) Del(commands ...*Command) { @@ -119,6 +129,7 @@ func (o *Container) RunWith(args []string) { // +---------------------------------------------------------------------------+ func (o *Container) init() *Container { + o.ctx = context.Background() o.argument = NewArgument() o.commands = make(map[string]*Command) o.output = NewOutput() @@ -154,6 +165,6 @@ func (o *Container) run(args []string) { // Verify // option and run it. if command.verify(o) { - command.run(o) + command.run(o.ctx, o) } } diff --git a/src/provider.go b/src/provider.go index 6ca87bb..419ef70 100644 --- a/src/provider.go +++ b/src/provider.go @@ -15,24 +15,26 @@ package src +import "context" + type ( // Provider // is a required handler. It's called when command scheduled and Before // returned nil. Provider interface { - Run(container *Container, command *Command) error + Run(ctx context.Context, container *Container, command *Command) error } // ProviderAfter // is an optional handler. It's called when required handler done. ProviderAfter interface { - After(container *Container, command *Command, err error) + After(ctx context.Context, container *Container, command *Command, err error) } // ProviderBefore // is an optional handler. It's called when command scheduled, Ignore other // handlers if error returned ProviderBefore interface { - Before(container *Container, command *Command) error + Before(ctx context.Context, container *Container, command *Command) error } ) diff --git a/src/provider_help.go b/src/provider_help.go index ed72f65..ac19c8b 100644 --- a/src/provider_help.go +++ b/src/provider_help.go @@ -16,6 +16,7 @@ package src import ( + "context" "fmt" "sort" "strings" @@ -42,7 +43,7 @@ type Help struct { // After // is a handler used to call in Command. -func (o *Help) After(container *Container, _ *Command, err error) { +func (o *Help) After(ctx context.Context, container *Container, _ *Command, err error) { if err == nil && o.self { container.GetOutput().Info("\nRun 'go run main.go -c ' for more information on a command.") } @@ -50,7 +51,7 @@ func (o *Help) After(container *Container, _ *Command, err error) { // Before // is a handler used to call in Command. -func (o *Help) Before(container *Container, command *Command) (err error) { +func (o *Help) Before(ctx context.Context, container *Container, command *Command) (err error) { // Use // current command. o.show = command @@ -96,7 +97,7 @@ func (o *Help) Before(container *Container, command *Command) (err error) { // Run // is a handler used to call in Command. -func (o *Help) Run(container *Container, command *Command) (err error) { +func (o *Help) Run(ctx context.Context, container *Container, command *Command) (err error) { // Print // options list of a command. o.printOptions(container, command) -- Gitee From d29244d7d90eaedea1abd04559d78f7597940ddc Mon Sep 17 00:00:00 2001 From: wsfuyibing Date: Tue, 23 Jul 2024 18:35:17 +0800 Subject: [PATCH 18/18] append context on event handler --- examples/simple/commands/example.go | 2 +- src/provider_help.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/simple/commands/example.go b/examples/simple/commands/example.go index a789245..7e172af 100644 --- a/examples/simple/commands/example.go +++ b/examples/simple/commands/example.go @@ -23,7 +23,7 @@ import ( type Example struct { } -func (o *Example) Run(ctx context.Context, container *console.Container, command *console.Command) (err error) { +func (o *Example) Run(_ context.Context, _ *console.Container, _ *console.Command) (err error) { return } diff --git a/src/provider_help.go b/src/provider_help.go index ac19c8b..9b1dd95 100644 --- a/src/provider_help.go +++ b/src/provider_help.go @@ -43,7 +43,7 @@ type Help struct { // After // is a handler used to call in Command. -func (o *Help) After(ctx context.Context, container *Container, _ *Command, err error) { +func (o *Help) After(_ context.Context, container *Container, _ *Command, err error) { if err == nil && o.self { container.GetOutput().Info("\nRun 'go run main.go -c ' for more information on a command.") } @@ -51,7 +51,7 @@ func (o *Help) After(ctx context.Context, container *Container, _ *Command, err // Before // is a handler used to call in Command. -func (o *Help) Before(ctx context.Context, container *Container, command *Command) (err error) { +func (o *Help) Before(_ context.Context, container *Container, command *Command) (err error) { // Use // current command. o.show = command @@ -97,7 +97,7 @@ func (o *Help) Before(ctx context.Context, container *Container, command *Comman // Run // is a handler used to call in Command. -func (o *Help) Run(ctx context.Context, container *Container, command *Command) (err error) { +func (o *Help) Run(_ context.Context, container *Container, command *Command) (err error) { // Print // options list of a command. o.printOptions(container, command) -- Gitee