{"id":26124446,"url":"https://github.com/dnaeon/clingon","last_synced_at":"2026-03-07T08:34:40.524Z","repository":{"id":48188287,"uuid":"398420452","full_name":"dnaeon/clingon","owner":"dnaeon","description":"Command-line options parser system for Common Lisp","archived":false,"fork":false,"pushed_at":"2026-02-09T15:43:15.000Z","size":789,"stargazers_count":150,"open_issues_count":4,"forks_count":10,"subscribers_count":7,"default_branch":"master","last_synced_at":"2026-02-09T19:52:49.763Z","etag":null,"topics":["common-lisp","lisp","options","optparse","parser"],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dnaeon.png","metadata":{"files":{"readme":"README.org","changelog":"CHANGELOG.org","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["dnaeon"]}},"created_at":"2021-08-20T23:14:13.000Z","updated_at":"2026-02-09T15:42:47.000Z","dependencies_parsed_at":"2023-12-15T08:25:21.644Z","dependency_job_id":"065e3913-9759-43ee-bec7-0b9a10b02558","html_url":"https://github.com/dnaeon/clingon","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/dnaeon/clingon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dnaeon%2Fclingon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dnaeon%2Fclingon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dnaeon%2Fclingon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dnaeon%2Fclingon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dnaeon","download_url":"https://codeload.github.com/dnaeon/clingon/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dnaeon%2Fclingon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30209957,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-07T05:23:27.321Z","status":"ssl_error","status_checked_at":"2026-03-07T05:00:17.256Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["common-lisp","lisp","options","optparse","parser"],"created_at":"2025-03-10T16:08:22.516Z","updated_at":"2026-03-07T08:34:40.487Z","avatar_url":"https://github.com/dnaeon.png","language":"Common Lisp","funding_links":["https://github.com/sponsors/dnaeon"],"categories":["Interfaces to other package managers"],"sub_categories":["Third-party APIs"],"readme":"* clingon\n\n=clingon= is a command-line options parser system for Common Lisp.\n\nA summary of the features supported by =clingon= is provided below.\n\n- Native support for sub-commands\n- Support for command aliases\n- Short and long option names support\n- Related options may be grouped into categories\n- Short options may be collapsed as a single argument, e.g. =-xyz=\n- Long options support both notations - =--long-opt arg= and\n  =--long-opt=arg=.\n- Automatic generation of help/usage information for commands and\n  sub-commands\n- Out of the box support for =--version= and =--help= flags\n- Support for various kinds of options like /string/, /integer/,\n  /boolean/, /switches/, /enums/, /list/, /counter/, /filepath/, etc.\n- Sub-commands can lookup global options and flags defined in parent\n  commands\n- Support for options, which may be required\n- Options can be initialized via environment variables\n- Single interface for creating options using =CLINGON:MAKE-OPTION=\n- Generate documentation for your command-line app\n- Support for =pre-hook= and =post-hook= actions for commands, which\n  allows invoking functions before and after the respective handler of\n  the command is executed\n- Support for Bash and Zsh shell completions\n- =clingon= is extensible, so if you don't find something you need you\n  can extend it by developing a new option kind, or even new mechanism\n  for initializing options, e.g. by looking up an external key/value\n  store.\n\nScroll to the demo section in order to see some examples of =clingon=\nin action.\n\nOther Common Lisp option parser systems, which you might consider\nchecking out.\n\n- [[https://github.com/libre-man/unix-opts][unix-opts]]\n- [[https://github.com/sjl/adopt/][adopt]]\n- [[https://github.com/didierverna/clon][clon]]\n\n* Quick Example\n\nHere's a really quick example of a simple CLI application, which\ngreets people.\n\n#+begin_src lisp\n(in-package :cl-user)\n(defpackage :clingon.example.greet\n  (:use :cl)\n  (:import-from :clingon)\n  (:export\n   :main))\n(in-package :clingon.example.greet)\n\n(defun greet/options ()\n  \"Returns the options for the `greet' command\"\n  (list\n   (clingon:make-option\n    :string\n    :description \"Person to greet\"\n    :short-name #\\u\n    :long-name \"user\"\n    :initial-value \"stranger\"\n    :env-vars '(\"USER\")\n    :key :user)))\n\n(defun greet/handler (cmd)\n  \"Handler for the `greet' command\"\n  (let ((who (clingon:getopt cmd :user)))\n    (format t \"Hello, ~A!~%\" who)))\n\n(defun greet/command ()\n  \"A command to greet someone\"\n  (clingon:make-command\n   :name \"greet\"\n   :description \"greets people\"\n   :version \"0.1.0\"\n   :authors '(\"John Doe \u003cjohn.doe@example.org\")\n   :license \"BSD 2-Clause\"\n   :options (greet/options)\n   :handler #'greet/handler))\n\n(defun main ()\n  \"The main entrypoint of our CLI program\"\n  (let ((app (greet/command)))\n    (clingon:run app)))\n#+end_src\n\nThis small example shows a lot of details about how apps are\nstructured with =clingon=.\n\nYou can see there's a =main= function, which will be the entrypoint\nfor our ASDF system. Then you can find the =greet/command= function,\nwhich creates and returns a new command.\n\nThe =greet/options= functions returns the options associated with our\nsample command.\n\nAnd we also have the =greet/handler= function, which is the function\nthat will be invoked when users run our command-line app.\n\nThis way of organizing command, options and handlers makes it easy to\nre-use common options, or even handlers, and wire up any sub-commands\nanyway you prefer.\n\nYou can find additional examples included in the test suite for\n=clingon=.\n\n* Demo\n\nYou can also build and run the =clingon= demo application, which\nincludes the =greet= command introduced in the previous section, along\nwith other examples.\n\n[[./images/clingon-demo.gif]]\n\nClone the [[https://github.com/dnaeon/clingon][clingon]] repo in your [[https://www.quicklisp.org/beta/faq.html][Quicklisp local-projects]] directory.\n\n#+begin_src shell\ngit clone https://github.com/dnaeon/clingon\n#+end_src\n\nRegister it to your local Quicklisp projects.\n\n#+begin_src lisp\nCL-USER\u003e (ql:register-local-projects)\n#+end_src\n\n** Building the Demo App\n\nYou can build the demo app using SBCL with the following command.\n\n#+begin_src shell\nLISP=sbcl make demo\n#+end_src\n\nBuild the demo app using Clozure CL:\n\n#+begin_src shell\nLISP=ccl make demo\n#+end_src\n\nIn order to build the demo app using ECL you need to follow these\ninstructions, which are ECL-specific. See [[https://common-lisp.net/project/ecl/static/manual/System-building.html#Compiling-with-ASDF][Compiling with ASDF from the\nECL manual]] for more details. First, load the =:clingon.demo= system.\n\n#+begin_src lisp\n(ql:quickload :clingon.demo)\n#+end_src\n\nAnd now build the binary with ECL:\n\n#+begin_src lisp\n(asdf:make-build :clingon.demo\n                 :type :program\n                 :move-here #P\"./\"\n                 :epilogue-code '(clingon.demo:main))\n#+end_src\n\nThis will create a new executable =clingon-demo=, which you can now\nexecute.\n\nOptionally, you can also enable the bash completions support.\n\n#+begin_src shell\nAPP=clingon-demo source extras/completions.bash\n#+end_src\n\nIn order to activate the Zsh completions, install the completions\nscript in your =~/.zsh-completions= directory (or anywhere else you\nprefer) and update your =~/.zshrc= file, so that the completions are\nloaded.\n\nMake sure that you have these lines in your =~/.zshrc= file.\n\n#+begin_src shell\n  fpath=(~/.zsh-completions $fpath)\n  autoload -U compinit\n  compinit\n#+end_src\n\nThe following command will generate the Zsh completions script.\n\n#+begin_src shell\n  ./clingon-demo zsh-completion \u003e ~/.zsh-completions/_clingon-demo\n#+end_src\n\nUse the =--help= flag to see some usage information about the demo\napplication.\n\n#+begin_src shell\n./clingon-demo --help\n#+end_src\n\n* Requirements\n\n- [[https://www.quicklisp.org/beta/][Quicklisp]]\n\n* Installation\n\nThe =clingon= system is not yet part of Quicklisp, so for now\nyou need to install it in your local Quicklisp projects.\n\nClone the repo in your [[https://www.quicklisp.org/beta/faq.html][Quicklisp local-projects]] directory.\n\n#+begin_src lisp\n(ql:register-local-projects)\n#+end_src\n\nThen load the system.\n\n#+begin_src lisp\n(ql:quickload :clingon)\n#+end_src\n\n* Step By Step Guide\n\nIn this section we will implement a simple CLI application, and\nexplain at each step what and why we do the things we do.\n\nOnce you are done with it, you should have a pretty good understanding\nof the =clingon= system and be able to further extend the sample\napplication on your own.\n\nWe will be developing the application interactively and in the\nREPL. Finally we will create an ASDF system for our CLI app, so we can\nbuild it and ship it.\n\nThe code we develop as part of this section will reside in a file\nnamed =intro.lisp=. Anything we write will be sent to the Lisp REPL, so\nwe can compile it and get quick feedback about the things we've done\nso far.\n\nYou can find the complete code we'll develop in this section in the\n=clingon/examples/intro= directory.\n\n** Start the REPL\n\nStart up your REPL session and let's load the =clingon= system.\n\n#+begin_src lisp\nCL-USER\u003e (ql:quickload :clingon)\nTo load \"clingon\":\n  Load 1 ASDF system:\n    clingon\n; Loading \"clingon\"\n\n(:CLINGON)\n#+end_src\n\n** Create a new package\n\nFirst, we will define a new package for our application and switch to\nit.\n\n#+begin_src lisp\n(in-package :cl-user)\n(defpackage :clingon.intro\n  (:use :cl)\n  (:import-from :clingon)\n  (:export :main))\n(in-package :clingon.intro)\n#+end_src\n\nWe have our package, so now we can proceed to the next section and\ncreate our first command.\n\n** Creating a new command\n\nThe first thing we'll do is to create a new command. Commands are\ncreated using the =CLINGON:MAKE-COMMAND= function.\n\nEach command has a name, description, any options that\nthe command accepts, any sub-commands the command knows about, etc.\n\nThe command in =clingon= is represented by the =CLINGON:COMMAND=\nclass, which contains many other slots as well, which you can lookup.\n\n#+begin_src lisp\n(defun top-level/command ()\n  \"Creates and returns the top-level command\"\n  (clingon:make-command\n   :name \"clingon-intro\"\n   :description \"my first clingon cli app\"\n   :version \"0.1.0\"\n   :license \"BSD 2-Clause\"\n   :authors '(\"John Doe \u003cjohn.doe@example.com\u003e\")))\n#+end_src\n\nThis is how our simple command looks like. For now it doesn't do much,\nand in fact it won't execute anything, but we will fix that as we go.\n\nWhat is important to note, is that we are using a convention here\nto make things easier to understand and organize our code base.\n\nFunctions that return new commands will be named =\u003cname\u003e/command=.  A\nsimilar approach is taken when we define options for a given command,\ne.g. =\u003cname\u003e/options= and for sub-commands we use\n=\u003cname\u003e/sub-commands=. Handlers will use the =\u003cname\u003e/handler=\nnotation.\n\nThis makes things easier later on, when we introduce new sub-commands,\nand when we need to wire things up we can refer to our commands using\nthe established naming convention. Of course, it's up to you to decide\nwhich approach to take, so feel free to adjust the layout of the code\nto your personal preferences. In this guide we will use the afore\nmentioned approach.\n\nCommands can be linked together in order to form a tree of commands\nand sub-commands. We will talk about that one in more details in the\nlater sections of this guide.\n\n** Adding options\n\nNext, we will add a couple of options. Similar to the previous section\nwe will define a new function, which simply returns a list of valid\noptions. Defining it in the following way would make it easier to\nre-use these options later on, in case you have another command, which\nuses the exact same set of options.\n\n=clingon= exposes a single interface for creating options via the\n=CLINGON:MAKE-OPTION= generic function. This unified interface will\nallow developers to create and ship new option kinds, and still have\ntheir users leverage a common interface for the options via the\n=CLINGON:MAKE-OPTION= interface.\n\n#+begin_src lisp\n(defun top-level/options ()\n  \"Creates and returns the options for the top-level command\"\n  (list\n   (clingon:make-option\n    :counter\n    :description \"verbosity level\"\n    :short-name #\\v\n    :long-name \"verbose\"\n    :key :verbose)\n   (clingon:make-option\n    :string\n    :description \"user to greet\"\n    :short-name #\\u\n    :long-name \"user\"\n    :initial-value \"stranger\"\n    :env-vars '(\"USER\")\n    :key :user)))\n#+end_src\n\nLet's break things down a bit and explain what we just did.\n\nWe've defined two options -- one of =:COUNTER= kind and another one,\nwhich is of =:STRING= kind. Each option specifies a short and long\nname, along with a description of what the option is meant for.\n\nAnother important thing we did is to specify a =:KEY= for our options.\nThis is the key which we will later use in order to get the value\nassociated with our option, when we use =CLINGON:GETOPT=.\n\nAnd we have also defined that our =--user= option can be initialized\nvia environment variables. We can specify multiple environment variables,\nif we need to, and the first one that resolves to something will be used\nas the initial value for the option.\n\nIf none of the environment variables are defined, the option will be\ninitialized with the value specified by the =:INITIAL-VALUE= initarg.\n\nBefore we move to the next section of this guide we will update the\ndefinition of our =TOP-LEVEL/COMMAND= function, so that we include our\noptions.\n\n#+begin_src lisp\n(defun top-level/command ()\n  \"Creates and returns the top-level command\"\n  (clingon:make-command\n   :name \"clingon-intro\"\n   ...\n   :usage \"[-v] [-u \u003cUSER\u003e]\"      ;; \u003c- new code\n   :options (top-level/options))) ;; \u003c- new code\n#+end_src\n\n** Defining a handler\n\nA /handler/ in =clingon= is a function, which accepts an instance of\n=CLINGON:COMMAND= and is responsible for performing some work.\n\nThe single argument a handler receives will be used to inspect the\nvalues of parsed options and any free arguments that were provided on the\ncommand-line.\n\nA command may or may not specify a handler. Some commands may be used\npurely as /namespaces/ for other sub-commands, and it might make no\nsense to have a handler for such commands. In other situations you may\nstill want to provide a handler for the parent commands.\n\nLet's define the handler for our /top-level/ command.\n\n#+begin_src lisp\n(defun top-level/handler (cmd)\n  \"The top-level handler\"\n  (let ((args (clingon:command-arguments cmd))\n        (user (clingon:getopt cmd :user))\n        (verbose (clingon:getopt cmd :verbose)))\n    (format t \"Hello, ~A!~%\" user)\n    (format t \"The current verbosity level is set to ~A~%\" verbose)\n    (format t \"You have provided ~A arguments~%\" (length args))\n    (format t \"Bye.~%\")))\n#+end_src\n\nWe are introducing a couple of new functions, which we haven't\ndescribed before.\n\n*** Positional (\"free\") arguments\n\nIn ~top-level/handler~, we are using =CLINGON:COMMAND-ARGUMENTS=,\nwhich returns the positional, or \"free\" arguments: the arguments that\nremain after the options are parsed.  The remaining free arguments are\navailable through ~CLINGON:COMMAND-ARGUMENTS~.  In this handler we\nbind ~args~ to the free arguments we've provided to our command, when\nwe invoke it on the command-line.\n\n*** Option arguments\n\nWe also use the =CLINGON:GETOPT= function to lookup the values\nassociated with our options. Remember the =:KEY= initarg we've used in\n=CLINGON:MAKE-OPTION= when defining our options?\n\nWe again update our =TOP-LEVEL/COMMAND= definition, this time\nwith our handler included:\n\n#+begin_src lisp\n(defun top-level/command ()\n  \"Creates and returns the top-level command\"\n  (clingon:make-command\n   :name \"clingon-intro\"\n   ...\n   :handler #'top-level/handler)) ;; \u003c- new code\n#+end_src\n\nAt this point we are basically done with our simple application. But\nbefore we move to the point where build our binary and start playing\nwith it on the command-line we can test things out on the REPL, just\nto make sure everything works as expected.\n\n** Testing things out on the REPL\n\nCreate a new instance of our command and bind it to some variable.\n\n#+begin_src lisp\nINTRO\u003e (defparameter *app* (top-level/command))\n*APP*\n#+end_src\n\nInspecting the returned instance would give you something like this.\n\n#+begin_src lisp\n#\u003cCLINGON.COMMAND:COMMAND {1004648293}\u003e\n--------------------\nClass: #\u003cSTANDARD-CLASS CLINGON.COMMAND:COMMAND\u003e\n--------------------\n Group slots by inheritance [ ]\n Sort slots alphabetically  [X]\n\nAll Slots:\n[ ]  ARGS-TO-PARSE    = NIL\n[ ]  ARGUMENTS        = NIL\n[ ]  AUTHORS          = (\"John Doe \u003cjohn.doe@example.com\u003e\")\n[ ]  CONTEXT          = #\u003cHASH-TABLE :TEST EQUAL :COUNT 0 {1004648433}\u003e\n[ ]  DESCRIPTION      = \"my first clingon cli app\"\n[ ]  EXAMPLES         = NIL\n[ ]  HANDLER          = #\u003cFUNCTION TOP-LEVEL/HANDLER\u003e\n[ ]  LICENSE          = \"BSD 2-Clause\"\n[ ]  LONG-DESCRIPTION = NIL\n[ ]  NAME             = \"clingon-intro\"\n[ ]  OPTIONS          = (#\u003cCLINGON.OPTIONS:OPTION-BOOLEAN-TRUE short=NIL long=bash-completions\u003e #\u003cCLINGON.OPTIONS:OPTION-BOOLEAN-TRUE short=NIL long=version\u003e #\u003cCLINGON.OPTIONS:OPTION-BOOLEAN-TRUE short=NIL long=help\u003e #\u003cCLINGON.OPTIONS:OPTION-COUNTER short=v long=verbose\u003e #\u003cCLINGON.OPTIONS::OPTION-STRING short=u long=user\u003e)\n[ ]  PARENT           = NIL\n[ ]  SUB-COMMANDS     = NIL\n[ ]  USAGE            = \"[-v] [-u \u003cUSER\u003e]\"\n[ ]  VERSION          = \"0.1.0\"\n\n[set value]  [make unbound]\n#+end_src\n\nYou might also notice that besides the options we've defined ourselves,\nthere are few additional options, that we haven't defined at all.\n\nThese options are automatically added by =clingon= itself for each new\ncommand and provide flags for =--help=, =--version= and\n=--bash-completions= for you automatically, so you don't have to deal\nwith them manually.\n\nBefore we dive into testing out our application, first we will check\nthat we have a correct help information for our command.\n\n#+begin_src lisp\nINTRO\u003e (clingon:print-usage *app* t)\nNAME:\n  clingon-intro - my first clingon cli app\n\nUSAGE:\n  clingon-intro [-v] [-u \u003cUSER\u003e]\n\nOPTIONS:\n      --help              display usage information and exit\n      --version           display version and exit\n  -u, --user \u003cVALUE\u003e      user to greet [default: stranger] [env: $USER]\n  -v, --verbose           verbosity level [default: 0]\n\nAUTHORS:\n  John Doe \u003cjohn.doe@example.com\u003e\n\nLICENSE:\n  BSD 2-Clause\n\nNIL\n#+end_src\n\nThis help information will make it easier for our users, when they\nneed to use it. And that is automatically handled for you, so you\ndon't have to manually maintain an up-to-date usage information, each\ntime you introduce a new option.\n\nTime to test out our application on the REPL. In order to test things\nout you can use the =CLINGON:PARSE-COMMAND-LINE= function by passing\nit an instance of your command, along with any arguments that need to\nbe parsed. Let's try it out without any command-line arguments.\n\n#+begin_src lisp\nINTRO\u003e (clingon:parse-command-line *app* nil)\n#\u003cCLINGON.COMMAND:COMMAND name=clingon-intro options=5 sub-commands=0\u003e\n#+end_src\n\nThe =CLINGON:PARSE-COMMAND-LINE= function will (as the name suggests)\nparse the given arguments against the options associated with our\ncommand. Finally it will return an instance of =CLINGON:COMMAND=.\n\nIn our simple CLI application, that would be the same instance as our\n=*APP*=, but things look differently when we have sub-commands.\n\nWhen we start adding new sub-commands, the result of\n=CLINGON:PARSE-COMMAND-LINE= will be different based on the arguments\nit needs to parse. That means that if our input matches a sub-command\nyou will receive an instance of the sub-command that matched the given\narguments.\n\nInternally the =clingon= system maintains a tree data structure,\ndescribing the relationships between commands. This allows a command\nto be related to some other command, and this is how the command and\nsub-commands support is implemented in =clingon=.\n\nEach command in =clingon= is associated with a /context/.  The\n/context/ or /environment/ provides the options and their values with\nrespect to the command itself. This means that a parent command and a\nsub-command may have exactly the same set of options defined, but they\nwill reside in different contexts. Depending on how you use it,\nsub-commands may /shadow/ a parent command option, but it also means\nthat a sub-command can refer to an option defined in a global command.\n\nThe /context/ of a command in =clingon= is available via the\n=CLINGON:COMMAND-CONTEXT= accessor. We will use the context in order\nto lookup our options and the values associated with them.\n\nThe function that operates on command's context and retrieves\nvalues from it is called =CLINGON:GETOPT=.\n\nLet's see what we've got for our options.\n\n#+begin_src lisp\nINTRO\u003e (let ((c (clingon:parse-command-line *app* nil)))\n         (clingon:getopt c :user))\n\"dnaeon\"\nT\n#+end_src\n\nThe =CLINGON:GETOPT= function returns multiple values -- first one\nspecifies the value of the option, if it had any, the second one\nindicates whether or not that option has been set at all on the\ncommand-line, and the third value is the command which provided the\nvalue for the option, if set.\n\nIf you need to simply test things out and tell whether an option has\nbeen set at all you can use the =CLINGON:OPT-IS-SET-P= function\ninstead.\n\nLet's try it out with a different input.\n\n#+begin_src lisp\nINTRO\u003e (let ((c (clingon:parse-command-line *app* (list \"-vvv\" \"--user\" \"foo\"))))\n         (format t \"Verbose is ~A~%\" (clingon:getopt c :verbose))\n         (format t \"User is ~A~%\" (clingon:getopt c :user)))\nVerbose is 3\nUser is foo\n#+end_src\n\nSomething else, which is important to mention here. The default\nprecedence list for options is:\n\n- The value provided by the =:INITIAL-VALUE= initarg\n- The value of the first environment variable, which successfully resolved,\n  provided by the =:ENV-VARS= initarg\n- The value provided on the command-line when invoking the application.\n\nPlay with it using different command-line arguments. If you specify\ninvalid or unknown options =clingon= will signal a condition and\nprovide you a few recovery options. For example, if you specify an\ninvalid flag like this:\n\n#+begin_src lisp\nINTRO\u003e (clingon:parse-command-line *app* (list \"--invalid-flag\"))\n#+end_src\n\nWe will be dropped into the debugger and be provided with restarts we\ncan choose from, e.g.\n\n#+begin_src lisp\nUnknown option --invalid-flag of kind LONG\n   [Condition of type CLINGON.CONDITIONS:UNKNOWN-OPTION]\n\nRestarts:\n 0: [DISCARD-OPTION] Discard the unknown option\n 1: [TREAT-AS-ARGUMENT] Treat the unknown option as a free argument\n 2: [SUPPLY-NEW-VALUE] Supply a new value to be parsed\n 3: [RETRY] Retry SLY mREPL evaluation request.\n 4: [ABORT] Return to sly-db level 1.\n 5: [RETRY] Retry SLY mREPL evaluation request.\n --more--\n...\n#+end_src\n\nThis is similar to the way other Common Lisp options parsing systems\nbehave such as [[https://github.com/sjl/adopt][adopt]] and [[https://github.com/libre-man/unix-opts][unix-opts]].\n\nAlso worth mentioning again here is that =CLINGON:PARSE-COMMAND-LINE= is\nmeant to be used within the REPL, and not called directly by handlers.\n\n** Adding a sub-command\n\nSub-commands are no different than regular commands, and in fact are\ncreated exactly the way we did it for our /top-level/ command.\n\n#+begin_src lisp\n(defun shout/handler (cmd)\n  \"The handler for the `shout' command\"\n  (let ((args (mapcar #'string-upcase (clingon:command-arguments cmd)))\n        (user (clingon:getopt cmd :user))) ;; \u003c- a global option\n    (format t \"HEY, ~A!~%\" user)\n    (format t \"~A!~%\" (clingon:join-list args #\\Space))))\n\n(defun shout/command ()\n  \"Returns a command which SHOUTS back anything we write on the command-line\"\n  (clingon:make-command\n   :name \"shout\"\n   :description \"shouts back anything you write\"\n   :usage \"[options] [arguments ...]\"\n   :handler #'shout/handler))\n#+end_src\n\nAnd now, we will wire up our sub-command making it part of the\n/top-level/ command we have so far.\n\n#+begin_src lisp\n(defun top-level/command ()\n  \"Creates and returns the top-level command\"\n  (clingon:make-command\n   :name \"clingon-intro\"\n   ...\n   :sub-commands (list (shout/command)))) ;; \u003c- new code\n#+end_src\n\nYou should also notice here that within the =SHOUT/HANDLER= we are\nactually referencing an option, which is defined somewhere else.  This\noption is actually defined on our top-level command, but thanks's to\nthe automatic management of relationships that =clingon= provides we\ncan now refer to global options as well.\n\nLet's move on to the final section of this guide, where we will create\na system definition for our application and build it.\n\n** Packaging it up\n\nOne final piece which remains to be added to our code is to provide an\nentrypoint for our application, so let's do it now.\n\n#+begin_src lisp\n(defun main ()\n  (let ((app (top-level/command)))\n    (clingon:run app)))\n#+end_src\n\nThis is the entrypoint which will be used when we invoke our\napplication on the command-line, which we'll set in our ASDF\ndefinition.\n\nAnd here's a simple system definition for the application we've\ndeveloped so far.\n\n#+begin_src lisp\n(defpackage :clingon-intro-system\n  (:use :cl :asdf))\n(in-package :clingon-intro-system)\n\n(defsystem \"clingon.intro\"\n  :name \"clingon.intro\"\n  :long-name \"clingon.intro\"\n  :description \"An introduction to the clingon system\"\n  :version \"0.1.0\"\n  :author \"John Doe \u003cjohn.doe@example.org\u003e\"\n  :license \"BSD 2-Clause\"\n  :depends-on (:clingon)\n  :components ((:module \"intro\"\n                :pathname #P\"examples/intro/\"\n                :components ((:file \"intro\"))))\n  :build-operation \"program-op\"\n  :build-pathname \"clingon-intro\"\n  :entry-point \"clingon.intro:main\")\n#+end_src\n\nNow we can build our application and start using it on the\ncommand-line.\n\n#+begin_src shell\nsbcl --eval '(ql:quickload :clingon.intro)' \\\n     --eval '(asdf:make :clingon.intro)' \\\n     --eval '(quit)'\n#+end_src\n\nThis will produce a new binary called =clingon-intro= in the directory\nof the =clingon.intro= system.\n\nThis approach uses the [[https://asdf.common-lisp.dev/asdf/Predefined-operations-of-ASDF.html][ASDF program-op operation]] in combination with\n=:entry-point= and =:build-pathname= in order to produce the resulting\nbinary.\n\nIf you want to build your apps using [[https://www.xach.com/lisp/buildapp/][buildapp]], please check the\n/Buildapp/ section from this document.\n\n** Testing it out on the command-line\n\nTime to check things up on the command-line.\n\n#+begin_src shell\n$ ./clingon-intro --help\nNAME:\n  clingon-intro - my first clingon cli app\n\nUSAGE:\n  clingon-intro [-v] [-u \u003cUSER\u003e]\n\nOPTIONS:\n      --help              display usage information and exit\n      --version           display version and exit\n  -u, --user \u003cVALUE\u003e      user to greet [default: stranger] [env: $USER]\n  -v, --verbose           verbosity level [default: 0]\n\nCOMMANDS:\n  shout  shouts back anything you write\n\nAUTHORS:\n  John Doe \u003cjohn.doe@example.com\u003e\n\nLICENSE:\n  BSD 2-Clause\n#+end_src\n\nLet's try out our commands.\n\n#+begin_src shell\n$ ./clingon-intro -vvv --user Lisper\nHello, Lisper!\nThe current verbosity level is set to 3\nYou have provided 0 arguments\nBye.\n#+end_src\n\nAnd let's try our sub-command as well.\n\n#+begin_src shell\n$ ./clingon-intro --user stranger shout why are yelling at me?\nHEY, stranger!\nWHY ARE YELLING AT ME?!\n#+end_src\n\nYou can find the full code we've developed in this guide in the\n[[https://github.com/dnaeon/clingon/tree/master/examples][clingon/examples]] directory of the repo.\n\n* Exiting\n\nWhen a command needs to exit with a given status code you can use the\n=CLINGON:EXIT= function.\n\n* Handling SIGINT (CTRL-C) signals\n\n=clingon= by default will provide a handler for =SIGINT= signals,\nwhich when detected will cause the application to immediately exit\nwith status code =130=.\n\nIf your commands need to provide some cleanup logic as part of their\njob, e.g. close out all open files, TCP session, etc., you could wrap\nyour =clingon= command handlers in [[http://www.lispworks.com/documentation/HyperSpec/Body/s_unwind.htm][UNWIND-PROTECT]] to make sure that\nyour cleanup tasks are always executed.\n\nHowever, using [[http://www.lispworks.com/documentation/HyperSpec/Body/s_unwind.htm][UNWIND-PROTECT]] may not be appropriate in all cases,\nsince the cleanup forms will always be executed, which may or may not\nbe what you need.\n\nFor example if you are developing a =clingon= application, which\npopulates a database in a transaction you would want to use\n[[http://www.lispworks.com/documentation/HyperSpec/Body/s_unwind.htm][UNWIND-PROTECT]], but only for releasing the database connection itself.\n\nIf the application is interrupted while it inserts or updates records,\nwhat you want to do is to rollback the transaction as well, so your\ndatabase is left in a consistent state.\n\nIn those situations you would want to use the [[https://github.com/compufox/with-user-abort][WITH-USER-ABORT]] system,\nso that your =clingon= command can detect the =SIGINT= signal and act\nupon it, e.g. taking care of rolling back the transaction.\n\n* Generating Documentation\n\n=clingon= can generate documentation for your application by using the\n=CLINGON:PRINT-DOCUMENTATION= generic function.\n\nCurrently the documentation generator supports only the /Markdown/\nformat, but other formats can be developed as separate extensions to\n=clingon=.\n\nHere's how you can generate the Markdown documentation for the\n=clingon-demo= application from the REPL.\n\n#+begin_src lisp\nCL-USER\u003e (ql:quickload :clingon.demo)\nCL-USER\u003e (in-package :clingon.demo)\nDEMO\u003e (with-open-file (out #P\"clingon-demo.md\" :direction :output)\n        (clingon:print-documentation :markdown (top-level/command) out))\n#+end_src\n\nYou can also create a simple command, which can be added to your\n=clingon= apps and have it generate the documentation for you, e.g.\n\n#+begin_src lisp\n(defun print-doc/command ()\n  \"Returns a command which will print the app's documentation\"\n  (clingon:make-command\n   :name \"print-doc\"\n   :description \"print the documentation\"\n   :usage \"\"\n   :handler (lambda (cmd)\n              ;; Print the documentation starting from the parent\n              ;; command, so we can traverse all sub-commands in the\n              ;; tree.\n              (clingon:print-documentation :markdown (clingon:command-parent cmd) t))))\n#+end_src\n\nAbove command can be wired up anywhere in your application.\n\nMake sure to also check the =clingon-demo= app, which provides a\n=print-doc= sub-command, which operates on the /top-level/ command and\ngenerates the documentation for all sub-commands.\n\nYou can also find the generated documentation for the =clingon-demo=\napp in the =docs/= directory of the =clingon= repo.\n\n** Generate tree representation of your commands in Dot\n\nUsing =CLINGON:PRINT-DOCUMENTATION= you can also generate the tree\nrepresentation of your commands in [[https://en.wikipedia.org/wiki/DOT_(graph_description_language)][Dot]] format.\n\nMake sure to check the =clingon.demo= system and the provided\n=clingon-demo= app, which provides an example command for generating\nthe Dot representation.\n\nThe example below shows the generation of the Dot representation for\nthe =clingon-demo= command.\n\n#+begin_src shell\n  \u003e clingon-demo dot\n  digraph G {\n    node [color=lightblue fillcolor=lightblue fontcolor=black shape=record style=\"filled, rounded\"];\n    \"clingon-demo\" -\u003e \"greet\";\n    \"clingon-demo\" -\u003e \"logging\";\n    \"logging\" -\u003e \"enable\";\n    \"logging\" -\u003e \"disable\";\n    \"clingon-demo\" -\u003e \"math\";\n    \"clingon-demo\" -\u003e \"echo\";\n    \"clingon-demo\" -\u003e \"engine\";\n    \"clingon-demo\" -\u003e \"print-doc\";\n    \"clingon-demo\" -\u003e \"sleep\";\n    \"clingon-demo\" -\u003e \"zsh-completion\";\n    \"clingon-demo\" -\u003e \"dot\";\n  }\n#+end_src\n\nWe can generate the resulting graph using [[https://graphviz.org/][graphviz]].\n\n#+begin_src shell\n  \u003e clingon-demo dot \u003e clingon-demo.dot\n  \u003e dot -Tpng clingon-demo.dot \u003e clingon-demo-tree.png\n#+end_src\n\nThis is what the resulting tree looks like.\n\n[[./images/clingon-demo-tree.png]]\n\n* Command Hooks\n\n=clingon= allows you to associate =pre= and =post= hooks with a\ncommand.\n\nThe =pre= and =post= hooks are functions which will be invoked before\nand after the respective command handler is executed. They are useful\nin cases when you need to set up or tear things down before executing\nthe command's handler.\n\nAn example of a =pre-hook= might be to configure the logging level of\nyour application based on the value of a global flag. A =post-hook=\nmight be responsible for shutting down any active connections, etc.\n\nThe =pre-hook= and =post-hook= functions accept a single argument,\nwhich is an instance of =CLINGON:COMMAND=. That way the hooks can\nexamine the command's context and lookup any flags or options.\n\nHooks are also hierachical in the sense that they will be executed\nbased on the command's lineage.\n\nConsider the following example, where we have a CLI app with three\ncommands.\n\n#+begin_src text\n  main -\u003e foo -\u003e bar\n#+end_src\n\nIn above example the =bar= command is a sub-command of =foo=, which in\nturn is a sub-command of =main=. Also, consider that we have added\npre- and post-hooks to each command.\n\nIf a user executed the following on the command-line:\n\n#+begin_src shell\n  $ main foo bar\n#+end_src\n\nBased on the above command-line =clingon= would do the following:\n\n- Execute any =pre-hook= functions starting from the least-specific up to the\n  most-specific node from the commands' lineage\n- Execute the command's handler\n- Execute any =post-hook= functions starting from the most-specific down to the\n  least-specific node from the command's lineage\n\nIn above example that would be:\n\n#+begin_src text\n  \u003e main (pre-hook)\n  \u003e\u003e foo (pre-hook)\n  \u003e\u003e\u003e bar (pre-hook)\n  \u003e\u003e\u003e\u003e bar (handler)\n  \u003e\u003e\u003e bar (post-hook)\n  \u003e\u003e foo (post-hook)\n  \u003e main (post-hook)\n#+end_src\n\nAssociating hooks with commands is done during instantiation of a\ncommand. The following example creates a new command with a =pre-hook=\nand =post-hook=.\n\n#+begin_src lisp\n  (defun foo/pre-hook (cmd)\n    \"The pre-hook for `foo' command\"\n    (declare (ignore cmd))\n    (format t \"foo pre-hook has been invoked~\u0026\"))\n\n  (defun foo/post-hook (cmd)\n    \"The post-hook for `foo' command\"\n    (declare (ignore cmd))\n    (format t \"foo post-hook has been invoked~\u0026\"))\n\n  (defun foo/handler (cmd)\n    (declare (ignore cmd))\n    (format t \"foo handler has been invoked~\u0026\"))\n\n  (defun foo/command ()\n    \"Returns the `foo' command\"\n    (clingon:make-command\n     :name \"foo\"\n     :description \"the foo command\"\n     :authors '(\"John Doe \u003cjohn.doe@example.org\u003e\")\n     :handler #'foo/handler\n     :pre-hook #'foo/pre-hook\n     :post-hook #'foo/post-hook\n     :options nil\n     :sub-commands nil))\n#+end_src\n\nIf we have executed above command we would see the following output.\n\n#+begin_src shell\n  foo pre-hook has been invoked\n  foo handler has been invoked\n  foo post-hook has been invoked\n#+end_src\n\n* Custom Errors\n\nThe =CLINGON:BASE-ERROR= condition may be used as the base for\nuser-defined conditions.\n\nThe =CLINGON:RUN= method will invoke =CLINGON:HANDLE-ERROR= for\nconditions which sub-class =CLINGON:BASE-ERROR=. The implementation of\n=CLINGON:HANDLE-ERROR= allows the user to customize the way errors are\nbeing reported and handled.\n\nThe following example creates a new custom condition.\n\n#+begin_src lisp\n  (in-package :cl-user)\n  (defpackage :my.clingon.app\n    (:use :cl)\n    (:import-from :clingon)\n    (:export :my-app-error))\n  (in-package :my.clingon.app)\n\n  (define-condition my-app-error (clingon:base-error)\n    ((message\n      :initarg :message\n      :initform (error \"Must specify message\")\n      :reader my-app-error-message))\n    (:documentation \"My custom app error condition\"))\n\n  (defmethod clingon:handle-error ((err my-app-error))\n    (let ((message (my-app-error-message err)))\n      (format *error-output* \"Oops, an error occurred: ~A~%\" message)))\n#+end_src\n\nYou can now use the =MY-APP-ERROR= condition anywhere in your command\nhandlers and signal it. When this condition is signalled =clingon=\nwill invoke the =CLINGON:HANDLE-ERROR= generic function for your\ncondition.\n\n* Customizing the parsing logic\n\nThe default implementation of =CLINGON:RUN= provides error handling\nfor the most common user-related errors, such as handling of missing\narguments, invalid options/flags, catching of =SIGINT= signals, etc.\n\nInternally =CLINGON:RUN= relies on =CLINGON:PARSE-COMMAND-LINE= for\nthe actual parsing. In order to provide custom logic during parsing,\nusers may provide a different implementation of either =CLINGON:RUN=\nand/or =CLINGON:PARSE-COMMAND-LINE= by subclassing the\n=CLINGON:COMMAND= class.\n\nAn alternative approach, which doesn't need a subclass of\n=CLINGON:COMMAND= is to provide =AROUND= methods for =CLINGON:RUN=.\n\nFor instance, the following code will treat unknown options as free\narguments, while still using the default implementation of\n=CLINGON:RUN=.\n\n#+begin_src lisp\n  (defmethod clingon:parse-command-line :around ((command clingon:command) arguments)\n    \"Treats unknown options as free arguments\"\n    (handler-bind ((clingon:unknown-option\n                     (lambda (c)\n                       (clingon:treat-as-argument c))))\n      (call-next-method)))\n#+end_src\n\nSee [[https://github.com/dnaeon/clingon/issues/11][this issue]] for more examples and additional discussion on this\ntopic.\n\n* Options\n\nThe =clingon= system supports various kinds of options, each of which\nis meant to serve a specific purpose.\n\nEach builtin option can be initialized via environment variables, and\nnew mechanisms for initializing options can be developed, if needed.\n\nOptions are created via the single =CLINGON:MAKE-OPTION= interface.\n\nThe supported option kinds include:\n\n- =counter=\n- =integer=\n- =string=\n- =boolean=\n- =boolean/true=\n- =boolean/false=\n- =flag=\n- =choice=\n- =enum=\n- =list=\n- =list/integer=\n- =filepath=\n- =list/filepath=\n- =switch=\n- etc.\n\n** Counters Options\n\nA =counter= is an option kind, which increments every time it is set\non the command-line.\n\nA good example for =counter= options is to provide a flag, which\nincreases the verbosity level, depending on the number of times the\nflag was provided, similar to the way =ssh(1)= does it, e.g.\n\n#+begin_src shell\nssh -vvv user@host\n#+end_src\n\nHere's an example of creating a =counter= option.\n\n#+begin_src lisp\n(clingon:make-option\n :counter\n :short-name #\\v\n :long-name \"verbose\"\n :description \"how noisy we want to be\"\n :key :verbose)\n#+end_src\n\nThe default =step= for counters is set to =1=, but you can change\nthat, if needed.\n\n#+begin_src lisp\n(clingon:make-option\n :counter\n :short-name #\\v\n :long-name \"verbose\"\n :description \"how noisy we want to be\"\n :step 42\n :key :verbose)\n#+end_src\n\n** Boolean Options\n\nThe following boolean option kinds are supported by =clingon=.\n\nThe =:boolean= kind is an option which expects an argument, which\nrepresents a boolean value.\n\nArguments =true= and =1= map to =T= in Lisp, anything else is\nconsidered a falsey value and maps to =NIL=.\n\n#+begin_src lisp\n(clingon:make-option\n :boolean\n :description \"my boolean\"\n :short-name #\\b\n :long-name \"my-boolean\"\n :key :boolean)\n#+end_src\n\nThis creates an option =-b, --my-boolean \u003cVALUE\u003e=, which can be\nprovided on the command-line, where =\u003cVALUE\u003e= should be =true= or =1=\nfor truthy values, and anything else maps to =NIL=.\n\nThe =:boolean/true= option kind creates a flag, which always returns\n=T=.\n\nThe =:boolean/false= option kind creates a flag, which always returns\n=NIL=.\n\nThe =:flag= option kind is an alias for =:boolean/true=.\n\n** Integer Options\n\nHere's an example of creating an option, which expects an integer\nargument.\n\n#+begin_src lisp\n(clingon:make-option\n :integer\n :description \"my integer opt\"\n :short-name #\\i\n :long-name \"int\"\n :key :my-int\n :initial-value 42)\n#+end_src\n\n** Choice Options\n\n=choice= options are useful when you have to limit the arguments\nprovided on the command-line to a specific set of values.\n\nFor example:\n\n#+begin_src lisp\n(clingon:make-option\n :choice\n :description \"log level\"\n :short-name #\\l\n :long-name \"log-level\"\n :key :choice\n :items '(\"info\" \"warn\" \"error\" \"debug\"))\n#+end_src\n\nWith this option defined, you can now set the logging level only to\n=info=, =warn=, =error= or =debug=, e.g.\n\n#+begin_src shell\n-l, --log-level [info|warn|error|debug]\n#+end_src\n\n** Enum Options\n\nEnum options are similar to the =choice= options, but instead of\nreturning the value itself they can be mapped to something else.\n\nFor example:\n\n#+begin_src lisp\n(clingon:make-option\n :enum\n :description \"enum option\"\n :short-name #\\e\n :long-name \"my-enum\"\n :key :enum\n :items '((\"one\" . 1)\n          (\"two\" . 2)\n          (\"three\" . 3)))\n#+end_src\n\nIf a user specifies =--my-enum=one= on the command-line the option\nwill be have the value =1= associated with it, when being looked up\nvia =CLINGON:GETOPT=.\n\nThe values you associate with the enum variant, can be any object.\n\nThis is one of the options being used by the /clingon-demo/\napplication, which maps user input to Lisp functions, in order to\nperform some basic math operations.\n\n#+begin_src lisp\n(clingon:make-option\n :enum\n :description \"operation to perform\"\n :short-name #\\o\n :long-name \"operation\"\n :required t\n :items `((\"add\" . ,#'+)\n          (\"sub\" . ,#'-)\n          (\"mul\" . ,#'*)\n          (\"div\" . ,#'/))\n :key :math/operation)\n#+end_src\n\n** List / Accumulator Options\n\nThe =:list= option kind accumulates each argument it is given on the\ncommand-line into a list.\n\nFor example:\n\n#+begin_src lisp\n(clingon:make-option\n :list\n :description \"files to process\"\n :short-name #\\f\n :long-name \"file\"\n :key :files)\n#+end_src\n\nIf you invoke an application, which uses a similar option like the one\nabove using the following command-line arguments:\n\n#+begin_src shell\n$ my-app --file foo --file bar --file baz\n#+end_src\n\nWhen you retrieve the value associated with your option, you will get a\nlist of all the files specified on the command-line, e.g.\n\n#+begin_src lisp\n(clingon:getopt cmd :files) ;; =\u003e '(\"foo\" \"bar\" \"baz\")\n#+end_src\n\nA similar option exists for integer values using the =:list/integer=\noption, e.g.\n\n#+begin_src lisp\n(clingon:make-option\n :list/integer\n :description \"list of integers\"\n :short-name #\\l\n :long-name \"int\"\n :key :integers)\n#+end_src\n\n** Switch Options\n\n=:SWITCH= options are a variation of =:BOOLEAN= options with an\nassociated list of known states that can turn a switch /on/ or\n/off/.\n\nHere is an example of a =:SWITCH= option.\n\n#+begin_src lisp\n(clingon:make-option\n :switch\n :description \"my switch option\"\n :short-name #\\s\n :long-name \"state\"\n :key :switch)\n#+end_src\n\nThe default states for a switch to be considered as /on/ are:\n\n- /on/, /yes/, /true/, /enable/ and /1/\n\nThe default states considered to turn the switch /off/ are:\n\n- /off/, /no/, /false/, /disable/ and /0/\n\nYou can customize the list of /on/ and /off/ states by specifying them\nusing the =:ON-STATES= and =:OFF-STATES= initargs, e.g.\n\n#+begin_src lisp\n(clingon:make-option\n :switch\n :description \"engine switch option\"\n :short-name #\\s\n :long-name \"state\"\n :on-states '(\"start\")\n :off-states '(\"stop\")\n :key :engine)\n#+end_src\n\nThese sample command-line arguments will turn a switch on and off.\n\n#+begin_src shell\nmy-app --engine=start --engine=stop\n#+end_src\n\nThe final value of the =:engine= option will be =NIL= in the above\nexample.\n\n** Persistent Options\n\nAn option may be marked as /persistent/. A /persistent/ option is such\nan option, which will be propagated from a parent command to all\nsub-commands associated with it.\n\nThis is useful when you need to provide the same option across\nsub-commands.\n\nThe following example creates one top-level command (=demo= in the\nexample), which has two sub-commands (=foo= and =bar= commands). The\n=foo= command has a single sub-command, =qux= in the following\nexample.\n\nThe =top-level= command has a single option (=persistent-opt= in the\nexample), which is marked as /persistent/.\n\n#+begin_src shell\n  (defun qux/command ()\n    \"Returns the `qux' command\"\n    (clingon:make-command\n     :name \"qux\"\n     :description \"the qux command\"\n     :handler (lambda (cmd)\n                (declare (ignore cmd))\n                (format t \"qux has been invoked\"))))\n\n  (defun foo/command ()\n    \"Returns the `foo' command\"\n    (clingon:make-command\n     :name \"foo\"\n     :description \"the foo command\"\n     :sub-commands (list (qux/command))\n     :handler (lambda (cmd)\n                (declare (ignore cmd))\n                (format t \"foo has been invoked\"))))\n\n  (defun bar/command ()\n    \"Returns the `bar' command\"\n    (clingon:make-command\n     :name \"bar\"\n     :description \"the bar command\"\n     :handler (lambda (cmd)\n                (declare (ignore cmd))\n                (format t \"bar has been invoked\"))))\n\n  (defun top-level/command ()\n    \"Returns the top-level command\"\n    (clingon:make-command\n     :name \"demo\"\n     :description \"the demo app\"\n     :options (list\n               (clingon:make-option\n                :string\n                :long-name \"persistent-opt\"\n                :description \"an example persistent option\"\n                :persistent t\n                :key :persistent-opt))\n     :sub-commands (list\n                    (foo/command)\n                    (bar/command))))\n#+end_src\n\nSince the option is marked as persistent and is associated with the\ntop-level command, it will be inherited by all sub-commands.\n\n* Generic Functions Operating on Options\n\nIf the existing options provided by =clingon= are not enough for you,\nand you need something a bit more specific for your use case, then you\ncan always implement a new option kind.\n\nThe following generic functions operate on options and are exported by\nthe =clingon= system.\n\n- =CLINGON:INITILIAZE-OPTION=\n- =CLINGON:FINALIZE-OPTION=\n- =CLINGON:DERIVE-OPTION-VALUE=\n- =CLINGON:OPTION-USAGE-DETAILS=\n- =CLINGON:OPTION-DESCRIPTION-DETAILS=\n- =CLINGON:MAKE-OPTION=\n\nNew option kinds should inherit from the =CLINGON:OPTION= class, which\nimplements all of the above generic functions. If you need to\ncustomize the behaviour of your new option, you can still override the\ndefault implementations.\n\n** CLINGON:INITIALIZE-OPTION\n\nThe =CLINGON:INITIALIZE-OPTION= as the name suggests is being used to\ninitialize an option.\n\nThe default implementation of this generic function supports\ninitialization from environment variables, but implementors\ncan choose to support other initialization methods, e.g.\nbe able to initialize an option from a key/value store like\n/Redis/, /Consul/ or /etcd/ for example.\n\n** CLINGON:FINALIZE-OPTION\n\nThe =CLINGON:FINALIZE-OPTION= generic function is called after\nall command-line arguments have been processed and values for them\nhave been derived already.\n\n=CLINGON:FINALIZE-OPTION= is meant to /finalize/ the option's value,\ne.g. transform it to another object, if needed.\n\nFor example the =:BOOLEAN= option kind transforms user-provided input\nlike =true=, =false=, =1= and =0= into their respective Lisp counterparts\nlike =T= and =NIL=.\n\nAnother example where you might want to customize the behaviour of\n=CLINGON:FINALIZE-OPTION= is to convert a string option provided on\nthe command-line, which represents a database connection string into\nan actual session object for the database.\n\nThe default implementation of this generic function simply returns the\nalready set value, e.g. calls =#'IDENTITY= on the last derived value.\n\n** CLINGON:DERIVE-OPTION-VALUE\n\nThe =CLINGON:DERIVE-OPTION-VALUE= is called whenever an option is\nprovided on the command-line.\n\nIf that option accepts an argument, it will be passed the respective\nvalue from the command-line, otherwise it will be called with a =NIL=\nargument.\n\nResponsibility of the option is to derive a value from the given input\nand return it to the caller. The returned value will be set by the\nparser and later on it will be used to produce a final value, by\ncalling the =CLINGON:FINALIZE-OPTION= generic function.\n\nDifferent kinds of options implement this one different -- for example\nthe =:LIST= option kind accumulates each given argument, while others\nignore any previously derived values and return the last provided\nargument.\n\nThe =:ENUM= option kind for example will derive a value from a\npre-defined list of allowed values.\n\nIf an option fails to derive a value (e.g. invalid value has been\nprovided) the implementation of this generic function should signal a\n=CLINGON:OPTION-DERIVE-ERROR= condition, so that =clingon= can provide\nappropriate restarts.\n\n** CLINGON:OPTION-USAGE-DETAILS\n\nThis generic function is used to provide a pretty-printed usage format\nfor the given option. It will be used when printing usage information\non the command-line for the respective commands.\n\n** CLINGON:OPTION-DESCRIPTION-DETAILS\n\nThis generic function is meant to enrich the description of the option\nby providing as much details as possible for the given option, e.g.\nlisting the available values that an option can accept.\n\n** CLINGON:MAKE-OPTION\n\nThe =CLINGON:MAKE-OPTION= generic function is the primary way for\ncreating new options. Implementors of new option kinds should simply\nprovide an implementation of this generic function, along with the\nrespective option kind.\n\nAdditional option kinds may be implemented as separate sub-systems,\nbut still follow the same principle by providing a single and\nconsistent interface for option creation.\n\n* Developing New Options\n\nThis section contains short guides explaining how to develop new\noptions for =clingon=.\n\n** Developing an Email Option\n\nThe option which we'll develop in this section will be used for\nspecifying email addresses.\n\nStart up your Lisp REPL session and do let's some work. Load the\n=:clingon= and =:cl-ppcre= systems, since we will need them.\n\n#+begin_src lisp\nCL-USER\u003e (ql:quickload :clingon)\nCL-USER\u003e (ql:quickload :cl-ppcre)\n#+end_src\n\nWe will first create a new package for our extension and import the\nsymbols we will need from the =:clingon= and =:cl-ppcre= systems.\n\n#+begin_src lisp\n(defpackage :clingon.extensions/option-email\n  (:use :cl)\n  (:import-from\n   :cl-ppcre\n   :scan)\n  (:import-from\n   :clingon\n   :option\n   :initialize-option\n   :derive-option-value\n   :make-option\n   :option-value\n   :option-derive-error)\n  (:export\n   :option-email))\n(in-package :clingon.extensions/option-email)\n#+end_src\n\nThen lets define the class, which will represent an email address\noption.\n\n#+begin_src lisp\n(defclass option-email (option)\n  ((pattern\n    :initarg :pattern\n    :initform \"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$\"\n    :reader option-email-pattern\n    :documentation \"Pattern used to match for valid email addresses\"))\n  (:default-initargs\n   :parameter \"EMAIL\")\n  (:documentation \"An option used to represent an email address\"))\n#+end_src\n\nNow we will implement =CLINGON:INITIALIZE-OPTION= for our new\noption. We will keep the default initialization logic as-is, but also\nadd an additional step to validate the email address, if we have any\ninitial value at all.\n\n#+begin_src lisp\n(defmethod initialize-option ((option option-email) \u0026key)\n  \"Initializes our new email address option\"\n  ;; Make sure to invoke our parent initialization method first, so\n  ;; various things like setting up initial value from environment\n  ;; variables can still be applied.\n  (call-next-method)\n\n  ;; If we don't have any value set, there's nothing else to\n  ;; initialize further here.\n  (unless (option-value option)\n    (return-from initialize-option))\n\n  ;; If we get to this point, that means we've got some initial value,\n  ;; which is either set as a default, or via environment\n  ;; variables. Next thing we need to do is make sure we've got a good\n  ;; initial value, so let's derive a value from it.\n  (let ((current (option-value option)))\n    (setf (option-value option)\n          (derive-option-value option current))))\n#+end_src\n\nNext we will implement =CLINGON:DERIVE-OPTION-VALUE= for our new\noption kind.\n\n#+begin_src lisp\n(defmethod derive-option-value ((option option-email) arg \u0026key)\n  \"Derives a new value based on the given argument.\n   If the given ARG represents a valid email address according to the\n   pattern we know of we consider this as a valid email address.\"\n  (unless (scan (option-email-pattern option) arg)\n    (error 'option-derive-error :reason (format nil \"~A is not a valid email address\" arg)))\n  arg)\n#+end_src\n\nFinally, lets register our new option as a valid kind by implemeting\nthe =CLINGON:MAKE-OPTION= generic function.\n\n#+begin_src lisp\n(defmethod make-option ((kind (eql :email)) \u0026rest rest)\n  (apply #'make-instance 'option-email rest))\n#+end_src\n\nWe can test things out now. Go back to your REPL and try these\nexpressions out. First we make a new instance of our new option.\n\n#+begin_src lisp\n(defparameter *opt*\n  (make-option :email :short-name #\\e :description \"email opt\" :key :email))\n#+end_src\n\nAnd now, lets validate a couple of good email addresses.\n\n#+begin_src lisp\nEXTENSIONS/OPTION-EMAIL\u003e (derive-option-value *opt* \"test@example.com\")\n\"test@example.com\"\nEXTENSIONS/OPTION-EMAIL\u003e (derive-option-value *opt* \"foo@bar.com\")\n\"foo@bar.com\"\n#+end_src\n\nIf we try deriving a value from a bad email address we will have a\ncondition of type =CLINGON:OPTION-DERIVE-ERROR= signalled.\n\n#+begin_src lisp\nEXTENSIONS/OPTION-EMAIL\u003e (derive-option-value opt \"bad-email-address-here\")\n; Debugger entered on #\u003cOPTION-DERIVE-ERROR {1002946463}\u003e\n...\nbad-email-address-here is not a valid email address\n   [Condition of type OPTION-DERIVE-ERROR]\n#+end_src\n\nGood, we can catch invalid email addresses as well. Whenever an option\nfails to derive a new value from a given argument, and we signal\n=CLINGON:OPTION-DERIVE-ERROR= condition we can recover by providing\nnew values or discarding them completely, thanks to the Common Lisp\nCondition System.\n\nLast thing to do is actually package this up as an extension system\nand register it in Quicklisp. That way everyone else can benefit from\nthe newly developed option.\n\n* Shell Completions\n\n=clingon= provides support for Bash and Zsh shell completions.\n\n** Bash Completions\n\nIn order to enable the Bash completions for your =clingon= app,\nfollow these instructions.\n\nDepending on your OS you may need to install the =bash-completion=\npackage. For example on Arch Linux you would install it like this.\n\n#+begin_src shell\n  sudo pacman -S bash-completion\n#+end_src\n\nThen source the completions script.\n\n#+begin_src shell\n  APP=app-name source extras/completions.bash\n#+end_src\n\nMake sure to set =APP= to your correct application name.\n\nThe [[https://github.com/dnaeon/clingon/blob/master/extras/completions.bash][completions.bash]] script will dynamically provide completions by\ninvoking the =clingon= app with the =--bash-completions= flag. This\nbuiltin flag when provided on the command-line will return completions\nfor the sub-commands and the available flags.\n\n** Zsh Completions\n\nWhen developing your CLI app with =clingon= you can provide an\nadditional command, which will take care of generating the Zsh\ncompletion script for your users.\n\nThe following code can be used in your app and added as a sub-command\nto your top-level command.\n\n#+begin_src lisp\n  (defun zsh-completion/command ()\n    \"Returns a command for generating the Zsh completion script\"\n    (clingon:make-command\n     :name \"zsh-completion\"\n     :description \"generate the Zsh completion script\"\n     :usage \"\"\n     :handler (lambda (cmd)\n                ;; Use the parent command when generating the completions,\n                ;; so that we can traverse all sub-commands in the tree.\n                (let ((parent (clingon:command-parent cmd)))\n                  (clingon:print-documentation :zsh-completions parent t)))))\n#+end_src\n\nYou can also check out the =clingon-demo= app for a fully working CLI\napp with Zsh completions support.\n\n[[./images/clingon-zsh-completions.gif]]\n\n* Buildapp\n\nThe demo =clingon= apps from this repo are usually built using [[https://asdf.common-lisp.dev/][ASDF]]\nwith =:build-operation= set to =program-op= and the respective\n=:entry-point= and =:build-pathname= specified in the system\ndefinition. See the included =clingon.demo.asd= and\n=clingon.intro.asd= systems for examples.\n\nYou can also use [[https://www.xach.com/lisp/buildapp/][buildapp]] for building the =clingon= apps.\n\nThis command will build the =clingon-demo= CLI app using =buildapp=.\n\n#+begin_src shell\n  $ buildapp \\\n    --output clingon-demo \\\n    --asdf-tree ~/quicklisp/dists/quicklisp/software/ \\\n    --load-system clingon.demo \\\n    --entry main \\\n    --eval '(defun main (argv) (let ((app (clingon.demo::top-level/command))) (clingon:run app (rest argv))))'\n#+end_src\n\nAnother approach to building apps using =buildapp= is to create a\n=main= entrypoint in your application, similarly to the way you create\none for use with ASDF and =:entry-point=. This function can be used as\nan entrypoint for [[https://www.xach.com/lisp/buildapp/][buildapp]] apps.\n\n#+begin_src lisp\n  (defun main (argv)\n    \"The main entrypoint for buildapp apps\"\n    (let ((app (top-level/command)))\n      (clingon:run app (rest argv))))\n#+end_src\n\nThen build your app with this command.\n\n#+begin_src shell\n  $ buildapp \\\n    --output my-app-name \\\n    --asdf-tree ~/quicklisp/dists/quicklisp/software/ \\\n    --load-system my-system-name \\\n    --entry my-system-name:main\n#+end_src\n\n* Ideas For Future Improvements\n\n** Additional Documentation Generators\n\nAs of now =clingon= supports generating documentation only in /Markdown/\nformat.\n\nWould be nice to have additional documentation generators, e.g.\n/man pages/, /HTML/, etc.\n\n** Performance Notes\n\n=clingon= has been developed and tested on a GNU/Linux system using\nSBCL.\n\nPerformance of the resulting binaries with SBCL seem to be good,\nalthough I have noticed better performance when the binaries have been\nproduced with Clozure CL. And by better I mean better in terms of\nbinary size and speed (startup + run time).\n\nAlthough you can enable compression on the image when using SBCL you\nhave to pay the extra price for the startup time.\n\nHere are some additional details. Build the =clingon-demo= app with\nSBCL.\n\n#+begin_src shell\n$ LISP=sbcl make demo\nsbcl --eval '(ql:quickload :clingon.demo)' \\\n        --eval '(asdf:make :clingon.demo)' \\\n                --eval '(quit)'\nThis is SBCL 2.1.7, an implementation of ANSI Common Lisp.\nMore information about SBCL is available at \u003chttp://www.sbcl.org/\u003e.\n\nSBCL is free software, provided as is, with absolutely no warranty.\nIt is mostly in the public domain; some portions are provided under\nBSD-style licenses.  See the CREDITS and COPYING files in the\ndistribution for more information.\nTo load \"clingon.demo\":\n  Load 1 ASDF system:\n    clingon.demo\n; Loading \"clingon.demo\"\n[package clingon.utils]...........................\n[package clingon.conditions]......................\n[package clingon.options].........................\n[package clingon.command].........................\n[package clingon].................................\n[package clingon.demo]\n[undoing binding stack and other enclosing state... done]\n[performing final GC... done]\n[defragmenting immobile space... (fin,inst,fdefn,code,sym)=1118+969+19070+19610+26536... done]\n[saving current Lisp image into /home/dnaeon/Projects/lisp/clingon/clingon-demo:\nwriting 0 bytes from the read-only space at 0x50000000\nwriting 736 bytes from the static space at 0x50100000\nwriting 31391744 bytes from the dynamic space at 0x1000000000\nwriting 2072576 bytes from the immobile space at 0x50200000\nwriting 12341248 bytes from the immobile space at 0x52a00000\ndone]\n#+end_src\n\nNow, build it using Clozure CL.\n\n#+begin_src shell\n$ LISP=ccl make demo\nccl --eval '(ql:quickload :clingon.demo)' \\\n        --eval '(asdf:make :clingon.demo)' \\\n                --eval '(quit)'\nTo load \"clingon.demo\":\n  Load 1 ASDF system:\n    clingon.demo\n; Loading \"clingon.demo\"\n[package clingon.utils]...........................\n[package clingon.conditions]......................\n[package clingon.options].........................\n[package clingon.command].........................\n[package clingon].................................\n[package clingon.demo].\n#+end_src\n\nIn terms of file size the binaries produced by Clozure CL are smaller.\n\n#+begin_src shell\n$ ls -lh clingon-demo*\n-rwxr-xr-x 1 dnaeon dnaeon 33M Aug 20 12:56 clingon-demo.ccl\n-rwxr-xr-x 1 dnaeon dnaeon 45M Aug 20 12:55 clingon-demo.sbcl\n#+end_src\n\nGenerating the Markdown documentation for the demo app when using the\nSBCL executable looks like this.\n\n#+begin_src shell\n$ time ./clingon-demo.sbcl print-doc \u003e /dev/null\n\nreal    0m0.098s\nuser    0m0.071s\nsys     0m0.027s\n#+end_src\n\nAnd when doing the same thing with the executable produced by Clozure\nCL we see these results.\n\n#+begin_src shell\n$ time ./clingon-demo.ccl print-doc \u003e /dev/null\n\nreal    0m0.017s\nuser    0m0.010s\nsys     0m0.007s\n#+end_src\n\n* Tests\n\nThe =clingon= tests are provided as part of the =:clingon.test= system.\n\nIn order to run the tests you can evaluate the following expressions.\n\n#+begin_src lisp\nCL-USER\u003e (ql:quickload :clingon.test)\nCL-USER\u003e (asdf:test-system :clingon.test)\n#+end_src\n\nOr you can run the tests using the =run-tests.sh= script instead, e.g.\n\n#+begin_src shell\nLISP=sbcl ./run-tests.sh\n#+end_src\n\nHere's how to run the tests against SBCL, CCL and ECL for example.\n\n#+begin_src shell\nfor lisp in sbcl ccl ecl; do\n    echo \"Running tests using ${lisp} ...\"\n    LISP=${lisp} make test \u003e ${lisp}-tests.out\ndone\n#+end_src\n\n* Docker Images\n\nA few Docker images are available.\n\nBuild and run the tests in a container.\n\n#+begin_src shell\ndocker build -t clingon.test:latest -f Dockerfile.sbcl .\ndocker run --rm clingon.test:latest\n#+end_src\n\nBuild and run the =clingon-intro= application.\n\n#+begin_src shell\ndocker build -t clingon.intro:latest -f Dockerfile.intro .\ndocker run --rm clingon.intro:latest\n#+end_src\n\nBuild and run the =clingon.demo= application.\n\n#+begin_src lisp\ndocker build -t clingon.demo:latest -f Dockerfile.demo .\ndocker run --rm clingon.demo:latest\n#+end_src\n\n* Contributing\n\n=clingon= is hosted on [[https://github.com/dnaeon/clingon][Github]]. Please contribute by reporting issues,\nsuggesting features or by sending patches using pull requests.\n\n* License\n\nThis project is Open Source and licensed under the [[http://opensource.org/licenses/BSD-2-Clause][BSD License]].\n\n* Authors\n\n- Marin Atanasov Nikolov \u003cdnaeon@gmail.com\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdnaeon%2Fclingon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdnaeon%2Fclingon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdnaeon%2Fclingon/lists"}