仿照laravel-artisan实现简易go开发脚手架
source link: https://studygolang.com/articles/14148?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
像Laravel-Artisan一样执行go命令
前言
作为一个 laravel
重度患者, artisan
是一个不可或缺的功能,可以说这是 laravel
的开发脚手架
可以快速创建需要的文件,加快开发速度
而我目前正在开发的 bingo
框架正是受到 laravel
启发,希望可以快速构建web应用
而一个脚手架是必不可少的东西,所以我实现了一个 bingo sword
工具
laravel-artisan实现思路
我曾经写过artisan的解析,链接在这里 laravel artisan 原理解析
简而言之,就是将 kernel.php
中注册的所有 commands
都实例化一次,然后比对 命令名,对于查找到的命令,调用 handle
方法执行即可
所以思路就有啦~
bingo sword 实现思路
先看图,随便画了画流程
下面直接上代码:
当命令行中输入 bingo sword make:command --name=MakeCommand
那么在 CLI
的 Run()
方法中,获取参数
swordCmd := flag.NewFlagSet("sword", flag.ExitOnError) // bingo sword 命令 err := swordCmd.Parse(os.Args[2:]) swordConfig = os.Args[2:] if swordCmd.Parsed() { cli.swordHandle(swordConfig) }
此时接收到了需要的参数,然后调用 swordHanle
方法:
func (cli *CLI) swordHandle(args []string) { // 解析这个参数,将数据传入外部 //fmt.Println(args) //获取env中的kernel路径 //根据kernel去 go shell执行 go run xxx/kernel.go make:controller AdminController consoleKernelPath := bingo.Env.Get("CONSOLE_KERNEL_PATH") // 获取命令当前执行目录 dir, _ := os.Getwd() // 拼接Kernel的 consoleKernelAbsolutePath := dir + "/" + consoleKernelPath + "/Kernel.go" // 使用go shell 调用 go run xxx/Kernel.go arg1 arg2 arg3 var tmpSlice = []string{"go", "run", consoleKernelAbsolutePath} args = append(tmpSlice, args...) // 先检查这个命令是否属于内部命令 // arg第一个就是命令 console := Console{} console.Exec(args[2:], InnerCommands) //[run /Users/silsuer/go/src/test/app/Console/Kernel.go aaa bbb ccc] // 执行 go run Kernel.go command:name cmd := exec.Command("go",args[1:]...) var out bytes.Buffer cmd.Stdout = &out // 开始执行命令 if err := cmd.Start(); err != nil { panic(err) } // 等待命令执行完成 if err := cmd.Wait(); err != nil { log.Fatal(err) } // 打印输出 fmt.Println(out.String()) }
所以我们使用 bingo sword command:name --name=CommandName
实际上执行的是 go run app/Console/Kernel.go command:name --name=CommandName
可以查看 Kernel.go
的源码,实例化了一个 console
结构体,并调用了 Exec()
方法
这个方法:
func (console *Console) Exec(args []string, commands []interface{}) { // 将参数封装成了input对象 input := console.initInput(args) // 遍历传入的commands数组(这是在kernel里注册的函数) for _, command := range commands { // 先做检查,查找对应的命令名 commandValue := reflect.ValueOf(command) // 初始化命令结构体 initCommand(&commandValue) // 映射期望参数与实际输入参数(验证参数输入是否正确) target := checkParams(command, &input) // 不是这个命令,跳过这个命令 if target == false { continue } // 获得输入和输出并准备作为参数传入Handle方法中 var params = []reflect.Value{reflect.ValueOf(input), reflect.ValueOf(Output{})} commandValue.MethodByName("Handle").Call(params) } }
如果传入的命令名没有对上的话,会跳过这次循环,否则会执行这个命令
值得注意的是,如果命令名一样的话,这些命令都会执行,如果命令中会报错的话,使用 panic()
只会抛出 bing/cli/cli_sword.go
中 swordHandle()
中的那行 panic
错误的代码
目前实现的功能
目前我只是简单的实现了创建命令的命令,安装好bingo后,在控制台输入 bingo sword make:command --name=HelloWorld
会在 app/Console/Commands
目录下生成一个 HelloWorld.go
文件,内容是:
package Commands import ( "github.com/silsuer/bingo/cli" ) type HelloWorld struct { cli.Command Name string Description string Args map[string]string } // 设置命令名 func (c *HelloWorld) SetName() { c.Name = "command:name" } // 设置命令所需参数 func (c *HelloWorld) SetArgs() { c.Args = make(map[string]string) c.Args["name"] = "" } // 设置命令描述 func (c *HelloWorld) SetDescription() { c.Description = "the command description." } // 设置命令实现的方法 func (c *HelloWorld) Handle(input cli.Input, output cli.Output) { }
我们只需要在其中修改我们要更改的信息就可以了,例如更新handle方法为 output.Info("Hello,World!")
,更新命令名是 hello:world
然后无需使用 go build
等方式重新编译,直接在命令行使用 bingo sword hello:world
,将在控制台打印输出 Hello,World
当然,这个工具只是为了加速开发,是 bingo
的一个模块,你随时可以把它拆出来作为独立模块使用
知识点
- 使用go执行系统命令
cmd := exec.Command("go","run","app/Console/Kernel.go","command:name")
使用 exec.Command
将会生成一个 cmd
对象,执行 cmd.Start()
即可执行命令,这并不会阻塞进程,如果需要获得结果
需要使用 cmd.Wait()
等待执行完成,再获取标准输出,当然也可以直接使用 cmd.Run()
,这行代码会阻塞进程,直到命令完成
如果获取标准输出呢?
var out bytes.Buffer cmd.Stdout = &out
将cmd的标准输出指向我们设定好的一个 buffer
即可
- 使用反射调用结构体的方法
使用 commandValue := reflect.ValueOf(command)
获取这个结构体的 Value
使用 commandValue.NumMethod()
可以获取这个结构体的方法数量,如果传入的 command
是一个结构体的指针的话
得到的方法数量包括了针对结构体的方法数量和针对结构体指针的方法数量之和,如果传入的是一个结构体对象,那么得到的
方法数量只是包括了针对结构体的方法数量
使用 commandValue.MethodByName("Handle").Call(params)
调用结构体对应的方法
- 在控制台输出带颜色的文字
和shell类似,只需要将控制台输出的信息使用一些颜色字符包裹起来即可
//其中0x1B是标记,[开始定义颜色,1代表高亮,48代表黑色背景,32代表绿色前景,0代表恢复默认颜色。 fmt.Printf("\n %c[0;48;32m%s%c[0m\n\n", 0x1B, "["+time.Now().Format("2006-01-02 15:04:05")+"]"+content, 0x1B)
最后贴一下项目链接 bingo ,欢迎star,更欢迎PR,欢迎提意见~~~
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK