diff options
-rw-r--r-- | app/app.lisp | 1 | ||||
-rw-r--r-- | lib/client.lisp | 793 | ||||
-rw-r--r-- | lib/oneliner.lisp | 31 | ||||
-rw-r--r-- | lib/package.lisp | 2 | ||||
-rw-r--r-- | lib/state.lisp | 11 |
5 files changed, 427 insertions, 411 deletions
diff --git a/app/app.lisp b/app/app.lisp index 44a5f97..fea4aa3 100644 --- a/app/app.lisp +++ b/app/app.lisp @@ -285,7 +285,6 @@ than the users." (uiop:quit)) (#+sbcl sb-sys:interactive-interrupt #+ccl ccl:interrupt-signal-condition - #+ecl ext:interactive-interrupt () (format t "Aborted by User Interrupt.~%") (uiop:quit)))) diff --git a/lib/client.lisp b/lib/client.lisp index 0c4c362..22d75b8 100644 --- a/lib/client.lisp +++ b/lib/client.lisp @@ -16,46 +16,67 @@ ;; along with this program. If not, see <http://www.gnu.org/licenses/>. (in-package :oneliners.cli) +;;; UTILITIES + +(defun cache-and-print-search-response (json) + "Takes a json string and parses it. Using the parse results, create +ONELINER instances. Print those using PRINT-ONELINER-RESULT-FOR-USER +and then ensure the cache is updated." + (merge-oneliners + (loop for oneliner-plist in (getf (jonathan:parse json) :oneliners) + for oneliner = (apply #'make-oneliner oneliner-plist) + collect oneliner + do (print-oneliner-result-for-user oneliner)))) + +(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)))) + + ;;; SEARCHING FOR 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.") - (with-local-state () - (api:get--oneliners :tags (str:join "," terms) - :limit limit - :notflagged (true-or-false not-flagged-p) - :newest (true-or-false newestp) - :onlyflagged (true-or-false all-flagged-p)))) - -(defun search-for-oneliners (terms limit not-flagged-p all-flagged-p newestp) - - (set-term-width) - (ensure-config) - (let ((response - (api:request-with - (:host (host)) + (with-local-state + (let ((json (api:get--oneliners :tags (str:join "," terms) :limit limit - :notflagged (if not-flagged-p "true" "false") - :newest (if newestp "true" "false") - :onlyflagged (if all-flagged-p "true" "false"))))) - (cache-and-print-search-response response))) + :notflagged (true-or-false not-flagged-p) + :newest (true-or-false newestp) + :onlyflagged (true-or-false all-flagged-p)))) + (cache-and-print-search-response json)))) + +(defun the-oneliner (name-or-id) + "Get the oneliner with name-or-id. First look in the local cache. If +not in the local cache, try to fetch from configured server." + (with-local-state + (a:if-let ((ol (get-cached name-or-id))) + ol + (let ((ol (jonathan:parse (api:get--oneliner-entry name-or-id)))) + (merge-oneliners (list ol)) + ol)))) +;;; RUNNING ONELINERS (defvar *ol-output-timeout* 1) (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)))) + (with-local-state + (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 bind-vars-and-run-oneliner (ol args &optional force-clip) (let* ((oneliner (oneliner-oneliner ol)) (runstyle (oneliner-runstyle ol)) (pos-args (collect-positional-arguments oneliner)) (named-args (collect-named-arguments oneliner))) - (when (or (not (oneliner-isflagged ol)) (y-or-n-p "This oneliner is flagged. Are you sure you want to run it?")) ;; substitute positional args @@ -79,393 +100,355 @@ (progn (trivial-clipboard:text ol) (format t "Copied oneliner to clipboard~%")) (progn - (ensure-config) (format t "Attempting to run:~%") (princ ol) (princ #\newline) (princ #\newline) - (run-with-shell ol :shell-name (or (get-shell) "bash"))))) - - -(defun make-fresh-config () - (format t "No configuration file has been found. Running Setup~%~%") - (setf *config* - (make-config - :host (prompt "Oneliner Instance Host: " - :prefill "https://api.oneliners.wiki") - :shell (prompt "With which shell should commands be run: " - :prefill "bash"))) - (write-config-to-disk) - (format t "Configuration has been written to ~a~%. Edit this at any time.~%~%" - (config-file))) - -(defun fetch-config-from-disk () - (let ((conf - (uiop:with-safe-io-syntax () - (uiop:read-file-form (config-file))))) - (assert (valid-config-p conf) () "Invalid configuration file") - (setf *config* conf))) - -(defun ensure-config () - (unless (uiop:file-exists-p (config-file)) - (make-fresh-config)) - (fetch-config-from-disk)) - - -;;; UTILITIES - + (run-with-shell ol :shell-name (or (config-shell *config*) "bash"))))) -;; (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))))) - - - - - -;;; API REQUEST FUNCTIONS - -(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 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) - (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) - (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) - (with-oneliner (ol item-number) - (ensure-config) - (api:request-with - (:host (host)) - (api:put--oneliner-oneliner-locked (getf ol :id) :token (api-token) :value "false")))) - - - -;;; PRINTING ONELINERS - - -(defun print-item-explanation (name-or-number) - (with-oneliner (ol name-or-number) - (set-term-width) - (print-oneliner-result-for-user ol) - (a:when-let (explanation (getf ol :explanation)) - (format t "EXPLANATION:~%~%") - (princ - (string-trim - '(#\newline #\space #\tab) - (if (str:starts-with? (getf ol :oneliner) explanation) - (subseq explanation (length (getf ol :oneliner))) - explanation))) - (terpri)))) - -(defun tags-from-oneliner (string) - "Splits a string using consequitive whitespace as a separator, -returning a set of tags" - (remove-duplicates - (remove-if-not #'executable-on-system-p (ppcre:split " +" string)) - :test #'equal)) - - -(defun add-new-oneliner () - (ensure-config) - (assert (api-token) () "Cannot add a oneliner without an api token.") - (let* ((oneliner - (prompt "Oneliner: " - :expect 'valid-oneliner-string-p - :retry-text "Oneliners must contain at least one command: ")) - (name - (string-trim - '(#\space #\newline #\tab #\linefeed) - (prompt "Name (leave blank for none): " - :expect 'valid-oneliner-name-p - :retry-text "Must begin with a letter contain only letters, numbers, - and _."))) - (init-tags - (tags-from-oneliner oneliner)) - (brief - (prompt "Brief Description: " - :expect 'valid-brief-description-p - :retry-text "Too long. Must be <= 72 characters: ")) - (tags - (progn - (format t "Tags include: ~{~a ~}~%" init-tags) - (append init-tags - (ppcre:split " +" - (prompt "More tags here, or Enter to skip: "))))) - (runstyle - (string-upcase - (prompt "Runstyle (auto or manual): " - :expect 'valid-runstyle-p - :retry-text "Must be (auto or manual): " - :prefill "auto"))) - (explanation - (when (y-or-n-p "Provide an explanation?") - (string-from-editor - (format nil "~a~%~%" oneliner))))) - (api:request-with - (:host (host) - :body (jonathan:to-json - (list :oneliner oneliner - :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)) - (format t "Added~%")))) - -(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 - :retry-text "Oneliners must contain at least one command: " - :prefill (getf ol :oneliner))) - (name - (string-trim - '(#\space #\newline #\tab #\linefeed) - (prompt "Name (leave blank for none): " - :expect 'valid-oneliner-name-p - :retry-text "Must begin with a letter contain only letters, numbers, - and _." - :prefill (if (getf ol :name) (getf ol :name) "")))) - (brief - (prompt "Brief Description: " - :expect 'valid-brief-description-p - :retry-text "Too long. Must be <= 72 characters: " - :prefill (getf ol :brief))) - (init-tags - (tags-from-oneliner oneliner)) - (tags - (progn - (format t "Tags include: ~{~a ~}~%" init-tags) - (append init-tags - (ppcre:split " +" - (prompt "More tags here, or Enter to skip: " - :prefill (str:join " " - (set-difference - (getf ol :tags) - init-tags - :test 'equal))))))) - (runstyle - (string-upcase - (prompt "Runstyle (auto or manual): " - :expect 'valid-runstyle-p - :retry-text "Must be (auto or manual): " - :prefill (getf ol :runstyle)))) - (explanation - (when (y-or-n-p "Provide an explanation?") - (string-from-editor (getf ol :explanation))))) - (let ((new-item - (list :oneliner oneliner - :tags tags - :brief brief - :name (if (plusp (length name)) name :null) - :explanation explanation - :runstyle runstyle))) - (api:request-with - (:host (host) - :body (jonathan:to-json - new-item) - :content-type "application/json") - (api:patch--oneliner-entry-edit (getf ol :id) :token (api-token)) - (update-cached-item new-item) - (format t "OK~%")))))) - -(defun request-invite-code () - (ensure-config) - (api:request-with - (:host (host)) - (let ((invite (jonathan:parse (api:post--invite :token (api-token))))) - (format t "Invite Code: ~a~%Expires: ~a~%" - (getf invite :code) - (getf invite :expires))))) - -(defun login (user pass) - (ensure-config) - (a:when-let (response (jonathan:parse - (api:request-with - (:host (host) - :body (jonathan:to-json (list :password pass :handle user)) - :content-type "application/json") - (api:post--access)))) - (setf (api-token) (getf response :token) - (contributor-handle) user) - (write-config-to-disk) - (format t "Access token written to ~a~%You may now make contributions to the wiki!.~%" - (config-file)))) - -(defun change-pw (current new repeated) - (unless (equal new repeated) - (error "The new password doesn't match the repeated value. Double check.")) - (ensure-config) - (api:request-with - (:host (host)) - (api:put--contributor-who-password (contributor-handle) - :token (api-token) - :value new - :repeated new - :current current))) - -(defparameter +agree-to-the-unlicense+ - "By creating this contributor account, I agree that my contributions - be released into the public domain, for the benefit of the public at - large, and to the detriment of my heirs and successors. I intend - this dedication to be an overt act of relinquishment in perpetuity - of all present and future rights to my contributions under software - copyright law copyright law. More specifically, I agree to release all of my - contributions using The Unlicense. (see https://unlicense.org/)") - - -(defun prompt-for-signature () - (if (y-or-n-p "Provide a contributor signature about yourself? ") - (prompt "Go ahead: ") - "")) - -(defun change-signature () - (let ((new-sig - (prompt-for-signature))) - (ensure-config) - (api:request-with - (:host (host) - :body (jonathan:to-json (list :signature new-sig)) - :content-type "application/json") - (api:put--contributor-who-signature (contributor-handle) :token (api-token)) - (format t "Your signature was changed.~%")))) - -(defun print-contributor (contributor) - (format t "~20a ~@[-- ~a~]~%" - (getf contributor :handle) - (getf contributor :signature))) - -(defun show-contributor (name) - (ensure-config) - (api:request-with - (:host (host)) - (a:when-let (contributor - (api:get--contributor-who name)) - (print-contributor (jonathan:parse contributor))))) - -(defun redeem-invite (token name pass) - (ensure-config ) - (when (yes-or-no-p +agree-to-the-unlicense+) - (api:request-with - (:host (host) - :body (jonathan:to-json (list :handle name - :password1 pass :password2 pass - :signature (prompt-for-signature))) - :content-type "application/json") - (api:post--invite-redeem-code token) - (format t "Account made for ~a. You may log in now~%" name)))) - -;;TODO: check this .. shouldnt access be a username??? -(defun revoke-access () - (ensure-config) - (api:request-with - (:host (host)) - (api:delete--access-access (api-token) :token (api-token)) - (format t "You were logged out~%"))) - -(defun update-cached-item (item) - (merge-into-cache (list item))) - -(defun print-oneliner-result-for-user (oneliner) - (unless *term-width* (set-term-width)) ; setting here as a fallback, can set it elswere if desired. - (let* ((title-line-format-str - (concatenate 'string "~" (prin1-to-string *term-width*) "<[~a]~;~a~;~a~>~%")) - (tags-line-format-string - (concatenate 'string "~" (prin1-to-string *term-width*) "<~a~;by ~a~>~%"))) - (loop repeat *term-width* do (princ #\_)) - (terpri) - (format t title-line-format-str - (getf oneliner :id) - (or (getf oneliner :name) " ") - (format nil "~:[ ~;⚠~]~:[ ~;🔒~]~:[ ~;📋~]" - (getf oneliner :isflagged) - (getf oneliner :islocked) - (equalp "manual" (getf oneliner :runstyle)))) - (format t tags-line-format-string - (format nil "tags: ~{~a~^ ~}" - (getf oneliner :tags)) - (getf oneliner :createdby)) - (loop - with brief = (getf oneliner :brief) - for x from 0 to (length brief) by *term-width* - do (format t "~a~%" - (string-trim '(#\space) - (alexandria-2:subseq* brief x (+ x *term-width*))))) - (format t "~%~a~%~%" (getf oneliner :oneliner)))) - -(defun cache-and-print-search-response (response) - (merge-into-cache - (loop for oneliner in (getf (jonathan:parse response) :oneliners) - collect oneliner - do (print-oneliner-result-for-user oneliner)))) -(defun newest-oneliners (&optional limit) - (ensure-config) - (api:request-with - (:host (host)) - (let ((response - (if limit - (api:get--oneliners-newest :limit limit) - (api:get--oneliners-newest)))) - (cache-and-print-search-response response)))) - -(defun all-flagged-oneliners (&optional limit) - (ensure-config) - (api:request-with - (:host (host)) - (let ((response - (if limit - (api:get--oneliners-all-flagged :limit limit) - (api:get--oneliners-all-flagged)))) - (cache-and-print-search-response response)))) - - - -;;; RUNNING THINGS IN THE SHELL. +;; (defun make-fresh-config () +;; (format t "No configuration file has been found. Running Setup~%~%") +;; (setf *config* +;; (make-config +;; :host (prompt "Oneliner Instance Host: " +;; :prefill "https://api.oneliners.wiki") +;; :shell (prompt "With which shell should commands be run: " +;; :prefill "bash"))) +;; (write-config-to-disk) +;; (format t "Configuration has been written to ~a~%. Edit this at any time.~%~%" +;; (config-file))) + +;; (defun fetch-config-from-disk () +;; (let ((conf +;; (uiop:with-safe-io-syntax () +;; (uiop:read-file-form (config-file))))) +;; (assert (valid-config-p conf) () "Invalid configuration file") +;; (setf *config* conf))) + +;; (defun ensure-config () +;; (unless (uiop:file-exists-p (config-file)) +;; (make-fresh-config)) +;; (fetch-config-from-disk)) + + +;; ;;; UTILITIES + + +;; ;; (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)))))) + + + + + + + +;; ;;; API REQUEST FUNCTIONS + +;; (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 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) +;; (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) +;; (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) +;; (with-oneliner (ol item-number) +;; (ensure-config) +;; (api:request-with +;; (:host (host)) +;; (api:put--oneliner-oneliner-locked (getf ol :id) :token (api-token) :value "false")))) + + + +;; ;;; PRINTING ONELINERS + + +;; (defun print-item-explanation (name-or-number) +;; (with-oneliner (ol name-or-number) +;; (set-term-width) +;; (print-oneliner-result-for-user ol) +;; (a:when-let (explanation (getf ol :explanation)) +;; (format t "EXPLANATION:~%~%") +;; (princ +;; (string-trim +;; '(#\newline #\space #\tab) +;; (if (str:starts-with? (getf ol :oneliner) explanation) +;; (subseq explanation (length (getf ol :oneliner))) +;; explanation))) +;; (terpri)))) + +;; (defun tags-from-oneliner (string) +;; "Splits a string using consequitive whitespace as a separator, +;; returning a set of tags" +;; (remove-duplicates +;; (remove-if-not #'executable-on-system-p (ppcre:split " +" string)) +;; :test #'equal)) + + +;; (defun add-new-oneliner () +;; (ensure-config) +;; (assert (api-token) () "Cannot add a oneliner without an api token.") +;; (let* ((oneliner +;; (prompt "Oneliner: " +;; :expect 'valid-oneliner-string-p +;; :retry-text "Oneliners must contain at least one command: ")) +;; (name +;; (string-trim +;; '(#\space #\newline #\tab #\linefeed) +;; (prompt "Name (leave blank for none): " +;; :expect 'valid-oneliner-name-p +;; :retry-text "Must begin with a letter contain only letters, numbers, - and _."))) +;; (init-tags +;; (tags-from-oneliner oneliner)) +;; (brief +;; (prompt "Brief Description: " +;; :expect 'valid-brief-description-p +;; :retry-text "Too long. Must be <= 72 characters: ")) +;; (tags +;; (progn +;; (format t "Tags include: ~{~a ~}~%" init-tags) +;; (append init-tags +;; (ppcre:split " +" +;; (prompt "More tags here, or Enter to skip: "))))) +;; (runstyle +;; (string-upcase +;; (prompt "Runstyle (auto or manual): " +;; :expect 'valid-runstyle-p +;; :retry-text "Must be (auto or manual): " +;; :prefill "auto"))) +;; (explanation +;; (when (y-or-n-p "Provide an explanation?") +;; (string-from-editor +;; (format nil "~a~%~%" oneliner))))) +;; (api:request-with +;; (:host (host) +;; :body (jonathan:to-json +;; (list :oneliner oneliner +;; :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)) +;; (format t "Added~%")))) + +;; (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 +;; :retry-text "Oneliners must contain at least one command: " +;; :prefill (getf ol :oneliner))) +;; (name +;; (string-trim +;; '(#\space #\newline #\tab #\linefeed) +;; (prompt "Name (leave blank for none): " +;; :expect 'valid-oneliner-name-p +;; :retry-text "Must begin with a letter contain only letters, numbers, - and _." +;; :prefill (if (getf ol :name) (getf ol :name) "")))) +;; (brief +;; (prompt "Brief Description: " +;; :expect 'valid-brief-description-p +;; :retry-text "Too long. Must be <= 72 characters: " +;; :prefill (getf ol :brief))) +;; (init-tags +;; (tags-from-oneliner oneliner)) +;; (tags +;; (progn +;; (format t "Tags include: ~{~a ~}~%" init-tags) +;; (append init-tags +;; (ppcre:split " +" +;; (prompt "More tags here, or Enter to skip: " +;; :prefill (str:join " " +;; (set-difference +;; (getf ol :tags) +;; init-tags +;; :test 'equal))))))) +;; (runstyle +;; (string-upcase +;; (prompt "Runstyle (auto or manual): " +;; :expect 'valid-runstyle-p +;; :retry-text "Must be (auto or manual): " +;; :prefill (getf ol :runstyle)))) +;; (explanation +;; (when (y-or-n-p "Provide an explanation?") +;; (string-from-editor (getf ol :explanation))))) +;; (let ((new-item +;; (list :oneliner oneliner +;; :tags tags +;; :brief brief +;; :name (if (plusp (length name)) name :null) +;; :explanation explanation +;; :runstyle runstyle))) +;; (api:request-with +;; (:host (host) +;; :body (jonathan:to-json +;; new-item) +;; :content-type "application/json") +;; (api:patch--oneliner-entry-edit (getf ol :id) :token (api-token)) +;; (update-cached-item new-item) +;; (format t "OK~%")))))) + +;; (defun request-invite-code () +;; (ensure-config) +;; (api:request-with +;; (:host (host)) +;; (let ((invite (jonathan:parse (api:post--invite :token (api-token))))) +;; (format t "Invite Code: ~a~%Expires: ~a~%" +;; (getf invite :code) +;; (getf invite :expires))))) + +;; (defun login (user pass) +;; (ensure-config) +;; (a:when-let (response (jonathan:parse +;; (api:request-with +;; (:host (host) +;; :body (jonathan:to-json (list :password pass :handle user)) +;; :content-type "application/json") +;; (api:post--access)))) +;; (setf (api-token) (getf response :token) +;; (contributor-handle) user) +;; (write-config-to-disk) +;; (format t "Access token written to ~a~%You may now make contributions to the wiki!.~%" +;; (config-file)))) + +;; (defun change-pw (current new repeated) +;; (unless (equal new repeated) +;; (error "The new password doesn't match the repeated value. Double check.")) +;; (ensure-config) +;; (api:request-with +;; (:host (host)) +;; (api:put--contributor-who-password (contributor-handle) +;; :token (api-token) +;; :value new +;; :repeated new +;; :current current))) + +;; (defparameter +agree-to-the-unlicense+ +;; "By creating this contributor account, I agree that my contributions +;; be released into the public domain, for the benefit of the public at +;; large, and to the detriment of my heirs and successors. I intend +;; this dedication to be an overt act of relinquishment in perpetuity +;; of all present and future rights to my contributions under software +;; copyright law copyright law. More specifically, I agree to release all of my +;; contributions using The Unlicense. (see https://unlicense.org/)") + + +;; (defun prompt-for-signature () +;; (if (y-or-n-p "Provide a contributor signature about yourself? ") +;; (prompt "Go ahead: ") +;; "")) + +;; (defun change-signature () +;; (let ((new-sig +;; (prompt-for-signature))) +;; (ensure-config) +;; (api:request-with +;; (:host (host) +;; :body (jonathan:to-json (list :signature new-sig)) +;; :content-type "application/json") +;; (api:put--contributor-who-signature (contributor-handle) :token (api-token)) +;; (format t "Your signature was changed.~%")))) + +;; (defun print-contributor (contributor) +;; (format t "~20a ~@[-- ~a~]~%" +;; (getf contributor :handle) +;; (getf contributor :signature))) + +;; (defun show-contributor (name) +;; (ensure-config) +;; (api:request-with +;; (:host (host)) +;; (a:when-let (contributor +;; (api:get--contributor-who name)) +;; (print-contributor (jonathan:parse contributor))))) + +;; (defun redeem-invite (token name pass) +;; (ensure-config ) +;; (when (yes-or-no-p +agree-to-the-unlicense+) +;; (api:request-with +;; (:host (host) +;; :body (jonathan:to-json (list :handle name +;; :password1 pass :password2 pass +;; :signature (prompt-for-signature))) +;; :content-type "application/json") +;; (api:post--invite-redeem-code token) +;; (format t "Account made for ~a. You may log in now~%" name)))) + +;; ;;TODO: check this .. shouldnt access be a username??? +;; (defun revoke-access () +;; (ensure-config) +;; (api:request-with +;; (:host (host)) +;; (api:delete--access-access (api-token) :token (api-token)) +;; (format t "You were logged out~%"))) + +;; (defun update-cached-item (item) +;; (merge-into-cache (list item))) + + + + +;; (defun newest-oneliners (&optional limit) +;; (ensure-config) +;; (api:request-with +;; (:host (host)) +;; (let ((response +;; (if limit +;; (api:get--oneliners-newest :limit limit) +;; (api:get--oneliners-newest)))) +;; (cache-and-print-search-response response)))) + +;; (defun all-flagged-oneliners (&optional limit) +;; (ensure-config) +;; (api:request-with +;; (:host (host)) +;; (let ((response +;; (if limit +;; (api:get--oneliners-all-flagged :limit limit) +;; (api:get--oneliners-all-flagged)))) +;; (cache-and-print-search-response response)))) + + + +;; ;;; RUNNING THINGS IN THE SHELL. diff --git a/lib/oneliner.lisp b/lib/oneliner.lisp index 48c78f6..ccfd3dc 100644 --- a/lib/oneliner.lisp +++ b/lib/oneliner.lisp @@ -64,3 +64,34 @@ (or (equal string "") (and (< 2 (length string)) (ppcre:scan "^[a-zA-Z][a-zA-Z0-9_\-]+$" string)))) + +;;; PRINTING + +(defun print-oneliner-result-for-user (ol) + "Prints information about the oneliner to the terminal." + (unless *term-width* (set-term-width)) ; setting here as a fallback, can set it elswere if desired. + (let* ((title-line-format-str + (concatenate 'string "~" (prin1-to-string *term-width*) "<[~a]~;~a~;~a~>~%")) + (tags-line-format-string + (concatenate 'string "~" (prin1-to-string *term-width*) "<~a~;by ~a~>~%"))) + (loop repeat *term-width* do (princ #\_)) + (terpri) + (with-slots + (id name isflagged islocked runstyle tags createdby brief oneliner) ol + + (format t title-line-format-str + id + (or name " ") + (format nil "~:[ ~;⚠~]~:[ ~;🔒~]~:[ ~;📋~]" + isflagged + islocked + (equalp "manual" runstyle))) + (format t tags-line-format-string + (format nil "tags: ~{~a~^ ~}" tags) + createdby) + (loop + for x from 0 to (length brief) by *term-width* + do (format t "~a~%" + (string-trim '(#\space) + (alexandria-2:subseq* brief x (+ x *term-width*))))) + (format t "~%~a~%~%" oneliner)))) diff --git a/lib/package.lisp b/lib/package.lisp index 5baf51e..0ed47b0 100644 --- a/lib/package.lisp +++ b/lib/package.lisp @@ -30,5 +30,7 @@ (defpackage #:oneliners.cli (:use #:cl) (:import-from #:oneliners.cli.prompt #:prompt) + (:import-from #:oneliners.cli.term #:*term-width* #:set-term-width) + (:import-from #:oneliners.cli.running #:run-with-shell) (:local-nicknames (#:api #:oneliners.api-client) (#:a #:alexandria))) diff --git a/lib/state.lisp b/lib/state.lisp index 5bab3ed..0c01d4d 100644 --- a/lib/state.lisp +++ b/lib/state.lisp @@ -92,13 +92,14 @@ CACHED-ONELINERS-FILE. NIL if there is no such file." sets the api's *host* variable. If BODY produces no errors, the " `(let* ((*config* (read-config-file)) (*cache* (read-cache-file)) - (api::*host* (config-host *config*))) - (assert *host* () "ol must be configured with a server host.") + (api:*host* (config-host *config*))) + (assert api:*host* () "ol must be configured with a server host.") + (set-term-width) (handler-case (progn ,@body ;; only if there is no error do we save the local state. (write-cache-to-disk) - (write-config-to-disk))) - (error (e) - (format *error-output* "~a~%" e)))) + (write-config-to-disk)) + (error (e) + (format *error-output* "~a~%" e))))) |