中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

從 golang flag 遷移到 cmdr

發布時間:2020-06-29 02:49:18 來源:網絡 閱讀:232 作者:banlyst 欄目:編程語言

基于 cmdr v1.0.3
06 - Migrating from flag

采用一個新的命令行解釋器框架,最痛苦地莫過于編寫數據結構或者流式定義了。我們首先回顧一下 cmdr 和其它大多數三方增強命令行解釋器都支持的最典型的兩種命令行界面定義方式,然后再來研究一下 cmdr 新增的最平滑的遷移方案。

典型的方式

通過結構數據體定義

有的增強工具(例如 cobra,viper)采用結構體數據定義方式來完成界面指定,如同 cmdr 的這樣:

rootCmd = &cmdr.RootCommand{
    Command: cmdr.Command{
        BaseOpt: cmdr.BaseOpt{
            Name:            appName,
            Description:     desc,
            LongDescription: longDesc,
            Examples:        examples,
        },
        Flags: []*cmdr.Flag{},
        SubCommands: []*cmdr.Command{
            // generatorCommands,
            // serverCommands,
            msCommands,
            testCommands,
            {
                BaseOpt: cmdr.BaseOpt{
                    Short:       "xy",
                    Full:        "xy-print",
                    Description: `test terminal control sequences`,
                    Action: func(cmd *cmdr.Command, args []string) (err error) {
                        fmt.Println("\x1b[2J") // clear screen

                        for i, s := range args {
                            fmt.Printf("\x1b[s\x1b[%d;%dH%s\x1b[u", 15+i, 30, s)
                        }

                        return
                    },
                },
            },
            {
                BaseOpt: cmdr.BaseOpt{
                    Short:       "mx",
                    Full:        "mx-test",
                    Description: `test new features`,
                    Action: func(cmd *cmdr.Command, args []string) (err error) {
                        fmt.Printf("*** Got pp: %s\n", cmdr.GetString("app.mx-test.password"))
                        fmt.Printf("*** Got msg: %s\n", cmdr.GetString("app.mx-test.message"))
                        return
                    },
                },
                Flags: []*cmdr.Flag{
                    {
                        BaseOpt: cmdr.BaseOpt{
                            Short:       "pp",
                            Full:        "password",
                            Description: "the password requesting.",
                        },
                        DefaultValue: "",
                        ExternalTool: cmdr.ExternalToolPasswordInput,
                    },
                    {
                        BaseOpt: cmdr.BaseOpt{
                            Short:       "m",
                            Full:        "message",
                            Description: "the message requesting.",
                        },
                        DefaultValue: "",
                        ExternalTool: cmdr.ExternalToolEditor,
                    },
                },
            },
        },
    },

    AppName:    appName,
    Version:    cmdr.Version,
    VersionInt: cmdr.VersionInt,
    Copyright:  copyright,
    Author:     "xxx <xxx@gmail.com>",
}
//... More

它的問題在于,如果你有 docker 那樣的較多的子命令以及選項需要安排的話,這個方案會相當難定位,寫起來也很痛苦,改起來更痛苦。

通過流式調用鏈方式定義

比結構體數據定義方案更好一點的是采用流式調用鏈方式。它可能長得像這樣:

    // root

    root := cmdr.Root(appName, "1.0.1").
        Header("fluent - test for cmdr - no version - hedzr").
        Description(desc, longDesc).
        Examples(examples)
    rootCmd = root.RootCommand()

    // soundex

    root.NewSubCommand().
        Titles("snd", "soundex", "sndx", "sound").
        Description("", "soundex test").
        Group("Test").
        Action(func(cmd *cmdr.Command, args []string) (err error) {
            for ix, s := range args {
                fmt.Printf("%5d. %s => %s\n", ix, s, cmdr.Soundex(s))
            }
            return
        })

    // xy-print

    root.NewSubCommand().
        Titles("xy", "xy-print").
        Description("test terminal control sequences", "test terminal control sequences,\nverbose long descriptions here.").
        Group("Test").
        Action(func(cmd *cmdr.Command, args []string) (err error) {
            fmt.Println("\x1b[2J") // clear screen

            for i, s := range args {
                fmt.Printf("\x1b[s\x1b[%d;%dH%s\x1b[u", 15+i, 30, s)
            }

            return
        })

    // mx-test

    mx := root.NewSubCommand().
        Titles("mx", "mx-test").
        Description("test new features", "test new features,\nverbose long descriptions here.").
        Group("Test").
        Action(func(cmd *cmdr.Command, args []string) (err error) {
            fmt.Printf("*** Got pp: %s\n", cmdr.GetString("app.mx-test.password"))
            fmt.Printf("*** Got msg: %s\n", cmdr.GetString("app.mx-test.message"))
            fmt.Printf("*** Got fruit: %v\n", cmdr.GetString("app.mx-test.fruit"))
            fmt.Printf("*** Got head: %v\n", cmdr.GetInt("app.mx-test.head"))
            return
        })
    mx.NewFlag(cmdr.OptFlagTypeString).
        Titles("pp", "password").
        Description("the password requesting.", "").
        Group("").
        DefaultValue("", "PASSWORD").
        ExternalTool(cmdr.ExternalToolPasswordInput)
    mx.NewFlag(cmdr.OptFlagTypeString).
        Titles("m", "message", "msg").
        Description("the message requesting.", "").
        Group("").
        DefaultValue("", "MESG").
        ExternalTool(cmdr.ExternalToolEditor)
    mx.NewFlag(cmdr.OptFlagTypeString).
        Titles("fr", "fruit").
        Description("the message.", "").
        Group("").
        DefaultValue("", "FRUIT").
        ValidArgs("apple", "banana", "orange")
    mx.NewFlag(cmdr.OptFlagTypeInt).
        Titles("hd", "head").
        Description("the head lines.", "").
        Group("").
        DefaultValue(1, "LINES").
        HeadLike(true, 1, 3000)

    // kv

    kvCmd := root.NewSubCommand().
        Titles("kv", "kvstore").
        Description("consul kv store operations...", ``)
//...More

這種方式很有效地改進的痛苦之源。要說起來,也沒有什么缺點了。所以這也是 cmdr 主要推薦你采用的方案。

通過結構 Tag 方式定義

這種方式被有一些第三方解釋器所采用,可以算是比較有價值的定義方式。其特點在于直觀、易于管理。

它的典型案例可能是這樣子的:

type argT struct {
    cli.Helper
    Port int  `cli:"p,port" usage:"short and long format flags both are supported"`
    X    bool `cli:"x" usage:"boolean type"`
    Y    bool `cli:"y" usage:"boolean type, too"`
}

func main() {
    os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
        argv := ctx.Argv().(*argT)
        ctx.String("port=%d, x=%v, y=%v\n", argv.Port, argv.X, argv.Y)
        return nil
    }))
}

不過,由于 cmdr 沒有打算支持這種方案,所以這里僅介紹到這個程度。

說明一下,cmdr 之所以不打算支持這種方案,是因為這樣做好處固然明顯,壞處也同樣令人煩惱:復雜的定義可能會因為被嵌套在 Tag 內而導致難以編寫,例如多行字符串在這里就很難過。

cmdr 新增的兼容 flag 的定義方式

那么,我們回顧了兩種或者三種典型的命令行界面定義方式之后,可以發現他們和 flag 之前的區別是比較大的,當你一開始設計你的 app 時,如果為了便宜和最快開始而采用了 flag 方案的話(畢竟,這是golang自帶的包嘛),再要想切換到一個增強版本的話,無論哪一個都會令你痛一下。

flag 方式

我們看看當你采用 flag 方式時,你的 main 入口可能是這樣的:

// old codes

package main

import "flag"

var (
    serv           = flag.String("service", "hello_service", "service name")
    host           = flag.String("host", "localhost", "listening host")
    port           = flag.Int("port", 50001, "listening port")
    reg            = flag.String("reg", "localhost:32379", "register etcd address")
    count          = flag.Int("count", 3, "instance's count")
    connectTimeout = flag.Duration("connect-timeout", 5*time.Second, "connect timeout")
)

func main(){
      flag.Parse()
      // ...
}

遷移到 cmdr

為了遷移為使用 cmdr,你可以簡單地替換 import "flag" 語句為這樣:

import (
  // “flag”
  "github.com/hedzr/cmdr/flag"
)

其它內容一律不變,也就是說完整的入口現在像這樣:

// new codes

package main

import (
  // “flag”
  "github.com/hedzr/cmdr/flag"
)

var (
    serv           = flag.String("service", "hello_service", "service name")
    host           = flag.String("host", "localhost", "listening host")
    port           = flag.Int("port", 50001, "listening port")
    reg            = flag.String("reg", "localhost:32379", "register etcd address")
    count          = flag.Int("count", 3, "instance's count")
    connectTimeout = flag.Duration("connect-timeout", 5*time.Second, "connect timeout")
)

func main(){
    flag.Parse()
    // ...
}

怎么樣,足夠簡單吧?

引入增強特性

那么我們現在期望引入更多 cmdr 專有特性怎么辦呢?

例如想要全名(完整單詞)作為長選項,補充短選項定義,這可以通過如下的序列來達成:

import (
    // “flag”
    "github.com/hedzr/cmdr"
    "github.com/hedzr/cmdr/flag"
)

var(
    // uncomment this line if you like long opt (such as --service)
    treatAsLongOpt = flag.TreatAsLongOpt(true)

    serv = flag.String("service", "hello_service", "service name",
                       flag.WithShort("s"),
                       flag.WithDescription("single line desc", `long desc`))
)

類似的可以完成其他增強特性的定義。

可用的增強特性

所有 cmdr 特性被濃縮在幾個少量的接口中了。此外,某些特性是當你使用 cmdr 時就立即獲得了,無需其它表述或設定(例如短選項的組合,自動的幫助屏,多級命令等等)。

所有的這些需要指定適當參數的特性,包含在如下的這些定義中:

flag.WithTitles(short, long string, aliases ...string) (opt Option)

定義短選項,長選項,別名。

綜合來說,你必須在某個地方定義了一個選項的長名字,因為這是內容索引的依據,如果長名字缺失,那么可能會有意料之外的錯誤。

別名是隨意的。

如果可以,盡可能提供短選項。

短選項一般來說是一個字母,然而使用兩個甚至更多字母是被允許的,這是為了提供多種風格的命令行界面的兼容性。例如 wget, rar 都采用了雙字母的短選項。而 golang flag 自身支持的是任意長度的短選項,沒有長選項支持。cmdr 在短選項上的寬松和兼容程度,是其它幾乎所有第三方命令行參數解釋器所不能達到的。

flag.WithShort(short string) (opt Option)

提供短選項定義。

flag.WithLong(long string) (opt Option)

提供長選項定義。

flag.WithAliases(aliases ...string) (opt Option)

提供別名定義。別名是任意多、可選的。

flag.WithDescription(oneLine, long string) (opt Option)

提供描述行文本。

oneLine 提供單行描述文本,這通常是在參數被列表時。long 提供的多行描述文本是可選的,你可以提供空字串給它,這個文本在參數被單獨顯示幫助詳情時會給予用戶更多的描述信息。

flag.WithExamples(examples string) (opt Option)

可以提供參數用法的命令行實例樣本。

這個字串可以是多行的,它遵照一定的格式要求,我們以前的文章中對該格式有過描述。這樣的格式要求是為了在 man/info page 中能夠獲得更視覺敏銳的條目,所以你可以自行判定要不要遵守規則。

flag.WithGroup(group string) (opt Option)

命令或者參數都是可以被分組的。

分組是可以被排序的。給 group 字串一個帶句點的前綴,則這個前綴會被切割出來用于排序,排序規則是 A-Z0-9a-z 按照 ASCII 順序。所以:

  • 1001.c++, 1100.golang, 1200.java, …;

  • abcd.c++, b999.golang, zzzz.java, …;

是有順序的。

由于第一個句點之前的排序用子串被切掉了,因此你的 group 名字可以不受這個序號的影響。

給分組一個空字串,意味著使用內置的 分組,這個分組被排列在其他所有分組之前。

給分組一個 cmdr.UnsortedGroup 常量,則它會被歸納到最后一個分組中。值得注意的是,最后一個分組,依賴的是 cmdr.UnsortedGroup 常量的具體值zzzz.unsorted,所以,你仍然有機會定義一個別的序號來繞過這個“最后”。

flag.WithHidden(hidden bool) (opt Option)

hidden為true是,該選項不會被列舉到幫助屏中。

flag.WithDeprecated(deprecation string) (opt Option)

一般來說,你需要給 deprecation 提供一個版本號。這意味著,你提醒最終用戶該選項從某個版本號開始就已經被廢棄了。

按照 Deprecated 的禮貌規則,我們廢棄一個選項時,首先標記它,并給出替代提示,然后在若干次版本迭代之后正式取消它。

flag.WithAction(action func(cmd *Command, args []string) (err error)) (opt Option)

按照 cmdr 的邏輯,一個選項在被顯式命中時,你可以提供一個即時的響應動作,這可能允許你完成一些特別的操作,例如為相關聯的其它一組選項調整默認值什么的。

flag.WithToggleGroup(group string) (opt Option)

如果你打算定義一組選項,帶有互斥效果,如同 radio button group 那樣,那么你可以為它們提供相同的 WithToggleGroup group name這個名字和 WithGroup group name 沒有任何關聯關系

flag.WithDefaultValue(val interface{}, placeholder string) (opt Option)

提供選項的默認值以及占位符。

默認值的數據類型相當重要,因為這個數據類型時后續抽取該選項真實值的參考依據。

例如,int數據一定要提供一個 int 數值,Duration數據一定要提供一個 3*time.Second 這樣的確切數值。

flag.WithExternalTool(envKeyName string) (opt Option)

提供一個環境變量名,例如 EDITOR。那么,如果該選項的 value 部分沒有在命令行中被提供時,cmdr 會搜索環境變量的值,將其作為控制臺終端應用程序運行,并收集該運行的結果(一個臨時文件的文件內容)用于為該選項復制。

如同 git commit -m 那樣。

flag.WithValidArgs(list ...string) (opt Option)

提供一個枚舉表,用于約束用戶所提供的值。

flag.WithHeadLike(enable bool, min, max int64) (opt Option)

當該選項被設定為 enable=true 時,識別用戶輸入的諸如 -1973, -211 之類的整數短選項,將其整數數值作為本選項的數值。

如同 head -9 等效于 head -n 9 那樣。

結束語

好了。很多內容。不過還是堆出來了,自己欣慰一下。

真正的結束語

嗯,cmdrv1.0.3 是一個 pre-release 版本,我們已經提供一個 flag 的最平滑遷移的基本實現。

最近的日子里,我們會考慮完成子命令部分,并最終釋出 v1.1.0,請期待。

如果認為這樣做有價值的話,考慮去鼓勵一下。

  • SRC: https://github.com/hedzr/cmdr

后記

v1.0.5 中,已經訂正了存在的問題,并加上了子命令的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

兴安县| 莱西市| 含山县| 财经| 合水县| 临漳县| 如皋市| 莱西市| 兴国县| 班玛县| 巴青县| 新田县| 如皋市| 宣威市| 青州市| 安康市| 广德县| 江城| 垦利县| 天峻县| 崇义县| 紫阳县| 永济市| 嘉祥县| 石阡县| 陵水| 怀仁县| 故城县| 胶南市| 永平县| 克拉玛依市| 新郑市| 万宁市| 海丰县| 梓潼县| 班戈县| 思茅市| 新邵县| 大兴区| 大理市| 常熟市|