第一次修改别人的东西哈哈
今天开始给 OneTiny 增加更新的功能,本来想做一个跟 oh-my-zsh 一样的效果,就是会突然跳出来问你是不是要更新,是的话就给你下载最新版然后重启一下终端。
但是写着写着渐渐面临一个问题,下载后放哪?如果是类 Unix 系统怎么把文件放到 /usr/bin 目录下?放完怎么让他自己启动?
慢慢的又修改思路,换成不自动检查更新或隔一段时间检查,通过 onetiny update
命令可以主动更新。
在写的时候新引入了两个库,一个时负责下载时显示进度的 progressbar,一个是解析命令参数的 flaggy。
{% ghcard schollz/progressbar, theme=algolia%} | {% ghcard integrii/flaggy, theme=algolia%} |
---|
| |
修改 progressbar 库#
在使用 progressbar 时,跑了官方给出的示例但是没得到官方的效果。
官网给出的例子:
1
2
3
4
5
6
7
| func main() {
bar := progressbar.Default(100)
for i := 0; i < 100; i++ {
bar.Add(1)
time.Sleep(40 * time.Millisecond)
}
}
|
官方演示的效果:
实际跑出来的效果:
1
2
3
4
5
6
7
8
9
10
| $ go run main.go
100% |█████████████████████████████████████| (100/100, 25bit/s)
|
有很多的空行。
应用到 OneTiny 里也是一样,官方并没有给出太多答案。
不过进度条的核心就在于 \r
字符串,于是顺着代码一步步找下去,最后在库的源文件 progressbar.go
中找到了一个函数 clearProgressBar()
:
{% folding cyan, 点击查看 clearProgressBar()
%}
1
2
3
4
5
6
7
8
9
10
11
12
13
| func clearProgressBar(c config, s state) error {
if c.useANSICodes {
// write the "clear current line" ANSI escape sequence
return writeString(c, "\033[2K\r")
}
// fill the empty content
// to overwrite the progress bar and jump
// back to the beginning of the line
str := fmt.Sprintf("\r%s\r", strings.Repeat(" ", s.maxLineWidth))
return writeString(c, str)
// the following does not show correctly if the previous line is longer than subsequent line
// return writeString(c, "\r")
}
|
{% endfolding %}
看到最下面的注释,虽然我不知道上面 return 了下,但是最后一句注释让我觉得有戏。
于是我把上面的 return 注释掉,取消了最后一行 return 的注释。
{% folding cyan, 点击查看修改后的 clearProgressBar()
%}
1
2
3
4
5
6
7
8
9
10
11
12
13
| func clearProgressBar(c config, s state) error {
if c.useANSICodes {
// write the "clear current line" ANSI escape sequence
return writeString(c, "\033[2K\r")
}
// fill the empty content
// to overwrite the progress bar and jump
// back to the beginning of the line
- str := fmt.Sprintf("\r%s\r", strings.Repeat(" ", s.maxLineWidth))
- return writeString(c, str)
// the following does not show correctly if the previous line is longer than subsequent line
+ return writeString(c, "\r")
}
|
{% endfolding %}
结果居然正常了…正常了…常了…了…
好吧没时间管那么多,提交了个 issue 给官方就走了。
修改 flaggy 库#
- 修改点:增加了修改
--help
和 --version
的描述信息的接口,增加了打印版本信息的简写 -v
之前使用的是 flag
包解析命令行参数,但这包对子命令不友好。
增加修改 SetHelpFlagDescription 和 SetVersionFlagDescription#
我准备把原本的命令改成:
1
2
3
4
5
6
7
8
| tiny
├── -a --allow 指定是否允许访问者上传。
├── -r --road 指定对外开放的目录的绝对路径。 (default: /home/boii/...)
├── -p --port 指定开放的端口 (default: 9090)
├── -h --help 打印帮助信息。
+ ├── -v --version 打印版本信息。当前版本: v0.2.1
+ └── update 更新到最新版本
+ └── -l --list 列出当前最新版本和更新内容
|
增加了打印版本信息、update 子命令; update 子命令不带参数时执行更新。
用 flag 包太麻烦了,找了一会儿 github 之后终于找到一个比较适合的库 flaggy。使用方式和 flag 大体相似,支持子命令等更丰富的功能。
但是在打印 help 信息时,它把 --help
和 --version
的描述写死了,还不提供接口给人修改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| tiny - 一个用于局域网内共享文件的FTP程序。
Usage:
tiny [update]
Subcommands:
update (u) update 可以帮你进行版本升级
Flags:
--version Displays the program version string.
-h --help Displays help with available flag, subcommand, and positional value parameters.
-r --road 指定对外开放的目录的绝对路径。 (default: /home/boii/...)
-p --port 指定开放的端口 (default: 9090)
-a --allow 指定是否允许访问者上传。
|
中英混杂,看着就别扭。
文档中给出的示例也只有一点点,让我猜了好久最后找到了。
{% folding cyan, 点击查看修改步骤 %}
{% tabs tab-id %}
在 main.go
中增加两个函数,开发者可以通过这两个函数修改默认的描述信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // SetName sets the name of the default package command parser
func SetName(name string) {
DefaultParser.Name = name
}
+ // SetHelpFlagDescription sets the help flag description of the default package command parser
+ func SetHelpFlagDescription(description string) {
+ DefaultParser.HelpFlagDescription = description
+ }
+
+ // SetVersionFlagDescription sets the version flag description of + the default package command parser
+ func SetVersionFlagDescription(description string) {
+ DefaultParser.VersionFlagDescription = description
+ }
// ShowHelpAndExit shows parser help and exits with status code 2
func ShowHelpAndExit(message string) {
...
|
当然,原本的默认描述还是要保留的
1
2
3
4
5
6
7
8
9
| // defaultVersion is applied to parsers when they are created
const defaultVersion = "0.0.0"
+ var defaultHelpFlagDescription = "Displays help with available flag, subcommand, and positional value parameters."
+ var defaultVersionFlagDescription = "Displays the program version string."
// DebugMode indicates that debug output should be enabled
var DebugMode bool
|
在 helpValues.go
中修改两处地方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // if the built-in version flag is enabled, then add it as a help flag
if p.ShowVersionWithVersionFlag {
defaultVersionFlag := HelpFlag{
ShortName: "",
LongName: versionFlagLongName,
- Description: "Displays the program version string.",
+ Description: DefaultParser.VersionFlagDescription,
DefaultValue: "",
Spacer: makeSpacer(versionFlagLongName, maxLength),
}
h.Flags = append(h.Flags, defaultVersionFlag)
}
// if the built-in help flag exists, then add it as a help flag
if p.ShowHelpWithHFlag {
defaultHelpFlag := HelpFlag{
ShortName: helpFlagShortName,
LongName: helpFlagLongName,
- Description: "Displays help with available flag, subcommand, and positional value parameters.",
+ Description: DefaultParser.HelpFlagDescription,
DefaultValue: "",
Spacer: makeSpacer(helpFlagLongName, maxLength),
}
h.Flags = append(h.Flags, defaultHelpFlag)
}
|
修改 subCommand.go
中的结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| type Subcommand struct {
Name string
ShortName string
Description string
+ HelpFlagDescription string // the help flag description
+ VersionFlagDescription string // the version flag description
Position int // the position of this subcommand, not including flags
Subcommands []*Subcommand
Flags []*Flag
PositionalFlags []*PositionalValue
ParsedValues []parsedValue // a list of values and positionals parsed
AdditionalHelpPrepend string // additional prepended message when Help is displayed
AdditionalHelpAppend string // additional appended message when Help is displayed
Used bool // indicates this subcommand was found and parsed
Hidden bool // indicates this subcommand should be hidden from help
}
|
我也想不明白为啥 DefaultParser 声明是个 *Parser
,但是在 VSCODE 中点击跳转会跳到 SubCommand。
最后,修改一下 parser.go
中的结构体。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| type Parser struct {
Subcommand
Version string // the optional version of the parser.
+ HelpFlagDescription string // the help flag description
+ VersionFlagDescription string // the version flag description
ShowHelpWithHFlag bool // display help when -h or --help passed
ShowVersionWithVersionFlag bool // display the version when --version passed
ShowHelpOnUnexpected bool // display help when an unexpected flag or subcommand is passed
TrailingArguments []string // everything after a -- is placed here
HelpTemplate *template.Template // template for Help output
trailingArgumentsExtracted bool // indicates that trailing args have been parsed and should not be appended again
parsed bool // indicates this parser has parsed
subcommandContext *Subcommand // points to the most specific subcommand being used
}
// NewParser creates a new ArgumentParser ready to parse inputs
func NewParser(name string) *Parser {
// this can not be done inline because of struct embedding
p := &Parser{}
p.Name = name
p.Version = defaultVersion
+ p.HelpFlagDescription = defaultHelpFlagDescription
+ p.VersionFlagDescription = defaultVersionFlagDescription
p.ShowHelpOnUnexpected = true
p.ShowHelpWithHFlag = true
p.ShowVersionWithVersionFlag = true
p.SetHelpTemplate(DefaultHelpTemplate)
p.subcommandContext = &Subcommand{}
return p
}
|
1
2
3
4
5
6
7
8
9
10
| flaggy.SetName("tiny")
flaggy.SetVersion(VERSION)
flaggy.SetDescription("一个用于局域网内共享文件的FTP程序。")
+ flaggy.SetHelpFlagDescription("打印帮助信息。")
+ flaggy.SetVersionFlagDescription("打印版本信息。当前版本: " + VERSION)
flaggy.String(&RootPath, "r", "road", "指定对外开放的目录的绝对路径。")
flaggy.String(&Port, "p", "port", "指定开放的端口")
flaggy.Bool(&IsAllowUpload, "a", "allow", "指定是否允许访问者上传。")
...
flaggy.Parse()
|
{% endtabs %}
{% endfolding %}
增加打印版本信息的 -v#
原本的打印信息有两种方式:
1
2
3
4
5
6
7
| # 第一种
$ cmd --version
0.0.1
# 第二种
$ cmd version
0.0.1
|
可能是为了把 -v 留给使用者定义成 --verbose
之类的,所以源码中只有几个默认的值:
1
2
3
4
| // strings used for builtin help and version flags both short and long
const versionFlagLongName = "version"
const helpFlagLongName = "help"
const helpFlagShortName = "h"
|
不过我暂时用不到,我希望能有第三种方式:
1
2
3
| # 第三种
$ cmd -v
0.0.1
|
所以进行了以下修改:
{% tabs tab-id %}
main.go
添加 versionFlagShortName
1
2
3
4
5
| // strings used for builtin help and version flags both short and long
const versionFlagLongName = "version"
+ const versionFlagShortName = "v"
const helpFlagLongName = "help"
const helpFlagShortName = "h"
|
helpValues.go
修改一下,这样打印帮助信息的时候才会显示出来 -v
1
2
3
4
5
6
7
8
9
10
11
12
| // if the built-in version flag is enabled, then add it as a help flag
if p.ShowVersionWithVersionFlag {
defaultVersionFlag := HelpFlag{
- ShortName: "",
+ ShortName: versionFlagShortName,
LongName: versionFlagLongName,
Description: DefaultParser.VersionFlagDescription,
DefaultValue: "",
Spacer: makeSpacer(versionFlagLongName, maxLength),
}
h.Flags = append(h.Flags, defaultVersionFlag)
}
|
subCommand.go
修改一下,这里主要是让 -v 生效
1
2
3
4
5
6
7
8
| // if the flag being passed is version or v and the option to display
// version with version flags, then display version
if p.ShowVersionWithVersionFlag {
- if flagName == versionFlagLongName {
+ if flagName == versionFlagLongName || flagName == versionFlagShortName {
p.ShowVersionAndExit()
}
}
|
同样在 subCommand.go
修改一下,这里主要是检查使用者是不是定义了 -v 或者 –version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // ensureNoConflictWithBuiltinVersion ensures that the flags on this subcommand do
// not conflict with the builtin version flag (-v/--version). Exits the program
// if a conflict is found.
func (sc *Subcommand) ensureNoConflictWithBuiltinVersion() {
for _, f := range sc.Flags {
if f.LongName == versionFlagLongName {
sc.exitBecauseOfVersionFlagConflict(f.LongName)
}
- if f.ShortName == versionFlagLongName {
+ if f.ShortName == versionFlagShortName {
sc.exitBecauseOfVersionFlagConflict(f.ShortName)
}
}
}
|
{% endtabs %}