-
Notifications
You must be signed in to change notification settings - Fork 9
/
taxy.info
1536 lines (1234 loc) · 49.9 KB
/
taxy.info
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
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
This is README.info, produced by makeinfo version 6.7 from README.texi.
INFO-DIR-SECTION Emacs
START-INFO-DIR-ENTRY
* Taxy: (taxy). Programmable taxonomical grouping for arbitrary objects.
END-INFO-DIR-ENTRY
File: README.info, Node: Top, Next: Examples, Up: (dir)
taxy.el
*******
https://elpa.gnu.org/packages/taxy.svg
(https://elpa.gnu.org/packages/taxy.html)
_Now, where did I put that..._
This library provides a programmable way to classify arbitrary
objects into a hierarchical taxonomy. (That’s a lot of fancy words to
say that this lets you automatically put things in nested groups.)
Helpful features include:
Dynamic taxonomies
Objects may be classified into hierarchies automatically defined at
runtime based on their attributes.
Reusable taxonomies
Taxonomy definitions may be stored in variables and reused in other
taxonomies’ descendant groups.
Classification domain-specific language
Easily define a custom DSL used to classify items dynamically
(which can be extended by users).
Flexible table view
Based on ‘magit-section’, with easily defined columns (also
extendable by users).
* Menu:
* Examples::
* Installation::
* Usage::
* Changelog::
* Development::
* Credits::
* License::
— The Detailed Node Listing —
Examples
* Numbery (starting basically)::
* Lettery (filling incrementally)::
* Sporty (understanding completely)::
* Applications::
Usage
* Reusable taxys::
* Threading macros::
* Modifying filled taxys::
* Dynamic taxys::
* Magit section::
* Reference::
Dynamic taxys
* Multi-level dynamic taxys::
* "Chains" of independent, multi-level dynamic taxys: "Chains" of independent multi-level dynamic taxys.
* Defining a classification domain-specific language::
Reference
* Functions::
* Macros::
Changelog
* 0.10.2: 0102.
* 0.10.1: 0101.
* 0.10: 010.
* 0.9: 09.
* 0.8: 08.
* 0.7: 07.
* 0.6: 06.
* 0.5: 05.
* 0.4: 04.
* 0.3: 03.
* 0.2: 02.
* 0.1: 01.
0.9
* Changes::
0.8
* Additions::
* Fixes::
0.7
* Additions: Additions (1).
0.6
* Additions: Additions (2).
0.5
* Additions: Additions (3).
* Fixes: Fixes (1).
0.3
* Changes: Changes (1).
* Fixes: Fixes (2).
0.2
* Changes: Changes (2).
* Additions: Additions (4).
* Fixes: Fixes (3).
Development
* Copyright assignment::
File: README.info, Node: Examples, Next: Installation, Prev: Top, Up: Top
1 Examples
**********
• • • •
May these examples help you classify your understanding.
* Menu:
* Numbery (starting basically)::
* Lettery (filling incrementally)::
* Sporty (understanding completely)::
* Applications::
File: README.info, Node: Numbery (starting basically), Next: Lettery (filling incrementally), Up: Examples
1.1 Numbery (starting basically)
================================
Let’s imagine a silly taxonomy of numbers below 100:
("Numbery" "A silly taxonomy of numbers."
(("< 10" "Numbers below 10"
;; These numbers are leftovers from the sub-taxys below.
(0 2 4 6 8)
;; These sub-taxys further classify the numbers below 10 into odd
;; and even. The odd taxy "consumes" numbers, while the even one
;; doesn't, leaving them to reappear in the parent taxy's items.
(("Odd" "(consuming)"
(1 3 5 7 9))
("Even" "(non-consuming)"
(0 2 4 6 8))))
(">= 10" "Numbers above 9"
;; Like in the "< 10" taxy, these numbers are leftovers from this
;; taxy's sub-taxys, three of which are non-consuming.
(10 11 13 14 17 19 22 23 25 26 29 31 34 35 37 38 41 43 46 47 49 50 53 55 58
59 61 62 65 67 70 71 73 74 77 79 82 83 85 86 89 91 94 95 97 98)
(("Divisible by 3" "(non-consuming)"
(12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72 75 78 81 84
87 90 93 96 99))
("Divisible by 4" "(non-consuming)"
(12 16 20 24 28 32 36 40 44 48 52 56 60 64 68 72 76 80 84 88 92 96))
("Divisible by 3 or 4" "(consuming)"
;; This taxy consumes numbers it takes in, but since these
;; numbers have already been taken in (without being consumed) by
;; the previous two sibling taxys, they may also appear in them.
(12 15 16 18 20 21 24 27 28 30 32 33 36 39 40 42 44 45 48 51 52 54 56 57 60
63 64 66 68 69 72 75 76 78 80 81 84 87 88 90 92 93 96 99))
("Divisible by 5" "(non-consuming)"
(10 25 35 50 55 65 70 85 95))))))
You might think about how to produce that by writing some imperative
code, but ‘taxy’ allows you to do so in a more declarative and
functional manner:
(require 'taxy)
(defvar numbery
(make-taxy
:name "Numbery"
:description "A silly taxonomy of numbers."
:taxys (list (make-taxy
:name "< 10"
:description "Numbers below 10 (consuming)"
:predicate (lambda (n) (< n 10))
:taxys (list
;; These sub-taxys further classify the numbers below 10 into odd
;; and even. The odd taxy "consumes" numbers, while the even one
;; doesn't, leaving them to reappear in the parent taxy's items.
(make-taxy :name "Odd"
:description "(consuming)"
:predicate #'oddp)
(make-taxy :name "Even"
:description "(non-consuming)"
:predicate #'evenp
:then #'identity)))
(make-taxy
:name ">= 10"
:description "Numbers above 9 (consuming)"
:predicate (lambda (n) (>= n 10))
:taxys (list
;; Like in the "< 10" taxy, these sub-taxys further classify
;; the numbers, but only one of them consumes numbers it
;; takes in, leaving the rest to reappear in the parent taxy.
(make-taxy :name "Divisible by 3"
:description "(non-consuming)"
:predicate (lambda (n) (zerop (mod n 3)))
:then #'identity)
(make-taxy :name "Divisible by 4"
:description "(non-consuming)"
:predicate (lambda (n) (zerop (mod n 4)))
:then #'identity)
(make-taxy :name "Divisible by 3 or 4"
:description "(consuming)"
;; Since this taxy's `:then' function is unset,
;; it defaults to `ignore', which causes it to
;; consume numbers it takes in. Since these
;; numbers have already been taken in (without
;; being consumed) by the previous two sibling
;; taxys, they also appear in them.
:predicate (lambda (n) (or (zerop (mod n 3))
(zerop (mod n 4)))))
(make-taxy :name "Divisible by 5"
:description "(non-consuming)"
:predicate (lambda (n) (zerop (mod n 5)))
:then #'identity))))))
(let ((numbers (cl-loop for i below 100 collect i))
;; Since `numbery' is stored in a variable, we use an emptied
;; copy of it to avoid mutating the original taxy.
(taxy (taxy-emptied numbery)))
(taxy-plain (taxy-fill (reverse numbers) taxy)))
The ‘taxy-fill’ function applies the numbers in a "cascade" down the
hierarchy of "taxys", and the ‘taxy-plain’ function returns a meaningful
subset of the taxys’ slots, suitable for display.
File: README.info, Node: Lettery (filling incrementally), Next: Sporty (understanding completely), Prev: Numbery (starting basically), Up: Examples
1.2 Lettery (filling incrementally)
===================================
You can also add more items after the hierarchy has been filled. In
this example we’ll make a comprehensive taxonomy of letters. The first
sub-taxy collects vowels, and the second, by leaving its predicate at
the default value, ‘identity’, collects all letters not collected by the
first taxy, i.e. non-vowels.
(defvar lettery
(make-taxy
:name "Lettery"
:description "A comprehensive taxonomy of letters."
:taxys (list (make-taxy
:name "Vowels"
:description "You know what those are."
:predicate (lambda (l)
(member-ignore-case l '("a" "e" "i" "o" "u"))))
(make-taxy
:name "Consonants"
:description "Well, if they aren't vowels..."))))
(taxy-plain
(taxy-fill (reverse
(cl-loop for l from ?a to ?n
collect (upcase (char-to-string l))))
lettery))
That produces:
("Lettery" "A comprehensive taxonomy of letters."
(("Vowels" "You know what those are."
("A" "E" "I"))
("Consonants" "Well, if they aren't vowels..."
("B" "C" "D" "F" "G" "H" "J" "K" "L" "M" "N"))))
Oops, we forgot the letters after N! Let’s add them, too:
(taxy-plain
(taxy-fill (reverse
(cl-loop for l from ?n to ?z
collect (upcase (char-to-string l))))
lettery))
Which gives us:
("Lettery" "A comprehensive taxonomy of letters."
(("Vowels" "You know what those are."
("O" "U" "A" "E" "I"))
("Consonants" "Well, if they aren't vowels..."
("N" "P" "Q" "R" "S" "T" "V" "W" "X" "Y" "Z" "B" "C" "D" "F" "G" "H" "J" "K" "L" "M" "N"))))
Oh, they’re out of order, now. That won’t do. Let’s fix that:
(taxy-plain
(taxy-sort #'string< #'identity lettery))
That’s better:
("Lettery" "A comprehensive taxonomy of letters."
(("Vowels" "You know what those are."
("A" "E" "I" "O" "U"))
("Consonants" "Well, if they aren't vowels..."
("B" "C" "D" "F" "G" "H" "J" "K" "L" "M" "N" "N" "P" "Q" "R" "S" "T" "V" "W" "X" "Y" "Z"))))
File: README.info, Node: Sporty (understanding completely), Next: Applications, Prev: Lettery (filling incrementally), Up: Examples
1.3 Sporty (understanding completely)
=====================================
Let’s try to understand a few things about sports. First we’ll define a
struct to make them easier to grasp:
(cl-defstruct sport
name uses venue fun)
Now we’ll make a list of sports:
(defvar sports
(list (make-sport :name "Baseball"
:uses '(bat ball glove)
:venue 'outdoor
:fun t)
(make-sport :name "Football"
:uses '(ball)
:venue 'outdoor
:fun t)
(make-sport :name "Basketball"
:uses '(ball hoop)
:venue 'indoor
:fun t)
(make-sport :name "Tennis"
:uses '(ball racket)
:venue 'outdoor
:fun t)
(make-sport :name "Racquetball"
:uses '(ball racket)
:venue 'indoor
:fun t)
(make-sport :name "Handball"
:uses '(ball glove)
:venue 'indoor
:fun t)
(make-sport :name "Soccer"
:uses '(ball)
:venue 'outdoor
:fun nil)
(make-sport :name "Disc golf"
:uses '(disc basket)
:venue 'outdoor
:fun t)
(make-sport :name "Ultimate"
:uses '(disc)
:venue 'outdoor
:fun t)
(make-sport :name "Volleyball"
:uses '(ball)
:venue 'indoor
:fun t)))
And finally we’ll define a taxy to organize them. In this, we use a
helper macro to make the ‘member’ function easier to use in the list of
key functions:
(defvar sporty
(cl-macrolet ((in (needle haystack)
`(lambda (item)
(when (member ,needle (funcall ,haystack item))
,needle))))
(make-taxy
:name "Sporty"
:take (lambda (item taxy)
(taxy-take-keyed
(list #'sport-venue
(in 'ball 'sport-uses)
(in 'disc 'sport-uses)
(in 'glove 'sport-uses)
(in 'racket 'sport-uses))
item taxy
;; We set the `:then' function of the taxys
;; created by `taxy-take-keyed' to `identity'
;; so they will not consume their items.
:then #'identity)))))
Now let’s fill the taxy with the sports and format it:
(thread-last sporty
taxy-emptied
(taxy-fill sports)
(taxy-mapcar #'sport-name)
taxy-plain)
((("Sporty"
((indoor
((ball
("Volleyball" "Basketball")
((glove
("Handball"))
(racket
("Racquetball"))))))
(outdoor
((disc
("Ultimate" "Disc golf"))
(ball
("Soccer" "Football")
((racket
("Tennis"))
(glove
("Baseball"))))))))))
That’s pretty sporty. But classifying them by venue first makes the
racket and glove sports not be listed together. Let’s swap the key
functions around so the venue is classified at the deepest level of the
hierarchy:
(defvar sporty
(cl-macrolet ((in (needle haystack)
`(lambda (item)
(when (member ,needle (funcall ,haystack item))
,needle))))
(make-taxy
:name "Sporty"
:take (lambda (item taxy)
(taxy-take-keyed
(list (in 'ball 'sport-uses)
(in 'disc 'sport-uses)
(in 'glove 'sport-uses)
(in 'racket 'sport-uses)
#'sport-venue)
item taxy
:then #'identity)))))
(thread-last sporty
taxy-emptied
(taxy-fill sports)
(taxy-mapcar #'sport-name)
taxy-plain)
((("Sporty"
((disc
((outdoor
("Ultimate" "Disc golf"))))
(ball
((racket
((indoor
("Racquetball"))
(outdoor
("Tennis"))))
(indoor
("Volleyball" "Basketball"))
(outdoor
("Soccer" "Football"))
(glove
((indoor
("Handball"))
(outdoor
("Baseball"))))))))))
That’s better. But I’d also like to see a very simple classification
to help me decide what to play:
(thread-last
(make-taxy
:name "Funny"
:take (lambda (item taxy)
(taxy-take-keyed
(list (lambda (sport)
(if (sport-fun sport)
'fun 'boring))
#'sport-venue)
item taxy)))
taxy-emptied
(taxy-fill sports)
(taxy-mapcar #'sport-name)
taxy-plain)
((("Funny"
((boring
((outdoor
("Soccer"))))
(fun
((indoor
("Volleyball" "Handball" "Racquetball" "Basketball"))
(outdoor
("Ultimate" "Disc golf" "Tennis" "Football" "Baseball"))))))))
Ah, now I understand.
File: README.info, Node: Applications, Prev: Sporty (understanding completely), Up: Examples
1.4 Applications
================
Some example applications may be found in the examples directory
(examples/):
• Deffy shows top-level definitions and forms in an Elisp project or
file:
• Diredy rearranges a Dired buffer into groups by file size and type:
• Musicy shows a music library with tracks categorized by genre,
artist, year, album, etc:
File: README.info, Node: Installation, Next: Usage, Prev: Examples, Up: Top
2 Installation
**************
‘taxy’ is distributed in GNU ELPA (https://elpa.gnu.org/), which is
available in Emacs by default. Use ‘M-x package-install RET taxy RET’,
then ‘(require 'taxy)’ in your Elisp project.
File: README.info, Node: Usage, Next: Changelog, Prev: Installation, Up: Top
3 Usage
*******
• • • • • •
A taxy is defined with the ‘make-taxy’ constructor, like:
(make-taxy :name "Numbery"
:description "A silly taxonomy of numbers."
:predicate #'numberp
:then #'ignore
:taxys (list ...))
The ‘:predicate’ function determines whether an object fits into that
taxy. If it does, ‘taxy-fill’ adds the object to that taxy’s descendant
‘:taxys’, if present, or to its own ‘:items’. The function defaults to
‘identity’, so a taxy "takes in" any object by default (i.e. if you
only apply objects you want to classify, there’s no need to test them at
the top-level taxy).
The ‘:then’ function determines what happens to an object after being
taken in to the taxy’s ‘:items’: if the function, called with the
object, returns a non-nil value, that value is applied to other taxys at
the same level until one of their ‘:then’ functions returns nil or no
more taxys remain. The function defaults to ‘ignore’, which makes a
taxy "consume" its items by default. Setting the function to, e.g.
‘identity’, makes it not consume them, leaving them eligible to also be
taken into subsequent taxys, or to appear in the parent taxy’s items.
After defining a taxy, call ‘taxy-fill’ with it and a list of objects
to fill the taxy’s hierarchy. *Note:* ‘taxy-fill’ modifies the taxy
given to it (filling its ‘:items’ and those of its ‘:taxys’), so when
using a statically defined taxy (e.g. one defined with ‘defvar’), you
should pass ‘taxy-fill’ a taxy copied with ‘taxy-emptied’, which
recursively copies a taxy without ‘:items’.
To return a taxy in a more human-readable format (with only relevant
fields included), use ‘taxy-plain’. You may also use ‘taxy-mapcar’ to
replace items in a taxy with, e.g. a more useful representation.
* Menu:
* Reusable taxys::
* Threading macros::
* Modifying filled taxys::
* Dynamic taxys::
* Magit section::
* Reference::
File: README.info, Node: Reusable taxys, Next: Threading macros, Up: Usage
3.1 Reusable taxys
==================
Since taxys are structs, they may be stored in variables and used in
other structs (being sure to copy the root taxy with ‘taxy-emptied’
before filling). For example, this shows using ‘taxy’ to classify
Matrix rooms in Ement.el (https://github.com/alphapapa/ement.el):
(defun ement-roomy-buffer (room)
(alist-get 'buffer (ement-room-local room)))
(defvar ement-roomy-unread
(make-taxy :name "Unread"
:predicate (lambda (room)
(buffer-modified-p (ement-roomy-buffer room)))))
(defvar ement-roomy-opened
(make-taxy :name "Opened"
:description "Rooms with buffers"
:predicate #'ement-roomy-buffer
:taxys (list ement-roomy-unread
(make-taxy))))
(defvar ement-roomy-closed
(make-taxy :name "Closed"
:description "Rooms without buffers"
:predicate (lambda (room)
(not (ement-roomy-buffer room)))))
(defvar ement-roomy
(make-taxy
:name "Ement Rooms"
:taxys (list (make-taxy
:name "Direct"
:description "Direct messaging rooms"
:predicate (lambda (room)
(ement-room--direct-p room ement-session))
:taxys (list ement-roomy-opened
ement-roomy-closed))
(make-taxy
:name "Non-direct"
:description "Group chat rooms"
:taxys (list ement-roomy-opened
ement-roomy-closed)))))
Note how the taxys defined in the first three variables are used in
subsequent taxys. As well, the ‘ement-roomy-opened’ taxy has an
"anonymous" taxy, which collects any rooms that aren’t collected by its
sibling taxy (otherwise those objects would be collected into the
parent, "Opened" taxy, which may not always be the most useful way to
present the objects).
Using those defined taxys, we then fill the ‘ement-roomy’ taxy with
all of the rooms in the user’s session, and then use ‘taxy-mapcar’ to
replace the room structs with useful representations for display:
(taxy-plain
(taxy-mapcar (lambda (room)
(list (ement-room--room-display-name room)
(ement-room-id room)))
(taxy-fill (ement-session-rooms ement-session)
(taxy-emptied ement-roomy))))
This produces:
("Ement Rooms"
(("Direct" "Direct messaging rooms"
(("Opened" "Rooms with buffers"
(("Unread"
(("Lars Ingebrigtsen" "!nope:gnus.org")))))
("Closed" "Rooms without buffers"
(("John Wiegley" "!not-really:newartisans.com")
("Eli Zaretskii" "!im-afraid-not:gnu.org")))))
("Non-direct" "Group chat rooms"
(("Opened" "Rooms with buffers"
(("Unread"
(("Emacs" "!WfZsmtnxbxTdoYPkaT:greyface.org")
("#emacs" "!KuaCUVGoCiunYyKEpm:libera.chat")))
;; The non-unread buffers in the "anonymous" taxy.
((("magit/magit" "!HZYimOcmEAsAxOcgpE:gitter.im")
("Ement.el" "!NicAJNwJawmHrEhqZs:matrix.org")
("#emacsconf" "!UjTTDnYmSAslLTtMCF:libera.chat")
("Emacs Matrix Client" "!ZrZoyXEyFrzcBZKNis:matrix.org")
("org-mode" "!rUhEinythPhVTdddsb:matrix.org")
("This Week in Matrix (TWIM)" "!xYvNcQPhnkrdUmYczI:matrix.org")))))
("Closed" "Rooms without buffers"
(("#matrix-spec" "!NasysSDfxKxZBzJJoE:matrix.org")
("#commonlisp" "!IiGsrmKRHzpupHRaKS:libera.chat")
("Matrix HQ" "!OGEhHVWSdvArJzumhm:matrix.org")
("#lisp" "!czLxhhEegTEGNKUBgo:libera.chat")
("Emacs" "!gLamGIXTWBaDFfhEeO:matrix.org")
("#matrix-dev:matrix.org" "!jxlRxnrZCsjpjDubDX:matrix.org")))))))
File: README.info, Node: Threading macros, Next: Modifying filled taxys, Prev: Reusable taxys, Up: Usage
3.2 Threading macros
====================
If you happen to like macros, ‘taxy’ works well with threading (i.e.
‘thread-last’ or ‘->>’):
(thread-last ement-roomy
taxy-emptied
(taxy-fill (ement-session-rooms ement-session))
(taxy-mapcar (lambda (room)
(list (ement-room--room-display-name room)
(ement-room-id room))))
taxy-plain)
File: README.info, Node: Modifying filled taxys, Next: Dynamic taxys, Prev: Threading macros, Up: Usage
3.3 Modifying filled taxys
==========================
Sometimes it’s necessary to modify a taxy after filling it with objects,
e.g. to sort the items and/or the sub-taxys. For this, use the
function ‘taxy-mapc-taxys’ (a.k.a. ‘taxy-mapc*’). For example, in the
sample application musicy.el (examples/musicy.el), the taxys and their
items are sorted after filling, like so:
(defun musicy-files (files)
(thread-last musicy-taxy
taxy-emptied
(taxy-fill files)
;; Sort sub-taxys by their name.
(taxy-sort* #'string< #'taxy-name)
;; Sort sub-taxys' items by name.
(taxy-sort #'string< #'identity)
taxy-magit-section-pp))
File: README.info, Node: Dynamic taxys, Next: Magit section, Prev: Modifying filled taxys, Up: Usage
3.4 Dynamic taxys
=================
• • •
You may not always know in advance what taxonomy a set of objects
fits into, so ‘taxy’ lets you add taxys dynamically by using the ‘:take’
function to add a taxy when an object is "taken into" a parent taxy’s
items. For example, you could dynamically classify buffers by their
major mode like so:
(defun buffery-major-mode (buffer)
(buffer-local-value 'major-mode buffer))
(defvar buffery
(make-taxy
:name "Buffers"
:taxys (list
(make-taxy
:name "Modes"
:take (apply-partially #'taxy-take-keyed (list #'buffery-major-mode))))))
;; Note the use of `taxy-emptied' to avoid mutating the original taxy definition.
(taxy-plain
(taxy-fill (buffer-list)
(taxy-emptied buffery)))
The taxy’s ‘:take’ function is set to the ‘taxy-take-keyed’ function,
partially applied with the ‘buffery-major-mode’ function as its list of
‘key-fns’ (‘taxy-fill’ supplies the buffer and the taxy as arguments),
and it produces this taxonomy of buffers:
("Buffers"
(("Modes"
((magit-process-mode
(#<buffer magit-process: taxy.el> #<buffer magit-process: > #<buffer magit-process: notes>))
(messages-buffer-mode
(#<buffer *Messages*>))
(special-mode
(#<buffer *Warnings*> #<buffer *elfeed-log*>))
(dired-mode
(#<buffer ement.el<emacs>>))
(Custom-mode
(#<buffer *Customize Apropos*>))
(fundamental-mode
(#<buffer *helm candidates:Bookmarks*> #<buffer *Backtrace*>))
(magit-diff-mode
(#<buffer magit-diff: taxy.el> #<buffer magit-diff: notes> #<buffer magit-diff: ement.el>))
(compilation-mode
(#<buffer *compilation*> #<buffer *Compile-Log*>))
(Info-mode
(#<buffer *helm info temp buffer*> #<buffer *info*>))
(help-mode
(#<buffer *Help*>))
(emacs-lisp-mode
(#<buffer ement.el<ement.el>> #<buffer ement-room-list.el> #<buffer *scratch*>
#<buffer ement-room.el> #<buffer init.el> #<buffer bufler.el>
#<buffer dash.el> #<buffer *Pp Eval Output*> #<buffer taxy.el> #<buffer scratch.el>))))))
* Menu:
* Multi-level dynamic taxys::
* "Chains" of independent, multi-level dynamic taxys: "Chains" of independent multi-level dynamic taxys.
* Defining a classification domain-specific language::
File: README.info, Node: Multi-level dynamic taxys, Next: "Chains" of independent multi-level dynamic taxys, Up: Dynamic taxys
3.4.1 Multi-level dynamic taxys
-------------------------------
Of course, the point of taxonomies is that they aren’t restricted to a
single level of depth, so you may also use the function
‘taxy-take-keyed’ to dynamically make multi-level taxys.
Expanding on the previous example, we use ‘cl-labels’ to define
functions which are used in the taxy’s definition, which are used in the
‘:take’ function, which calls ‘taxy-take-keyed’ (rather than using
‘apply-partially’ like in the previous example, we use a lambda
function, which performs better than partially applied functions). Then
when the taxy is filled, a multi-level hierarchy is created dynamically,
organizing buffers first by their directory, and then by mode in each
directory.
(defvar buffery
(cl-labels ((buffer-mode (buffer) (buffer-local-value 'major-mode buffer))
(buffer-directory (buffer) (buffer-local-value 'default-directory buffer)))
(make-taxy
:name "Buffers"
:taxys (list
(make-taxy
:name "Directories"
:take (lambda (item taxy)
(taxy-take-keyed (list #'buffer-directory #'buffer-mode) item taxy)))))))
(taxy-plain
(taxy-fill (buffer-list)
(taxy-emptied buffery)))
That produces a list like:
("Buffers"
(("Directories"
(("~/src/emacs/ement.el/"
((dired-mode
(#<buffer ement.el<emacs>))
(emacs-lisp-mode
(#<buffer ement.el<ement.el> #<buffer ement-room-list.el> #<buffer ement-room.el>))
(magit-diff-mode
(#<buffer magit-diff: ement.el>))))
("~/src/emacs/taxy.el/"
((dired-mode
(#<buffer taxy.el<emacs>))
(Info-mode
(#<buffer *info*>))
(magit-status-mode
(#<buffer magit: taxy.el>))
(emacs-lisp-mode
(#<buffer taxy-magit-section.el> #<buffer taxy.el<taxy.el> #<buffer scratch.el>))))))))
File: README.info, Node: "Chains" of independent multi-level dynamic taxys, Next: Defining a classification domain-specific language, Prev: Multi-level dynamic taxys, Up: Dynamic taxys
3.4.2 "Chains" of independent, multi-level dynamic taxys
--------------------------------------------------------
_Naming things is hard._
Going a step further, each element in the ‘taxy-take-keyed’
function’s ‘KEY-FNS’ argument may be a list of functions (or a list of
lists of functions, etc.), which creates a "chain" of "independent"
dynamic taxys. Each such chain may be said to "short-circuit" the
filling process in that, when an object is "taken" by the first key
function in a chain, the object is not "offered" to other functions
outside that chain. This allows each dynamic sub-taxy to have its own
set of sub-taxys, rather than sharing the same "global" set. In effect,
this creates multiple, unique taxonomies that share a single root taxy.
Building on the ‘sporty’ example, let’s define a taxy in which
outdoor sports are classified only by whether they involve a disc, but
indoor sports are additionally classified by whatever equipment they may
use:
(defvar sporty-dynamic
(cl-macrolet ((in (needle haystack)
`(lambda (item)
(when (member ,needle (funcall ,haystack item))
,needle))))
(cl-labels ((outdoor-p
(sport) (when (eq 'outdoor (sport-venue sport))
"Outdoor"))
(indoor-p
(sport) (when (eq 'indoor (sport-venue sport))
"Indoor"))
(disc-p
(sport) (if (funcall (in 'disc 'sport-uses) sport)
'disc
'non-disc)))
(make-taxy
:name "Sporty (dynamic)"
:take (lambda (item taxy)
(taxy-take-keyed
(list (list #'outdoor-p #'disc-p)
(list #'indoor-p
(in 'ball 'sport-uses)
(in 'disc 'sport-uses)
(in 'glove 'sport-uses)
(in 'racket 'sport-uses)))
item taxy))))))
Now let’s fill the taxy with the sports and format it:
(thread-last sporty-dynamic
taxy-emptied
(taxy-fill sports)
(taxy-mapcar #'sport-name)
taxy-plain)
("Sporty (dynamic)"
(("Indoor"
((ball
("Volleyball" "Basketball")
((glove
("Handball"))
(racket
("Racquetball"))))))
("Outdoor"
((disc
("Ultimate" "Disc golf"))
(non-disc
("Soccer" "Tennis" "Football" "Baseball"))))))
File: README.info, Node: Defining a classification domain-specific language, Prev: "Chains" of independent multi-level dynamic taxys, Up: Dynamic taxys
3.4.3 Defining a classification domain-specific language
--------------------------------------------------------
When writing a larger Taxy-based application, it may be necessary to
define a number of key functions that would be unwieldy to manage in a
‘cl-labels’ form. For this case, Taxy provides the macro
‘taxy-define-key-definer’ to easily define Taxy key functions in an
application library. Those functions are then passed to the function
‘taxy-make-take-function’ at runtime, along with a list of keys being
used to classify items. Using these allows key functions to be defined
in top-level forms, and it allows an application to be extended by users
by defining additional key functions in their configurations.
Extending the previous ‘sporty’ example, let’s redefine its key
functions using ‘taxy-define-key-definer’:
(taxy-define-key-definer sporty-define-key
sporty-keys "sporty"
"Define a `sporty' key function.")
(sporty-define-key disc-based ()
(if (member 'disc (sport-uses item))
"Disc-based"
"Non-disc-based"))
(sporty-define-key uses (&optional thing)
(pcase thing
(`nil (sport-uses item))
(_ (when (cl-typecase (sport-uses item)
(symbol (equal thing (sport-uses item)))
(list (member thing (sport-uses item))))
thing))))
(sporty-define-key venue (&optional place)
(pcase place
(`nil (sport-venue item))
(_ (when (equal place (sport-venue item))
(sport-venue item)))))
Now we’ll define the default keys to use when classifying items.
This list is equivalent to the one passed to ‘taxy-take-keyed’ in the
previous, "Chains" example.
(defvar sporty-default-keys
'(
((venue 'outdoor)
disc-based)
((venue 'indoor)
(uses 'ball)
(uses 'disc)
(uses 'glove)
(uses 'racket))))
Finally, rather than using a pre-made taxy struct, we make one at
runtime, making the ‘:take’ function with ‘taxy-make-take-function’.
(let ((taxy (make-taxy
:name "Sporty (DSL)"
:take (taxy-make-take-function sporty-default-keys
sporty-keys))))
(thread-last taxy
(taxy-fill sports)
(taxy-mapcar #'sport-name)
taxy-plain))
Which gives us:
("Sporty (DSL)"
((indoor
((ball
("Volleyball" "Basketball")
((glove
("Handball"))
(racket
("Racquetball"))))))
(outdoor
(("Disc-based"
("Ultimate" "Disc golf"))
("Non-disc-based"
("Soccer" "Tennis" "Football" "Baseball"))))))
As you can see, the result is the same as that in the previous
example, but we’ve defined a kind of DSL for grouping sports in a
modular, extendable way.
This also allows the grouping keys to be easily changed at runtime,
producing a different result. For example, we could group sports by,
first, whether they use a ball, and then by venue. Let’s do this in a
function so that users can pass their own list of keys: