branch: externals/taxy commit e09dc38a5786b3de2a70437f232968b644f32e13 Author: Adam Porter <a...@alphapapa.net> Commit: Adam Porter <a...@alphapapa.net>
Add: (taxy-mapc*) And musicy.el example --- README.org | 49 +++++++++++--------- examples/README.org | 26 +++++++++++ examples/musicy.el | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++ images/musicy.png | Bin 0 -> 28010 bytes taxy.el | 11 +++++ taxy.info | 77 +++++++++++++++++++++++++------- 6 files changed, 252 insertions(+), 37 deletions(-) diff --git a/README.org b/README.org index 4624c6f..168a687 100644 --- a/README.org +++ b/README.org @@ -23,37 +23,17 @@ Helpful features include: :END: :CONTENTS: - [[#examples][Examples]] + - [[#example-applications][Example applications]] - [[#usage][Usage]] - [[#dynamic-taxys][Dynamic taxys]] - [[#reusable-taxys][Reusable taxys]] - [[#threading-macros][Threading macros]] + - [[#modifying-filled-taxys][Modifying filled taxys]] - [[#magit-section][Magit section]] - [[#changelog][Changelog]] - [[#development][Development]] :END: -# * Installation -# :PROPERTIES: -# :TOC: :depth 0 -# :END: -# -# ** MELPA -# -# If you installed from MELPA, you're done. Just run one of the commands below. -# -# ** Manual -# -# Install these required packages: -# -# + =foo= -# + =bar= -# -# Then put this file in your load-path, and put this in your init file: -# -# #+BEGIN_SRC elisp -# (require 'taxy) -# #+END_SRC - * Examples Let's imagine a silly taxonomy of numbers below 100: @@ -220,6 +200,10 @@ That's better: ("B" "C" "D" "F" "G" "H" "J" "K" "L" "M" "N" "N" "P" "Q" "R" "S" "T" "V" "W" "X" "Y" "Z")))) #+END_SRC +** Example applications + +Some example applications may be found in the [[file:examples/README.org][examples directory]]. + * Usage :PROPERTIES: :TOC: :include descendants :depth 1 @@ -228,6 +212,7 @@ That's better: - [[#dynamic-taxys][Dynamic taxys]] - [[#reusable-taxys][Reusable taxys]] - [[#threading-macros][Threading macros]] +- [[#modifying-filled-taxys][Modifying filled taxys]] - [[#magit-section][Magit section]] :END: @@ -458,6 +443,26 @@ If you happen to like macros, ~taxy~ works well with threading (i.e. ~thread-las taxy-plain) #+END_SRC +** Modifying filled taxys + +Sometimes it's necessary to modify a taxy after filling it with objects, e.g. to sort the objects and/or the sub-taxys. For this, use the function ~taxy-mapc-taxys~ (a.k.a. ~taxy-mapc*~). For example, in the sample application [[file:examples/musicy.el][musicy.el]], the taxys and their objects are sorted after filling, like so: + +#+BEGIN_SRC elisp + (defun musicy-files (files) + (thread-last musicy-taxy + taxy-emptied + (taxy-fill files) + (taxy-mapc* (lambda (taxy) + ;; Sort sub-taxys by their name. + (setf (taxy-taxys taxy) + (cl-sort (taxy-taxys taxy) #'string< + :key #'taxy-name)) + ;; Sort sub-taxys' objects by name. + (setf (taxy-objects taxy) + (cl-sort (taxy-objects taxy) #'string<)))) + taxy-magit-section-pp)) +#+END_SRC + ** Magit section Showing a =taxy= with =magit-section= is very easy: diff --git a/examples/README.org b/examples/README.org new file mode 100644 index 0000000..06d7325 --- /dev/null +++ b/examples/README.org @@ -0,0 +1,26 @@ +#+TITLE: Taxy Examples + +Some example applcations using ~taxy~. + +* Musicy + +[[file:musicy.el][Musicy]] displays a music library in a ~magit-section~ buffer. Use it like: + +#+BEGIN_SRC elisp + (require 'musicy) + + (musicy "~/Music") +#+END_SRC + +Since it calls the =mediainfo= program on every file, it can be slow on large music libraries, so you might want to test it on only a subset of them, like: + +#+BEGIN_SRC elisp + (musicy-files + (seq-take (directory-files-recursively + "~/Music" (rx "." (or "mp3" "ogg") eos)) + 100)) +#+END_SRC + +The resulting buffer shows tracks organized by genre, then artist, then year, then album, then track name: + +[[../images/musicy.png]] diff --git a/examples/musicy.el b/examples/musicy.el new file mode 100644 index 0000000..be74fcd --- /dev/null +++ b/examples/musicy.el @@ -0,0 +1,126 @@ +;;; musicy.el --- View a music library in a useful taxonomy -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Adam Porter + +;; Author: Adam Porter <a...@alphapapa.net> +;; Keywords: convenience + +;; 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This is a sample application using `taxi'. It uses the "mediainfo" +;; program to get info about audio files, but any function could be +;; swapped into its place (e.g. one that retrieved data from MPD). + +;;; Code: + +;;;; Requirements + +(require 'cl-lib) + +(require 'taxy) +(require 'taxy-magit-section) + +;; Used to avoid repeated calls to "mediainfo" for the same file. +(require 'memoize) + +;;;; Variables + +(defvar musicy-taxy + (cl-labels ((call-proc (process &rest args) + "Return results of running PROCESS with ARGS." + (declare (indent defun)) + (with-temp-buffer + (if (zerop (apply #'call-process process nil t nil + args)) + (buffer-substring-no-properties (point-min) (point-max)) + (warn "mediainfo failed for: %S" args)))) + (mediainfo (file) + (call-proc "mediainfo" file)) + (mediainfo-attr (attr file) + (if-let ((info (musicy-mediainfo file))) + (when (string-match + (rx-to-string `(seq (eval ,attr) (1+ blank) ":" (1+ blank) (group (1+ nonl)))) + info) + (match-string 1 info)) + (format "No info for file: %S" file))) + (genre (file) + (or (mediainfo-attr "Genre" file) + "[unknown genre]")) + (year (file) + (or (when-let (date (or (mediainfo-attr "Recorded date" file) + (mediainfo-attr "Original/Released date" file) + (mediainfo-attr "Year" file))) + (when (string-match (rx (group (1+ digit))) date) + (match-string 1 date))) + "[unknown year]")) + (artist (file) + (mediainfo-attr "Performer" file)) + (album (file) + (mediainfo-attr "Album" file)) + (track-name (file) + (mediainfo-attr "Track name" file)) + (track-number (file) + (mediainfo-attr "Track name/Position" file)) + (track-string (file) + (concat + (pcase (track-number file) + ((or "-1" 'nil) nil) + (number (format "%s: " number))) + (track-name file)))) + (make-taxy + :name "Musicy" + :taxys (list (make-taxy + :name "Genres" + :take (apply-partially #'taxy-take-keyed* + (list #'genre #'artist #'year #'album #'track-string))))))) + +;;;; Customization + + +;;;; Commands + +(defun musicy (directory) + (interactive (list (read-directory-name "Directory of music files: "))) + (let ((files (directory-files-recursively + directory (rx "." (or "mp3" "ogg") eos)))) + (musicy-files files))) + +(defun musicy-files (files) + (thread-last musicy-taxy + taxy-emptied + (taxy-fill files) + (taxy-mapc* (lambda (taxy) + (setf (taxy-taxys taxy) + (cl-sort (taxy-taxys taxy) #'string< + :key #'taxy-name)) + (setf (taxy-objects taxy) + (cl-sort (taxy-objects taxy) #'string<)))) + ;; taxy-plain + taxy-magit-section-pp)) + +;;;; Functions + +(defmemoize musicy-mediainfo (file) + (with-temp-buffer + (if (zerop (call-process "mediainfo" nil t nil file)) + (buffer-substring-no-properties (point-min) (point-max)) + (warn "mediainfo failed for: %S" file)))) + +;;;; Footer + +(provide 'musicy) + +;;; musicy.el ends here diff --git a/images/musicy.png b/images/musicy.png new file mode 100644 index 0000000..8b2af29 Binary files /dev/null and b/images/musicy.png differ diff --git a/taxy.el b/taxy.el index 335f7dd..6ba04a5 100644 --- a/taxy.el +++ b/taxy.el @@ -116,6 +116,17 @@ replace objects with a more useful form after classification." (defalias 'taxy-mapcar #'taxy-mapcar-objects) +(defun taxy-mapc-taxys (fn taxy) + "Return TAXY having applied FN to it and its descendants. +Does not copy TAXY. Destructively modifies TAXY, if FN does." + (declare (indent defun)) + (funcall fn taxy) + (cl-loop for sub-taxy in-ref (taxy-taxys taxy) + do (setf sub-taxy (taxy-mapc-taxys fn sub-taxy))) + taxy) + +(defalias 'taxy-mapc* #'taxy-mapc-taxys) + (cl-defun taxy-take-keyed (key-fn object taxy &key (key-name-fn #'identity)) "Take OBJECT into TAXY, adding new taxys dynamically. Places OBJECT into a taxy in TAXY for the value returned by diff --git a/taxy.info b/taxy.info index 43ef1bc..ccb6d37 100644 --- a/taxy.info +++ b/taxy.info @@ -21,12 +21,17 @@ taxy.el — The Detailed Node Listing — +Examples + +* Example applications:: + Usage * Dynamic taxys:: * Reusable taxys:: * Threading macros:: +* Modifying filled taxys:: * Magit section:: Dynamic taxys @@ -210,13 +215,26 @@ subset of the taxys’ slots, suitable for display. ("Consonants" "Well, if they aren't a vowel..." ("B" "C" "D" "F" "G" "H" "J" "K" "L" "M" "N" "N" "P" "Q" "R" "S" "T" "V" "W" "X" "Y" "Z")))) +* Menu: + +* Example applications:: + + +File: README.info, Node: Example applications, Up: Examples + +1.1 Example applications +======================== + +Some example applications may be found in the examples directory +(examples/README.org). + File: README.info, Node: Usage, Next: Changelog, Prev: Examples, Up: Top 2 Usage ******* - • • • • + • • • • • A taxy is defined with the ‘make-taxy’ constructor, like: (make-taxy :name "Numbery" @@ -257,6 +275,7 @@ replace objects in a taxy with, e.g. a more useful representation. * Dynamic taxys:: * Reusable taxys:: * Threading macros:: +* Modifying filled taxys:: * Magit section:: @@ -477,7 +496,7 @@ replace the room structs with useful representations for display: ("#matrix-dev:matrix.org" "!jxlRxnrZCsjpjDubDX:matrix.org"))))))) -File: README.info, Node: Threading macros, Next: Magit section, Prev: Reusable taxys, Up: Usage +File: README.info, Node: Threading macros, Next: Modifying filled taxys, Prev: Reusable taxys, Up: Usage 2.3 Threading macros ==================== @@ -494,9 +513,35 @@ If you happen to like macros, ‘taxy’ works well with threading (i.e. taxy-plain) -File: README.info, Node: Magit section, Prev: Threading macros, Up: Usage +File: README.info, Node: Modifying filled taxys, Next: Magit section, Prev: Threading macros, Up: Usage + +2.4 Modifying filled taxys +========================== + +Sometimes it’s necessary to modify a taxy after filling it with objects, +e.g. to sort the objects 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 +objects are sorted after filling, like so: + + (defun musicy-files (files) + (thread-last musicy-taxy + taxy-emptied + (taxy-fill files) + (taxy-mapc* (lambda (taxy) + ;; Sort sub-taxys by their name. + (setf (taxy-taxys taxy) + (cl-sort (taxy-taxys taxy) #'string< + :key #'taxy-name)) + ;; Sort sub-taxys' objects by name. + (setf (taxy-objects taxy) + (cl-sort (taxy-objects taxy) #'string<)))) + taxy-magit-section-pp)) + + +File: README.info, Node: Magit section, Prev: Modifying filled taxys, Up: Usage -2.4 Magit section +2.5 Magit section ================= Showing a taxy with magit-section is very easy: @@ -552,17 +597,19 @@ GPLv3 Tag Table: Node: Top218 -Node: Examples1101 -Node: Usage9001 -Node: Dynamic taxys11087 -Node: Multi-level dynamic taxys13505 -Node: Reusable taxys15668 -Node: Threading macros19837 -Node: Magit section20367 -Node: Changelog20962 -Node: 01-pre21100 -Node: Development21194 -Node: License21365 +Node: Examples1164 +Node: Example applications9099 +Node: Usage9304 +Node: Dynamic taxys11421 +Node: Multi-level dynamic taxys13839 +Node: Reusable taxys16002 +Node: Threading macros20171 +Node: Modifying filled taxys20710 +Node: Magit section21803 +Node: Changelog22404 +Node: 01-pre22542 +Node: Development22636 +Node: License22807 End Tag Table