;;;; main.lisp -- oneliners.cli entrypoint (defpackage oneliners.cli (:use :cl) (:local-nicknames (#:api #:oneliners.api-client))) (in-package :oneliners.cli) ;;; CLI OPTIONS ;; (opts:define-opts ;; (:name :add ;; :description "Intaractively add a oneliner to the a wiki." ;; :long "add") ;; (:name :tags ;; :description "A comma separated list of tags to filter search results." ;; :short #\t ;; :long "tags" ;; :arg-parser #'identity ;; :meta-var "'T1, T2, ...'") ;; (:name :limit ;; :description "An integer. The maximum number of results to return." ;; :short #\l ;; :long "limit" ;; :meta-var "N" ;; :default 10 ;; :arg-parser #'parse-integer) ;; (:name :edit ;; :description "An integer, a result number. Interactively edit af command." ;; :long "edit" ;; :meta-var "RESULT" ;; :arg-parser #'parse-integer) ;; (:name :not-flagged ;; :description "Filter flagged oneliners from the search results" ;; :long "not-flagged")) (defparameter +help-suffix+ "Unless RESULT is an integer, search for oneliners that involve each command in COMMANDS. E.g. # search for oneliners involving both 'find' and 'grep' ol find grep Otherwise, when RESULT is an integer, run the oneliner with index RESULT from the most recent search command, and supply ARGS to that oneliner, if provided. E.g. # run the third result from the last search with arguments foo and bar ol 3 foo bar") ;; (defun help-text () ;; (opts:describe :prefix "Oneliners Wiki Command Line Tool" ;; :args "[[RESULT ARGS | COMMANDS]" ;; :usage-of "ol" ;; :suffix +help-suffix+)) ;;; CONFIG AND RESULTS FILE LOCATIONS (defun config-file () (merge-pathnames ".config/oneliners.config" (user-homedir-pathname) )) (defun last-search-file () (merge-pathnames ".last_oneliners_search" (user-homedir-pathname))) ;;; RUNNING COMMANDS (defun parent-process-name () "Prints the name of the parent process of the current process." (let ((ppidfile (format nil "/proc/~a/status" (osicat-posix:getppid)))) (first (last (ppcre:split "\\s" (with-open-file (input ppidfile) (read-line input))))))) (defmacro wait-until ((&key (timeout 1) (poll-every 0.01)) &body check) "Run CHECK every POLL-EVERY seconds until either TIMEOUT seconds have passed or CHECK returns non-nil." (let ((clockvar (gensym)) (var (gensym))) `(loop for ,clockvar from 0 by ,poll-every to ,timeout for ,var = (progn ,@check) when ,var return ,var do (sleep ,poll-every)))) (defun run-with-shell (command &key (shell-name (parent-process-name)) (await-output-p 0.8) (output-stream *standard-output*)) "run COMMAND, a string, in a fresh shell environment, initialized with SHELL-NAME. The output from the command read line by line and is printed to OUTPUT-STREAM. " (let ((shell (uiop:launch-program shell-name :input :stream :output :stream))) (symbol-macrolet ((shell-input (uiop:process-info-input shell)) (shell-output (uiop:process-info-output shell))) (write-line command shell-input) (finish-output shell-input) (when await-output-p (wait-until (:timeout await-output-p :poll-every 0.005) (listen shell-output)) (loop while (listen shell-output) do (princ (read-line shell-output) output-stream) (terpri output-stream)))))) ;;; main ;; (defun main () ;; (handler-case ;; (multiple-value-bind (options free-args) (opts:get-opts) ;; (print (list :options options :free-args free-args)) ;; (terpri) ;; (uiop:quit)) ;; (unix-opts:unknown-option (err) ;; (declare (ignore err)) ;; (princ (help-text)) ;; (terpri))))