-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a Texinfo manual to the project.
This patch will also add a Github Actions workflow `ci` that will be run on push, PR, &c.
- Loading branch information
Showing
9 changed files
with
601 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
name: ci | ||
on: | ||
workflow_dispatch: | ||
pull_request: | ||
types: [opened, edited, reopened] # don't say `synchronize`-- that is taken care of by `push` | ||
push: | ||
schedule: | ||
- cron: '42 02 * * *' | ||
|
||
jobs: | ||
|
||
# These all seem to run in `/home/runner/work/elmpd/elmpd` | ||
lint_and_test: | ||
name: Lint & Test | ||
strategy: | ||
matrix: | ||
os: [ubuntu-20.04, ubuntu-latest] | ||
runs-on: ${{ matrix.os }} | ||
steps: | ||
|
||
- name: Checkout repo | ||
uses: actions/checkout@v2 | ||
|
||
- name: Lint | ||
shell: bash | ||
run: | | ||
set -ex | ||
admin/run-linters | ||
- name: Test | ||
shell: bash | ||
run: | | ||
set -ex | ||
admin/configure-to-distcheck | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,5 +9,6 @@ configure | |
env.sh | ||
*.bz2 | ||
*.gz | ||
*.tar | ||
*.xz | ||
*.zst |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,19 @@ | ||
dist_lisp_LISP = elmpd.el | ||
SUBDIRS = test | ||
EXTRA_DIST = README.org | ||
CLEANFILES = .pkg-tmp elmpd-$(PACKAGE_VERSION).tar | ||
SUBDIRS = doc test | ||
AM_ELCFLAGS = --eval '(require (quote bytecomp))' | ||
|
||
dist-hook: | ||
$(EMACS) --batch --eval '(checkdoc-file "$(srcdir)/elmpd.el")' | ||
package: elmpd-$(PACKAGE_VERSION).tar $(srcdir)/README.org | ||
|
||
srclisp=$(dist_lisp_LISP:%.el=$(srcdir)/%.el) | ||
|
||
elmpd-$(PACKAGE_VERSION).tar: $(srclisp) $(srcdir)/README.org | ||
mkdir -p .pkg-tmp/elmpd-$(PACKAGE_VERSION)/ && \ | ||
cp $(srclisp) .pkg-tmp/elmpd-$(PACKAGE_VERSION)/ && \ | ||
cp $(srcdir)/README.org .pkg-tmp/elmpd-$(PACKAGE_VERSION)/ && \ | ||
cd .pkg-tmp && tar cf $@ elmpd-$(PACKAGE_VERSION)/ && \ | ||
cd .. && mv -v .pkg-tmp/elmpd-$(PACKAGE_VERSION).tar . && \ | ||
rm -rf .pkg-tmp | ||
|
||
dist-hook: package |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
#+TITLE: elmpd | ||
#+DESCRIPTION: A tight, async mpd library in Emacs Lisp | ||
#+DATE: <2024-08-30 Fri 07:47> | ||
#+DATE: <2024-09-01 Sun 17:10> | ||
#+AUTHOR: sp1ff | ||
#+EMAIL: [email protected] | ||
#+AUTODATE: t | ||
|
@@ -13,13 +13,22 @@ | |
* Introduction | ||
|
||
[[https://github.com/sp1ff/elmpd][elmpd]] is a tight, asynchronous, ergonomic [[https://www.musicpd.org/][MPD]] client library in Emacs Lisp. | ||
* License | ||
|
||
This package is released under the [[https://www.gnu.org/licenses/gpl-3.0.en.html][GPL v3]]. | ||
* Prerequisites | ||
|
||
Emacs 25.1. | ||
* Installing | ||
|
||
The simplest way to install [[https://github.com/sp1ff/elmpd][elmpd]] is from [[https://melpa.org][MELPA]]. | ||
|
||
You can also install the package manually. Download the .tar file from [[https://github.com/sp1ff/elmpd/releases][Github]] or my personal [[https://www.unwoundstack/distros.html][page]] and say: | ||
|
||
#+BEGIN_SRC elisp | ||
(package-install-file "elmpd-0.3.0.tar") | ||
#+END_SRC | ||
|
||
I'm now making GitHub releases that include Autotools source distributions: | ||
|
||
#+BEGIN_SRC bash | ||
|
@@ -40,142 +49,8 @@ sudo make install | |
#+END_SRC | ||
* Getting Started | ||
|
||
** Creating Connections | ||
:PROPERTIES: | ||
:CUSTOM_ID: creating_connections | ||
:END: | ||
|
||
Create an MPD connection by calling =elmpd-connect=; this will return an =elmpd-connection= instance immediately. Asynchronously, it will be parsing the MPD greeting message, perhaps sending an initial password, and if so requested, sending the "idle" command. | ||
|
||
There are two idioms I've seen in MPD client libraries for sending commands while receiving notifications of server-side changes: | ||
|
||
1. just maintain two connections (e.g. [[https://github.com/vincent-petithory/mpdfav][mpdfav]]); issue the "idle" command on one, send commands on the other | ||
2. use one connection, issue the "idle" command, and when asked to issue another command, send "noidle", issue the requested command, collect the response, and then send "idle" again (e.g. [[https://gitea.petton.fr/mpdel/libmpdel][libmpdel]]). Note that this is not a race condition per the MPD [[https://www.musicpd.org/doc/html/protocol.html#idle][docs]] -- any server-side changes that took place while processing the command will be saved & returned on "idle" | ||
|
||
Since =elmpd= is a library, I do not make that choice here, but rather support both styles. See the docstring for =elmpd-connect= on how to configure your new connection in either way. | ||
|
||
The implementation is callback-based; each connection comes at the cost of a single socket plus whatever memory is needed to do the text processing in handling responses. In particular, I declined to use =tq= despite the natural fit because I didn't want to use a buffer for each connection, as well. | ||
** Invoking Commands | ||
|
||
Send commands via =elmpd-send=. For example, to start MPD playing: | ||
|
||
#+BEGIN_SRC elisp | ||
(let ((conn (elmpd-connect :host "localhost"))) | ||
(elmpd-send conn "play")) | ||
#+END_SRC | ||
|
||
sends the "play" command to your MPD server listening on port 6600 on localhost. Note that this code will likely return *before* anything actually happens. As mentioned [[#creating_connections][above]], =elmpd-connect= returns immediately after creating the network process; it only reads & parses the MPD greeting asynchronously. Likewise, =elmpd-send= only queues up the "play" command; it will actually be sent & its response read in the background. | ||
|
||
If you'd like to do something with the response, you can provide a callback: | ||
|
||
#+BEGIN_SRC elisp | ||
(let ((conn (elmpd-connect :host "localhost"))) | ||
(elmpd-send | ||
conn | ||
"getvol" | ||
(lambda (_conn ok rsp) | ||
(if ok | ||
(message "volume is %s" (substring rsp 7)) | ||
(error "Failed to get volume: %s" rsp))))) | ||
#+END_SRC | ||
|
||
You can send command lists by specifying a list rather than a string as the second parameter: | ||
|
||
#+BEGIN_SRC elisp | ||
(let ((conn (elmpd-connect :host "localhost"))) | ||
(elmpd-send | ||
conn | ||
'("random 1" "consume 1" "crossfade 5" "play"))) | ||
#+END_SRC | ||
|
||
Will send the following to your local MPD daemon: | ||
|
||
#+BEGIN_EXAMPLE | ||
command_list_begin | ||
random 1 | ||
consume 1 | ||
crossfade 5 | ||
play | ||
command_list_end | ||
#+END_EXAMPLE | ||
|
||
Now that we're sending multiple commands, we may be interested in processing the responses in different ways. For instance: | ||
|
||
#+BEGIN_SRC elisp | ||
(let ((conn (elmpd-connect :host "localhost")) | ||
(groups '("Pogues" "Rolling Stones" "Flogging Molly"))) | ||
(elmpd-send | ||
conn | ||
(cl-mapc (lambda (x) (format "count \"(Artist =~ '%s')\"" x)) groups) | ||
(lambda (_conn ok rsp) | ||
(if ok | ||
;; `rsp' is a list; one response per command | ||
(cl-mapc | ||
(lambda (x) | ||
(let* ((lines (split x "\n" t)) | ||
(line (car lines))) | ||
(message (substring line 7))))) | ||
(error "Error counting: %s" rsp))) | ||
'list)) | ||
#+END_SRC | ||
|
||
will issue the "count" command in a command list (once for each of "Pogues", "Rolling Stones" & "Flogging Molly"), receive the responses as a list, and process the list. If you just can't wait, you can specify ='stream= instead of ='list=; in this case as soon as a response from a sub-command is available, your callback will be invoked with it: | ||
|
||
#+BEGIN_SRC elisp | ||
(let ((conn (elmpd-connect :host "localhost")) | ||
(groups '("Pogues" "Rolling Stones" "Flogging Molly"))) | ||
(elmpd-send | ||
conn | ||
(cl-mapc (lambda (x) (format "count \"(Artist =~ '%s')\"" x)) groups) | ||
(lambda (_conn ok rsp) | ||
(if ok | ||
;; `rsp' is a string; one invocation per command | ||
(let* ((lines (split x "\n" t)) | ||
(line (car lines))) | ||
(message (substring line 7))) | ||
(error "Error counting: %s" rsp))) | ||
'stream)) | ||
#+END_SRC | ||
|
||
Prior to 0.2.2, sending a subsequent response meant you had to invoke =elmpd-send= from your callback, like so: | ||
|
||
#+BEGIN_SRC elisp | ||
(let ((conn (elmpd-connect :host "localhost"))) | ||
(elmpd-send | ||
conn | ||
"getvol" | ||
(lambda (_conn ok rsp) | ||
(if ok | ||
(let ((vol (string-to-number (substring rsp 7 -1)))) | ||
(if (< vol 50) | ||
(elmpd-send | ||
conn | ||
"setvol 50" | ||
(lambda (_conn ok rsp) | ||
(if ok | ||
(message "Increased volume from %d to 50." vol) | ||
(message "Failed to increase volume: %s" rsp)))))) | ||
(error "Failed to get volume: %s" rsp))))) | ||
#+END_SRC | ||
|
||
As with Javascript futures, this quickly became inconvenient & difficult to read, so I introduced the =elmpd-chain= macro in the hopes of achieving a syntax more like async Rust: | ||
|
||
#+BEGIN_SRC elisp | ||
(let ((conn (elmpd-connect :host "localhost")) | ||
(vol 0)) | ||
(elmpd-chain | ||
conn | ||
("getvol" | ||
(lambda (_conn rsp) | ||
(setq vol (string-to-number (substring rsp 7 -1))))) | ||
:or-else | ||
(lambda (_conn rsp) (error "Failed to get volume: %s" rsp)) | ||
:and-then | ||
((format "setvol %d" (max 50 vol)) | ||
(lambda (_ _) (message "Set volume to %d." vol))) | ||
:or-else | ||
(message "Failed to increase volume: %s" rsp))) | ||
#+END_SRC | ||
User documentation is provided with the package, and may also be found | ||
[[https://unwoundstack.com/doc/elmpd/curr/elmpd.html][here]]. | ||
* Motivation & Design Philosphy | ||
|
||
[[https://github.com/DamienCassou][Damien Cassou]], the author of [[https://github.com/mpdel/mpdel][mpdel]] and [[https://gitea.petton.fr/mpdel/libmpdel][libmpdel]], [[https://github.com/sp1ff/elmpd/issues/1][reached out]] to ask "Why elmpd?" His question prompted me to clarify my thoughts around this project & I've adapted my response here. | ||
|
@@ -185,7 +60,7 @@ I've looked at a few [[https://www.musicpd.org/][MPD]] clients, including [[http | |
My next move was to read through a number of client libraries for inspiration, both in C & Emacs LISP. Many of them had strong opinions on how one should talk to MPD. Having been programming MPD for a while I had come to appreciate its simplicity (after all, one can program it from bash by simply =echo=-ing commands to =/dev/tcp/$host/$port=). My experience with async Rust inspired me to see how simple I could make this using just callbacks. =elmpd= exports two primary functions: =elmpd-connect= & =elmpd-send=. Each connection consumes a socket & optionally a callback-- that's it (no buffer, no transaction queue). Put another way, if other libraries are Gnus (featureful, encourages you to read your e-mail in a certain way), then elmpd is [[https://mailutils.org/][Mailutils]] (small utilities that leave it up to the user to assemble them into something useful). | ||
* Status & Roadmap | ||
|
||
I've been using the library for some time with good results. The bulk of the work has been in getting the asynchronous logic right; as such it is not very featureful. It is ripe for being used to build up a more caller-friendly API: =(something-play)= instead of: | ||
As of September 2024 I'm calling this "1.0". The package is ripe for being used to build up a more caller-friendly API: =(something-play)= instead of: | ||
|
||
#+BEGIN_SRC elisp | ||
(let ((conn (elmpd-connect))) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
AC_INIT([elmpd], [0.3.0], [sp1ff@poboxcom], [elmpd], [https://github.com/sp1ff/elmpd]) | ||
AC_CONFIG_AUX_DIR([build-aux]) | ||
AC_CONFIG_SRCDIR([elmpd.el]) | ||
AM_INIT_AUTOMAKE([-Wall -Werror gnits std-options dist-bzip2 dist-xz dist-zstd]) | ||
AM_INIT_AUTOMAKE([-Wall -Werror gnits std-options dist-xz dist-zstd]) | ||
|
||
AM_PATH_LISPDIR | ||
|
||
AC_CONFIG_FILES([Makefile test/Makefile]) | ||
AC_CONFIG_FILES([Makefile doc/Makefile test/Makefile]) | ||
AC_OUTPUT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
elmpd.info | ||
stamp-vti | ||
dir |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
info_TEXINFOS = elmpd.texi |
Oops, something went wrong.