-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathflex-compile-cli.el
393 lines (364 loc) · 14.1 KB
/
flex-compile-cli.el
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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
;;; flex-compile-cli.el --- Compile functions -*- lexical-binding: t; -*-
;; Copyright (C) 2015 - 2023 Paul Landes
;; Author: Paul Landes
;; Maintainer: Paul Landes
;; Keywords: command line compile flexible processes
;; URL: https://github.com/plandes/flex-compile
;; Package-Requires: ((emacs "26.1"))
;; Package-Version: 0
;; This file is not part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; Implementation compiler for Zensols action based command line.
;; See: https://plandes.github.io/util/doc/command-line.html
;;; Code:
(require 'cl-lib)
(require 'json)
(require 'dash)
(require 'choice-program-complete)
(require 'flex-compile-manage)
(defclass flex-compile-cli-arg (config-prop-entry)
((value :initarg :value
:initform nil
:documentation "The value of the argument from user input.")
(arg-name :initarg :arg-name
:type string
:documentation "The long argument name.")
(type :initarg :type
:type symbol
:documentation "Either `option' or `positional'."))
:documentation "\
Contains user provided arguments that is given to the command line.")
(cl-defmethod config-prop-save-config ((this flex-compile-cli-arg))
"Does nothing as all data is in-memory ephemeral for THIS compiler."
(ignore this))
;;; cli file compiler
(defclass cli-flex-compiler (single-buffer-flex-compiler
conf-file-flex-compiler)
((cli-metadata :initarg :cli-metadata
:initform nil
:documentation "The CLI action metadata.")
(action :initarg :action
:initform nil
:documentation "The action to invoke on the program.")
(arg-properties :initarg :arg-properties
:initform nil
:documentation
"Properties used to read command line arguments.")
(arguments :initarg :arguments
:initform nil
:documentation "The arguments to give to the script.")
(cache-metadata :initarg :cache-metadata
:initform t
:documentation "
Whether or not to cache the Python program's CLI metadata."))
:method-invocation-order :c3
:documentation "\
Provides support for user input for action mnemonics and options using Python
programs that use the
\[Zensols action CLI]\(https://plandes.github.io/util/doc/command-line.html).
This compiler gets the command line metadata as a list of actions and their
respective positional and option arguments. It this prompts the user with
documentation associated, first the action, then the action's arguments.
Finally, it constructs the command line and executes the Python program with
the arguments.")
(cl-defmethod initialize-instance ((this cli-flex-compiler)
&optional slots)
"Initialize the THIS instance with SLOTS."
(let* ((read-action (lambda (this compiler default prompt history)
(ignore this)
(flex-compiler-cli-read-action
compiler default prompt history)))
(read-args (lambda (this compiler &rest args)
(ignore this args)
(flex-compiler-cli-read-arguments compiler t)))
(props (list (config-eval-prop :object-name 'action
:prompt "Action"
:func read-action
:prop-entry this
:required t
:input-type 'last
:order 0)
(config-eval-prop :object-name 'arguments
:prompt "Arguments"
:func read-args
:prop-entry this
:required nil
:input-type 'last
:order 1)
(config-boolean-prop :object-name 'cache-metadata
:prompt "Cache metadata"
:prop-entry this
:initial-value t
:required nil
:input-type 'toggle
:order 2))))
(setq slots (plist-put slots :object-name "cli")
slots (plist-put slots :description "CLI Python")
slots (plist-put slots :buffer-name "Command Line Interface")
slots (plist-put slots :kill-buffer-clean t)
slots (plist-put slots :validate-modes '(python-mode))
slots (plist-put slots
:props (append (plist-get slots :props) props))))
(cl-call-next-method this slots))
(cl-defmethod flex-compiler-load-libraries
((this cli-flex-compiler))
"Load the `json' library for THIS compiler."
(ignore this)
(require 'json))
(cl-defmethod config-prop-set ((this cli-flex-compiler) prop val)
"Set property PROP to VAL on THIS compiler."
(let (wipes)
(cond ((eq (config-prop-name prop) 'config-file)
(config-persistent-reset (config-prop-by-name this 'action))
(setq wipes '(cli-metadata arg-properties arguments action)))
((eq (config-prop-name prop) 'action)
(setq wipes
(append wipes '(cli-metadata arg-properties arguments)))))
(dolist (slot wipes)
(setf (slot-value this slot) nil)))
(cl-call-next-method this prop val))
(cl-defmethod flex-compiler-cli-metadata ((this cli-flex-compiler)
&optional action)
"Read the CLI metadata from the command line on THIS compiler.
If ACTION is non-nil, then return only the metadata for the \(symbol) action."
;; make sure the action is set first
(config-prop-entry-set-required this 'config-file)
(with-slots (config-file cli-metadata cache-metadata) this
(when (null config-file)
(error "Must first set the configuration file"))
(setq cli-metadata
(or (and cache-metadata cli-metadata)
(let* ((json (->> (format "%s list --lstfmt json" config-file)
shell-command-to-string)))
(condition-case err
(json-read-from-string json)
(error "Could not parse <%s>: %s" json err)))))
(if action
(cdr (assq action cli-metadata))
cli-metadata)))
(cl-defmethod flex-compiler-cli-has-positional-p ((this cli-flex-compiler)
action)
"Return if THIS compiler's ACTION program has positional arguments."
(->> (flex-compiler-cli-metadata this)
(assq action)
cdr
(assq 'positional)
cdr
length
(< 0)))
(cl-defmethod flex-compiler-cli-read-action ((this cli-flex-compiler)
default prompt history)
"Read the action for THIS compiler using DEFAULT, PROMPT and HISTORY."
(let ((action
(->> (flex-compiler-cli-metadata this)
(-map (lambda (action)
(let ((name (cdr (assq 'name action)))
(doc (cdr (assq 'doc action))))
(format "%s: %s" name doc))))
(funcall (lambda (actions)
(choice-program-complete
prompt actions t nil nil history default t))))))
(unless (string-match "^\\([^:]+\\)" action)
(error "Could not parse action from desc: %s" action))
(let ((action (intern (match-string 1 action)))
(arg-prop (config-prop-by-name this 'arguments)))
(oset arg-prop :required
(flex-compiler-cli-has-positional-p this action))
action)))
(cl-defmethod flex-compiler-cli--arg-properties ((this cli-flex-compiler))
"Create the argument properties for THIS compiler."
;; make sure the action is set first
(config-prop-entry-set-required this 'action)
(cl-flet ((to-config-class
(elt)
(let ((regex "\\(?:directory\\|folder\\)")
(type (intern (cdr (assq 'dtype elt)))))
(cl-case type
(bool 'config-boolean-prop)
(int 'config-number-prop)
(float 'config-number-prop)
(str 'config-prop)
(path (let* ((doc (cdr (assq 'doc elt)))
(is-dir (string-match regex doc)))
(if is-dir
'config-directory-prop
'config-file-prop)))
(t (error "Unknown type: %s" type)))))
(to-arg
(key elt)
(cdr (assq key elt)))
(to-doc
(elt)
(let* ((doc (cdr (assq 'doc elt)))
(first-char (substring doc nil 1))
(rest-str (substring doc 1)))
(concat (capitalize first-char) rest-str))))
(with-slots (action) this
;; return a config-prop for each command line argument/option
(let* ((meta (flex-compiler-cli-metadata this action))
;; collect required positional arguments for the action
(positional (->> (cdr (assq 'positional meta))
(-map (lambda (elt)
`((prop . (,(to-config-class elt)
:required t
:object-name 'value
:prompt ,(to-doc elt)))
(arg-name . ,(to-arg 'name elt))
(type . position))))))
;; collect optional arguments for the action
(options (->> (cdr (assq 'options meta))
(-map (lambda (elt)
`((prop . (,(to-config-class elt)
:required t
:initial-value ,(assq 'defautl elt)
:object-name 'value
:prompt ,(to-doc elt)))
(arg-name . ,(to-arg 'long_name elt))
(type . option))))))
(order 0))
;; instantiate a new config-prop by type for each
(->> (append positional options)
(-map (lambda (elt)
(let ((prop (cdr (assq 'prop elt)))
(arg-name (cdr (assq 'arg-name elt))))
(append
prop
`(:prop-entry
,(flex-compile-cli-arg :arg-name arg-name
:type (cdr (assq 'type elt)))
:order ,(cl-incf order)
:input-type 'last)))))
(-map #'eval)
(-map (lambda (prop)
(let ((container (slot-value prop 'prop-entry)))
(oset container :props (list prop))
prop))))))))
(cl-defmethod flex-compiler-cli-argument-plist ((this cli-flex-compiler)
&optional resetp)
"Read the arguments and return them as a list of strings for THIS compiler.
If RESETP is non-nil, reset all previously set configuration to force the user
to add again."
(with-slots (arg-properties) this
(setq arg-properties
(or arg-properties (flex-compiler-cli--arg-properties this)))
(when resetp
(dolist (prop arg-properties)
(oset (slot-value prop 'prop-entry) :value nil)))
;; create a string command line parameter for each argument (except false
;; booleans as they are set as flags/store true)
(->> arg-properties
(-map (lambda (prop)
(let ((container (slot-value prop 'prop-entry))
(value-type (config-prop-type prop)))
;; get the user input now
(config-prop-entry-set-required container)
(with-slots (arg-name type value) container
(list :value-type value-type
:arg-name arg-name
:arg-type type
:value value
:str-value
(cond ((eq value-type 'boolean)
(if value "true" "false"))
((or (null value) (stringp value)) value)
(t (prin1-to-string value)))))))))))
(cl-defmethod flex-compiler-cli-read-arguments ((this cli-flex-compiler)
&optional resetp)
"Read the arguments and return them as a list of strings for THIS compiler.
If RESETP is non-nil, reset all previously set configuration to force the user
to add again."
;; create a string command line parameter for each argument (except false
;; booleans as they are set as flags/store true)
(->> (flex-compiler-cli-argument-plist this resetp)
(-map (lambda (pl)
(let ((arg-name (plist-get pl :arg-name))
(value (plist-get pl :value))
(str-value (plist-get pl :str-value))
(value-type (plist-get pl :value-type))
(arg-type (plist-get pl :arg-type)))
;; positional arguments have no (option) long name
(if (eq arg-type 'position)
(cons str-value nil)
(if (eq value-type 'boolean)
;; add just the optiona name as flags for booleans
(if value
(list (format "--%s" arg-name)))
;; add the option name and value
(list (format "--%s" arg-name) str-value))))))
;; aggregate the list of argument lists in to a single list
(apply #'append)))
(cl-defmethod flex-compiler-config-help ((this cli-flex-compiler))
"Return the command line help from the Python program for THIS compiler."
(with-slots (config-file start-directory) this
(let ((default-directory start-directory)
(cmd (format "%s --help" config-file)))
(shell-command-to-string cmd))))
(cl-defmethod config-prop-entry-write-configuration ((this cli-flex-compiler)
&optional level header)
"Add the command line argument metadata and values to the output for THIS.
LEVEL is the indentation level.
HEADER is a string written to describe the property, otherise the description
is used."
(cl-call-next-method this level header)
(setq level (or level 0))
(with-slots (props) this
(let ((space (make-string (* 2 level) ? ))
(space2 (make-string (* 2 (1+ level)) ? )))
(insert (format "%sarguments:\n" space))
(->> (flex-compiler-cli-argument-plist this)
(-map (lambda (plist)
(apply
#'format "%s%s: \"%s\" (%s, %S)" space2
(-map (lambda (k)
(plist-get plist k))
'(:arg-name :str-value :value-type :arg-type)))))
(funcall (lambda (args)
(mapconcat #'identity args "\n")))
insert)
(newline)
(insert (format "%s[Usage]\n" space))
(->> (flex-compiler-config-help this)
(replace-regexp-in-string "[']" "’")
(replace-regexp-in-string "[\"]" "”")
insert)
(newline))))
(cl-defmethod flex-compiler-start-buffer ((this cli-flex-compiler)
start-type)
"Return a new buffer for THIS compiler with a processing compilation.
See the `single-buffer-flex-compiler' implementation of
`flex-compiler-start-buffer' for more information and START-TYPE."
(cl-case start-type
(compile
(with-slots (config-file start-directory action arguments) this
(let ((default-directory start-directory)
(buffer-name (flex-compiler-buffer-name this))
(cmd (concat
;; the python entry point script file name
config-file " "
;; action mnemonic
(symbol-name action) " "
;; any arguments separated with a space
(mapconcat #'identity arguments " ")))
buf)
(with-current-buffer
(setq buf
(compilation-start cmd nil (lambda (_) buffer-name))))
buf)))
(run (config-prop-entry-show-configuration this))))
;; register the compiler
(flex-compile-manager-register flex-compile-manage-inst
(cli-flex-compiler))
(provide 'flex-compile-cli)
;;; flex-compile-cli.el ends here