亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

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

另一個go命令行參數處理器 - cmdr [mod]

發布時間:2020-07-27 04:11:51 來源:網絡 閱讀:768 作者:banlyst 欄目:編程語言

cmdr 是另一個命令行參數處理器。

Golang 自己帶有 flags 進行命令行參數處理,算是便利的,然而和 Google 一貫的做法相同,非常獨,非常反人類。

在計算機人機交互界面的歷史上,命令行的交互方式只有一種是貫穿始終,得到傳承和延續的,那就是 getopt 以及 getopt_long。說起 getopt 來也可以講述一個怪長的故事,然而本文不做此打算。無論如何,你需要知道的就是,getopt及其交互界面已經是POSIX的一部分,一個卓有成效的程序員、開發者、科學家,或者計算機從業者,對于這個界面都已經是訓練有素,無需成本了。你可能在用著它,但你或許只是沒有意識到它的存在而已。GNU的大部分命令行小刀都采用了這樣的界面,所以,例如tar, gwk, gzip, ls, rm, …,以及無法列舉的那些工具都是這樣的界面。

所以,自行其是,自己搞一套,并非不可以。但我可以不買賬。

那么,這并非我獨自一人的自賞。我們只需要知道,在 Golang 的開源圈子里,已經有了數十種 getopt-like 的復刻本,用以為 Golang 開發的應用程序提供更好的命令行界面。這里面不乏 viper/cobra, cli 那樣的巨作,也有一些小巧精干的實現。

cmdr 也是這么一個 getopt-like 的實現。和已有的其它實現不同之處在于,cmdr基本上原樣復制了 getopt 的表現。也就是說,一個典型的 Unix/Linux 應用程序,例如 cp,mv 等等,是怎么做的,那么基于 cmdr 的應用程序也就是怎么做的。這里講的當然是關于命令行參數怎么被解釋的問題,而非應用程序的具體邏輯。

讓我們來看看都有哪些具體方面。

POSIX 約定

POSIX 表示可移植操作系統接口(英語:Portable Operating System Interface,縮寫為POSIX)是 IEEE(電氣和電子工程師協會,Institute of Electrical and Electronics Engineers)為要在各種UNIX操作系統上運行軟件,而定義API的一系列互相關聯的標準的總稱,其正式稱呼為IEEE Std 1003,而國際標準名稱為ISO/IEC 9945。此標準源于一個大約開始于1985年的項目。POSIX這個名稱是由 理查德·斯托曼(RMS)應IEEE的要求而提議的一個易于記憶的名稱。它基本上是Portable Operating System Interface(可移植操作系統接口)的縮寫,而X則表明其對Unix API的傳承。 電氣和電子工程師協會(Institute of Electrical and Electronics Engineers,IEEE)最初開發 POSIX 標準,是為了提高 UNIX 環境下應用程序的可移植性。然而,POSIX 并不局限于 UNIX。許多其它的操作系統,例如 DEC OpenVMS 和 Microsoft Windows NT,都支持 POSIX 標準。

下面是 POSIX 標準中關于程序名、參數的約定:

  • 程序名不宜少于2個字符且不多于9個字符;
  • 程序名應只包含小寫字母和阿拉伯數字;
  • 選項名應該是單字符活單數字,且以短橫‘-‘為前綴;
  • 多個不需要選項參數的選項,可以合并。(譬如:foo -a -b -c ---->foo -abc
  • 選項與其參數之間用空白符隔開;
  • 選項參數不可選。
  • 若選項參數有多值,要將其并為一個字串傳進來。譬如:myprog -u "arnold,joe,jane"。這種情況下,需要自己解決這些參數的分離問題。
  • 選項應該在操作數出現之前出現。
  • 特殊參數 ‘--' 指明所有參數都結束了,其后任何參數都認為是操作數。
  • 選項如何排列沒有什么關系,但對互相排斥的選項,如果一個選項的操作結果覆蓋其他選項的操作結果時,最后一個選項起作用;如果選項重復,則順序處理。
  • 允許操作數的順序影響程序行為,但需要作文檔說明。
  • 讀寫指定文件的程序應該將單個參數'-'作為有意義的標準輸入或輸出來對待。
GNU長選項約定
  • 對于已經遵循POSIX約定的GNU程序,每個短選項都有一個對應的長選項。
  • 額外針對GNU的長選項不需要對應的短選項,僅僅推薦要有。
  • 長選項可以縮寫成保持惟一性的最短的字串。
  • 選項參數與長選項之間或通過空白字符活通過一個'='來分隔。
  • 選項參數是可選的(只對短選項有效)。
  • 長選項允許以一個短橫線為前綴。

getopt 界面

以下對 getopt 以及 getopt_long 提供的界面進行描述,cmdr 具備相同的能力。

在以下的行文中,短參數短選項是等同的概念,其它詞匯也類似如此,不再贅述。

短參數

單個短橫線引導的單個字符的參數,被稱為短參數。例如:-v-d,等等。有的時候,短參數也可能有兩個字符甚至更多個字母。然而,短參數的用意就在于縮略,因此多字符的短參數很少見,且通常被用于組合,更像是典型的單字符短參數后綴以一個取值。例如 rar 的選項中有 -ep, -ep1, -ep3:

  ep            Exclude paths from names
  ep1           Exclude base directory from names
  ep3           Expand paths to full including the drive letter

然而在實現其處理器時,我們可以提供 -ep<n> 的處理器就夠了,所以你仍然可以將其視為 -ep 短參數的變形。

長參數

兩個短橫線引導的多個字符的參數,被稱為長參數。例如:—debug--version 等等。

一般來說,長參數更具備描述性,通常使用單詞、詞組來構成長參數。例如 docker 的子命令 docker checkpoint create

$ docker checkpoint create --help

Usage:  docker checkpoint create [OPTIONS] CONTAINER CHECKPOINT

Create a checkpoint from a running container

Options:
      --checkpoint-dir string   Use a custom checkpoint storage directory
      --leave-running           Leave the container running after checkpoint
參數描述

每條命令或參數選項可以被一段文件以描述。

參數重復堆疊

無論長短參數,可以以任意順序出現,也可以任意出現多次。對于多次出現的參數,一般來說是最后一次出現的為準,之前出現過的會被覆蓋。

例如命令行:-1 -a yy -a dd -a cc,則對于參數a來說,其有效值為 ”cc“,此前出現的都被覆蓋了。

bool型短參數的組合

對于getopt不帶值的參數,例如 "1abc" ,以下的命令行都是有效的:

  • -1 -a -b -c
  • -abc1
  • -ac -1b
  • ...

順序是不敏感的,組合是任意的。

必須帶值的參數

getopt的定義是參數后加一個冒號,例如 “1a:b::" 中的參數 a,對它你需要指定命令行形如 -1 -a xxx

可選值的參數

getopt的定義是參數后加兩個冒號,例如 “1a?:b:?:" ?中的參數 b,對它你需要指定命令行形如 -1 -b 或者 -1 -bvalue

在 getopt 界面上的增強

命令和子命令

以 docker 的子命令 checkpoint 為例:

graph LR
A[docker] -->|Commands| B(checkpoint)
B --> D[create]
B --> E[ls]
B --> F[rm]

事實上,命令與子命令是沒有區別的,如果有必要,可以建立任意多級的命令和子命令嵌套層次。不過在實際的 Command-Line UI 設計中,超過4層的子命令嵌套都是極少數,因為這也會給使用工具的人帶來麻煩。

Shell自動完成

在現代的命令行界面中,自動完成(Shell Completion)已經是一個關鍵性特性了。流行的命令行界面例如 Bash、Zsh、Fish 都提供了自動完成的特性。通常一個應用程序需要面向這個Shells 提供配套的自動完成腳本,從而獲得自動完成能力。

一個已經支持自動完成的應用程序的命令行輸入可能是這樣子的:

Bash 的自動完成

docker 的 自動完成

另一個go命令行參數處理器 - cmdr [mod]

Zsh 的自動完成

docker 在 zsh 中的自動完成。可以注意到 zsh 的 TAB 按鍵次數更簡練,而且列表選擇界面也更有效和更具有提示性。當然,zsh的自動完成也存在一些bug,例如一級命令列表超出終端屏幕可視行數時列表選擇界面就被破碎掉了。

另一個go命令行參數處理器 - cmdr [mod]

cmdr 的使用方法

cmdr 的使用方法盡可能簡單化了,接下來我們做一個簡明的介紹。

一個簡單的入口可以這樣:

package main

import (
    "fmt"
    "github.com/hedzr/cmdr"
)

func main() {
    // logrus.SetLevel(logrus.DebugLevel)
    // logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true,})

    // 可選的四個選項:
    cmdr.EnableVersionCommands = true
    cmdr.EnableVerboseCommands = true
    cmdr.EnableHelpCommands = true
    cmdr.EnableGenerateCommands = true

    if err := cmdr.Exec(rootCmd); err != nil {
        fmt.Printf("Error: %v", err) // or log, logrus
    }
}

var(

    rootCmd = &cmdr.RootCommand{
        Command: cmdr.Command{
            BaseOpt: cmdr.BaseOpt{
                Name: "short",
                Flags: []*cmdr.Flag{

                },
            },
            SubCommands: []*cmdr.Command{
                serverCommands,
                // msCommands,
            },
        },

        AppName:    "short",
        Version:    cmdr.Version,
        VersionInt: cmdr.VersionInt,
        Copyright:  "austr is an effective devops tool",
        Author:     "Your Name <yourmail@gmail.com>",
    }

    serverCommands = &cmdr.Command{
        BaseOpt: cmdr.BaseOpt{
            Short:       "s",
            Full:        "server",
            Aliases:     []string{"serve", "svr",},
            Description: "server ops: for linux service/daemon.",
            Flags: []*cmdr.Flag{
                {
                    BaseOpt: cmdr.BaseOpt{
                        Short:       "f",
                        Full:        "foreground",
                        Aliases:     []string{"fg",},
                        Description: "running at foreground",
                    },
                },
            },
        },
        SubCommands: []*cmdr.Command{
            {
                BaseOpt: cmdr.BaseOpt{
                    Short:       "s",
                    Full:        "start",
                    Aliases:     []string{"run", "startup",},
                    Description: "startup this system service/daemon.",
                    Action: func(cmd *cmdr.Command, args []string) (err error) {
                        return
                    },
                },
            },
            {
                BaseOpt: cmdr.BaseOpt{
                    Short:       "t",
                    Full:        "stop",
                    Aliases:     []string{"stp", "halt", "pause",},
                    Description: "stop this system service/daemon.",
                },
            },
            {
                BaseOpt: cmdr.BaseOpt{
                    Short:       "r",
                    Full:        "restart",
                    Aliases:     []string{"reload",},
                    Description: "restart this system service/daemon.",
                },
            },
            {
                BaseOpt: cmdr.BaseOpt{
                    Full:        "status",
                    Aliases:     []string{"st",},
                    Description: "display its running status as a system service/daemon.",
                },
            },
            {
                BaseOpt: cmdr.BaseOpt{
                    Short:       "i",
                    Full:        "install",
                    Aliases:     []string{"setup",},
                    Description: "install as a system service/daemon.",
                },
            },
            {
                BaseOpt: cmdr.BaseOpt{
                    Short:       "u",
                    Full:        "uninstall",
                    Aliases:     []string{"remove",},
                    Description: "remove from a system service/daemon.",
                },
            },
        },
    }
)

可以看到的是,cmdr.RootCommand 和 cmdr.Command 的區別不大,只是多了應用程序信息的成員字段。而 cmdr.Command 和 cmdr.Flag 的區別也不大,它們都有相同的 BaseOpt 嵌入結構。

因此,定義命令和定義選項是很相似的,然后你需要進行正確的結構嵌套。如果感到嵌套結構迷亂了眼睛,則可以抽出一個子命令、或者一組子命令到一個獨立的變量中,然后使用引用的方式嵌入到上級命令的恰當位置。

這種抽出的方式也適合于進行相似結構的共享,但要注意引用和深度拷貝的區別。此處不做進一步討論了,總之,如果感到沒有把握,不妨一級一級地老老實實地完成定義,一般的工具開發也不會有太多的嵌套的吧。

言歸正傳,完成了上面的定義之后,就可以編譯成執行文件運行了(或者用 go run main.go)。你可以在終端中嘗試使用它:

bin/short
bin/short --help
bin/short --version
bin/short -#
bin/short --debug --verbose server --help
bin/short svr --help --debug -v
bin/short s start -f ~~debug
指定 Action

每條命令(cmdr.Command)都可以指定 Action,一個 func 對象。你將要為命令編寫的業務邏輯就在這里。

如果有必要的話,一個選項(cmdr.Flag)也可以被提供一個 Action,如果你需要在選項被掃描到時觸發點其他邏輯的話。

對于命令而言,你可以提供額外的 PreActionPostAction,它們分別是在命令的 Action 被執行的前后被調用的。特別是 PreAction 允許返回一個特別的錯誤值 cmdr.ShouldBeStopException 來告訴 cmdr 終止后續處理,所以你可以有機會避免 命令的 Action 部分被執行。

由于 RootCommand 也是一個 Command,所以定義在 RootCommand 中的 PreActionpostAction 有著特別的處理邏輯:

RootCommand.PreAction 將會在具體 Command.PreAction 執行之前被執行;RootCommand.PostAction 將會在具體 Command.PostAction 執行之后被執行。

這樣的特別邏輯是為了便于開發者定義自己的前置、退出邏輯。例如一個微服務應該在開始提供服務之前完成注冊中心登記,以及在停止服務時撤銷登記,這些任務適合于在 RootCommandPre/PostAction 中來做。

~~debug

~~debug 是一個隱藏性的標志。~~ 和 long 參數 是相似的,不過它的不同在于,相應的參數的入口不會被建立在 標準名字空間中,因此你需要在頂級名字空間中抽取它的值。

~~debug 有著一個特別的作用,在調試階段,這個選項將會使得正常處理邏輯結束后,附加一段調試性的信息輸出,其中包含 所有有效的選項及其最終值,還包含這些選項的 yaml 文本形式。

一個式樣是:

另一個go命令行參數處理器 - cmdr [mod]

你可以通過:

bin/wget-demo ~~debug

來查看相似的輸出結果。

我相信這個功能可以幫助你解決很多問題,不必再來猜來猜去的了。

名字空間

所有的選項值都被放在標準名字空間中,cmdr.RxxtPrefix 定義了標準名字空間的層級,其默認值為 app

這意味著 RootCommand 的 Flags,例如 --version ,可以用 cmdr.GetBool("app.version") 來抽取其值。類似的,--debug 的抽取語句為 cmdr.GetBool("app.debug")

前面說過 ~~debug 有點特殊,這樣的不加前綴的選項的值可以直接抽取:cmdr.GetBool("debug")

每一級命令或子命令就會建立一個嵌套的名字空間,其名稱取自命令的 Full 字段,也就是長參數名。因此 bin/short server start -f-f 的抽取語句為 cmdr.GetBool("app.server.start.foreground")

你當然可以執行不同的 cmdr.RxxtPrefix,例如:

cmdr.RxxtPrefix = []string{"server",}
// 等價于使用 ”server.xxx" 而不是 “app.xxx”

一個選項的值是可以多種形態的,但總的來說我們支持四種數據類型:

  • bool
  • int
  • string
  • string slice

更多的類型,我們暫不直接支持。未來或會予以增強。

環境變量重載

可以使用環境變量重載去覆蓋命令行參數。

所以:

CMDR_APP_SERVER_START_FOREGROUND=1 bin/short server start 等價于 bin/short server start -f

如果你希望使用非 ”CMDR_“ 的環境變量前綴,你可以設置 cmdr.EnvPrefix 來自行控制前綴。例如

cmdr.EnvPrefix = []string{ "Rx", "cd", }
// 等價于使用 RX_CD_ 前綴
當前版本的問題

環境變量的優先級較低,如果配置文件或者命令行參數有指定值,則環境變量的設定值就被掩蓋了。

這不符合慣例,我們考慮在下一版本中解決此問題。

我們將會實現的優先級為:defaultValue -> config-file -> env-var -> command-line opts。

配置文件的自動加載

默認情況下,cmdr 自動查看如下文件:

  • /etc/&lt;appname&gt;/&lt;appname&gt;.yml
  • /usr/local/etc/&lt;appname&gt;/&lt;appname&gt;.yml
  • $HOME/.&lt;appname&gt;/&lt;appname&gt;.yml

cmdr 也會自動裝載相應的 conf.d 子目錄中的所有 yaml 文件,并依次載入和覆蓋選項的定義值。因此你可以切分大型配置文件到多個小文件中,以便于運維部署和管理。

對于開發者來說,cmdr 還會首先檢查項目目錄下的 ./ci/etc/&lt;appname&gt;/&lt;appname&gt;.yml 是否有效并試圖自動加載它及其 conf.d 子目錄。

cmdr 支持 conf.d 文件夾的監視,其中的變化會被傳送給所有注冊的 listeners。關于這個方面的細節,可以查看:

  • cmdr.AddOnConfigLoadedListener(c)
  • cmdr.RemoveOnConfigLoadedListener(c)
  • cmdr.SetOnConfigLoadedListener(c, enabled)
當前版本的問題

無法定制加載位置、無法忽略加載位置,等等。

其他的配置文件格式也暫時不支持。

實例 wget-demo

我們已經實現了一個 wget 的命令行界面復刻版本,但是僅提供小部分命令行參數的處理,因為完整的復刻版本基本上只是一個重復的勞作了,作為示例我們已經實現了足夠多的選項,足以說明 cmdr 的能力了。

wget-demo 的幫助屏是這樣的:

另一個go命令行參數處理器 - cmdr [mod]

和 gnu 的 wget 相比較而言,看起來也算是沒有區別了。

wget-demo 的源碼可以在這里找到:

https://github.com/hedzr/cmdr/examples/wget-demo/

cmdr 的版本規劃

semver是符合規范的。

關于 semver 的含義可以查看如下兩個鏈接,無需多言:

  • <http://npm.broofa.com/?q=react,fbjs>
  • semver 中文版:semver.org/lang/zh-CN/

更多的介紹

cmdr 是在早前若干個非正式實現的基礎上重寫的一個新的實現,其首要目標就是完完全全地 Unix/Linux 命令行界面,而不是 golang 風格的、或者其它的部分實現的風格。

getopt 以及 getopt_long 都有自己的參數定義方式,不過在這個方面,cmdr 不打算實現它們的仿真風格,因為那并不方便也不算直觀。

cmdr 盡力做到的是,命令和參數定義完成之后就完成了一切。除此而外,你無需做別的事就能得到:

  • 自動的幫助屏
  • 自動的配置文件載入
  • 配置文件切分到 conf.d 子目錄,且自動監視其變更
  • 完全的 Unix/Linux Command-Line UI
  • 允許環境變量重載到選項
  • 支持 Shell 自動完成特性
  • 更多特性...

目前已經實現的是主體的大部分特性,細節尚未打磨完美,還需要繼續投入力量進行改善。然而作為建設的主要目標已經可作為已達成了。

更多 cmdr 用法,今后繼續進行描述。

參考

  • Source: https://github.com/hedzr/cmdr
向AI問一下細節

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

AI

英吉沙县| 叙永县| 郸城县| 贡山| 安康市| 万山特区| 巫山县| 滕州市| 明星| 新泰市| 望江县| 恩施市| 江山市| 子长县| 大余县| 隆德县| 白城市| 科尔| 赣榆县| 岳普湖县| 分宜县| 文昌市| 泾源县| 观塘区| 卢龙县| 呼和浩特市| 邓州市| 柘城县| 绥芬河市| 米林县| 榆中县| 基隆市| 商南县| 龙岩市| 定州市| 隆林| 习水县| 潜山县| 阿荣旗| 罗定市| 吴桥县|