From 80ad5967e5507b732a1ecb7ff77d60b944ec10b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breannd=C3=A1n=20=C3=93=20Nuall=C3=A1in?= Date: Fri, 4 Jul 2025 16:01:00 +0200 Subject: [PATCH 1/5] Add doc printer for Org Mode --- src/command.lisp | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/command.lisp b/src/command.lisp index 37f9f83..fbe0178 100644 --- a/src/command.lisp +++ b/src/command.lisp @@ -1172,6 +1172,74 @@ _~~A() { (format stream "~A~%" line))) (format stream "~%")))) +(defmethod print-documentation ((kind (eql :org)) (top-level command) stream &key (wrap-at 80)) + "Prints the documentation for the given TOP-LEVEL command in Markdown format" + (with-command-tree (node top-level) + ;; Initialize command, so that options get propagated + (initialize-command node) + + ;; Command name + (format stream "#+TITLE: ~A~2%" (command-full-name node)) + + ;; Print description + (cond + ;; Print long description + ((command-long-description node) + (let ((lines (split-sequence #\Newline + (bobbin:wrap (command-long-description node) wrap-at)))) + (dolist (line lines) + (format stream "~A~%" line)) + (format stream "~%"))) + ;; Print short description only + (t (format stream "=~A= -- ~A~2%" (command-full-name node) (command-description node)))) + + ;; Usage info + (format stream "* Usage~2%") + (format stream "#+begin_src shell~%~A~%#+end_src~2%" (command-usage-string node)) + + ;; Options + (when (command-options node) + (format stream "* Options~2%") + (format stream "=~A= accepts the following options:~2%" (command-full-name node)) + (format stream "#+begin_src shell~%") + (print-options-usage node stream) + (format stream "#+end_src~2%")) + + ;; Sub-commands + (when (command-sub-commands node) + (format stream "* Sub Commands~2%") + (format stream "=~A= provides the following sub commands:~2%" (command-full-name node)) + (format stream "#+begin_src shell~%") + (print-sub-commands-info node stream) + (format stream "#+end_src~2%")) + + ;; Examples + (when (command-examples node) + (format stream "* Examples~2%") + (dolist (example (command-examples node)) + (let* ((description (car example)) + (code (cdr example)) + (lines (split-sequence #\Newline (bobbin:wrap description wrap-at)))) + (dolist (line lines) + (format stream "~A~%" line)) + (format stream "~%") + (format stream "#+begin_src shell~%~A~%#+end_src~2%" code)))) + + ;; Authors + (when (command-authors node) + (format stream "* Authors~2%") + (dolist (author (command-authors node)) + (format stream "* ~A~%" author)) + (format stream "~%")) + + ;; License + (when (command-license node) + (format stream "* License~2%") + (let ((lines (split-sequence #\Newline (bobbin:wrap (command-license node) wrap-at)))) + (dolist (line lines) + (format stream "~A~%" line))) + (format stream "~%")))) + (defmethod print-documentation ((kind (eql :mandoc)) (top-level command) stream &key (wrap-at 80)) "Generates section 1 man pages in the mdoc(7) format. Documentation available at https://man.openbsd.org/mdoc.7" From 6ffdafad4fe4e6d0779bdaabca51f47a12fa4ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breannd=C3=A1n=20=C3=93=20Nuall=C3=A1in?= Date: Fri, 4 Jul 2025 16:02:15 +0200 Subject: [PATCH 2/5] Add option for comma separated lists Useful, for example, for --extensions md,org,txt --- src/options.lisp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/options.lisp b/src/options.lisp index 449d5ef..59b426a 100644 --- a/src/options.lisp +++ b/src/options.lisp @@ -325,6 +325,16 @@ (write-string (call-next-method) s) (format s ":_files"))) +(defclass option-csv (option-string) + () + (:documentation "An option which represents a comma-separated list")) + +(defmethod make-option ((kind (eql :csv)) &rest rest) + (apply #'make-instance 'option-csv rest)) + +(defmethod finalize-option ((o option-csv) &key) + (setf (option-value o) (cl-ppcre:split "," (option-value o)))) + ;;;; ;;;; Boolean options ;;;; From 547babe9c1460792decdc7c8daccdeb35e7bc246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breannd=C3=A1n=20=C3=93=20Nuall=C3=A1in?= Date: Sat, 5 Jul 2025 12:19:30 +0200 Subject: [PATCH 3/5] Fix docstring --- src/command.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command.lisp b/src/command.lisp index fbe0178..dcb8a0e 100644 --- a/src/command.lisp +++ b/src/command.lisp @@ -1173,7 +1173,7 @@ _~~A() { (format stream "~%")))) (defmethod print-documentation ((kind (eql :org)) (top-level command) stream &key (wrap-at 80)) - "Prints the documentation for the given TOP-LEVEL command in Markdown format" + "Prints the documentation for the given TOP-LEVEL command in Org Mode format" (with-command-tree (node top-level) ;; Initialize command, so that options get propagated (initialize-command node) From 25bc14c3229cdaa74e26baabc56075def0999642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breannd=C3=A1n=20=C3=93=20Nuall=C3=A1in?= Date: Sat, 5 Jul 2025 13:25:16 +0200 Subject: [PATCH 4/5] Ignore *.fasl --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 778b7e8..d3a4846 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ # Emacs backup files *~ +*.fasl \ No newline at end of file From ffa4ff36dc10f7fa26901d4765a5d13cf0d6e480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breannd=C3=A1n=20=C3=93=20Nuall=C3=A1in?= Date: Sat, 5 Jul 2025 13:28:36 +0200 Subject: [PATCH 5/5] Add options for float and list/float --- README.org | 30 +++++++++++++++++- clingon.asd | 3 +- src/options.lisp | 81 +++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/README.org b/README.org index 641576b..2534a79 100644 --- a/README.org +++ b/README.org @@ -1081,6 +1081,7 @@ The supported option kinds include: - =counter= - =integer= +- =float= - =string= - =boolean= - =boolean/true= @@ -1090,6 +1091,7 @@ The supported option kinds include: - =enum= - =list= - =list/integer= +- =list/float= - =filepath= - =list/filepath= - =switch= @@ -1178,6 +1180,21 @@ argument. :initial-value 42) #+end_src +** Float Options + +Here's an example of creating an option, which expects a float +argument. + +#+begin_src lisp +(clingon:make-option + :float + :description "my float opt" + :short-name #\f + :long-name "float" + :key :my-float + :initial-value 4.2) +#+end_src + ** Choice Options =choice= options are useful when you have to limit the arguments @@ -1287,6 +1304,18 @@ option, e.g. :key :integers) #+end_src +A similar option exists for float values using the =:list/float= +option, e.g. + +#+begin_src lisp +(clingon:make-option + :list/float + :description "list of floats" + :short-name #\l + :long-name "float" + :key :floats) +#+end_src + ** Switch Options =:SWITCH= options are a variation of =:BOOLEAN= options with an @@ -1917,4 +1946,3 @@ This project is Open Source and licensed under the [[http://opensource.org/licen * Authors - Marin Atanasov Nikolov - diff --git a/clingon.asd b/clingon.asd index deaf94a..60688fb 100644 --- a/clingon.asd +++ b/clingon.asd @@ -44,7 +44,8 @@ :bobbin :cl-reexport :split-sequence - :with-user-abort) + :with-user-abort + :parse-float) :components ((:module "utils" :pathname #P"src/" :components ((:file "utils"))) diff --git a/src/options.lisp b/src/options.lisp index 59b426a..2bf6236 100644 --- a/src/options.lisp +++ b/src/options.lisp @@ -30,10 +30,13 @@ :clingon.utils :join-list) (:import-from - :clingon.conditions - :invalid-option - :missing-required-option-value - :option-derive-error) + :parse-float + :parse-float) + (:import-from + :clingon.conditions + :invalid-option + :missing-required-option-value + :option-derive-error) (:export :*end-of-options-marker* :option @@ -548,6 +551,76 @@ (cons (parse-integer-or-lose arg :radix (option-integer-radix option)) (option-value option))) +;;;; +;;;; Float options +;;;; + +(defun parse-float-or-lose (value &key (radix 10)) + (when (floatp value) + (return-from parse-float-or-lose value)) + + (let ((int (parse-float value :radix radix :junk-allowed t))) + (unless int + (error 'option-derive-error :reason (format nil "Cannot parse ~A as float" value))) + int)) + +(defclass option-float (option) + ((radix + :initarg :radix + :initform 10 + :reader option-float-radix)) + (:default-initargs + :parameter "INT") + (:documentation "An option class to represent an float")) + +(defmethod make-option ((kind (eql :float)) &rest rest) + (apply #'make-instance 'option-float rest)) + +(defmethod initialize-option ((option option-float) &key) + "Initializes the float option. In case the option was + first initialized by other means, such as environment variables, + we make sure that the provided value is a valid float." + (call-next-method) + + ;; Nothing to initialize further + (unless (option-value option) + (return-from initialize-option)) + + (let ((value (option-value option))) + (setf (option-value option) + (etypecase value + (float value) + (string (parse-float-or-lose value :radix (option-float-radix option))))))) + +(defmethod derive-option-value ((option option-float) arg &key) + (parse-float-or-lose arg :radix (option-float-radix option))) + +(defclass option-list-float (option-list) + ((radix + :initarg :radix + :initform 10 + :reader option-float-radix)) + (:documentation "An option which collects floats into a list")) + +(defmethod make-option ((kind (eql :list/float)) &rest rest) + (apply #'make-instance 'option-list-float rest)) + +(defmethod initialize-option ((option option-list-float) &key) + (call-next-method) + (unless (option-value option) + (return-from initialize-option)) + + (setf (option-value option) + (mapcar (lambda (x) + (etypecase x + (float x) + (string (parse-float-or-lose x :radix (option-float-radix option))))) + (option-value option)))) + +(defmethod derive-option-value ((option option-list-float) arg &key) + (cons (parse-float-or-lose arg :radix (option-float-radix option)) + (option-value option))) + ;;;; ;;;; Choice/enum options ;;;;