-
Notifications
You must be signed in to change notification settings - Fork 2
/
iron-main-utils.el
345 lines (269 loc) · 9.82 KB
/
iron-main-utils.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
;;; iron-main-utils --- A major mode to handle MVS or Z/OS JCL.
;;; -*- Mode: Emacs-Lisp; lexical-binding: t; -*-
;;; iron-main-utils.el
;;
;; See the file COPYING license and copyright information.
;;
;; Author: Marco Antoniotti <marcoxa [at] gmail.com>
;;
;; Created: December 17th, 2020.
;;
;; Version: 20230504.1
;;
;; Keywords: languages, operating systems.
;;; Commentary:
;;
;; Utilities in support of `iron-main-mode'.
;; A minor mode to edit files and interact with IBM MVS or z/OS.
;;
;; These are mostly data structures and functions to handle dataset
;; representations.
;;
;; To Do:
;;
;; JCL generation.
;;; Code:
;;;; IRON MAIN Mode utilities functions.
(require 'cl-lib)
(require 'url)
(require 'iron-main-vars)
;;;; Mainframe setup.
(defun iron-main-running-os (os-flavor)
"Check whether IRON MAIN is running OS-FLAVOR."
(string-prefix-p os-flavor iron-main-os-flavor))
(defun iron-main-running-machine (machine)
"Check whether IRON MAIN is running MACHINE."
(string-prefix-p machine iron-main-machine))
(defun iron-main-running-on (os-flavor machine)
"Check whether IRON MAIN is running OS-FLAVOR on MACHINE."
(and (string-prefix-p os-flavor iron-main-os-flavor)
(string-prefix-p machine iron-main-machine)))
;;;; Datasets
(cl-defstruct (iron-main-dataset-representation
(:constructor make-iron-main-ds-rep)
(:conc-name iron-main-ds-rep-))
"The IRON MAIN dataset representation."
(name "")
(recfm "FB" :type string) ; From Rob Prin's RPE.
(lrecl 80 :type integer) ; From Rob Prin's RPE.
(blksize 3120 :type integer) ; From Rob Prin's RPE.
(dsorg "PS" :type string)
(unit "SYSDA" :type string)
(vol "" :type string)
(space-unit "TRK" :type string)
(primary 1 :type integer)
(secondary 0 :type string)
(directory 0 :type string)
;; ..
)
(defun iron-main--split-pathname (pathname)
"Splits a pathname (Un*x or Windows) on the directory separators."
(if (stringp pathname)
(split-string pathname "[\\/]" t)
(error "PATHNAME %s is not a string" pathname)))
(cl-defun iron-main--join-by (sl &optional (sep "") enclose)
"Joins strings.
The function takes a list of objects (SL) and PRINCs them to a string
that is eventually returned. The separator SEP (a string) is used
between each element of the list SL.
ENCLOSE can be T, the keyword :LEFT, :RIGHT, or :BOTH. If T or :BOTH,
SEP is prepended and postpended (always by PRINC) to the result; if
:RIGHT only at the end, and if :LEFT only at the beginning.
Notes:
This function can be used to build strings that represent pathnames
starting from a list of strings.
"
(with-output-to-string
(when (and enclose (member enclose '(t :left :both)))
(princ sep))
(princ (first sl))
(dolist (s (rest sl))
(princ (format "%s%s" sep s)))
(when (and enclose (member enclose '(t :right :both)))
(princ sep))
))
(cl-defun iron-main--shorten-pathname (pathname-string
&optional (last-n 3))
(let* ((pname-strings (iron-main--split-pathname pathname-string))
(last-pname-strings (last pname-strings last-n))
(short-pname
(iron-main--join-by last-pname-strings "/" :left))
)
(if (> (length pname-strings) (length last-pname-strings))
(concatenate 'string "..." short-pname)
short-pname)))
(defun iron-main-ds-to-string (ds)
"Convert a `iron-main-dataset-representation' object DS to a string."
(format
"DSN=%s,RECFM=%s,LRECL=%s,BLKSIZE=%s,DSORG=%s,UNIT=%s,VOL=%s,SPACE=(%s,(%s,%s,%s))"
(iron-main-ds-rep-name ds)
(iron-main-ds-rep-recfm ds)
(iron-main-ds-rep-lrecl ds)
(iron-main-ds-rep-blksize ds)
(iron-main-ds-rep-dsorg ds)
(iron-main-ds-rep-unit ds)
(iron-main-ds-rep-vol ds)
(iron-main-ds-rep-space-unit ds)
(iron-main-ds-rep-primary ds)
(iron-main-ds-rep-secondary ds)
(iron-main-ds-rep-directory ds)
))
(defun iron-main-check-ds-rep (ds)
"Check consistency of dataset representation DS.
The function runs a number of checks ensuring that a dataset
representation is properly set up. At a minimum it checks the lenght
of the dsname and its breakdown in high, intermediate and low
qualifiers; it also checks that a sequential (PS) dataset
organization (DSORG) has 0 directory blocks (DIR) or a positive number
if DSORG denotes a partioned dataset (PDS).
Notes:
More tests will be added in the future."
(and (iron-main-check-dsname (iron-main-ds-rep-name ds))
(iron-main-check-dsorg-and-dir (iron-main-ds-rep-dsorg ds)
(iron-main-ds-rep-directory ds))
))
(defun iron-main-check-dsname (ds)
"Check the constraints of the name of DS (the 'DSNAME')."
;; (declare (type string ds))
(let ((dsname (cl-first (split-string ds "'" t " +")))
;; The above is a kludge to remove the quotes.
)
(when (> (length dsname) 44) ; Make this a constant.
(error "DSNAME '%s' is too long" dsname))
(let* ((quals-member (split-string dsname "[()]" nil " +")) ; Ok. Kludge.
(quals-string (cl-first quals-member))
(member-name (cl-second quals-member))
(quals (and quals-string
(split-string (cl-first quals-member) "\\." nil)))
)
(when (zerop (length quals-string))
(error "DSNAME is empty"))
(when (equal "." quals-string)
(error "DSNAME '%'s is invalid" quals-string))
(when (member "" quals)
;; Found a null string in quals; this means that we had two
;; dots `.' next to each other, which is an error.
(error "DSNAME '%s' cannot contain an empty qualifier"
dsname))
(when (cl-some (lambda (q) (> (length q) 8)) quals)
(error "DSNAME '%s' cannot contain a qualifier longer than 8 characters"
dsname))
(when (and member-name (zerop (length member-name)))
(error "DSNAME '%s' cannot have the member long 0 characters"
dsname))
(when (and member-name (> (length member-name) 8))
(error "DSNAME '%s' cannot have the member longer that 8 characters"
dsname))
;; All is fine.
t
)))
(defun iron-main-check-dsorg-and-dir (dsorg dir-blocks)
"Check compatibility of DSORG and DIR-BLOCKS."
(when (and (equal dsorg "PO") (zerop dir-blocks))
(error "DS REP cannot have \"PO\" DSORG and 0 directory blocks"))
;; More checks later.
t
)
;;;; Hercules interface.
;;;; This may end up in a different file.
(defun iron-main-hercules-set-dasd-dir (dasd-dir)
"Set the `iron-main-dasd-dir' to DASD-DIR."
(if (file-directory-p dasd-dir)
(setq-local iron-main-hercules-dasd-dir dasd-dir)
(error "Cannot set the DASD folder to %s; not a directory"
dasd-dir)))
;; (defun iron-main-hercules-call-utility (utility output &rest args)
;; "Call the Hercules utility program UTILITY with ARGS.
;; If OUTPUT is NIL the output of the utility is collected into a
;; string. If OUTPUT is the atom `list' then the output of the utility
;; is collected into a list of lines."
;; (let ((output-lines (apply 'process-lines utility args)))
;; (cl-case output
;; ((nil) (apply 'concat output-lines))
;; (list output-lines)
;; ;; ...
;; )))
(cl-defun iron-main-hercules-is-listening (&optional
(herc-host
iron-main-hercules-http-host)
(herc-port
iron-main-hercules-http-port))
"Check whether there is a HTTP-enable Hercules instance running.
The Hercules instance should be running on HERC-HOST listening on
HERC-PORT. HERC-HOST defaults to `iron-main-hercules-http-host',
while HERC-PORT defaults to `iron-main-hercules-http-port'.
The function returns a non NIL value if there is such an instance
running.
Notes:
The current value returned in the case a Hercules instance is
listening is a list with the network process as first element. The
`process-status' of this process will turn to 'closed'; therefore it
cannot be relied upon for anthing other than this check."
(let* ((herc-string-url
(concat "http://" herc-host ":" (format "%s" herc-port)))
(herc-url
(url-generic-parse-url herc-string-url)) ; Maybe useless.
(url-current-object herc-url) ; Being paranoid, given the
; stupidity of this variable.
)
(message "IMHE000I: trying to connect to %S" herc-string-url)
(condition-case e
(let ((c (make-network-process
:name (format "IMHEHTTP test %S:%S"
herc-host
herc-port)
:host (url-host herc-url)
:service (url-port herc-url)
:server nil
))
)
(when c
(prog1
(list c (process-status c))
(delete-process c))))
(file-error
(message "IMHE000W: failed with %S %S" (cl-first e) (cl-second e))
nil)
(error
(message "IMHE001W: %S %S" (cl-first e) (cl-second e))
nil)
)))
(cl-defun iron-main-hercules-cmd (cmd
&key
(host
iron-main-hercules-http-host)
(port
iron-main-hercules-http-port)
(timeout 15)
(check-listening nil)
)
"Issue a command CMD to a running Hercules instance.
The command is issued to the HTTP-enabled Hercules instance running on
HOST and listening on PORT. If the the Hercules instance responds,
then the buffer containing the response is returned, NIL otherwise.
If CHECK-LISTENING is non null, then the
test `iron-main-hercules-is-listening' is run; if its result is NIL
then the command is not issued.
The command is issued synchronously with a TIMEOUT (cfr.,
`url-retrieve-synchronously')."
(when (and check-listening
(not (iron-main-hercules-is-listening host port)))
(message "IMHE002W: command %S not issued." cmd)
(cl-return-from iron-main-hercules-cmd nil))
;; The following CGI url was desumed from Hercules HTTP
;; implementation.
(let* ((cmd-urlified
(replace-regexp-in-string " " "%20" cmd))
(cmd-url
(format "http://%s:%s/cgi-bin/tasks/cmd?cmd=%s"
host
port
cmd-urlified))
)
(ignore-errors
(url-retrieve-synchronously cmd-url nil nil timeout)))
)
;;;; Epilogue
;;;; ========
(provide 'iron-main-utils)
;;; iron-main-utils.el ends here