-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathconfig-manage-base.el
730 lines (647 loc) · 25.5 KB
/
config-manage-base.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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
;;; config-manage-base.el --- Configuration management base classes -*- lexical-binding: t; -*-
;; Copyright (C) 2017 - 2021 Paul Landes
;; Author: Paul Landes
;; Maintainer: Paul Landes
;; Keywords: internal maint
;; URL: https://github.com/plandes/buffer-manage
;; 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:
;; This library contains the base classes for the configuration management
;; system.
;;; Code:
(require 'cl-lib)
(require 'seq)
(require 'time-stamp)
(require 'dash)
(require 'eieio)
(require 'eieio-base)
(require 'choice-program)
(require 'config-manage-declare)
(config-manage-declare-variables config-manager-instance)
(defvar config-entry-status)
(defclass config-persistent (eieio-named)
((pslots :initarg :pslots
:initform nil
:type list))
:method-invocation-order :c3
:documentation "\
Super class for objects that want to persist to the file system.
This class is necessary since EIEIO list types can't unpersist as they produce
this error:
eieio-persistent-validate/fix-slot-value: In save file, list of object
constructors found, but no :type specified for slot displays of type nil")
(cl-defmethod initialize-instance ((this config-persistent) &optional slots)
"Initialize THIS instance using SLOTS as initial values."
(setq slots (plist-put slots :pslots
(append (plist-get slots :pslots)
'(object-name))))
(cl-call-next-method this slots))
(cl-defmethod config-persistent--unimplemented ((this config-persistent) method)
"Signal error CONFIG-MANAGE-UN-IMPLEMENTED for EIEIO METHOD.
THIS is the class instance."
(with-temp-buffer
(set-buffer (get-buffer-create "*config-manage-backtrace*"))
(erase-buffer)
(let ((standard-output (current-buffer)))
(backtrace)))
(signal 'config-manage-un-implemented
(list method (with-temp-buffer
(cl-print-object this (current-buffer))))))
(cl-defmethod config-persistent-destruct ((this config-persistent))
"Deallocate any resources of THIS when the instance falls out of use.
The EIEIO `destructor' is deprecated starting in 26. However is still used and
needed in this framework."
(ignore this))
(cl-defmethod config-persistent-persist-value ((this config-persistent) val)
"Generate a persistent state of VAL using slot data in THIS instance."
(ignore this)
(or (and (consp val)
(let ((fval (car val)))
(and fval
(eieio-object-p fval)
(object-of-class-p fval 'config-persistent)
(-map (lambda (val)
(config-persistent-persist val))
val))))
val))
(cl-defmethod config-persistent-persist-slots ((this config-persistent))
"Generate a persistent state the slots of THIS instance."
(with-slots (pslots) this
(->> pslots
(-map (lambda (slot)
(let ((val (->> (slot-value this slot)
(config-persistent-persist-value this))))
(cons slot val)))))))
(cl-defmethod config-persistent-persist ((this config-persistent))
"Generate a persistent state of THIS instance."
(append `((class . ,(eieio-object-class this))
(slots . ,(config-persistent-persist-slots this)))
(condition-case nil
(cl-call-next-method this)
(cl-no-next-method))))
(cl-defmethod config-persistent-unpersist-value ((this config-persistent) val)
"Unpersist VAL using THIS persistent.
This is done by determining its type and then recursively applying to create
unerpsist \(optionally) children classes and slots."
(ignore this)
(or (and (consp val)
(let ((fval (car val)))
(and (consp fval)
(consp (car fval))
(eq 'class (caar fval))
(-map (lambda (val)
(config-persistent-unpersist val))
val))))
val))
(cl-defmethod config-persistent-unpersist ((this config-persistent) vals)
"Set the slot data of THIS instance with data given in VALS as `pslots'."
(with-slots (pslots) this
(->> pslots
(-map (lambda (slot)
(let ((val (->> (cdr (assq slot vals))
(config-persistent-unpersist-value this))))
(setf (slot-value this slot) val)))))))
(cl-defmethod config-persistent-unpersist ((vals list) &optional obj)
"Restore the objects state from VALS.
VALS has has a symbols `class' and `slots'.
OBJ is recursively invoked with this method."
(let* ((class (cdr (assq 'class vals)))
(slots (cdr (assq 'slots vals)))
(obj (if obj
(if (not (eq (eieio-object-class obj) class))
(error "Class mismatch during unpersist: %S != %S"
(eieio-object-class obj) class)
obj)
(make-instance class))))
(config-persistent-unpersist obj slots)
obj))
(cl-defmethod config-persistent-reset ((this config-persistent))
"Reset all persistable slots in THIS to initial state.
This implementation sets all slots to nil."
(with-slots (pslots) this
(dolist (slotsym pslots)
(setf (slot-value this slotsym) nil))))
(cl-defmethod eieio-object-name-string ((this config-persistent))
"Return a string as a representation of the in memory instance of THIS."
(ignore this)
(with-slots (pslots) this
(->> pslots
(cl-remove 'object-name)
(-map #'(lambda (slot)
(let ((val (condition-case nil
(slot-value this slot)
(error
(message "Invalid slot: %S.%s"
(eieio-object-class this) slot)
nil))))
(->> (cond ((stringp val) val)
(t (prin1-to-string val)))
(format "%S=%s" slot)))))
(funcall #'(lambda (slots)
(concat (format "\"%s\" " (slot-value this 'object-name))
(let ((slot-str (mapconcat #'identity slots " ")))
(if (> (length slot-str) 70)
(format "|slots|=%d" (length slot-str))
(concat "slots=[" slot-str "]")))))))))
(defclass config-persistable (config-persistent)
((file :initarg :file
:initform nil
:type (or null string)
:documentation "The file to persist the state of the object."))
:method-invocation-order :c3
:documentation "Subclasses that can persist to a file.
Note this class was written before `eieio-persistent', which might be a better
class to extend over this class.")
(cl-defmethod config-persistable-save ((this config-persistable))
"Persist manager and compiler configuration using THIS instant."
(with-slots (file) this
(when file
(let ((save-class-name (->> this eieio-object-class eieio-class-name))
(state (config-persistent-persist this)))
(with-temp-buffer
(insert (format "\
;; -*- emacs-lisp -*- <%s %s>
;; Object: %s. Don't change this file.\n"
(time-stamp-string "%02y/%02m/%02d %02H:%02M:%02S")
file save-class-name))
(insert (with-output-to-string
(pp state)))
(write-region (point-min) (point-max) file))
(message "Wrote %s" file)))))
(cl-defmethod config-persistable-load ((this config-persistable))
"Restore the state of THIS instance persistable object."
(with-slots (file) this
(let ((file (expand-file-name file)))
(if (file-exists-p file)
(with-temp-buffer
(insert-file-contents file)
(let ((config (read (buffer-string))))
(config-persistent-unpersist config this)))))))
;;; config objects
(defclass config-entry (config-persistent)
((description :initarg :description
:initform "<none>"
:reader config-entry-description
:type string
:documentation "\
The description of this entry, used in `config-manager-list-entries-buffer'.")
(manager :initarg :manager
:initform nil
:protection :protected))
:abstract true
:method-invocation-order :c3
:documentation "Abstract class for all configurable entries.")
(cl-defmethod config-entry-name ((this config-entry))
"Return the entry name of THIS instance."
(slot-value this 'object-name))
(cl-defmethod config-entry-set-name ((this config-entry) name)
"Set the name of the entry to NAME.
NAME's is stripped of properties since it might be fontified when
generated the buffer in `config-manage-mode'.
THIS is the class instance."
(let ((name (substring-no-properties name)))
(setf (slot-value this 'object-name) name)))
(cl-defmethod config-entry-save ((this config-entry))
"Save the current entry configuration.
THIS is the class instance."
(ignore this))
(cl-defmethod config-entry-restore ((this config-entry))
"Restore the current entry configuration.
THIS is the class instance."
(ignore this))
(cl-defmethod config-persistent-doc ((this config-entry) level)
"Write compiler documentation to the current buffer.
LEVEL is the recursion level, which is used for formatting.
THIS is the class instance."
(with-slots (description) this
(let ((doc (-> (eieio-object-class this)
cl--find-class
cl--class-docstring)))
(setq doc
(if (not doc)
""
(setq doc
(with-temp-buffer
(insert doc)
(goto-char (point-min))
(while (search-forward-regexp "`\\(.+?\\)'" nil t)
(replace-match "`\\1`"))
(buffer-string)))))
(if (> level 1)
(insert "\n\n"))
(if (> level 0)
(insert (format "%s " (make-string level ?#))))
(insert (format "%s\n\n%s\n" description doc)))))
;; config manager
(defconst config-manage-base-list-col-space 4
"Space between columns.")
(defconst config-manage-base-status-defs
'((alive . " ")
(to-delete . "D")
(to-show . "S"))
"Enumeration of status for buffers in `config-manager-list-entries'.")
(defun config-manage-base-insert-at-position (seq elt pos)
"Return SEQ with ELT inserted at position POS."
(append (cl-subseq seq 0 pos)
(list elt)
(cl-subseq seq pos)))
(defclass config-manager (config-persistent)
((cycle-method :initarg :cycle-method
:initform last-visit
:reader config-manager-cycle-method
:writer config-manager-set-cycle-method
:type symbol
:custom (choice :tag "Cycle Method"
(const :tag "Last Visited" last-visit)
(const :tag "Next In Line" next))
:documentation "\
How entries are cycled by default when invoking `config-manager-activate'.
This parameter is used as the default for `criteria' \(see
`config-manager-activate'), which is `cycle'.")
(entries :initarg :entries
:initform nil ; initialize with 0 entries
:type (or null list)
:protection :private
:documentation
"Contains the data structure for the buffer entries.")
(list-header-fields :initarg :list-header-fields
:initform '("C" "Name" "Description")
:type list
:documentation "\
List of fields used in output of `buffer-list'.")
;; careful: this slot keeps stale entries after they've been removed/killed
(last-switched-to :initform nil
:protection :private
:documentation "\
Keeps track of the last entry for last-visit cycle method."))
:method-invocation-order :c3
:documentation "Manages configurations.")
(cl-defmethod initialize-instance ((this config-manager) &optional slots)
"Initialize THIS instance using SLOTS as initial values."
(setq slots (plist-put slots :pslots
(append (plist-get slots :pslots)
'(object-name entries))))
(cl-call-next-method this slots))
(cl-defmethod config-manager-name ((this config-manager))
"Return the name of the configuration manager.
THIS is the instance."
(slot-value this 'object-name))
(cl-defmethod config-manager-new-entry ((this config-manager) &optional slots)
"Create a new nascent entry object.
SLOTS are passed as a property list on instantiating the object.
THIS is the instance."
(ignore slots)
(error "No implementation of `config-manager-new-entry' for class `%S'"
(eieio-object-class this)))
(cl-defmethod config-manager-entry-default-name ((this config-manager))
"Return a name for a new entry created with `config-manager-new-entry'.
THIS is the instance."
(error "No implementation of `config-manager-entry-default-name' for class `%S'"
(eieio-object-class this)))
(cl-defmethod config-manager--update-entries ((this config-manager) entries)
"Observer pattern to for observers to react to entries modifications.
ENTRIES, the entries to update.
THIS is the instance."
(ignore this entries))
(cl-defmethod config-manager-cycle-entries ((this config-manager) entry
&optional mode)
"Rearrange the entry order to place ENTRY in place after cycling.
THIS is the instance."
(ignore mode)
(with-slots (entries cycle-method) this
(let ((first-entry (car entries)))
(setq entries (cons entry (remove entry entries)))
(if (and (eq 'next cycle-method)
(> (length entries) 1)
(not (eq first-entry entry)))
(setq entries (append (remove first-entry entries)
(if first-entry (list first-entry)))))
(config-manager--update-entries this entries)
entries)))
(cl-defmethod config-manager-entry-exists-p ((this config-manager) entry)
"If ENTRY is an instance of a class or subclass of `config-entry' return it.
THIS is the instance."
(with-slots (entries) this
(and (eieio-object-p entry)
(object-of-class-p entry 'config-entry)
(member entry entries)
entry)))
(cl-defmethod config-manager-entry ((this config-manager)
criteria &optional assertp)
"Return an entry based on CRITERIA from THIS manager.
CRITERIA is:
a string: the entry name to switch to the entry with that name
an integer: get it by index
a symbol: if `first', the highest priority entry is selected,
if `last' the last most priority entry is selected,
if `next' the next entry in the list or start back with the first
if `cycle' the next (after the current) most desirable
entry is selected based on the value of slot `cycle-method'"
(let* ((entries (config-manager--entries this))
(len (length entries))
entry)
(setq entry
(cond ((stringp criteria)
(cl-block entry-lookup
(dolist (entry (config-manager--entries this))
(if (equal criteria (config-entry-name entry))
(cl-return-from entry-lookup entry)))))
((config-manager-entry-exists-p this criteria))
((= len 0) nil)
((= len 1) (car entries))
((eq criteria 'first) (car entries))
((eq criteria 'last) (last entries))
;; TODO
((eq criteria 'cycle) (config-manager-entry-cycle this))
(t (error "Illegal argument for criteria: %S"
criteria))))
(if (and assertp (null entry))
(error "No entry exists that satisfies criteria `%S'" criteria))
entry))
(cl-defmethod config-manager--entries ((this config-manager) &optional
include-fn exclude-fn sort-form)
"Return entries that match INCLUDE-FN and don't match EXCLUDE-FN.
Entries returned are only entries contained in this instance of the
`config-manager'.
Both INCLUDE-FN and EXCLUDE-FN take an instance of `config-entry' as a
singleton parameter.
Sorting on the returned entries are done when SORT-FORM is non-nil. Any
sorting is only done on the returned set of entries and doesn't change any
of the object's internal state. Sorting is done based on SORT-FORM's value:
- symbol 'lexical: sort lexically based on the config entry's name
- function: sort using SORT-FORM as a predicate \(see `sort').
THIS is the instance."
(with-slots (entries) this
(setq include-fn (or include-fn (lambda (_) t))
exclude-fn (or exclude-fn (lambda (_) nil)))
(let ((entries
(remove nil (mapcar (lambda (entry)
(if (and (funcall include-fn entry)
(not (funcall exclude-fn entry)))
entry))
entries))))
(when sort-form
(let ((lexical-fn
#'(lambda (a b)
(string< (config-entry-name a) (config-entry-name b)))))
(let ((sort-fn (cond ((eq sort-form 'lexical) lexical-fn)
((functionp sort-form) sort-form)
(t (error "Illegal sort form: %S" sort-form)))))
(setq entries (sort entries sort-fn)))))
entries)))
(cl-defmethod config-manager-current-instance ((this config-manager)
&optional assertp)
"The current entry we're in/at, if there is one.
ASSERTP, if non-nil, raise an error if there is no current entry.
THIS is the instance."
(with-slots (last-switched-to) this
(or last-switched-to
(cl-first (config-manager--entries this))
(and assertp
(error "No current entry")))))
(cl-defmethod config-manager-entry-cycle ((this config-manager))
"Return the next `cycled' entry based on slot `cycle-method'.
The default uses:
last-visit: go to the last visited entry
next: go to the next highest priority entry
THIS is the instance."
(with-slots (last-switched-to cycle-method) this
(let ((entries (config-manager--entries this))
;; the current entry we're in (if there is one)
(cur-entry (config-manager-current-instance this))
(method cycle-method))
(if (not (member method (config-manager-cycle-methods this)))
(error "Invalid cycle method: %S" method))
(or
;; only can switch to one if there is one
(if (= (length entries) 1) (car entries))
;; try to use the last entry that was switched into if we can
(if (and last-switched-to
;; don't pick the one we are in or we won't switch at all
(not cur-entry))
last-switched-to)
(or (cl-case method
(last-visit (cl-second entries))
(next (cl-second entries))
(otherwise (error "Unimplemented (but value) cycle method: %S"
method)))
(car entries))))))
(defun config-manage-base-iterate-name (name names)
"Create a unique NAME from existing NAMES by iterating FORM<N> integer.
where N is an integer.
This is the typical unique name (buffers, files etc) creation."
(let* ((regex "\\(?:<\\([0-9]+\\)>\\)?$")
(idxs (->> names
(-map (lambda (elt)
(if (string-match (concat "^" name regex) elt)
(let ((expr (match-string 1 elt)))
(or (and expr (read expr)) 1)))))
(-filter #'identity)
(cons 0)))
(to-remove (->> (apply #'max idxs)
1+
(number-sequence 1)))
(idx (car (seq-difference to-remove idxs))))
(if (= 1 idx)
name
(concat name "<" (-> idx prin1-to-string) ">"))))
(cl-defmethod config-manage-base-add-entry ((this config-manager) &optional slots)
"Add and optionally create first a new entry in THIS manager.
SLOTS, used to create the new entry that is added."
(let* ((entry (config-manager-new-entry this slots))
(name (or (config-entry-name entry)
(config-manager-entry-default-name this))))
(with-slots (entries entry-index) this
(->> entries
(-map (lambda (elt)
(config-entry-name elt)))
(config-manager-iterate-name name)
(config-entry-set-name entry))
(config-manager-cycle-entries this entry 'after)
entry)))
(cl-defmethod config-manager-insert-entry ((this config-manager) &optional _)
"Insert a new entry for THIS."
(config-manager-add-entry this))
(cl-defmethod config-manager-set-name ((this config-manager) &optional new-name)
"Set the name of THIS `config-manager' to NEW-NAME."
(with-slots (name) this
(let ((new-name (or new-name (config-manager-entry-default-name this))))
(setq name new-name))))
(cl-defmethod config-manager-entry-restore ((this config-manager)
&optional entry)
"Restore THIS `config-manager' and contained `config-entry' instances.
ENTRY the entry to restore, or if nil, get the first entry available."
(let ((entry (or entry (config-manager-entry this 0))))
(config-entry-restore entry)))
(cl-defmethod config-manager-remove-entry ((this config-manager) entry)
"Remove/kill ENTRY from THIS manager."
(with-slots (entries) this
(when (memq entry entries)
(setq entries (remove entry entries))
(config-manager--update-entries this entries)
(config-persistent-destruct entry)
entry)))
(cl-defmethod config-manager-activate ((this config-manager) criteria)
"Switch to a config entry in THIS manager.
If the config CRITERIA is the name of the config to switch to, go to that
config, otherwise, create a new one with that name and switch to it.
Returns the config entry we switched to based on CRITERIA \(see
`config-manager-entry')."
(let ((entry (or (config-manager-entry this criteria)
(apply #'config-manager-new-entry
this (and (stringp criteria)
`(:object-name ,criteria))))))
(config-entry-restore entry)
(config-manager-cycle-entries this entry)
(config-manage-refresh-windows)
entry))
(cl-defmethod config-manager-list-clear ((this config-manager))
"Clear all entries of THIS manager."
(with-slots (entries) this
(setq entries nil)
(config-manager--update-entries this entries)))
(cl-defmethod config-manager-cycle-methods ((this config-manager))
"All valid cycle methods (see `config-manager-entry-cycle').
THIS is the instance."
(ignore this)
'(last-visit next))
(cl-defmethod config-manager-toggle-cycle-method ((this config-manager))
"Rotate through the cycle methods for THIS configuration manager."
(let* ((methods (config-manager-cycle-methods this))
(method (config-manager-cycle-method this)))
(setq method (or (cadr (member method methods)) (car methods)))
(config-manager-set-cycle-method this method)
method))
(cl-defmethod config-manager-list-entries ((this config-manager))
"Return a multi-listing of the entries contained in this manager.
THIS is the instance."
(cl-flet* ((get-max
(getter-fn)
(let ((entries (config-manager--entries this)))
(when entries
(apply #'max
(mapcar (lambda (entry)
(length (funcall getter-fn entry)))
entries)))))
(get-desc
(entry col-space name-len)
(let* ((name (config-entry-description entry))
(len (length name))
(width 79)
(max-len (- (- width col-space) name-len 0)))
(if (> len max-len)
(concat (substring name 0 (- max-len 3)) "...")
name))))
(let* ((entries (config-manager--entries this))
(entry-status (if (boundp 'config-entry-status)
config-entry-status
(make-hash-table :test 'equal)))
(name-len (get-max #'config-entry-name))
(col-space config-manage-base-list-col-space)
(name-len (or name-len col-space))
(headers (slot-value this 'list-header-fields))
format-meta)
(dolist (entry entries)
(let ((name (config-entry-name entry)))
(unless (gethash name entry-status)
(puthash name 'alive entry-status))))
(setq format-meta (format "%%-%ds %%-%ds%%s"
col-space (+ col-space name-len)))
(insert (apply 'format format-meta headers)
"\n"
(apply 'format format-meta
(mapcar #'(lambda (arg)
(make-string (length arg) ?-))
headers))
"\n")
(cl-do ((lst entries (setq lst (cdr lst)))
entry)
((null lst))
(setq entry (car lst))
(let ((name (config-entry-name entry))
(status (cdr (assq (gethash (config-entry-name entry)
entry-status)
config-manage-base-status-defs))))
(put-text-property 0 (length name) 'mouse-face 'highlight name)
(insert (apply #'format format-meta
(append (list status name
(get-desc entry col-space name-len))))))
(if (cdr lst) (insert "\n")))
(set (make-local-variable 'config-entry-status) entry-status))))
(cl-defmethod config-manager-read-new-name ((this config-manager)
&optional prompt auto-generate-p)
"Read an entry name from user input for THIS configuration manager.
PROMPT is used for the user input.
AUTO-GENERATE-P, if non-nil, generate a default name rather than reading it
from the user"
(let ((def (config-manager-entry-default-name this))
name)
(if auto-generate-p
def
(setq prompt (or prompt (capitalize (config-manager-name this))))
(setq prompt (choice-program-complete-default-prompt prompt def))
(setq name (read-string prompt nil nil def))
(if (= 0 (length name)) (setq name nil))
name)))
(cl-defmethod config-persistent-doc ((this config-manager) &optional level)
"Create markdown documentation on THIS manager and its entries.
LEVEL is the depth of recursion, which effects the indention.
The buffer is set to `markdown-mode' if library is available."
(setq level (or level 2))
(let* ((name (capitalize (config-manager-name this)))
(buf (->> (format "*%s Documentation*" name)
get-buffer-create))
(doc (-> (eieio-object-class this)
cl--find-class
cl--class-docstring)))
(with-current-buffer buf
(read-only-mode 0)
(erase-buffer)
(insert (format "%s %s\n\n%s\n" (make-string level ?#) name doc))
(goto-char (point-min))
(while (search-forward-regexp "`\\([a-z0-9-]+?\\)'" nil t)
(replace-match "`\\1`"))
(goto-char (point-max))
(dolist (entry (config-manager--entries this nil nil 'lexical))
(config-persistent-doc entry (1+ level)))
(goto-char (point-min))
(and (fboundp 'markdown-mode) (markdown-mode))
(read-only-mode 1))
(display-buffer buf)
buf))
;;; mode
(defun config-manage-base-mode-refresh ()
"Refresh the entry listing buffer."
(interactive)
(config-manage-mode-assert)
(let ((line (count-lines (point-min) (point))))
(setq buffer-read-only nil)
(erase-buffer)
(config-manager-list-entries config-manager-instance)
(setq buffer-read-only t)
(goto-char (point-min))
(forward-line line)))
(defun config-manage-base-refresh-windows ()
"Refresh config entries list buffer."
(->> (window-list)
(-map (lambda (win)
(let ((buf (window-buffer win)))
(with-current-buffer buf
(if (config-manage-mode-assert t)
(config-manage-mode-refresh))))))))
(provide 'config-manage-base)
;;; config-manage-base.el ends here