Pain Points in Command Line Management
Previously, we introduced command line management by obtaining parsed parameters and option data through the parser
object of the callback function. The following pain points exist when using it:
- Need to manually pass hard-coded parameter index or option name information to obtain data.
- Difficult to define descriptions and introductions for parameters/options.
- Difficult to define data types for parameters/options.
- Difficult to perform general data validation on parameters/options.
- It is a disaster for projects that need to manage a large number of command lines.
Object-Oriented Command Management
Let's take a simple example of structured parameter management. We transform the previously introduced Command
example into structured management:
package main
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gctx"
)
type cMain struct {
g.Meta `name:"main"`
}
type cMainHttpInput struct {
g.Meta `name:"http" brief:"start http server"`
}
type cMainHttpOutput struct{}
type cMainGrpcInput struct {
g.Meta `name:"grpc" brief:"start grpc server"`
}
type cMainGrpcOutput struct{}
func (c *cMain) Http(ctx context.Context, in cMainHttpInput) (out *cMainHttpOutput, err error) {
fmt.Println("start http server")
return
}
func (c *cMain) Grpc(ctx context.Context, in cMainGrpcInput) (out *cMainGrpcOutput, err error) {
fmt.Println("start grpc server")
return
}
func main() {
cmd, err := gcmd.NewFromObject(cMain{})
if err != nil {
panic(err)
}
cmd.Run(gctx.New())
}
As you can see, we manage the parent command in the form of an object, manage its subcommands in the form of methods, and define the description/parameters/options of subcommands through standardized Input
input parameter objects. In most scenarios, you can ignore the use of the Output
return object, but for standardization and extensibility, it must be retained. If not used, just return nil
for this return parameter. The struct tags used will be introduced later.
We compile the example code and run it to see the effect:
$ main
USAGE
main COMMAND [OPTION]
COMMAND
http start http server
grpc start grpc server
DESCRIPTION
this is the command entry for starting your process
Using the http
command:
$ main http
start http server
Using the grpc
command:
$ main grpc
start grpc server
The effect is consistent with the previously introduced example.
Structured Parameter Management
Since the command line is managed through objects, let's carefully look at how parameters/options are managed through structure.
We simplify the above instance a bit for a simple example of starting an http
service through the http
command:
package main
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gctx"
)
type cMain struct {
g.Meta `name:"main" brief:"start http server"`
}
type cMainHttpInput struct {
g.Meta `name:"http" brief:"start http server"`
Name string `v:"required" name:"NAME" arg:"true" brief:"server name"`
Port int `v:"required" short:"p" name:"port" brief:"port of http server"`
}
type cMainHttpOutput struct{}
func (c *cMain) Http(ctx context.Context, in cMainHttpInput) (out *cMainHttpOutput, err error) {
s := g.Server(in.Name)
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("Hello world")
})
s.SetPort(in.Port)
s.Run()
return
}
func main() {
cmd, err := gcmd.NewFromObject(cMain{})
if err != nil {
panic(err)
}
cmd.Run(gctx.New())
}
We defined two input parameters for the http
command:
NAME
The name of the service, entered through a parameter. The uppercase form is used here for easy display in the automatically generated help information.port
The port of the service, entered through thep/port
option.
We also use the v:"required"
validation tag to bind mandatory validation rules for these two parameters. Yes, in the GoFrame
framework, a unified validation component is used wherever validation is involved. For details, please refer to the chapter: Data Validation
Let's compile it and see the effect:
$ main http
arguments validation failed for command "http": The Name field is required
1. arguments validation failed for command "http"
1). github.com/gogf/gf/v2/os/gcmd.newCommandFromMethod.func1
/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_object.go:290
2). github.com/gogf/gf/v2/os/gcmd.(*Command).doRun
/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_run.go:120
3). github.com/gogf/gf/v2/os/gcmd.(*Command).RunWithValueError
/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_run.go:77
4). github.com/gogf/gf/v2/os/gcmd.(*Command).RunWithValue
/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_run.go:32
5). github.com/gogf/gf/v2/os/gcmd.(*Command).Run
/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.test/test.go:38
2. The Name field is required
Upon execution, there's an error due to data validation indicating that both mandatory parameters (Name/Port
) must be passed.
The error here prints stack information because the GoFrame
framework uses a full error stack design, where all component errors come with a bottom-up error stack to facilitate quick error localization. Of course, we can obtain the returned error object and disable the stack information through the RunWithError
method.
Let's add parameter input and try again:
$ main http my-http-server -p 8199
2022-01-19 22:52:45.808 [DEBU] openapi specification is disabled
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
my-http-server | default | :8199 | ALL | / | main.(*cMain).Http.func1 |
-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
my-http-server | default | :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE
-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
2022-01-19 22:52:45.810 66292: http server started listening on [:8199]
Yes, that's correct.
Complete Usage Example
The development tool of the GoFrame
framework typically uses object-oriented, structured command line management. If interested, you can check the source code for more understanding: https://github.com/gogf/gf/tree/master/cmd/gf
Predefined Tags
In structured design, we use some struct tags, most of which originate from the attributes of the Command
command. Let's introduce them here:
Tag | Abbreviation | Description | Note |
---|---|---|---|
name | - | Name | If it is an input parameter structure, it will automatically read the method name as name when name is not specified |
short | - | Command abbreviation | |
usage | - | Command usage | |
brief | - | Command description | |
arg | - | Indicates the input parameter is from a parameter rather than an option | Only for attribute tags |
orphan | - | Indicates the option is without parameters | Attributes are usually of bool type |
description | dc | Detailed description of the command | |
additional | ad | Additional description information of the command | |
examples | eg | Usage examples of the command | |
root | - | Specifies the subcommand name as the parent command, and other methods as its subcommands | Only for main command object struct Meta tags |
strict | - | Indicates the command strictly parses parameters/options, returning an error when unsupported parameters/options are input | Only for object struct Meta tags |
config | - | Indicates that option data for the command supports reading from a specified configuration, sourced from the default global singleton configuration object | Only for method input struct Meta tags |
Advanced Features
Automatic Data Conversion
Structured parameter input supports automatic data type conversion. You just need to define the data types, and the rest is handled by the framework components. Automatic data type conversion is present in many components of the framework, especially in parameter inputs for HTTP/GRPC
services. The underlying data conversion component used is: Type Conversion
The command line parameter data conversion uses case insensitive, and ignores special characters rules to match attribute fields. For example, if there is a Name
field property in the input parameter structure, no matter whether the command line inputs name
or NAME
as a named parameter, it will be received by the Name
field property.
Automatic Data Validation
Similarly, the data validation component is also a unified component. Please refer to the chapter: Data Validation for details.
Reading Data from Configuration
When the corresponding data is not passed in the command line, the input parameter's structure data supports automatic acquisition from the configuration component, which only needs to set the config
tag in Meta
. The configuration source is the default global singleton configuration object. You can refer to the example in the GoFrame
framework development tool source code: https://github.com/gogf/gf/tree/master/cmd/gf