;;; build-app.lisp -- Contains the command line frontend definition ;;; to build the cli, simply load this file from the command line. (asdf:load-system "oneliners.cli") (defpackage #:oneliners.cli.app (:use #:cl #:net.didierverna.clon) (:local-nicknames (#:a #:alexandria) (#:cli #:oneliners.cli))) (in-package :oneliners.cli.app) ;;; HELP TEXTS (defparameter +invite-help-text+ " New contributor accounts are added to the your oneliners server by redeeming invite tokens. When the --redeem option is passed, the ARGS section is expected to be three items long, and is interpreted as TOKEN USERNAME PASSWORD. E.g.: ol --redeem PHONEYTOKEN c00lhacker my1337pw Would attempt to make a new user named c00lhacker with password my1337pw.") ;;; CLON SYNOPSIS DEFINITION (defsynopsis (:postfix "[TAGS ...] | N [ARGS ...]") (group (:header "SEARCH OPTIONS") (text :contents "Return oneliners tagged with all of TAGS") (lispobj :long-name "limit" :argument-type :optional :argument-name "NUMBER" :default-value 10 :description "The maximum number of results to return." :typespec 'integer) (flag :long-name "all-flagged" :description "Return flagged oneliners. Ignores TAGS. Respects --limit") (flag :long-name "not-flagged" :description "Request that no flagged oneliners are returned.") (flag :long-name "newest" :description "Return newest oneliners. Ignores TAGs. Respects --limit up to server specified maxiumum.") (stropt :long-name "brief-like" :argument-name :optional :default-value ".*" :argument-name "REGEX" :description "A regular expression that must match some portion of the oneliner's title.")) (text :contents " ") (group (:header "ONELINER EXECUTION OPTIONS") (text :contents "Runs the Nth search result with possible arguments ARGS.") (flag :long-name "clip" :description "Put oneliner into clipboard instead of running it.") (lispobj :long-name "timeout" :argument-type :optional :argument-name "SECONDS" :default-value 1.0 :typespec 'float :description "How long to wait for output before giving up. Make longer if you dont see any output.")) (text :contents " ") (group (:header "HELP OPTIONS") (flag :long-name "explain" :description "View oneliner explaination text.") (enum :long-name "help" :enum '(:access :wiki :invites) :argument-name "TOPIC" :description "Print help for a topic. Topics: wiki, access, invites")) (group (:header "Wiki" :hidden t) (text :contents "Options For Managing Oneliners") (flag :long-name "add" :description "Intaractively add a oneliner and update the wiki.") (flag :long-name "edit" :description "Interactively edit a oneliner and update the wiki.") (flag :long-name "flag" :description "Flag a oneliner for review.") (flag :long-name "unflag" :description "If you have admin priviliges, unflag a oneliner.") (flag :long-name "lock" :description "If you have admin priviliges, lock a oneliner from being edited.") (flag :long-name "unlock" :description "If you have admin priviliges, unlock a oneliner.")) (group (:header "Access" :hidden t) (text :contents "Options for Managing Access Tokens and Contributor Your Account") (flag :long-name "login" :description "Attempt to login to your contributor account. ARGS are interpreted as USERNAME PASSWORD.") (flag :long-name "logout" :description "Revoke your own access token.") (flag :long-name "change-password" :description "Change your password. ARGS are interpreted as CURRENTPW NEWPW NEWPWAGAIN")) (group (:header "Invites" :hidden t) (text :contents "Options For Making Invite and Redeeming Tokens") (flag :long-name "invite" :description "Request an invite token to send to a friend.") (flag :long-name "redeem" :description "Redeem an invite token.") (text :contents +invite-help-text+))) ;;; HELPERS (defun find-group-with-header (header) "This function should be built in. Is it? How to know? The documentation is both extensive and trash. Any manual that expects you to go to sleep with it at night is written for the author more than the users." (loop for item in (net.didierverna.clon::items *synopsis*) when (and (typep item 'net.didierverna.clon::group) (string-equal header (net.didierverna.clon::header item))) return item)) ;;; MAIN ENTRY POINT (defun main () "Entry point for our standalone application." (make-context) (a:when-let (topic (getopt :long-name "help")) (help :item (find-group-with-header (symbol-name topic))) (uiop:quit)) (handler-case (let ((arguments (remainder))) (cond ((getopt :long-name "redeem") (destructuring-bind (token name pass) arguments (cli::redeem-invite token name pass))) ((getopt :long-name "login") (destructuring-bind (user pass) arguments (cli::login user pass))) ((getopt :long-name "change-password") (destructuring-bind (current new repeated) arguments (cli::change-pw current new repeated))) ((getopt :long-name "invite") (cli::request-invite-code)) ((getopt :long-name "logout") (cli::revoke-access)) ((getopt :long-name "add") (cli::add-new-oneliner)) ((getopt :long-name "all-flagged") (cli::all-flagged-oneliners (getopt :long-name "limit"))) ((getopt :long-name "newest") (cli::newest-oneliners (getopt :long-name "limit"))) (arguments ;; when the first argument is a number, try run a oneliner (a:when-let (hist-number (parse-integer (first arguments) :junk-allowed t)) (cond ((getopt :long-name "flag") (cli::flag-item hist-number)) ((getopt :long-name "unflag") (cli::unflag-item hist-number)) ((getopt :long-name "lock") (cli::lock-item hist-number)) ((getopt :long-name "unlock") (cli::unlock-item hist-number)) ((getopt :long-name "edit") (cli::edit-item hist-number)) ((getopt :long-name "explain") (cli::print-item-explanation hist-number)) ;; ((getopt :long-name "alias") ;; (cli::make-alias-for-item hist-number (second arguments))) (t (cli::run-item hist-number (rest arguments) :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"))) (t (help))) (uiop:quit)) (error (e) (format *error-output* "~%ERROR: ~a~%" e) (uiop:quit)) (#+sbcl sb-sys:interactive-interrupt #+ccl ccl:interrupt-signal-condition #+ecl ext:interactive-interrupt () (format t "Aborted by User Interrupt.~%") (uiop:quit)))) ;;; DUMP EXECUTABLE (dump "ol" main)