From f4b6c94bbd91d5061f1a449f407999be7e8e5814 Mon Sep 17 00:00:00 2001 From: Colin Okay Date: Fri, 18 Feb 2022 12:41:23 -0600 Subject: config file checking and generation --- build-app.lisp | 58 ++++++++++++++++++++------- clpmfile.lock | 23 +++++++++-- oneliners.cli.asd | 1 + src/lib.lisp | 115 ++++++++++++++++++++++++++---------------------------- 4 files changed, 120 insertions(+), 77 deletions(-) diff --git a/build-app.lisp b/build-app.lisp index c7f8667..1b16837 100644 --- a/build-app.lisp +++ b/build-app.lisp @@ -1,14 +1,26 @@ +;;; 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))) + (:local-nicknames (#:a #:alexandria) + (#:cli #:oneliners.cli))) (in-package :oneliners.cli.app) -(defsynopsis (:postfix "TERMS ... | N ARGS ...") - (text :contents "Search for oneliner mentioning TERMS or run the Nth search result with arguments ARGS.") +;;; HELP TEXTS +(defparameter +invite-help-text+ + "") + + +;;; CLON SYNOPSIS DEFINITION + +(defsynopsis (:postfix "[TERMS ...] | N [ARGS ...]") (group (:header "Search") + (text :contents "Usage: ol [OPTIONS] [TERMS ...]") + (text :contents "Each term may be a command name or some tag like 'server' or 'tunnel'") (lispobj :long-name "count" :argument-type :optional :argument-name "Count" @@ -17,8 +29,22 @@ :typespec 'integer) (flag :long-name "not-flagged" :description "Request that no flagged oneliners are returned.")) + (text :contents " ") + (group (:header "Running Oneliners") + (text :contents "Usage: ol [OPTIONS] N [ARGS ...]") + (text :contents "Runs the Nth search result with possible arguments ARGS.") + (flag :long-name "clip" + :description "Force an attempt to copy the oneliner to the clipboard instead of running it.")) + (text :contents " ") + (group (:header "Help") + (flag :long-name "help" + :description "Print this help menu.") + (enum :long-name "help-topic" + :enum '(:admin :wiki :invites) + :argument-name "TOPIC" + :description "Print help for a topic. Topics are: wiki, admin, invites")) (group (:header "Wiki" :hidden t) - (text :contents "This is text") + (text :contents "This section documents options used to add and edit oneliners in the wiki.") (flag :long-name "add-interactive" :description "Interactively add a new oneliner") (flag :long-name "update-interactive" @@ -30,25 +56,26 @@ :argument-type :optional :default-value "DUMMY-TOKEN" :argument-name "TOKEN" - :description "Redeem an invite token. Enter an - interactive process of setting up a new contributor - account with the inviting server")) - (group (:header "Help") - (flag :long-name "help" - :description "Print this help menu.") - (enum :long-name "help-topic" - :enum '(:admin :wiki :search :help) - :argument-name "TOPIC" - :description "Print help for a topic. Topics are: search, wiki, admin, help"))) + :description "Redeem an invite token. See also --help-topic=invites")) + (group (:header "Invites" :hidden t) + (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) @@ -71,9 +98,10 @@ hist-number (rest arguments)) (uiop:quit)) - (format t "TBD: Going to search for commands mentioning the terms ~a~%" arguments)) + (cli:search-for-oneliners arguments :)) (uiop:quit)) +;;; DUMP EXECUTABLE (dump "ol" main) diff --git a/clpmfile.lock b/clpmfile.lock index 53f4c43..c694db9 100644 --- a/clpmfile.lock +++ b/clpmfile.lock @@ -43,11 +43,16 @@ ("chunga" :version "2020-04-27" :source "quicklisp" :systems ("chunga")) ("cl+ssl" :version "2021-12-30" :source "quicklisp" :systems ("cl+ssl")) ("cl-base64" :version "2020-10-16" :source "quicklisp" :systems ("cl-base64")) +("cl-change-case" :version "2021-04-11" :source "quicklisp" :systems + ("cl-change-case")) ("cl-clon" :version "2021-04-11" :source "quicklisp" :systems ("net.didierverna.clon" "net.didierverna.clon.core" "net.didierverna.clon.setup" "net.didierverna.clon.setup/termio")) ("cl-cookie" :version "2019-10-07" :source "quicklisp" :systems ("cl-cookie")) -("cl-ppcre" :version "2019-05-21" :source "quicklisp" :systems ("cl-ppcre")) +("cl-ppcre" :version "2019-05-21" :source "quicklisp" :systems + ("cl-ppcre" "cl-ppcre-unicode")) +("cl-str" :version "2021-05-31" :source "quicklisp" :systems ("str")) +("cl-unicode" :version "2021-02-28" :source "quicklisp" :systems ("cl-unicode")) ("cl-utilities" :version "2010-10-07" :source "quicklisp" :systems ("cl-utilities")) ("dexador" :version "2021-12-09" :source "quicklisp" :systems ("dexador")) @@ -134,6 +139,8 @@ ("cl-base64" ((:system :name "dexador") (:system :name "cl-base64"))) +("cl-change-case" ((:system :name "str") (:system :name "cl-change-case"))) + ("cl-clon" ((:system :name "oneliners.cli") (:system :name "net.didierverna.clon")) ((:system :name "net.didierverna.clon.termio") @@ -153,8 +160,18 @@ ("cl-cookie" ((:system :name "dexador") (:system :name "cl-cookie"))) -("cl-ppcre" ((:system :name "dexador") (:system :name "cl-ppcre")) - ((:system :name "cl-cookie") (:system :name "cl-ppcre"))) +("cl-ppcre" ((:system :name "str") (:system :name "cl-ppcre")) + ((:system :name "str") (:system :name "cl-ppcre-unicode")) + ((:system :name "dexador") (:system :name "cl-ppcre")) + ((:system :name "cl-unicode") (:system :name "cl-ppcre")) + ((:system :name "cl-ppcre-unicode") (:system :name "cl-ppcre")) + ((:system :name "cl-cookie") (:system :name "cl-ppcre")) + ((:system :name "cl-change-case") (:system :name "cl-ppcre")) + ((:system :name "cl-change-case") (:system :name "cl-ppcre-unicode"))) + +("cl-str" ((:system :name "oneliners.cli") (:system :name "str"))) + +("cl-unicode" ((:system :name "cl-ppcre-unicode") (:system :name "cl-unicode"))) ("cl-utilities" ((:system :name "quri") (:system :name "cl-utilities")) ((:system :name "fast-http") (:system :name "cl-utilities"))) diff --git a/oneliners.cli.asd b/oneliners.cli.asd index 10e8f39..45d5d5b 100644 --- a/oneliners.cli.asd +++ b/oneliners.cli.asd @@ -3,6 +3,7 @@ :author "Colin Okay" :license "AGPLv3" :depends-on ("trivial-clipboard" + "str" "dexador" "osicat" "net.didierverna.clon" diff --git a/src/lib.lisp b/src/lib.lisp index 20f75ea..2480dab 100644 --- a/src/lib.lisp +++ b/src/lib.lisp @@ -3,62 +3,71 @@ (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+)) +(in-package :oneliners.cli) ;;; CONFIG AND RESULTS FILE LOCATIONS +(defvar *config* nil + "A configuration plist") + +(defun make-config (&key host api-token) + (append (when host (list :host host)) + (when api-token (list :api-token api-token)))) + +(defun valid-config-p (config) + (and (listp config) + (evenp (length config)) + (stringp (getf config :host)) + t)) + +(defun write-default-config-to-disk () + (let ((conf-file (config-file))) + (ensure-directories-exist conf-file) + (with-open-file (out conf-file :direction :output) + (print (make-config :host "https://oneliners.wiki") out)))) + +(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)) + (write-default-config-to-disk)) + (fetch-config-from-disk)) + +(defun host () (getf *config* :host)) +(defun api-token () (getf *config* :api-token)) + (defun config-file () - (merge-pathnames ".config/oneliners.config" (user-homedir-pathname) )) + (merge-pathnames ".config/oneliners.config" (user-homedir-pathname))) (defun last-search-file () (merge-pathnames ".last_oneliners_search" (user-homedir-pathname))) +(defun fetch-nth-oneliner (n) + "Returns nil if there is no nth oneliner from the search history." + (when (uiop:file-exists-p (last-search-file)) + (nth n (uiop:read-file-form (last-search-file))))) + +;;; SEARCHNG THE WIKI + +(defun search-for-oneliners (terms limit not-flagged-p) + (assert (loop for term in terms never (find #\, term) )) + (handler-case + (api:request-with + (:host (host)) + (api:get--search :tags (str:join ",") + :limit limit + :notflagged (if not-flagged-p "true" "false"))) + (dex:http-request-failed (e) + (format *error-output* "~a -- ~a" + (dex:response-status e) + (dex:response-body e))))) + ;;; RUNNING COMMANDS (defun parent-process-name () @@ -103,15 +112,3 @@ printed to OUTPUT-STREAM. " 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)))) -- cgit v1.2.3