当开发者不在他们的IDE文本编辑器中时,他们通常在终端中。
作为一个开发者,你很有可能为你的项目使用过命令行界面(CLI)。
大多数开发者工具在命令行上运行的主要原因是:易于配置。
CLI应用程序允许一定程度的自由,这在图形用户界面(GUI)应用程序中是不容易找到的。
Cobra是一个用于构建CLI应用程序的Go库。它相当流行,在很多流行的开发者工具中使用,如Github CLI、Hugo等等。
在本教程中,我们将通过建立一个简单的会计CLI应用程序来学习Cobra,该程序可以向用户收费,将信息存储在JSON文件中,记录收据,并跟踪用户的总余额。
安装Cobra
有两种方法可以创建Cobra应用程序。
- 安装Cobra生成器
- 手动添加Cobra到Go应用程序中
在本教程中,我们将安装Cobra Generator。这提供了一种简单的方法来生成命令,为应用程序注入活力。
首先,运行下面的命令来安装Cobra Generator。
go get github.com/spf13/cobra/cobra
这将把Cobra安装在GOPATH 目录中,然后生成Cobra应用程序。
了解Cobra CLI命令和标志
在开始构建我们的应用程序之前,我们必须了解CLI
应用程序的主要组成部分。
当使用Git克隆一个项目时,我们通常会运行以下内容。
git clone <url.to.project>
这包括
git,应用程序名称clone, 命令url.to.project,传递给命令的参数和我们要git的项目。clone
一个CLI应用程序通常包括应用程序的名称、命令、标志和参数。
考虑一下这个例子。
npm install --save-dev nodemon
这里,npm 是正在运行的应用程序,install 是命令。--save-dev 是传递给install 命令的标志,而nodemon 是传递给命令的参数。
Cobra允许我们非常容易地创建命令并向其添加标志。对于我们的应用程序,我们将创建两个命令:credit 和debit **。**通过使用各种标志,我们可以指定交易的用户、交易的金额和交易的说明等项目。
创建Cobra应用程序
要创建一个新的Cobra应用程序,请运行以下命令。
cobra init --pkg-name github.com/<username>/accountant accountant
该命令创建了一个新的文件夹,accountant ,并创建了一个main.go 文件,一个LICENSE 文件,以及一个cmd 文件夹和一个root.go 文件。
注意,这个命令并没有创建一个go.mod 文件,所以我们必须自己初始化go 模块。
go mod init github.com/<username>/accountant
go mod tidy
我们现在可以像运行任何正常的Go程序一样运行这个程序。
go run .
然而,我们也可以通过以下方式构建应用程序。
go build .
并通过以下方式运行该应用程序。
./accountant
Cobra应用程序的入口点
我们的Cobra应用程序的入口点是main.go ,保持主包的精简是很重要的,这样我们就可以把应用程序的不同方面分开。看一下Cobra生成的main.go 文件,我们发现主函数只有一个功能:执行根命令。
cmd.Execute()
根命令,cmd/root.go, ,包含以下内容。
rootCmd结构,它是一个类型的cobraCommandExecute函数,它被调用于main.goinit函数,它初始化配置并设置根标志。initConfig函数,它初始化任何设定的配置
目前,运行的应用程序包含一堆Cobra生成的文本。让我们通过将cmd\root.go 修改为以下内容来改变这种情况,这样我们就可以解释我们的应用程序是用来干什么的。
package cmd
import (
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "accountant",
Short: "An application that helps manage accounts of users",
Long: `
This is a CLI that enables users to manage their accounts.
You would be able to add credit transactions and debit transactions to various users.
`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
cobra.CheckErr(rootCmd.Execute())
}
运行该应用程序现在应该给出以下响应。
This is a CLI that enables users to manage their accounts.
You would be able to add credit transactions and debit transactions to various users.
Usage:
accountant [command]
这里,我们删除了Cobra生成的init 和initConfig 函数。这是因为我们不需要这个应用程序的任何环境变量,而且root命令也没有什么作用。相反,该应用程序的所有功能都是由特定的命令来完成的。
在Cobra中创建命令
我们的应用程序应该能够处理两个主要功能:借记和贷记给用户。因此,我们必须创建两个命令:debit 和credit 。
运行下面的程序来生成这些命令。
cobra add credit
cobra add debit
这将创建两个新文件:debit.go 和credit.go 在/cmd 目录中。
检查新创建的文件后,在init 函数中添加以下内容。
rootCmd.AddCommand(debitCmd)
这行代码将新创建的命令添加到根命令中;现在,应用程序已经知道了新的命令。
要运行debitCmd 命令,我们必须通过go build . 构建应用程序,并像这样运行应用程序。
./accountant debit
添加一个JSON存储层
对于这个应用程序,我们将使用一个非常简单的存储层。在这种情况下,我们将在一个JSON文件中存储我们的数据,并通过一个go 模块来访问它们。
在根目录下,创建一个database 文件夹,然后创建一个db.go 文件和一个db.json 文件。
将以下内容添加到db.go ,以便与数据库进行交互。
package database
import (
"encoding/json"
"os"
"strings"
)
type User struct {
Username string `json:"username"`
Balance int64 `json:"balance"`
Transactions []Transaction `json:"transactions"`
}
type Transaction struct {
Amount int64 `json:"amount"`
Type string `json:"string"`
Narration string `json:"narration"`
}
func getUsers() ([]User, error) {
data, err := os.ReadFile("database/db.json")
var users []User
if err == nil {
json.Unmarshal(data, &users)
}
return users, err
}
func updateDB(data []User) {
bytes, err := json.Marshal(data)
if err == nil {
os.WriteFile("database/db.json", bytes, 0644)
} else {
panic(err)
}
}
func FindUser(username string) (*User, error) {
users, err := getUsers()
if err == nil {
for index := 0; index < len(users); index++ {
user := users[index]
if strings.EqualFold(user.Username, username) {
return &user, nil
}
}
}
return nil, err
}
func FindOrCreateUser(username string) (*User, error) {
user, err := FindUser(username)
if user == nil {
var newUser User
newUser.Username = strings.ToLower(username)
newUser.Balance = 0
newUser.Transactions = []Transaction{}
users, err := getUsers()
if err == nil {
users = append(users, newUser)
updateDB(users)
}
return &newUser, err
}
return user, err
}
func UpdateUser(user *User) {
// Update the json with this modified user information
users, err := getUsers()
if err == nil {
for index := 0; index < len(users); index++ {
if strings.EqualFold(users[index].Username, user.Username) {
// Update the user details
users[index] = *user
}
}
// update database
updateDB(users)
}
}
这里,我们定义了两个结构:User 和Transaction 。User 结构定义了如何存储和访问用户的信息,如:username,balance 和transactions 。Transaction 结构存储交易,包括amount,type 和narration 。
我们还有两个向数据库写入的函数。getUsers 加载数据库文件并返回存储的用户数据,而updateDB 将更新的数据写入数据库。
这些函数对这个包来说是私有的,需要公共函数来与它们进行交互。
FindUser 在数据库中找到一个有用户名的用户并返回该用户。如果没有找到用户,则返回nil 。FindOrCreateUser 检查是否有一个具有用户名的用户并返回;如果没有用户,则用该用户名创建一个新的用户并返回。
UpdateUser 接收用户数据并更新数据库中的相应条目。
这三个函数被导出,在对用户进行贷记和借记时在命令中使用。
用Cobra实现信用交易
用以下内容修改credit 命令,为该命令创建一个适当的描述,并在长描述中增加一个使用部分。
// cmd/credit.go
var creditCmd = &cobra.Command{
Use: "credit",
Short: "Create a credit transaction",
Long: `
This command creates a credit transaction for a particular user.
Usage: accountant credit <username> --amount=<amount> --narration=<narration>.`,
Run: func(cmd *cobra.Command, args []string) {
},
}
然后,当用户试图获得该命令的帮助时,长描述会出现。
接下来,我们必须为credit 命令添加必要的标志:amount 和narration 。
在creditCmd 的定义后添加以下内容。
var creditNarration string
var creditAmount int64
func init() {
rootCmd.AddCommand(creditCmd)
creditCmd.Flags().StringVarP(&creditNarration, "narration", "n", "", "Narration for this credit transaction")
creditCmd.Flags().Int64VarP(&creditAmount, "amount", "a", 0, "Amount to be credited")
creditCmd.MarkFlagRequired("narration")
creditCmd.MarkFlagRequired("amount")
}
在init 方法中,我们通过rootCmd.AddCommand 将creditCmd 命令附加到root 命令。
接下来,我们必须使用StringVarP 方法创建一个字符串标志,narration 。这个方法接收五个参数。
- 一个指向存储标志值的变量的指针
- 标志的名称
- 标志的短名称
- 该标志的默认值
- 当用户通过
--help标志请求帮助时,将提供一条帮助信息。
另外,我们必须通过Int64VarP 方法创建一个新的标志,amount 。这个方法类似于StringVarP ,但是创建了一个64位的整数标志。
之后,我们必须根据需要设置这两个标志。通过这样做,只要在没有这些标志的情况下调用该命令,Cobra就会输出一个错误,说明这些标志是必需的。
完成了信贷命令,我们使用数据库函数来创建交易,并将其添加到用户中。
要做到这一点,请修改run 函数,使其看起来像下面这样。
var creditCmd = &cobra.Command{
...
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
log.Fatal("Username not specified")
}
username := args[0]
user, err := database.FindOrCreateUser(username)
if err != nil {
log.Fatal(err)
}
user.Balance = user.Balance + creditAmount
creditTransaction := database.Transaction{Amount: creditAmount, Type: "credit", Narration: creditNarration}
user.Transactions = append(user.Transactions, creditTransaction)
database.UpdateUser(user)
fmt.Println("Transaction created successfully")
},
}
run 函数是该命令最重要的部分,因为它处理了该命令的主要动作。
因此,我们希望该命令有如下签名。
./accountant credit <username> --amount=<amount> --narration<narration>
这里发送给命令的参数是username ,更确切地说,是args 数组中的第一个项目。这就保证了至少有一个参数传给了命令。
在得到用户名后,我们可以使用数据库包中的FindOrCreateUser 方法来得到该用户名的相应用户信息。
如果该操作成功,我们就会增加该用户的余额,并添加一个新的交易,其中包括金额和说明。然后,我们用新的用户数据更新数据库。
把所有东西放在一起,信贷命令应该是这样的。
package cmd
import (
"fmt"
"log"
"github.com/jameesjohn/accountant/database"
"github.com/spf13/cobra"
)
// creditCmd represents the credit command
var creditCmd = &cobra.Command{
Use: "credit",
Short: "Create a credit transaction",
Long: `
This command creates a credit transaction for a particular user.
Usage: accountant credit <username> --amount=<amount> --narration=<narration>.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
log.Fatal("Username not specified")
}
username := args[0]
user, err := database.FindOrCreateUser(username)
if err != nil {
log.Fatal(err)
}
user.Balance = user.Balance + creditAmount
creditTransaction := database.Transaction{Amount: creditAmount, Type: "credit", Narration: creditNarration}
user.Transactions = append(user.Transactions, creditTransaction)
database.UpdateUser(user)
fmt.Println("Transaction created successfully")
},
}
var creditNarration string
var creditAmount int64
func init() {
rootCmd.AddCommand(creditCmd)
creditCmd.Flags().StringVarP(&creditNarration, "narration", "n", "", "Narration for this credit transaction")
creditCmd.Flags().Int64VarP(&creditAmount, "amount", "a", 0, "Amount to be credited")
creditCmd.MarkFlagRequired("narration")
creditCmd.MarkFlagRequired("amount")
}
这样,我们就成功地实现了credit 命令。
用Cobra实现借记交易
debit 命令看起来与credit 命令类似。唯一不同的是run 功能。Debit 减少用户的余额,而credit 增加用户的余额。
debit 命令应该看起来像下面这样。
./accountant debit <username> --amount=<amount> --narration=<narration>
run 函数与debit 的不同之处在于检查用户的余额是否大于借出的金额;我们不希望在我们的数据库中出现负余额。
要做到这一点,请修改debit.go ,使其看起来像下面这样。
package cmd
import (
"fmt"
"log"
"github.com/jameesjohn/accountant/database"
"github.com/spf13/cobra"
)
// debitCmd represents the debit command
var debitCmd = &cobra.Command{
Use: "debit",
Short: "Create a debit transaction",
Long: `
This command creates a debit transaction for a particular user.
Usage: accountant debit <username> --amount=<amount> --narration=<narration>.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
log.Fatal("Username not specified")
}
username := args[0]
user, err := database.FindOrCreateUser(username)
if err != nil {
log.Fatal(err)
}
if user.Balance > debitAmount {
user.Balance = user.Balance - debitAmount
debitTransaction := database.Transaction{Amount: debitAmount, Type: "debit", Narration: debitNarration}
user.Transactions = append(user.Transactions, debitTransaction)
database.UpdateUser(user)
fmt.Println("Transaction created successfully")
} else {
fmt.Println("Insufficient funds!")
}
},
}
var debitNarration string
var debitAmount int64
func init() {
rootCmd.AddCommand(debitCmd)
debitCmd.Flags().StringVarP(&debitNarration, "narration", "n", "", "Narration for this debit transaction")
debitCmd.Flags().Int64VarP(&debitAmount, "amount", "a", 0, "Amount to be debited")
debitCmd.MarkFlagRequired("narration")
debitCmd.MarkFlagRequired("amount")
}
如果用户有足够的余额进行交易,我们用借记金额减少他们的余额,创建一个新的借记交易,并将交易添加到用户身上。最后,我们用更新的用户更新数据库。
如果用户没有足够的余额,我们就输出一个错误信息,说明他们的余额不足。
我们现在可以使用accountant ,向用户扣款。
./accountant debit henry --amount=40 --narration="Paid James"
现在可以通过运行go build 来构建应用程序。
总结
我们刚刚学会了如何使用Cobra来构建CLI应用程序!考虑到Cobra为我们做的大量工作,我们不难理解为什么流行的开源应用程序和工具会在他们的CLI应用程序中使用它。
The postUsing Cobra to build a CLI accounting appappeared first onLogRocket Blog.