使用urfave/cli实现命令行备忘录
🍕前言
本周抽空了解了一下go的工具包:github.com/urfave/cli还是挺有意思的;
于是就写了一个基于github.com/urfave/cli包的简单命令行备忘录demo;
功能很简单,代码也并不是很复杂,当作入门来看还是很不错的;
当然,学这些工具并不是就是为了去实现一个简单备忘录,在这个学习的过程中能了解到很多新鲜的东西亦能拓宽我们的视野,比如说,知道了黑窗口中各种命令究竟是如何去实现的,为什么go语言适合去制作这种命令行工具,shell脚本如何去实现的,docker,数据库命令,git等等的命令原理,这有助于我们对计算机整体知识和底层的理解;
话不多说,我们进入正题;
🍔urfave/cli入🚪
github.com/urfave/cli包的介绍
github.com/urfave/cli
是一个 Go 语言的命令行应用程序开发包。它提供了一组简单且功能强大的工具,用于构建命令行界面的应用程序,包括解析命令行参数、定义命令和子命令、显示帮助信息等功能。
具体来说,github.com/urfave/cli
包的主要功能包括:
- 解析命令行参数: 可以方便地解析命令行中的选项和参数,并将它们映射到相应的数据结构中。
- 定义命令和子命令: 可以定义主命令以及其对应的子命令,使得命令行应用程序可以拥有层次结构和多个操作选项。
- 显示帮助信息: 自动生成并显示命令行应用程序的帮助信息,包括命令、选项、参数的说明和用法示例等。
- 处理命令执行: 可以将函数与特定的命令关联起来,当用户输入相应的命令时,执行相应的函数逻辑。
- 处理错误和异常情况: 可以定义自定义的错误处理逻辑,以及处理未知命令和参数错误的方式。
入门程序介绍
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
func main() {
// 创建一个新的命令行应用程序
app := cli.NewApp()
// 设置应用程序的名称、版本和简要描述
app.Name = "MyCLI"
app.Version = "1.0.0"
app.Usage = "A simple command-line application"
// 定义一个命令
app.Commands = []cli.Command{
{
Name: "greet", //命令触发词:greet
Aliases: []string{"g"}, //简写:g
Usage: "Greet someone",
Action: greet,
Flags: []cli.Flag{
cli.StringFlag{
Name: "name, n",
Value: "World",
Usage: "Name of the person to greet",
},
},
},
}
// 运行命令行应用程序
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
// greet 函数用于执行 greet 命令的逻辑
func greet(c *cli.Context) error {
name := c.String("name")
fmt.Printf("Hello, %s!\n", name)
return nil
}
这段代码意思是:
定义了一个名为 “MyCLI” 的命令行应用程序,它包含一个命令 “greet”,用于向某人打招呼。命令 “greet” 可以通过
greet
或g
触发,并接受一个名为 “name” 的选项,用于指定要打招呼的人的姓名。如果用户没有提供姓名,则默认使用 “World”。当用户执行命令
./MyCLI greet
时,默认会输出 “Hello, World!”。如果用户执行命令./MyCLI greet --name Alice
,则会输出 “Hello, Alice!”。
其实整个cli项目框架就是:
1.初始化命令行程序,定义版本等等信息;
2.定义各种命令(包括触发关键词,命令报错,命令方法的调用等)
3.写各种命令的详细代码实现;
ok;相信你已经对这个包的基础使用已经有了一部分的了解;
现在就引入备忘录工具的实现;
🍟备忘录具体代码讲解
先看项目的分层:
各文件的含义:
- commands文件存储命令的具体实现代码
- memo为可执行文件
- memo.go为项目入口,也包含了命令说明及调用
- memos.txt存储备忘录中的数据
memo
按照刚刚讲的初始化会放在:memo.go文件中:确实如此:
package main
import (
"github.com/urfave/cli"
"log"
"os"
"time"
)
// Memo represents a memo item
type Memo struct {
ID int
Text string
CreatedAt time.Time
ModifiedAt time.Time
}
var memos []Memo
var lastID int
func main() {
loadMemos() // 加载备忘录数据
app := cli.NewApp() //初始化app类型数据
app.Name = "Memo"
app.Usage = "A simple command-line memo application"
app.Version = "1.0.0"
app.Commands = []cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "Add a new memo",
Action: addMemo,
},
{
Name: "list",
Aliases: []string{"l"},
Usage: "List all memos",
Action: listMemos,
},
{
Name: "delete",
Aliases: []string{"d"},
Usage: "Delete a memo by ID",
Action: deleteMemo,
Flags: []cli.Flag{
cli.IntFlag{
Name: "id",
Usage: "ID of the memo to delete",
},
},
},
{
Name: "search",
Aliases: []string{"s"},
Usage: "Search memos by keyword",
Action: searchMemos,
Flags: []cli.Flag{
cli.StringFlag{
Name: "keyword, k",
Usage: "Keyword to search for in memos",
},
},
},
{
Name: "update",
Aliases: []string{"u"},
Usage: "Update a memo by ID",
Action: updateMemo,
Flags: []cli.Flag{
cli.IntFlag{
Name: "id",
Usage: "ID of the memo to update",
},
cli.StringFlag{
Name: "text, t",
Usage: "New text for the memo",
},
},
},
}
err := app.Run(os.Args) //没有启动起来就直接报错;
if err != nil {
log.Fatal(err)
}
}
工具主页:
别看代码这么多,其实并没有多复杂,因为我们要实现备忘录那么必定是要存数据的,因为是学习的小项目,所以数据干脆直接存到txt文件中了,实际上会存到gob后缀或者存入数据库中,为了项目的简化和易用性以及性能,存gob中应该是最好的选择,但是由于gob是二进制文件,当作教学并不是很直观,txt存少量数据也可以;
所以既然要存数据,那么数据肯定是有属性的,这里只写了几个简单数据:id,内容,生成时间,修改时间
type Memo struct {
ID int
Text string
CreatedAt time.Time
ModifiedAt time.Time
}
var memos []Memo //切片从前到后存储所有的数据结构体;
var lastID int //给数据排序,为下次加入数据时编号;
初始化:
func main() {
loadMemos() // 加载备忘录数据
app := cli.NewApp() //初始化app类型数据
app.Name = "Memo"
app.Usage = "A simple command-line memo application"
app.Version = "1.0.0"
//...各种命令的定义
//...
//...
err := app.Run(os.Args) //没有启动起来报错;
if err != nil {
log.Fatal(err)
}
}
各种命令的触发和参数定义:
app.Commands = []cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "Add a new memo",
Action: addMemo,
},
{
Name: "list",
Aliases: []string{"l"},
Usage: "List all memos",
Action: listMemos,
},
{
Name: "delete",
Aliases: []string{"d"},
Usage: "Delete a memo by ID",
Action: deleteMemo,
Flags: []cli.Flag{
cli.IntFlag{
Name: "id",
Usage: "ID of the memo to delete",
},
},
},
{
Name: "search",
Aliases: []string{"s"},
Usage: "Search memos by keyword",
Action: searchMemos,
Flags: []cli.Flag{
cli.StringFlag{
Name: "keyword, k",
Usage: "Keyword to search for in memos",
},
},
},
{
Name: "update",
Aliases: []string{"u"},
Usage: "Update a memo by ID",
Action: updateMemo,
Flags: []cli.Flag{
cli.IntFlag{
Name: "id",
Usage: "ID of the memo to update",
},
cli.StringFlag{
Name: "text, t",
Usage: "New text for the memo",
},
},
},
}
经过上边简单示例说明,相信这个也是可以一看就懂的,接下来讲解每个命令的具体实现:
具体方法实现
commands.go
package main
import (
"bufio"
"fmt"
"github.com/urfave/cli"
"log"
"os"
"strconv"
"strings"
"time"
)
func addMemo(c *cli.Context) error {
text := c.Args().First()
if text == "" {
return fmt.Errorf("Please provide memo text")
}
lastID++
now := time.Now()
memos = append(memos, Memo{ID: lastID, Text: text, CreatedAt: now, ModifiedAt: now})
saveMemos() // 保存备忘录数据到文本文件
fmt.Println("Memo added successfully")
return nil
}
//显示数据列表
func listMemos(c *cli.Context) error {
if len(memos) == 0 {
fmt.Println("No memos found")
return nil
}
fmt.Println("ID\tMemo\tCreated At\tModified At")
for _, memo := range memos {
fmt.Printf("%d\t%s\t%s\t%s\n", memo.ID, memo.Text, memo.CreatedAt.Format("2006-01-02 15:04:05"), memo.ModifiedAt.Format("2006-01-02 15:04:05"))
}
return nil
}
func deleteMemo(c *cli.Context) error {
id := c.Int("id")
if id <= 0 {
return fmt.Errorf("Please provide a valid memo ID")
}
found := false
for i, memo := range memos {
if memo.ID == id {
memos = append(memos[:i], memos[i+1:]...)
found = true
break
}
}
if found {
saveMemos() // 保存备忘录数据到文本文件
fmt.Println("Memo deleted successfully")
} else {
fmt.Println("Memo not found")
}
return nil
}
func searchMemos(c *cli.Context) error {
keyword := c.String("keyword")
if keyword == "" {
return fmt.Errorf("Please provide a keyword to search for")
}
found := false
for _, memo := range memos {
if strings.Contains(memo.Text, keyword) {
fmt.Printf("ID: %d\tMemo: %s\n", memo.ID, memo.Text)
found = true
}
}
if !found {
fmt.Println("No memos found matching the keyword")
}
return nil
}
func updateMemo(c *cli.Context) error {
id := c.Int("id")
if id <= 0 {
return fmt.Errorf("Please provide a valid memo ID")
}
text := c.String("text")
if text == "" {
return fmt.Errorf("Please provide new text for the memo")
}
found := false
for i, memo := range memos {
if memo.ID == id {
memos[i].Text = text
memos[i].ModifiedAt = time.Now()
found = true
break
}
}
if found {
saveMemos() // 保存备忘录数据到文本文件
fmt.Println("Memo updated successfully")
} else {
fmt.Println("Memo not found")
}
return nil
}
// 加载备忘录数据
func loadMemos() {
file, err := os.OpenFile("memos.txt", os.O_RDONLY|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Split(line, "\t")
if len(fields) != 4 {
log.Printf("Invalid memo data: %s", line)
continue
}
id, err := strconv.Atoi(fields[0])
if err != nil {
log.Printf("Invalid memo ID: %s", fields[0])
continue
}
createdAt, err := time.Parse("2006-01-02 15:04:05", fields[2])
if err != nil {
log.Printf("Invalid created at time: %s", fields[2])
continue
}
modifiedAt, err := time.Parse("2006-01-02 15:04:05", fields[3])
if err != nil {
log.Printf("Invalid modified at time: %s", fields[3])
continue
}
memos = append(memos, Memo{
ID: id,
Text: fields[1],
CreatedAt: createdAt,
ModifiedAt: modifiedAt,
})
}
// 设置 lastID 为最大的备忘录项ID加1
for _, memo := range memos {
if memo.ID > lastID {
lastID = memo.ID
}
}
}
// 保存备忘录数据到文本文件
func saveMemos() {
file, err := os.OpenFile("memos.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) //写入模式
if err != nil {
log.Fatal(err)
}
defer file.Close()
writer := bufio.NewWriter(file) //缓存数据,提高写入效率
for _, memo := range memos {
line := fmt.Sprintf("%d\t%s\t%s\t%s\n", memo.ID, memo.Text, memo.CreatedAt.Format("2006-01-02 15:04:05"), memo.ModifiedAt.Format("2006-01-02 15:04:05"))
if _, err := writer.WriteString(line); err != nil {
log.Println("Error writing memo:", err)
}
}
if err := writer.Flush(); err != nil {
log.Println("Error flushing writer:", err)
}
}
方法很多,重复度也很高,并不会全部都讲
add:增加备忘录数据
func addMemo(c *cli.Context) error {
text := c.Args().First()
if text == "" {
return fmt.Errorf("Please provide memo text")
}
lastID++
now := time.Now()
memos = append(memos, Memo{ID: lastID, Text: text, CreatedAt: now, ModifiedAt: now})
saveMemos() // 保存备忘录数据到文本文件
fmt.Println("Memo added successfully")
return nil
}
判断关键词是add才会调用该方法:
判断text不符合格式会报错,若符合格式则计数id+1,将时间初始化,调用保存方法将结构体存入文件中并返回添加成功;
list:显示备忘录中所有数据;
delete删除备忘录中数据
根据id删除数据
search 查询备忘录中内容
查询的是内容中的数据,当有多条符合可以全部查出;
update 更新指定id的数据;
其他就不说了,项目代码可以自己看具体的实现;
项目地址:命令行备忘录工具
下次再见;