天天看點

指令行工具開發:如何快速實作指令行提示?

簡介: 對于稍微複雜一些的指令行工具,指令行的提示功能必不可少。那麼對于不同語言的開發者,有沒有一種簡單快捷的實作方式呢?本文分享一種快速實作的方法,使用YAML檔案定義指令行工具的使用規範,再通過工具自動生成各種shell的指令行提示腳本,最後分享一些至關重要的指令行解析器。
指令行工具開發:如何快速實作指令行提示?

不少同學喜歡開發指令行工具,主要是開發快捷,而且和其他指令行工具配合,借助腳本,非常容易實作一些任務的自動化。指令行工具開發比較簡單,以Java舉一個例子,通常我們隻需要一個指令行參數解析器,如Java,就有args4j, jopt,picocli等,轉換為結構化的對象,根據輸入的參數進行相關的邏輯判斷,完成對應的邏輯。其他如Node.js, Deno, Python等,也是一樣的流程,都有指令行參數解析器,然後基于指令行輸入執行對應的邏輯。

一 指令行提示

如果指令行工具稍微複雜一些,那麼必須要提供對應的指令行提示,不然開發者幾乎沒法使用。舉一個例子,阿裡雲有對應的指令行工具aliyun-cli[1],下載下傳安裝後就可以使用aliyun指令行工具了。執行 aliyun --help,會發現非常多的子指令,如果沒有指令行工具提示,開發者使用這個工具就非常複雜,要去查文檔,或者通過指令行的help來輸入指令。

aliyun的指令行工具也提供了對應的代碼提示,如下所示:

指令行工具開發:如何快速實作指令行提示?

這個指令行提示還不錯,你隻需要選擇對應的子指令然後再進行提示就可以了。

大多數開發者喜歡帶描述的指令行提示。并不是所有的子指令和指令參數都命名得非常好,如aliyun指令行給出的live子指令提示,大家可能完全不知道這個live是什麼(當然,作為阿裡雲的同學,我還是知道的, live是視訊直播)。而像如下包括描述的指令行提示就直覺很多:

指令行工具開發:如何快速實作指令行提示?

二 生成指令行提示

這裡不再介紹bash,zsh,fish等各種shell的指令行提示的機制,沒有人會手動編寫這些指令行提示腳本,大家都會使用架構生成對應shell的指令行提示腳本。

我找了一些指令行解析架構,并且能自動生成指令行提示的,如Java的picocli,Node.js的commander.js,Python的argparse,以及Rust的clap-rs等。我都嘗試了一下,最終發現還是clap-rs生成的指令行提示比較好,就是我說的那種帶描述,而且還有檔案名和目錄自動提示,枚舉值的提示等,關鍵是也非常簡單。如果有同學有更好的指令行解析架構,希望能留言分享一下。

那麼如何讓其他語言,如Node.js,Java,Python這些語言編寫的指令行工具也能實作和clap-rs的指令行提示一樣的效果呢?

三 clap-rs的指令行YAML檔案

clap-rs包含了一個指令行工具的YAML規範。我們都知道指令行工具互動比較簡單,主要就兩個部分:參數和子指令。你看到類似 --conf xxx.yaml 這些帶參數名的都屬于參數,也可以省略參數名,如 convert a.jpg a.png 其中的a.jpg和a.png也都是參數。子指令就比較容易了解了,我們每天使用的git就是大量使用子指令的,如 git add xxx.jpg 這些。當子指令還可以繼續套用子指令,子指令同時也擁有自己的參數。

基于指令行這樣的特性,我們完全可以将指令行工具的使用規範通過YAML描述出來,現在一切皆可YAML。

這裡我給出一個阿裡雲指令行工具的YAML定義,當然隻是demo。如下:

name: aliyun2
version: "0.1.0"
about: "cli for Alibaba Cloud"
args:
  - version:
      short: v
      long: version
      takes_value: false
      about: Display version
subcommands:
  - oss:
      about: 對象存儲
      subcommands:
        - cat:
            about: cat文本檔案
            args:
              - file:
                  takes_value: true
                  required: true
                  about: 檔案名稱
        - ls:
            about: list檔案
  - ecs:
      about: 雲伺服器
      subcommands:
        - SendFile:
            about: send file
        - AddTags:
            about: add tags
           

可以看出,我首先定義了兩個子指令:oss和ecs,然後oss子指令下我又定義了兩個子指令:cat和ls。對于oss的cat子指令,我又添加了file這個參數,這樣我就可以使用cat來檢視oss上文本檔案的内容。

有了這個指令行工具YAML規範定義後,我就可以調用clap-rs提供的指令行工具接口,生成對應的shell的提示腳本。效果如下:

指令行工具開發:如何快速實作指令行提示?

這個指令行提示的效果是不是比原先的要好多了?提示有了描述,選擇子指令和參數的時候就簡單多了。

四 為所有指令行工具寫YAML

講到這裡,相信大家都明白了。無論這個工具是Java,Python,Node.js還是Rust編寫的,首先定義好該工具的YAML規範,接下來開發人員根據該規範去編寫代碼,他可以選擇他喜歡的語言,他喜歡的指令行解析器,然後實作對應的功能即可。沒有代碼提升,編寫YAML檔案不出錯是非常難的,是以我做了一個JSON Schema[2]檔案,在編寫YAML檔案時可以進行代碼提示,做到編寫指令行YAML規範檔案更加簡單。JSON Schema的使用方法如下:

指令行工具開發:如何快速實作指令行提示?

接下來我們會基于該YAML檔案,為各種shell生成對應的指令行提示腳本,如bash,zsh,fish和powershell,這樣分開後,開發人員也不需要去處理那些他不清楚的指令行提示,或者找該程式設計語言對應的SDK來做指令行代碼提示。如果沒有怎麼辦?即便有了,生成的提示非常簡單怎麼辦?畢竟指令行工具提示非常重要。

相信Node.js的開發者也不希望還要學習一下Rust和clap-rs,這樣就太不高效了。是以我又編寫了一個工具cli-completion[3], 其主要目的根據上面說的YAML檔案幫你自動生成各種shell的指令行提示腳本。來看一下zsh的例子:

$ cli-completion --zsh commands/aliyun2.yaml > /usr/local/share/zsh/site-functions/_aliyun2
$ autoload -U compinit && compinit
           

再看一下oh-my-zsh的例子:

$ mkdir ~/.oh-my-zsh/custom/plugins/aliyun2 
$ cli-completion --zsh aliyun2.yaml >  ~/.oh-my-zsh/custom/plugins/aliyun2/_aliyun2
           

通過這種方式,cli-completion可以為任何指令提供指令行提示。也就是說,以後,你隻要編寫指令行邏輯,關于指令行提示的問題,全部交給cli-completion幫你生成即可。當然考慮到使用者體驗,你可能需要在指令行工具中,将cli-completion生成的腳本,通過某一子指令,快速同步到用戶端環境。

指令行的開發流程:YAML規範編寫,指令行提示自動生成,開發人員下班前完成功能實作。

指令行工具開發:如何快速實作指令行提示?

有同學可能會問,我能否基于YAML檔案,并結合某一指令行解析架構,自動完成整個應用的骨架生成,這完全可以,開發人員隻要實作一些函數即可,開發會更簡單。我個人認為使用PicoCli這些架構自動生成代碼,是完全沒有問題的。

五 将cli-completion FaaS化

這個功能大家一年都未必用上兩次,費時安裝也挺麻煩的。現在不是到處都是FaaS,我們也可以嘗試一下。首先cli-completion是用Rust編寫的,是以可以用傳統的方式編寫Rust Cloud Lambda,然後部署到雲服務上,另外也可以寫一個Rust Web應用,如用actix-web,也非常簡單。

這些都不夠時髦,我們打算将cli-completion的代碼WebAssembly化,然後以FaaS方式部署,這裡我選擇CloudFlare作為FaaS的運作平台。讓我們來看一下Demo。

建立一個cli.yaml檔案,如下:

name: cli1
version: "0.1.0"
about: "CLI completion for bash, zsh, fish and powershell."
args:
  - help:
      short: h
      long: help
      takes_value: false
      about: Display this help
           

然後調用cli-completion的FaaS服務,就可以得到對應的指令行提示腳本代碼。指令如下:

curl -H 'Content-Type: application/x-yaml' --data-binary "@cli.yaml" https://cli-completion.linux-china.workers.dev/completion/zsh
           

對比傳統的cloud lambda或者cloud function,這種方式FaaS響應速度最快,這種服務調用次數非常少,基本就是每次請求都是冷啟動,而WebAssembly這方面就非常有優勢。

當然還有一個最大的原因:就是WebAssembly方式的FaaS,它最便宜。

題外話探讨一下cloudflare的WebAssmebly的實作,純技術讨論,代碼如下:

async function handleRequest(request) {
    const { greet } = wasm_bindgen
    await wasm_bindgen(wasm)
    const greeting = greet()
    return new Response(greeting, {status: 200})
}
           

上述代碼中,wasm是一個WebAssembly.Module對象,它是從外部注入的,而不是開發者寫的,是FaaS生成的。接下來就是從wasm_bindgen這個函數中擷取wasm的導出函數,然後調用 wasm_bindgen(wasm) 将greet函數和wasm module中的export函數進行關聯,然後調用greet就會轉到wasm module的調用。如果是這樣的話,WebAssembly.Module其實是可以外部管理的,當有請求時,再和JavaScript的函數進行關聯,這樣就可以保證WebAssembly的快速響應。

六 總結

以後大家在寫指令行工具時,不用再擔心代碼提示的問題了。在動手開發工具前,寫一下YAML檔案,整理和厘清一下你的思路,有哪些子指令,有哪些參數等,然後再基于該YAML檔案進行開發,使用什麼語言都沒有關系,最後配合cli-completion完成指令行提示,你的指令行工具算是相當專業的了,至少從面子上看起來是的 :)

最後列出一些指令行應用涉及的至關重要的指令行解析器,友善大家後續參考:

  • Java:Picocli, JCommander, JOpt, kotlinx-cli, JLine, args4j
  • Node.js:Commander.js, clap.js, minimist, yargs[4]
  • Deno:yargs
  • Python:argparse, docopt, cli-args, clap
  • Golang:argparse, flaggy
  • Rust:clap-rs, pico-args, paw
  • Ruby:cmdparse, commander, GLI
  • C++:gflags, cli, docopt.cpp

整理的不全,歡迎大家補充 :)

相關連結

[1]https://github.com/aliyun/aliyun-cli

[2]https://github.com/linux-china/cli-completion/blob/master/cli-schema.json

[3]https://crates.io/crates/cli-completion

[4]https://www.npmjs.com/search?q=args%20parser

原文連結:

https://developer.aliyun.com/article/779826?

版權聲明: 本文内容由阿裡雲實名注冊使用者自發貢獻,版權歸原作者所有,阿裡雲開發者社群不擁有其著作權,亦不承擔相應法律責任。具體規則請檢視《阿裡雲開發者社群使用者服務協定》和《阿裡雲開發者社群知識産權保護指引》。如果您發現本社群中有涉嫌抄襲的内容,填寫侵權投訴表單進行舉報,一經查實,本社群将立刻删除涉嫌侵權内容。