头图

clingon

clingon is a Common Lisp command-line option parser that can easily parse command-line options with complex formats. For example, the following code prints a given number of hello messages

 #!/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:

A little explanation. First execute the command ros init hello to generate the prototype of the above code---load dependencies, package definitions, and empty functions main . To load clingon, pass it as an argument to the function ql:quickload . Then define a command , handler , and option .

In clingon, instance objects of class clingon:command represent a command that can be triggered in the shell, and they are created by function clingon:make-command . Every command must have at least three elements:

  1. :handler , a function responsible for using command line options and implementing business logic;
  2. :name , the name of the command, which is usually displayed in the command's usage description;
  3. :options , the options accepted by this command.

此处的:handler就是函数top-level/handler ,它会被函数---23f33bcdc272c77948836fe520326d0d clingon:run (依赖注入的味道), clingon:command object passed in. :options currently only carries the definition of one option, namely

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

It defines an option whose value is an integer, specified on the command line by --count . If this option is not passed in, the default value of 1 will be obtained when the function clingon:getopt is used. If you want to get the value of this option from a command object, you need to call the function clingon:getopt with the value of its :key parameter as an argument, just like the function above top-level/handler shown.

subcommand

clingon can also implement subcommand features such as git add , git branch . Subcommands like add , branch are still instances of class clingon:command to clingon, but they are not passed to the function clingon:run scheduling, but the parameter passed to the function clingon:make-command :sub-command , as shown in the following code

 (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)))

Options and Parameters

The information passed to the process through the command line in clingon is divided into two forms: options and parameters. Options are referenced by name, while parameters are referenced by their subscripts. For example, in the first example, an option named --count is defined, which is given the keyword :count in the parsing result, which can be passed through the function clingon:getopt to refer to its value; in contrast, the variable name is the first of the remaining arguments after the option has been parsed from the command line, identified by position. clingon defines options through the function clingon:make-option which provides a wealth of control capabilities.

option name

There are several names for the option, one is called :key :long-name which is the name used inside the program as one of the parameters of the function clingon:getopt ; :long-name , generally a string with more than one character, such as "count" , the name needs to be prefixed with two hyphens to use on the command line, such as --count 3 ; the last one It is called :short-name , which is a single character, such as #\v , and is used with a hyphen prefix in the command line, such as -v .

Necessity and Defaults

By passing the parameter :required t to the function clingon:make-option , an option can be required to be passed. For example, the options of the following command --n are required

 (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)))

If you don't want to tediously write --n 1 such command line parameters in some simplest cases, you can use :initial-value 1 to specify. In addition, you can also make the option read the value of the specified environment variable by default, use :env-vars to specify the environment variable name.

 (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)))

Options that can be used multiple times

Options like curl -H can be used multiple times, and an HTTP header can be added to the request each time it is specified, as shown in the following figure

curl多次传入-H的效果

In clingon, it can be achieved by passing in :list to the function clingon:make-option . When clingon:getopt is used to take out the value of an option whose type is :list , what is obtained is a list in which the strings of the input values are stored in sequence.

 (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)))

Another case is when the same option is used multiple times despite having no value. For example, the option -v of the command ssh ---, the more times it is used (up to 3 times), the more detailed debugging information will be printed by ssh . This type of option is called :counter in clingon.

 (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)))

Signal options

There are some options that only need to distinguish between [yes] and [no], and do not need to care about the value of this option - or such options themselves are not allowed to have a value, such as docker run command The options -d and --detach . The type of this option is :boolean/true . If this option is specified, the value will always be t . In contrast, the type :boolean/false always takes the value 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)))

select option

If an option accepts a string, not all input is meaningful, such as option -T for command dot eec077afffce5e2790840924d6b3f2a4---. As can be seen from the man document of dot , the image types it supports are limited, such as ps , pdf , png etc. . Instead of declaring an option of type :string , it is easier for clingon to check the validity of the input value on behalf of clingon. Here you can use the type :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)))

read the original


用户bPGfS
169 声望3.7k 粉丝