~evhan/chicken-sourcehut

ref: 37dc620efa73034094296bf2631624ec85469542 chicken-sourcehut/cli.scm -rw-r--r-- 10.7 KiB
37dc620eEvan Hanson Fix long option parsing 2 years ago
                                                                                
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; CLI for sr.ht
;;;
;;; Copyright (c) 2019-2020, Evan Hanson
;;;
;;; See LICENSE for details.
;;;

(declare (module (sourcehut cli)))

(import (chicken condition)
        (chicken keyword)
        (chicken io)
        (chicken pretty-print)
        (chicken process-context)
        (chicken string)
        (openssl)
        (sourcehut)
        (sourcehut builds)
        (sourcehut git)
        (sourcehut meta)
        (sourcehut paste)
        (medea)
        (optimism getopt-long)
        (simple-exceptions)
        (only (srfi 1) any)
        (only (srfi 133) vector-append))

(import-syntax (matchable))

(define common-options
  '((--access-token . token)
    ((-o --output) . format)
    ((-h --help))))

(define paging-options
  '((-n . lines)))

(define output-format
  (make-parameter "s" string->symbol))

(define (alist-ref* keys alist #!optional (test eqv?) default)
  (or (any (lambda (key) (alist-ref key alist test)) keys) default))

(define (error-reason e)
  (and-let* ((body   (get-condition-property e 'sourcehut 'body '()))
             (errors (alist-ref 'errors body)))
    (alist-ref 'reason (vector-ref errors 0))))

(define (error-message e)
  (let ((m (message e))
        (a (arguments e)))
    (if (null? a)
        m
        (conc m ": " (string-intersperse (map conc a) ", ")))))

(define (print-json . args)
  (apply write-json args)
  (print))

(define (string->id x)
  (let ((id (string->number x)))
    (if (and (integer? id) (positive? id))
        id
        (error "Invalid ID" x))))

(define (crud? x)
  (and (pair? x)
       (pair? (car x))
       (keyword? (caar x))))

(define (output x)
  (let ((printer
         (case (output-format)
           ((s) pretty-print)
           ((j json) print-json))))
    (cond
      ((crud? x) (printer (cdr x)))
      ((string? x) (print (string-chomp x)))
      (else (printer x)))))

(define (input-file x)
  (if (equal? x "-")
      (current-input-port)
      (open-input-file x)))

(define (input x)
  (if (equal? x "-")
      (read-string)
      (with-input-from-file x read-string)))

(define (retrieve-paged crud #!optional (limit 50))
  (let loop ((crud crud) (count 0))
    (let* ((response (retrieve crud))
           (results  (alist-ref 'results response))
           (next     (alist-ref 'next response))
           (count*   (+ count (vector-length results))))
      (if (or (>= count* limit) (eq? next 'null))
          (subvector results 0
                     (min (vector-length results)
                          (- (min (alist-ref 'total response) limit) count)))
          (vector-append results (loop (page crud next) count*))))))

(define (get-paged endpoint args)
  (define opts
    (parse-command-line args paging-options))
  (retrieve-paged
   (endpoint)
   (let* ((n  (alist-ref* '(-n) opts eq? "50"))
          (n* (string->number n)))
     (if (and (integer? n*) (positive? n*))
         (inexact->exact n*)
         (error "Invalid result limit" n)))))

(define (get-profile)
  (retrieve (profile)))

(define (get-audit-log args)
  (get-paged audit-log args))

(define (get-ssh-keys args)
  (get-paged ssh-keys args))

(define (get-pgp-keys args)
  (get-paged pgp-keys args))

(define ((key-creator type function) args)
  (define opts
    (parse-command-line
     args
     '(((-f --filename) . input))))
  (define file
    (alist-ref* '(-f --filename) opts eq? "-"))
  (match (assq '-- opts)
    ((_)
     (create (apply function (list type (input file)))))
    ((_ . _)
     (usage (current-error-port))
     (exit 1))))

(define ((key-deleter function) id)
  (delete (function (string->id id))))

(define (create-job args)
  (define opts
    (parse-command-line
     args
     '(((-m --manifest) . input)
       ((-f --filename) . input)
       ((-n --note) . message))))
  (define manifest
    (alist-ref* '(-m --manifest -f --filename) opts eq? "-"))
  (define note
    (alist-ref* '(-n --note) opts))
  (match (assq '-- opts)
    ((_)
     (let ((args* (append (list manifest: (input manifest))
                          (if (not note)
                              (list)
                              (list note: note)))))
       (create (apply job args*))))
    ((_ . _)
     (usage (current-error-port))
     (exit 1))))

(define (get-jobs args)
  (get-paged jobs args))

(define (get-manifest id)
  (retrieve (manifest (string->id id))))

(define (get-job id)
  (retrieve (job (string->id id))))

(define (get-refs repository args)
  (get-paged (cut refs repository) args))

(define (create-artifact args)
  (define opts
    (parse-command-line
     args
     '(((-f --filename) . input)
       ((-n --name) . message))))
  (define file
    (alist-ref* '(-f --filename) opts eq? "-"))
  (define name
    (alist-ref* '(-n --name) opts))
  (match (assq '-- opts)
    ((_ repository reference)
     (define args*
       (append (list file: (input-file file))
               (cond ((string? name)
                      (list filename: name))
                     ((equal? file "-")
                      (error "A name must be specified when reading from stdin"))
                     (else (list)))))
     (create (apply artifact repository reference args*)))
    ((_ . _)
     (usage (current-error-port))
     (exit 1))))

(define (create-paste args)
  (define opts
    (parse-command-line
     args
     '(((-c --contents) . input)
       ((-f --filename) . input)
       ((-n --name) . name)
       ((-v --visibility) . level))))
  (define contents
    (alist-ref* '(-c --contents -f --filename) opts eq? "-"))
  (define filename
    (alist-ref* '(-n --name) opts eq? 'null))
  (define visibility
    (alist-ref* '(-v --visibility) opts eq? "unlisted"))
  (match (assq '-- opts)
    ((_)
     (let ((args* (list contents: (input contents) filename: filename visibility: visibility)))
       (create (apply paste args*))))
    ((_ . _)
     (usage (current-error-port))
     (exit 1))))

(define (create-paste* args)
  (let ((paste (create-paste args)))
    (conc "https://paste." (service-domain) "/blob/"
          (alist-ref 'blob_id (vector-ref (alist-ref 'files paste) 0)))))

(define (get-paste sha)
  (retrieve (paste sha)))

(define (get-pastes args)
  (get-paged pastes args))

(define (get-blob sha)
  (and-let* ((blob (retrieve (blob sha))))
    (alist-ref 'contents blob)))

(define (usage #!optional (port (current-output-port)))
  (parameterize ((current-output-port port))
    (print "Usage: " (program-name) " [options ...] command ...")
    (print)
    (print "Options:")
    (print "        --access-token <token>   Set API token (SRHT_ACCESS_TOKEN)")
    (print "    -o, --output <format>        Set output format {s|j|json} (s)")
    (print "    -h, --help                   Show this message")
    (print)
    (print "Commands:")
    (print)
    (print "  get profile                    Fetch user details")
    (print "  get audit-log                  Fetch audit log")
    (print "  get ssh-keys                   List SSH keys")
    (print "  get pgp-keys                   List PGP keys")
    (print "  create ssh-key                 Create SSH key")
    (print "    -f, --filename <input>         filename (stdin)")
    (print "  create pgp-key                 Create PGP key")
    (print "    -f, --filename <input>         filename (stdin)")
    (print "  delete ssh-key <id>            Delete SSH key")
    (print "  delete pgp-key <id>            Delete PGP key")
    (print)
    (print "  get jobs                       Fetch job list")
    (print "  get job <id>                   Fetch job details")
    (print "  get manifest <id>              Fetch job manifest")
    (print "  create job                     Create job")
    (print "    -m, --manifest <input>         manifest (stdin)")
    (print "    -n, --note <message>           description (null)")
    (print)
    (print "  get refs <repo>                Fetch references")
    (print "  create artifact <repo> <ref>   Attach artifact to reference")
    (print "    -f, --filename <input>         source (stdin)")
    (print "    -n, --name <name>              name (null)")
    (print)
    (print "  get pastes                     Fetch paste list")
    (print "  get paste <sha>                Fetch paste details")
    (print "  get blob <sha>                 Fetch blob contents")
    (print "  create paste                   Create paste")
    (print "  paste                          Create paste and print URL")
    (print "    -c, --contents <input>         source (stdin)")
    (print "    -n, --name <name>              name (null)")
    (print "    -v, --visibility <level>       visibility (unlisted)")
    (print)))

(define (handle-global-options! opts)
  (and-let* ((help (alist-ref* '(-h --help) opts)))
    (usage)
    (exit 0))
  (and-let* ((format (alist-ref* '(-o --output) opts)))
    (output-format format)
    (unless (member (output-format) '(s j json))
      (error "Invalid output format" format)))
  (and-let* ((token (alist-ref '--access-token opts)))
    (access-token token)))

(define (main args)
  (let ((opts (parse-command-line args common-options)))
    (handle-global-options! opts)
    (match (assq '-- opts)
      ((_ "get" "profile")
       (output (get-profile)))
      ((_ "get" (or "audit" "audit-log" "audit-logs") . args*)
       (output (get-audit-log args*)))
      ((_ "get" "ssh-keys" . args*)
       (output (get-ssh-keys args*)))
      ((_ "create" "ssh-key" . args*)
       (output ((key-creator ssh-key: ssh-key) args*)))
      ((_ "delete" "ssh-key" id)
       (output ((key-deleter ssh-key) id)))
      ((_ "get" (or "pgp-keys" "gpg-keys") . args*)
       (output (get-pgp-keys args*)))
      ((_ "create" (or "pgp-key" "gpg-key") . args*)
       (output ((key-creator pgp-key: pgp-key) args*)))
      ((_ "delete" (or "pgp-key" "gpg-key") id)
       (output ((key-deleter pgp-key) id)))
      ((_ "get" "jobs" . args*)
       (output (get-jobs args*)))
      ((_ "get" "job" id)
       (output (get-job id)))
      ((_ "create" "job" . args*)
       (output (create-job args*)))
      ((_ "get" "manifest" id)
       (output (get-manifest id)))
      ((_ "get" "refs" repository . args*)
       (output (get-refs repository args)))
      ((_ "create" "artifact" . args*)
       (output (create-artifact args*)))
      ((_ "get" "pastes" . args*)
       (output (get-pastes args*)))
      ((_ "get" "paste" sha)
       (output (get-paste sha)))
      ((_ "get" "blob" sha)
       (output (get-blob sha)))
      ((_ "create" "paste" . args*)
       (output (create-paste args*)))
      ((_ "paste" . args*)
       (output (create-paste* args*)))
      ((_ . _)
       (usage (current-error-port))
       (exit 1)))))

(cond-expand
  (compiling
   (with-exception-handler
    (lambda (e)
      (print "Error: " (or (error-reason e) (error-message e)))
      (exit 1))
    (lambda ()
      (main (command-line-arguments)))))
  (else))