首頁 > 軟體

CommonLisp中解析命令列引數範例

2022-08-22 14:02:08

clingon

clingon 是一個 Common Lisp 的命令列選項的解析器,它可以輕鬆地解析具有複雜格式的命令列選項。例如,下面的程式碼可以列印給定次數的打招呼資訊

#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#
(progn ;;init forms
  (ros:ensure-asdf)
  #+quicklisp(ql:quickload '(clingon) :silent t)
  )
(defpackage :ros.script.hello.3868869124
  (:use :cl
        :clingon))
(in-package :ros.script.hello.3868869124)
(defun top-level/handler (cmd)
  (check-type cmd clingon:command)
  (let ((count (clingon:getopt cmd :count))
        (name (first (clingon:command-arguments cmd))))
    (dotimes (_ count)
      (declare (ignorable _))
      (format t "Hello ~A!~%" name))))
(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "hello"
              :options (list
                        (clingon:make-option
                         :integer
                         :description "number of greetings"
                         :initial-value 1
                         :key :count
                         :long-name "count")))))
    (clingon:run app argv)))
;;; vim: set ft=lisp lisp:

稍微做一些解釋。首先執行命令ros init hello生成上面的程式碼的雛形——載入依賴、包定義,以及空的函數main。為了載入 clingon,將其作為函數ql:quickload的引數。然後分別定義一個commandhandler,以及option

在 clingon 中,類clingon:command的範例物件表示一個可以在 shell 中被觸發的命令,它們由函數clingon:make-command建立。每一個命令起碼要有三個要素:

  • :handler,負責使用命令列選項、實現業務邏輯的函數;
  • :name,命令的名字,一般會被展示在命令的用法說明中;
  • :options,該命令所接受的選項。

此處的:handler就是函數top-level/handler,它會被函數clingon:run呼叫(依賴注入的味道),並將一個合適的clingon:command物件傳入。:options目前只承載了一個選項的定義,即

                        (clingon:make-option
                         :integer
                         :description "number of greetings"
                         :initial-value 1
                         :key :count
                         :long-name "count")

它定義了一個值為整數的選項,在命令列中通過--count指定。如果沒有傳入該選項,那麼在使用函數clingon:getopt取值時,會獲得預設值 1。如果要從一個命令物件中取出這個選項的值,需要以它的:key引數的值作為引數來呼叫函數clingon:getopt,正如上面的函數top-level/handler所示。

子命令

clingon 也可以實現諸如git addgit branch這樣的子命令特性。像addbranch這樣的子命令,對於 clingon 而言仍然是類clingon:command的範例物件,只不過它們不會傳遞給函數clingon:run排程,而是傳遞給函數clingon:make-command的引數:sub-command,如下列程式碼所示

(defun top-level/handler (cmd)
  (declare (ignorable cmd)))
(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "cli"
              :sub-commands (list
                             (clingon:make-command
                              :handler #'(lambda (cmd)
                                           (declare (ignorable cmd))
                                           (format t "Dropped the database~%"))
                              :name "dropdb")
                             (clingon:make-command
                              :handler #'(lambda (cmd)
                                           (declare (ignorable cmd))
                                           (format t "Initialized the database~%"))
                              :name "initdb")))))
    (clingon:run app argv)))

選項與引數

在 clingon 中通過命令列傳遞給程序的資訊分為選項和引數兩種形態,選項是通過名字來參照,而引數則通過它們的下標來參照。

例如在第一個例子中,就定義了一個名為--count的選項,它在解析結果中被賦予了:count這個關鍵字,可以通過函數clingon:getopt來參照它的值;

與之相反,變數name是從命令列中解析了選項後、剩餘的引數中的第一個,它是以位置來標識的。clingon 通過函數clingon:make-option來定義選項,它提供了豐富的控制能力。

選項名稱

選項有好幾種名字,一種叫做:key,是在程式內部使用的名字,用作函數clingon:getopt的引數之一;

一種叫做:long-name,一般為多於一個字元的字串,如"count",在命令列該名稱需要帶上兩個連字元的字首來使用,如--count 3

最後一種叫做:short-name,為一個單獨的字元,如#v,在命令列中帶上一個連字元字首來使用,如-v

必要性與預設值

通過傳入引數:required t給函數clingon:make-option,可以要求一個選項為必傳的。

例如下面的命令的選項--n就是必傳的

(defun top-level/handler (cmd)
  (dotimes (i (clingon:getopt cmd :n))
    (declare (ignorable i))
    (format t ".")))
(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "dots"
              :options (list
                        (clingon:make-option
                         :integer
                         :description "列印的英文句號的數量"
                         :key :n
                         :long-name "n"
                         :required t)))))
    (clingon:run app argv)))

如果不希望在一些最簡單的情況下也要繁瑣地編寫--n 1這樣的命令列引數,可以用:initial-value 1來指定。除此之外,也可以讓選項預設讀取指定的環境變數中的值,使用:env-vars指定環境變數名即可

(defun top-level/handler (cmd)
  (format t "Hello ~A~%" (clingon:getopt cmd :username)))
(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "greet"
              :options (list
                        (clingon:make-option
                         :string
                         :description "使用者名稱"
                         :env-vars '("GREETER_USERNAME")
                         :key :username
                         :long-name "username")))))
    (clingon:run app argv)))

可多次使用的選項

curl中的選項-H就是可以多次使用的,每指定一次就可以在請求中新增一個 HTTP 頭部,如下圖所示

在 clingon 中可以通過往函數clingon:make-option傳入:list來實現。當用clingon:getopt取出型別為:list的選項的值時,得到的是一個列表,其中依次存放著輸入的值的字串。

(defun top-level/handler (cmd)
  (let ((messages (clingon:getopt cmd :message)))
    (format t "~{~A~^~%~}" messages)))
(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "commit"
              :options (list
                        (clingon:make-option
                         :list
                         :description "提交的訊息"
                         :key :message
                         :long-name "message"
                         :short-name #m)))))
    (clingon:run app argv)))

另一種情況是儘管沒有值,但仍然多次使用同一個選項。例如命令ssh的選項-v,使用的次數越多(最多為 3 次),則ssh列印的偵錯資訊也就越詳細。這種型別的選項在 clingon 中稱為:counter

(defun top-level/handler (cmd)
  (format t "Verbosity: ~D~%" (clingon:getopt cmd :verbose)))
(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "log"
              :options (list
                        (clingon:make-option
                         :counter
                         :description "囉嗦程度"
                         :key :verbose
                         :long-name "verbose"
                         :short-name #v)))))
    (clingon:run app argv)))

訊號選項

有一些選項只需要區分【有】和【沒有】兩種情況就可以了,而不需要在意這個選項的值——或者這類選項本身就不允許有值,例如docker run命令的選項-d--detach

這種選項的型別為:boolean/true,如果指定了這個選項,那麼取出來的值始終為t。與之相反,型別:boolean/false取出來的值始終為nil

(defun top-level/handler (cmd)
  (let ((rv (software-type)))
    (when (clingon:getopt cmd :shout)
      (setf rv (concatenate 'string (string-upcase rv) "!!!!111")))
    (format t "~A~%" rv)))
(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "info"
              :options (list
                        (clingon:make-option
                         :boolean/true
                         :description "大喊"
                         :key :shout
                         :long-name "shout")))))
    (clingon:run app argv)))

選擇型選項

如果一個選項儘管接受的是字串,但並非所有輸入都是有意義的,例如命令dot的選項-T。從dot的 man 檔案可以看到,它所支援的圖片型別是有限的,如pspdfpng等。

比起宣告一個:string型別的選項,讓 clingon 代勞輸入值的有效性檢查來得更輕鬆,這裡可以使用:choice型別

(defun top-level/handler (cmd)
  (format t "~A~%" (clingon:getopt cmd :hash-type)))
(defun main (&rest argv)
  (let ((app (clingon:make-command
              :handler #'top-level/handler
              :name "digest"
              :options (list
                        (clingon:make-option
                         :choice
                         :description "雜湊型別"
                         :items '("MD5" "SHA1")
                         :key :hash-type
                         :long-name "hash-type")))))
    (clingon:run app argv)))

以上就是CommonLisp中解析命令列引數範例的詳細內容,更多關於CommonLisp命令列引數的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com