aboutsummaryrefslogtreecommitdiff
path: root/src/lib.lisp
blob: a54cf7f7617f2c6cc811f72897a7bbbbe049df24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
;;;; main.lisp -- oneliners.cli entrypoint

(defpackage oneliners.cli
  (:use :cl)
  (:local-nicknames (#:api #:oneliners.api-client)
                    (#:a #:alexandria)))

(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 "http://localhost:8888") out))))

(defun write-config-to-disk ()
  (let ((conf-file (config-file)))
    (ensure-directories-exist conf-file)
    (with-open-file (out conf-file :direction :output :if-exists :supersede)
      (print *config* 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 (setf api-token) (newval)
  (setf (getf *config* :api-token) newval))


(defun config-file ()
  (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)))))

(defun make-cookie-from-api-token ()
  (a:when-let (token (api-token)) 
    (let ((cookies (cl-cookie:make-cookie-jar)))
      (cl-cookie:merge-cookies
       cookies
       (list 
        (cl-cookie:make-cookie
         :name "olauthtoken"
         :value token
         :path "/"
         :domain "localhost")))
      cookies)))

;;; API REQUEST FUNCTIONS

(defun request-invite-code ()
  (ensure-config)
  (api:request-with
      (:host (host)
       :cookies (make-cookie-from-api-token))
    (format t "Invite Code: ~a~%" (getf  (jonathan:parse (api:post--make-invite)) :code))))

(defun login (user pass)
  (ensure-config)
  (a:when-let (response (jonathan:parse
                         (api:request-with
                             (:host (host)) 
                           (api:post--token-contributor user :password pass))))
    (setf (api-token) (getf response :token))
    (write-config-to-disk)
    (format t "Access token written to ~a~%" (config-file))))

(defun redeem-invite (token name pass) 
  (ensure-config )
  (api:request-with
      (:host (host))
    (api:post--redeem-invite token
                             :username name
                             :password1 pass
                             :password2 pass)))

(defun search-for-oneliners (terms limit not-flagged-p)
  (assert (loop for term in terms never (find #\, term) ))
  (ensure-config)
  (print (api:request-with
             (:host (host))
           (api:get--search :tags (str:join "," terms)
                            :limit limit
                            :notflagged (if not-flagged-p "true" "false")))))

;;; RUNNING COMMANDS

(defun parent-process-name ()
  "Prints the name of the parent process of the current process."
  (let ((ppidfile (format nil "/proc/~a/status" (osicat-posix:getppid))))
    (first (last 
            (ppcre:split "\\s" 
                         (with-open-file (input ppidfile)
                           (read-line input)))))))

(defmacro wait-until ((&key (timeout 1) (poll-every 0.01)) &body check)
  "Run CHECK every POLL-EVERY seconds until either TIMEOUT seconds
have passed or CHECK returns non-nil."
  (let ((clockvar (gensym))
        (var (gensym)))
    `(loop
       for ,clockvar from 0 by ,poll-every to ,timeout
       for ,var = (progn ,@check)
       when ,var
         return ,var
       do (sleep ,poll-every))))

(defun run-with-shell
    (command
     &key
       (shell-name (parent-process-name))
       (await-output-p 0.8)
       (output-stream *standard-output*))
  "run COMMAND, a string, in a fresh shell environment, initialized
with SHELL-NAME. The output from the command read line by line and is
printed to OUTPUT-STREAM. "
  (let ((shell
          (uiop:launch-program shell-name :input :stream :output :stream)))
    (symbol-macrolet ((shell-input (uiop:process-info-input shell))
                      (shell-output (uiop:process-info-output shell)))
      (write-line command shell-input)
      (finish-output shell-input)
      (when await-output-p
        (wait-until (:timeout await-output-p :poll-every 0.005)
          (listen shell-output))
        (loop while (listen shell-output)
              do (princ (read-line shell-output) output-stream)
                 (terpri output-stream))))))