暫時來說,這是最後一篇關於 cmdr
的系列介紹文章了。git
全部這個系列包括:github
這一次的內容算是雜燴亂燉。數據庫
~~debug
已經在前文講述過了。這裏再也不湊字數了。小程序
--tree
cmdr
提供了一個內置的選項:--tree
。segmentfault
雖然這是一個選項,但它和 --version
同樣是有着命令同樣的效果:若是 cmdr
在命令行參數中檢測到了 --tree
,那麼它會忽略已經處理的和將要處理的子命令、選項,直接執行 --tree
的 Action
。markdown
要想達到相似的效果並不困難:定義一個選項,重載其 Action 字段到一個響應函數,而且在該響應函數的結尾返回
cmdr.ErrShouldBeStopException
,這樣就會在該選項被識別時並執行Action後直接退出應用程序了。函數
--tree
的功能是打印出所有命令和子命令,以樹結構方式呈現出來。微服務
一個樣例以下圖:ui
這是我在開發階段執行 examples/demo 小程序所獲得的結果。
--tree
其實是利用了 cmdr
內建的 WalkAllCommands()
所提供的遍歷方式。spa
對全部命令及其選項進行遍歷,實際上有兩種方式:一是利用 Painter
以及相應的內部機制,二是經過 WalkAllCommands
明確地遍歷。
Painter
Painter
是一個接口。它被用在輸出幫助屏這個方面。儘管輸出幫助屏只是一個小小的功能,但你仍是能夠自定義它的行爲。你能夠自行實現 Painter
接口並經過 SetCurrentHelpPainter(painter)
來更改幫助屏的顯示內容。
若是你真的想這麼作,能夠查閱 Painter 的定義,也能夠 issue 到我,或許說不定我可以有所建議。
Walker
WalAllCommands(cmd, index, walker)
是一個更爲強大的遍歷器,實際上 manpage,markdown 的輸出就是經過這個機制來實現的。利用這個遍歷器,你能夠便利整個命令集的樹狀結構。通常來講,你應該給它傳遞 cmd=nil, index=0
的參數值來開始你的遍歷,這表示將會從頂級命令開始遍歷,並且將其視做第 0 層。index
這個參數將會在遍歷器遞歸時自動修正到符合層級計數,而後會被傳遞給 walker。我只是懶得將它改爲 level
名字了,它就是那個用途。
例如 --tree
的實現源代碼以下:
func dumpTreeForAllCommands(cmd *Command, args []string) (err error) { command := &rootCommand.Command _ = walkFromCommand(command, 0, func(cmd *Command, index int) (e error) { if cmd.Hidden { return } deep := findDepth(cmd) - 1 if deep == 0 { fmt.Println("ROOT") } else { sp := strings.Repeat(" ", deep) // fmt.Printf("%s%v - \x1b[%dm\x1b[%dm%s\x1b[0m\n", // sp, cmd.GetTitleNames(), // BgNormal, CurrentDescColor, cmd.Description) if len(cmd.Deprecated) > 0 { fmt.Printf("%s\x1b[%dm\x1b[%dm%s - %s\x1b[0m [deprecated since %v]\n", sp, BgNormal, CurrentDescColor, cmd.GetTitleNames(), cmd.Description, cmd.Deprecated) } else { fmt.Printf("%s%s - \x1b[%dm\x1b[%dm%s\x1b[0m\n", sp, cmd.GetTitleNames(), BgNormal, CurrentDescColor, cmd.Description) } } return }) return ErrShouldBeStopException }
能夠想象到你可以藉助這個遍歷器實現某些更強大的特性,在具有遍歷能力的基礎上,咱們其實能夠設計更強大的命令行界面結構,而沒必要擔憂過度複雜帶來的負面效果。
關於如何設計命令行界面的體系結構,保持其清晰性,這個不是咱們再這個系列文章中要討論的話題。
至於 Painter 和 Walker,其區別也很明顯。Painter 是被限定在幫助屏構造層面的,且不會遞歸下去,除非你想自行實現。Walker 是全局層面的遞歸遍歷器,面向的是全部的命令。
Command
Command
的 Action
字段能夠定義你的命令的業務實現邏輯。
func MsTagsList(cmd *cmdr.Command, args []string) (err error) { return }
通常來講,你在 impl
package 中定義業務實現邏輯的入口,如同上面的代碼示例,並在某個 Command
的數據定義中引用它。
msTagsListCommand = &cmdr.Command { BaseOpt: cmdr.BaseOpt { Short: "ls", Full: "list", Description: "list all tags of a micro-service", Action: impl.MsTagsList, } }
因此,對於 Command
來講,Action 多是最重要的 Hook。
Command
在 Command
的 Action
被執行以前,其 PreAction
會被首先調用,你能夠定義本身的邏輯,例如檢查特定條件是否知足,若是不知足則返回一個error以通知cmdr錯誤性結束。若是你認爲並無錯誤發生,但仍應該結束處理,你能夠返回一個 cmdr.ErrShouldBeStopException
,這樣的話 cmdr
也會結束處理,但整個 program 會被正常終止:反應到 Shell 層面上時,此時程序是無錯的,Shell返回值爲0。
在 Command
的 Action
被執行以後,不管處理結果如何,PostAction
將會被調用。它能夠用來進行某些退出時邏輯處理。
RootCommand
在被命中的命令的 PreAction
和 PostAction
被執行之際,RootCommand
的 PreAction
和 PostAction
也會分別被執行。這是一個關鍵性的特性。它使得你能夠妥善地實現你的全局預處理和後處理邏輯,例如註冊微服務到註冊中心以及撤銷註冊,鏈接到數據庫和關閉數據庫鏈接,等等。
Flag
對於 Flag 來講,沒有 Pre/Post 機制。
Flag 具備 Action 的 Hook,因此你能夠在每一個 Flag 被命中時作點什麼事。例如,反轉或復位 Owner 的全部其餘 bool flags,或者爲應用程序的其餘配置設定一整組預設方案,等等。
值得一提的是,在 v0.2.15 以後,咱們已經實現了 ToggleGroup
,所以對於想要創建RadioButtonGroup 效果的場景來講,你卻是沒必要再手寫邏輯了。
Xref 術語是一個特定節點的表述。
當 cmdr.Exec(rootCmd) 進入狀態時,它依次作這些事:
在 buildXref 階段,cmdr
實際上創建了 rootCmd 及其全部子命令和選項集合 的內部map、索引等等交叉引用。這個過程當中,cmdr
處理 rootCmd,也查找配置文件以及子目錄 conf.d,也對環境變量進行搜尋。
當 buildXref 完成以後,cmdr.GetXXX()
就是徹底可用的狀態了。
如此,beforeXrefBuilding 和 afterXrefBuilt 的 hooks 其實是你想要進行定製化操做的關鍵節點。
歡迎使用。
應該沒必要解釋太多。
當外部文件被修改時,cmdr自動載入變化,併合併到已有的選項集合中,而後發出回調通知觀察者應該作點什麼了。
你能夠在任何地方任意地發出觀察約定。總體上說這是極爲輕量級的。這麼作的緣由是不一樣業務模塊有必要自行管理本身的相關選項集而不是在全局的某一個集中點處理所有模塊的所有選項集合,那太耦合了。
須要特別指出的是,配置文件老是一個主文件,加上可選多個子配置文件(都被放在配置文件所在目錄的 conf.d
子目錄之下)。而咱們內部的文件系統 Watcher 只會監聽 conf.d 之中的文件變化,而忽略主文件的變化。
因此你不該該在主配置文件中放置太多具體選項。好的實踐是將一切配置都切分到 conf.d 之中。
SetPredefinedLocations(locations string) 容許你指定配置文件的搜索路徑。
SetOnConfigLoadedListener
的用途是臨時禁用和啓用一個觀察者。
說它們是 Hook 也不算錯。
cmdr
自動提供 ShowBuildInfo 和 ShowVersion 實現,用於打印 編譯信息屏 和 版本信息屏。
SetCustonShowBuildInfo 和 SetCustomShowVersion 則容許你自行提供你的實現。
暫時結束。繼續改進。