diff options
-rw-r--r-- | build-app.lisp | 62 | ||||
-rw-r--r-- | src/lib.lisp | 191 |
2 files changed, 100 insertions, 153 deletions
diff --git a/build-app.lisp b/build-app.lisp index 3511a8c..a3ef6fc 100644 --- a/build-app.lisp +++ b/build-app.lisp @@ -62,8 +62,6 @@ you run the oneliner, all positional variables appear first. ;;; CLON SYNOPSIS DEFINITION (defsynopsis (:postfix "[ARGUMENTS ...]") - - (group (:header "SEARCH OPTIONS") (text :contents "By default, ARGUMENTS are interpeted as search terms for oneliners. For example:") (text :contents "$ ol grep awk # search for oneliners involving both grep and awk") @@ -82,15 +80,11 @@ you run the oneliner, all positional variables appear first. (flag :long-name "newest" :description "Return newest oneliners.")) (text :contents " ") - - (group (:header "EXECUTION OPTIONS") (text :contents "Several options override the default interpretation of ARGUMENTS.") (text :contents "Execution options interpret the first argument as the identifier of a oneliner: ") (text :contents "$ ol <EXECUTION OPTION> <NAME or ID> [MORE ARGUMENTS...]") (text :contents " ") - - (flag :long-name "run" :description "Executes a oneliner by NAME or ID. See also help topic 'variables'.") (flag :long-name "clip" @@ -111,11 +105,9 @@ you run the oneliner, all positional variables appear first. :description "View information about a contributor. The first argument is a contributor handle.") (flag :long-name "help" :description "Print help for a topic. Topics: wiki, account, invites, variables")) - (group (:header "Advanced Options" :hidden t) (flag :long-name "clear-cache" :description "Clears all cached search results from your system.")) - (group (:header "Variables" :hidden t) (text :contents +oneliners-variables-help-text+)) (group (:header "Wiki" :hidden t) @@ -166,6 +158,14 @@ than the users." ;;; MAIN ENTRY POINT +(defun prepare-oneliner-arguments (arguments) + "Takes a list of arguments, as gathered by (REMAINDER), and returns + a list that looks like (ID-OR-NAME . ARGS) where ID-OR-NAME is + either an integer or a string." + (a:if-let (id (parse-integer (first arguments) :junk-allowed t)) + (cons id (rest arguments)) + arguments)) + (defun main () "Entry point for our standalone application." (make-context) @@ -176,18 +176,21 @@ than the users." (let ((arguments (remainder))) (cond ((getopt :long-name "whois") - (assert (first arguments)) + (assert (first arguments) () "--whois requires an argument, a user handle.") (cli::show-contributor (first arguments))) ((getopt :long-name "redeem") + (assert (= 3 (length arguments)) () "--redeem requires exatly three arguments.") (destructuring-bind (token name pass) arguments (cli::redeem-invite token name pass))) ((getopt :long-name "login") + (assert (= 2 (length arguments)) () "--login requires exactly two arguments.") (destructuring-bind (user pass) arguments (cli::login user pass))) ((getopt :long-name "change-password") + (assert (= 3 (length arguments)) () "--change-password requires exactly three arguments." ) (destructuring-bind (current new repeated) arguments (cli::change-pw current new repeated))) @@ -204,40 +207,43 @@ than the users." (cli::add-new-oneliner)) ((getopt :long-name "all-flagged") + (format t "WARNING: --all-flagged is likely to soon change its behavior~%.") (cli::all-flagged-oneliners (getopt :long-name "limit"))) ((getopt :long-name "newest") + (format t "WARNING: --newest is likely to soon change its behavior~%") (cli::newest-oneliners (getopt :long-name "limit"))) ((getopt :long-name "clear-cache") (cli::wipe-cache)) (arguments - ;; when the first argument is a number, try run a oneliner - (a:when-let (hist-number (parse-integer (first arguments) :junk-allowed t)) + (destructuring-bind (id-or-name . args) (prepare-oneliner-arguments arguments) (cond ((getopt :long-name "flag") - (cli::flag-item hist-number (getopt :long-name "id"))) + (cli::flag-item id-or-name )) ((getopt :long-name "unflag") - (cli::unflag-item hist-number (getopt :long-name "id"))) + (cli::unflag-item id-or-name)) ((getopt :long-name "lock") - (cli::lock-item hist-number (getopt :long-name "id"))) + (cli::lock-item id-or-name )) ((getopt :long-name "unlock") - (cli::unlock-item hist-number (getopt :long-name "id"))) + (cli::unlock-item id-or-name )) ((getopt :long-name "edit") - (cli::edit-item hist-number (getopt :long-name "id"))) - ((getopt :long-name "explain") - (cli::print-item-explanation hist-number (getopt :long-name "id"))) - (t - (cli::run-item hist-number (rest arguments) - :by-id (getopt :long-name "id") - :force-clip (getopt :long-name "clip") - :timeout (getopt :long-name "timeout")))) - (uiop:quit)) - ;; otherwise search for oneliners - (cli::search-for-oneliners arguments - (getopt :long-name "limit") - (getopt :long-name "not-flagged"))) + (cli::edit-item id-or-name )) + ((getopt :long-name "info") + (cli::print-item-explanation id-or-name)) + ((getopt :long-name "clip") + (cli::run-item id-or-name (rest arguments) + :force-clip t + :timeout (getopt :long-name "timeout"))) + ((getopt :long-name "run") + (cli::run-item id-or-name (rest arguments) + :timeout (getopt :long-name "timeout"))) + (t + (cli::search-for-oneliners arguments + (getopt :long-name "limit") + (getopt :long-name "not-flagged"))))) + (uiop:quit)) (t (help))) (uiop:quit)) diff --git a/src/lib.lisp b/src/lib.lisp index 070e021..2ade79b 100644 --- a/src/lib.lisp +++ b/src/lib.lisp @@ -69,29 +69,26 @@ (defun config-file () (merge-pathnames ".config/oneliners.config" (user-homedir-pathname))) -(defun last-search-file () - (merge-pathnames ".last_oneliners_search" (user-homedir-pathname))) - (defun cached-oneliners-file () (merge-pathnames ".cached_oneliners" (user-homedir-pathname))) (defun wipe-cache () - (uiop:delete-file-if-exists (cached-oneliners-file)) - (uiop:delete-file-if-exists (last-search-file ))) + (uiop:delete-file-if-exists (cached-oneliners-file))) -(defun merge-into-cache (ols) +(defun merge-into-cache (new-ols) (if (uiop:file-exists-p (cached-oneliners-file)) - (let ((cached (with-open-file (input (cached-oneliners-file)) (read input)))) + (let ((cached-ols (with-open-file (input (cached-oneliners-file)) (read input)))) (with-open-file (out (cached-oneliners-file) :direction :output :if-exists :supersede) - (print (nconc - ols - (remove-if - (lambda (old) - (member (getf old :id) ols :test 'equal :key (lambda (x) (getf x :id)))) - cached)) - out))) - (with-open-file (out (cached-oneliners-file) :direction :output :if-exists :supersede) - (print ols out)))) + (print + (nconc + new-ols + (remove-if + (lambda (old) + (find (getf old :id) new-ols :key (lambda (x) (getf x :id)))) + cached-ols)) + out))) + (with-open-file (out (cached-oneliners-file) :direction :output) + (print new-ols out)))) ;;; UTILITIES (defun make-temp-file-name () @@ -145,70 +142,69 @@ the directories that appear in the value of that variable." ;; unregisters the hook. (rl:register-hook :pre-input nil))) -(defun cached-result (n &optional idp) - (if idp - (when (uiop:file-exists-p (cached-oneliners-file)) - (let ((contents (with-open-file (input (cached-oneliners-file)) (read input)))) - (find n contents :key (lambda (x) (getf x :id))))) - (when (uiop:file-exists-p (last-search-file)) - (let ((contents (with-open-file (input (last-search-file)) (read input)))) - (nth n contents))))) - -(defmacro with-cached-result ((olvar n &optional idp) &body body) - (a:with-gensyms (nvar idpvar) - `(let ((,nvar ,n) - (,idpvar ,idp)) - (assert (plusp ,nvar) () "Item number must be 1 or greater") - (a:if-let (,olvar (if ,idpvar (cached-result ,nvar t) (cached-result (1- ,nvar)))) +(defun the-oneliner (name-or-id) + "Get the oneliner with name-or-id. Try to fetch from local cache, +and, failing that, try to fetch from configured server." + (a:if-let ((ol (cached-result name-or-id))) + ol + (progn + (ensure-config) + (a:when-let (ol + (api:request-with (:host (host)) + (jonathan:parse + (api:get--oneliner-entry name-or-id)))) + (merge-into-cache (list ol)) + ol)))) + +(defun cached-result (n) + (when (uiop:file-exists-p (cached-oneliners-file)) + (let ((contents (with-open-file (input (cached-oneliners-file)) (read input)))) + (etypecase n + (integer + (find n contents :key (lambda (x) (getf x :id)))) + (string + (find n contents :key (lambda (x) (getf x :name)) :test #'equal)))))) + +(defmacro with-oneliner ((var name-or-id) &body body) + (assert (symbolp var)) + (let ((nvar (gensym))) + `(let ((,nvar ,name-or-id)) + (a:if-let (,var (the-oneliner ,nvar)) (progn ,@body) (format t "Could not find the oneliner specified by ~a~%" ,nvar))))) -(defun print-item-explanation (number &optional idp) - (let ((found nil)) - (with-cached-result (ol number idp) - (setf found t) - (when (getf ol :explanation) - (princ #\newline) - (princ (getf ol :explanation)))) - (when (and idp (not found)) - (format t "Trying to fetch oneliner with id ~a from the wiki.~%" number) - (ensure-config) - (a:if-let ((ol - (api:request-with (:host (host)) - (jonathan:parse - (api::get--oneliner-entry number))))) - (progn - (when (getf ol :explanation) - (princ #\newline) - (princ (getf ol :explanation))) - (merge-into-cache (list ol))) - (format t "Still couldn't find it. Are you connected?"))))) +(defun print-item-explanation (name-or-number) + (with-oneliner (ol name-or-number) + (print-oneliner-result-for-user ol) + (when (getf ol :explanation) + (princ #\newline) + (princ (getf ol :explanation))))) ;;; API REQUEST FUNCTIONS -(defun flag-item (item-number &optional idp) - (with-cached-result (ol item-number idp) +(defun flag-item (ident) + (with-oneliner (ol ident) (ensure-config) (api:request-with (:host (host)) (api:put--oneliner-entry-flag (getf ol :id) :token (api-token) :value "true")))) -(defun unflag-item (item-number &optional idp) - (with-cached-result (ol item-number idp) +(defun unflag-item (item-number) + (with-oneliner (ol item-number) (ensure-config) (api:request-with (:host (host)) (api:put--oneliner-entry-flag (getf ol :id) :token (api-token) :value "false")))) -(defun lock-item (item-number &optional idp) - (with-cached-result (ol item-number idp) +(defun lock-item (item-number) + (with-oneliner (ol item-number) (ensure-config) (api:request-with (:host (host)) (api:put--oneliner-oneliner-locked (getf ol :id) :token (api-token) :value "true")))) -(defun unlock-item (item-number &optional idp) - (with-cached-result (ol item-number idp) +(defun unlock-item (item-number) + (with-oneliner (ol item-number) (ensure-config) (api:request-with (:host (host)) @@ -262,22 +258,10 @@ the directories that appear in the value of that variable." (handle-run-oneliner oneliner (or force-clip (equalp runstyle "manual")))))) -(defun run-item (item-number args &key by-id force-clip (timeout nil timeout-p)) - (let ((*ol-output-timeout* (if timeout-p timeout *ol-output-timeout*)) - (found nil)) - (with-cached-result (ol item-number by-id) - (setf found t) - (bind-vars-and-run-oneliner ol args force-clip)) - (when (and by-id (not found)) - (format t "Trying to fetch oneliner with id ~a from the wiki.~%" item-number) - (ensure-config) - (a:if-let ((ol - (api:request-with (:host (host)) - (jonathan:parse - (api::get--oneliner-entry item-number))))) - (progn (bind-vars-and-run-oneliner ol args force-clip) - (merge-into-cache (list ol))) - (format t "Still couldn't find it. Are you connected?"))))) +(defun run-item (ident args &key force-clip (timeout nil timeout-p)) + (with-oneliner (ol ident) + (let ((*ol-output-timeout* (if timeout-p timeout *ol-output-timeout*))) + (bind-vars-and-run-oneliner ol args force-clip)))) (defun valid-oneliner-string-p (string) (and (not (find #\newline string)) @@ -289,28 +273,6 @@ the directories that appear in the value of that variable." (defun valid-runstyle-p (string) (member string '("auto" "manual") :test 'equalp)) -(defun aliases () - (getf *config* :aliases)) - -(defun (setf aliases) (newval) - (setf (getf *config* :aliases) newval)) - -(defun alias-item (item alias) - (with-cached-result (ol item) - (ensure-config) - (a:if-let (found (assoc alias (aliases))) - (setf (cdr found) ol) - (push (cons alias ol) (aliases))) - (write-config-to-disk))) - -(defun lookup-alias (alias) - (cdr (assoc alias (aliases)))) - -(defun run-alias (alias args &optional force-clip) - (ensure-config) - (a:when-let (ol (lookup-alias alias)) - (bind-vars-and-run-oneliner ol args force-clip))) - (defun add-new-oneliner () (ensure-config) (assert (api-token) () "Cannot add a oneliner without an api token.") @@ -352,10 +314,10 @@ the directories that appear in the value of that variable." (api:post--oneliner :token (api-token)) (format t "Added~%")))) -(defun edit-item (n &optional idp) - (ensure-config) - (assert (api-token) () "Cannot edit a oneliner without an api token.") - (with-cached-result (ol n idp) +(defun edit-item (ident) + (with-oneliner (ol ident) + (ensure-config) + (assert (api-token) () "Cannot edit a oneliner without an api token.") (let* ((oneliner (prompt "Oneliner: " :expect 'valid-oneliner-string-p @@ -400,9 +362,7 @@ the directories that appear in the value of that variable." new-item) :content-type "application/json") (api:patch--oneliner-entry-edit (getf ol :id) :token (api-token)) - (if idp - (update-history-item n new-item) - (update-cached-item new-item)) + (update-cached-item new-item) (format t "OK~%")))))) (defun request-invite-code () @@ -499,26 +459,8 @@ the directories that appear in the value of that variable." (api:delete--access-access (api-token) :token (api-token)) (format t "You were logged out~%"))) -(defun update-history-item (n item) - (when (uiop:file-exists-p (last-search-file)) - (let* ((results (with-open-file (input (last-search-file)) (read input))) - (ol (nth (1- n) results))) - (when ol - (setf (nth (1- n) results) (append item ol)) - (cache-search-results-to-last-search-file results)))) - (merge-into-cache (list item))) - (defun update-cached-item (item) - (let* ((history (with-open-file (input (last-search-file)) (read input))) - (pos (position (getf item :id) history :key (lambda (x) (getf item :id))))) - (if pos - (update-history-item (1+ pos) item) - (merge-into-cache (list item))))) - -(defun cache-search-results-to-last-search-file (results) - (with-open-file (output (last-search-file) :direction :output :if-exists :supersede) - (print results output)) - (merge-into-cache results)) + (merge-into-cache (list item))) (defvar *term-width* nil) @@ -557,7 +499,7 @@ the directories that appear in the value of that variable." (format t "~%~a~%~%" (getf oneliner :oneliner)))) (defun cache-and-print-search-response (response) - (cache-search-results-to-last-search-file + (merge-into-cache (loop for oneliner in (getf (jonathan:parse response) :oneliners) collect oneliner do (print-oneliner-result-for-user oneliner)))) @@ -585,7 +527,6 @@ the directories that appear in the value of that variable." (defun search-for-oneliners (terms limit not-flagged-p) (assert (loop for term in terms never (find #\, term) )) (set-term-width) - (format t "TERM WIDTH IS ~a~%" *term-width*) (ensure-config) (let ((response (api:request-with |