第一次修改别人的东西哈哈

今天开始给 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 %}