diff options
Diffstat (limited to 'lib/client.lisp')
-rw-r--r-- | lib/client.lisp | 255 |
1 files changed, 174 insertions, 81 deletions
diff --git a/lib/client.lisp b/lib/client.lisp index fe29932..61c3130 100644 --- a/lib/client.lisp +++ b/lib/client.lisp @@ -16,12 +16,16 @@ ;; along with this program. If not, see <http://www.gnu.org/licenses/>. (in-package :oneliners.cli) -;; NOTE WHILE HACKING. Each of the functions below that make HTTP -;; requests is meant to be called within the body of a -;; WITH-LOCAL-STATE form. If you are hacking in the REPL, make sure -;; to wrap function calls appropriately. +;;;; >>>>>NOTE WHILE HACKING<<<<<. +;;;; ---------------------------------------------------------------- +;;;; Each of the functions below that make HTTP requests are meant to +;;;; be called within the body of a WITH-LOCAL-STATE form. If you are +;;;; hacking in the REPL, make sure to wrap function calls +;;;; appropriately. +;;;; ---------------------------------------------------------------- -;;; GETTING ONELINERS & Displaying oneliners + +;;; GETTING ONELINERS & DISPLAYING ONELINERS (defun search-for-oneliners (terms limit &optional not-flagged-p all-flagged-p newestp) (assert (loop for term in terms never (find #\, term)) () "Search terms may not contain commas.") @@ -45,12 +49,18 @@ not in the local cache, try to fetch from configured server." (defmacro when-oneliner ((var name-or-id) &body body) "Finds the oneliner with name-or-id and binds it to var before running the body. If such a oneliner can be found." - (assert (symbolp var)) (let ((nvar (gensym))) `(let ((,nvar ,name-or-id)) (a:when-let (,var (the-oneliner ,nvar)) ,@body)))) +(defmacro when-draft ((var name) &body body) + "Like when-oneliner but restricts itself to local drafts." + (let ((nvar (gensym))) + `(let ((,nvar ,name)) + (a:when-let (,var (fetch-draft ,nvar)) + ,@body)))) + (defun newest-oneliners (&optional limit) (let ((response (if limit @@ -72,52 +82,62 @@ running the body. If such a oneliner can be found." (princ #\newline) (princ (oneliner-explanation ol))))) +(defun print-drafts () + (when *drafts* + (format t (concatenate 'string "~%~" (prin1-to-string *term-width*) "< ~;DRAFTS~; ~>~%")) + (dolist (draft *drafts*) + (print-oneliner-result-for-user (cdr draft))))) + ;;; RUNNING ONELINERS (defvar *ol-output-timeout* 1) -(defun run-item (ident args &key force-clip (timeout nil timeout-p)) - (when-oneliner (ol ident) - (let ((*ol-output-timeout* (if timeout-p timeout *ol-output-timeout*))) - (bind-vars-and-run-oneliner ol args force-clip)))) +(defun run-item (ident args &key force-clip (timeout nil timeout-p) draftp verbose confirm) + "Runs a oneliner identified by IDENT (if available) with arguments ARGS." + (let ((ol (if draftp (fetch-draft ident) (the-oneliner ident)))) + (when ol + (let ((*ol-output-timeout* (if timeout-p timeout *ol-output-timeout*))) + (bind-vars-and-run-oneliner ol args force-clip verbose confirm))))) -(defun bind-vars-and-run-oneliner (ol args &optional force-clip) +(defun bind-vars-and-run-oneliner (ol args &optional force-clip verbose confirm) (let* ((oneliner (oneliner-oneliner ol)) (runstyle (oneliner-runstyle ol)) (pos-args (get-positional-arguments ol)) (named-args (get-named-arguments ol))) + + (loop for param in pos-args + for arg in args + do (setf oneliner (str:replace-all param arg oneliner))) + ;; substitute named args + + (setf args + (mapcar (lambda (s) (str:split "=" s)) + (nthcdr (length pos-args) args))) + (loop for var in named-args + for bound = (assoc (subseq var 1) args :test #'equal) + when bound + do (setf oneliner + (str:replace-all var (second bound) oneliner))) + (when (or (not (oneliner-isflagged ol)) (y-or-n-p "This oneliner is flagged. Are you sure you want to run it?")) + (when (or verbose confirm) + (format t "Attempting to run:~%") + (princ oneliner) + (princ #\newline)) ;; substitute positional args - (loop for param in pos-args - for arg in args - do (setf oneliner (str:replace-all param arg oneliner))) - ;; substitute named args - (setf args - (mapcar (lambda (s) (str:split "=" s)) - (nthcdr (length pos-args) args))) - (loop for var in named-args - for bound = (assoc (subseq var 1) args :test #'equal) - when bound - do (setf oneliner - (str:replace-all var (second bound) oneliner))) - - (handle-run-oneliner oneliner (or force-clip (equalp runstyle "manual")))))) + (when (or (not confirm) + (y-or-n-p "Proceed?")) + (handle-run-oneliner oneliner (or force-clip (equalp runstyle "manual"))))))) (defun handle-run-oneliner (ol &optional clip) (if clip (progn (trivial-clipboard:text ol) (format t "Copied oneliner to clipboard~%")) - (progn - (format t "Attempting to run:~%") - (princ ol) - (princ #\newline) - (princ #\newline) - (run-with-shell ol :shell-name (or (shell) "bash") :await-output-p *ol-output-timeout*)))) + (run-with-shell ol :shell-name (or (shell) "bash") :await-output-p *ol-output-timeout*))) ;;; ADDING ONELINERS - (defun add-new-oneliner () (api-token) ;; fails with error if not set. ;; read each field required to make a onelienr in from a prompt. @@ -131,8 +151,18 @@ running the body. If such a oneliner can be found." (prompt "Name (leave blank for none): " :expect 'valid-oneliner-name-p :retry-text "Must begin with a letter contain only letters, numbers, - and _."))) + + (draft-name + (unless (y-or-n-p "Upload immediately instead of keeping a draft?") + (if (plusp (length name)) + name + (prompt "No name was provided, name this draft: " + :expect 'valid-oneliner-name-p + :retry-text "Must begin with a letter contain only letters, numbers, - and _.")))) + (init-tags (parse-oneliner-tags oneliner-string)) + (brief (prompt "Brief Description: " :expect 'valid-brief-description-p @@ -153,25 +183,34 @@ running the body. If such a oneliner can be found." (when (y-or-n-p "Provide an explanation?") (string-from-editor (format nil "~a~%~%" oneliner-string))))) - (api:request-with - (:body (jonathan:to-json - (list :oneliner oneliner-string - :name (if (plusp (length name)) name :null) - :tags tags - :brief brief - :explanation explanation - :runstyle runstyle)) - :content-type "application/json") - (api:post--oneliner :token (api-token)) ;TODO: update api to return the instance created. - (format t "Added~%")))) + (let ((local + (make-oneliner + :oneliner oneliner-string + :name name + :tags tags + :brief brief + :explanation explanation + :runstyle runstyle))) + (if draft-name + ;; if this is a draft, save it to disk. + (progn + (put-draft draft-name local) + (format t "Saved draft ~a~%Do `ol --draft run ~a` to test~%" + draft-name + draft-name)) + ;; otherwise, format the oneliner as json and make a request + ;; to create a new oneliner in the wiki + (api:request-with + (:body (oneliner-to-json-body local) + :content-type "application/json") + (api:post--oneliner :token (api-token)) ;TODO: update api to return the instance created. + (format t "Added Oneliner~%")))))) ;;; EDITING ONELINERS -(defun edit-item (ident) +(defun edit-item (ident &optional draftp) (api-token) ;; fails with error if not set. - (when-oneliner (ol ident) - ;; Like adding a oneliner, read each field in from a prompt. - ;; Here, prefil the field with its current value. + (let ((ol (if draftp (fetch-draft ident) (the-oneliner ident)))) (let* ((oneliner-string (prompt "Oneliner: " :expect 'valid-oneliner-string-p @@ -184,13 +223,23 @@ running the body. If such a oneliner can be found." :expect 'valid-oneliner-name-p :retry-text "Must begin with a letter contain only letters, numbers, - and _." :prefill (or (oneliner-name ol) "")))) + + (draft-name + (unless (y-or-n-p "Upload edits immediately instead of keeping a draft?") + (if (plusp (length name)) + name + (prompt "No name was provided, name this draft: " + :expect 'valid-oneliner-name-p + :retry-text "Must begin with a letter contain only letters, numbers, - and _.")))) (brief (prompt "Brief Description: " :expect 'valid-brief-description-p :retry-text "Too long. Must be <= 72 characters: " :prefill (oneliner-brief ol))) + (init-tags (parse-oneliner-tags oneliner-string)) + (tags (progn (format t "Tags include: ~{~a ~}~%" init-tags) @@ -208,49 +257,93 @@ running the body. If such a oneliner can be found." :expect 'valid-runstyle-p :retry-text "Must be (auto or manual): " :prefill (oneliner-runstyle ol)))) + (explanation - (when (y-or-n-p "Provide an explanation?") + (when (y-or-n-p "Alter the explanation?") (string-from-editor (oneliner-explanation ol))))) - (let ((new-item - (list :oneliner oneliner-string - :tags tags - :brief brief - :name (if (plusp (length name)) name :null) - :explanation explanation - :runstyle runstyle))) - (api:request-with - (:body (jonathan:to-json new-item) - :content-type "application/json") + + (let ((local + (make-oneliner + :id (oneliner-id ol) + :oneliner oneliner-string + :name (if (plusp (length name)) name :null) + :tags tags + :brief brief + :explanation explanation + :runstyle runstyle))) + (if draft-name + (progn + (put-draft draft-name local) + (format t "Saved draft ~a~%Do `ol --draft run ~a` to test~%" + draft-name + draft-name)) + (api:request-with + (:body (oneliner-to-json-body local) + :content-type "application/json") + (api:patch--oneliner-entry-edit (oneliner-id ol) :token (api-token)) + ;(merge-oneliners (list new-item)) ;;TODO: this is broken, wait for API update. + (format t "Edits accepted~%"))))))) + +;;TODO: need to sync cache here. +(defun publish-draft (name) + (when-draft (ol name) + (api:request-with + (:body (oneliner-to-json-body ol) + :content-type "application/json") + (if (oneliner-id ol) (api:patch--oneliner-entry-edit (oneliner-id ol) :token (api-token)) - (merge-oneliners (list new-item)) ;;TODO: this is broken, wait for API update. - (format t "OK~%")))))) + (api:post--oneliner :token (api-token))) + ;; if that worked, no http error occured, so this next part will run + (drop-draft name) + (format t "Draft ~a published and removed from drafts.~%" name)))) ;;; ADMIN OF ONELINER ENTRIES +(defun delete-item (ident) + (when-oneliner (ol ident) + (api:delete--oneliner-oneliner + ident + :token (api-token)) + ;; if we've made it this far no http error has been returned, + ;; hence we can delete it from the cache + (remove-from-cache ident))) + (defun flag-item (ident) + (when-oneliner (ol ident) + (api:put--oneliner-entry-flag + ident + :token (api-token) + :value "true") + ;; no http error, so we flag the cached version, ol. + (setf (oneliner-isflagged ol) t))) + +(defun unflag-item (ident) + (when-oneliner (ol ident) + (api:put--oneliner-entry-flag + ident + :token (api-token) + :value "false") + ;; no http error, so we can unflag the cached version, ol + (setf (oneliner-isflagged ol) nil))) + +(defun lock-item (ident) + (when-oneliner (ol ident) + (api:put--oneliner-oneliner-locked + ident + :token (api-token) + :value "true") + ;; no http error, so we can lock the cached version, ol + (setf (oneliner-islocked ol) t))) + +(defun unlock-item (ident) (when-oneliner (ol ident) - (api:put--oneliner-entry-flag (oneliner-id ol) - :token (api-token) - :value "true"))) - -(defun unflag-item (item-number) - (when-oneliner (ol item-number) - (api:put--oneliner-entry-flag (oneliner-id ol) - :token (api-token) - :value "false"))) - -(defun lock-item (item-number) - (when-oneliner (ol item-number) - (api:put--oneliner-oneliner-locked (oneliner-id ol) - :token (api-token) - :value "true"))) - -(defun unlock-item (item-number) - (when-oneliner (ol item-number) - (api:put--oneliner-oneliner-locked (oneliner-id ol) - :token (api-token) - :value "false"))) + (api:put--oneliner-oneliner-locked + ident + :token (api-token) + :value "false") + ;; no http error, so we can unlock the cached version, ol + (setf (oneliner-islocked ol) nil))) ;;; ACCOUNT AND INVITE STUFF |