From a1bb332d045c1c375ad2fa331476ee95d964017c Mon Sep 17 00:00:00 2001 From: kaliko Date: Sat, 9 Mar 2024 15:39:11 +0100 Subject: [PATCH] Update documentation --- doc/extract_supported_commands.py | 41 +++++++ doc/make.bat | 35 ++++++ doc/source/changelog.rst | 10 ++ doc/source/commands.rst | 168 +++++++++++++++++++++++++++++ doc/source/explanations.rst | 33 ++++-- doc/source/index.rst | 4 + doc/source/reference.rst | 14 +-- doc/source/tutorial.rst | 60 +++++++++++ doc/source/tutorial/mpdaio | 1 - doc/source/tutorial/tutorial-00.py | 11 +- doc/source/tutorial/tutorial-01.py | 20 +++- 11 files changed, 376 insertions(+), 21 deletions(-) create mode 100644 doc/extract_supported_commands.py create mode 100644 doc/make.bat create mode 100644 doc/source/changelog.rst create mode 100644 doc/source/commands.rst delete mode 120000 doc/source/tutorial/mpdaio diff --git a/doc/extract_supported_commands.py b/doc/extract_supported_commands.py new file mode 100644 index 0000000..d6a7283 --- /dev/null +++ b/doc/extract_supported_commands.py @@ -0,0 +1,41 @@ +#!/usr/bin/python3 +import re +import sys + +START = 'self._commands = {' +END = '}' + +def find_start(fd): + line = fd.readline() + while START not in line: + line = fd.readline() + if not line: + break + if not line: + print('Reach end of file!', file=sys.stderr) + sys.exit(1) + + +def main(): + with open('mpdaio/client.py', 'r', encoding='utf-8') as fd: + # fast forward to find self._commands + find_start(fd) + cmd_patt = '"(?P.*)":' + tit_patt = '# ?(?P.*)' + cmd_regex = re.compile(cmd_patt) + tit_regex = re.compile(tit_patt) + # Now extract supported commands + line = 'foo' + while line and END not in line: + line = fd.readline() + cmd = cmd_regex.search(line) + tit = tit_regex.search(line) + if tit: + print(f'\n{tit[1]}') + print('^'*len(tit[1])) + if cmd: + print(f'* {cmd[1]}') + + +if __name__ == '__main__': + main() diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst new file mode 100644 index 0000000..c39c502 --- /dev/null +++ b/doc/source/changelog.rst @@ -0,0 +1,10 @@ +Changelog +========= + +`Semantic Versioning`_. is used. + +Unreleased +---------- + + +.. _Semantic Versioning: https://semver.org/spec/v2.0.0.html diff --git a/doc/source/commands.rst b/doc/source/commands.rst new file mode 100644 index 0000000..3e29512 --- /dev/null +++ b/doc/source/commands.rst @@ -0,0 +1,168 @@ + +Status Commands +^^^^^^^^^^^^^^^ + +* clearerror +* currentsong +* idle +* noidle +* status +* stats + +Playback Option Commands +^^^^^^^^^^^^^^^^^^^^^^^^ + +* consume +* crossfade +* mixrampdb +* mixrampdelay +* random +* repeat +* setvol +* getvol +* single +* replay_gain_mode +* replay_gain_status +* volume + +Playback Control Commands +^^^^^^^^^^^^^^^^^^^^^^^^^ + +* next +* pause +* play +* playid +* previous +* seek +* seekid +* seekcur +* stop + +Queue Commands +^^^^^^^^^^^^^^ + +* add +* addid +* clear +* delete +* deleteid +* move +* moveid +* playlist +* playlistfind +* playlistid +* playlistinfo +* playlistsearch +* plchanges +* plchangesposid +* prio +* prioid +* rangeid +* shuffle +* swap +* swapid +* addtagid +* cleartagid + +Stored Playlist Commands +^^^^^^^^^^^^^^^^^^^^^^^^ + +* listplaylist +* listplaylistinfo +* listplaylists +* load +* playlistadd +* playlistclear +* playlistdelete +* playlistmove +* rename +* rm +* save + +Database Commands +^^^^^^^^^^^^^^^^^ + +* albumart +* count +* getfingerprint +* find +* findadd +* list +* listall +* listallinfo +* listfiles +* lsinfo +* readcomments +* readpicture +* search +* searchadd +* searchaddpl +* update +* rescan + +Mounts and neighbors +^^^^^^^^^^^^^^^^^^^^ + +* mount +* unmount +* listmounts +* listneighbors + +Sticker Commands +^^^^^^^^^^^^^^^^ + +* sticker get +* sticker set +* sticker delete +* sticker list +* sticker find + +Connection Commands +^^^^^^^^^^^^^^^^^^^ + +* close +* kill +* password +* ping +* binarylimit +* tagtypes +* tagtypes disable +* tagtypes enable +* tagtypes clear +* tagtypes all + +Partition Commands +^^^^^^^^^^^^^^^^^^ + +* partition +* listpartitions +* newpartition +* delpartition +* moveoutput + +Audio Output Commands +^^^^^^^^^^^^^^^^^^^^^ + +* disableoutput +* enableoutput +* toggleoutput +* outputs +* outputset + +Reflection Commands +^^^^^^^^^^^^^^^^^^^ + +* config +* commands +* notcommands +* urlhandlers +* decoders + +Client to Client +^^^^^^^^^^^^^^^^ + +* subscribe +* unsubscribe +* channels +* readmessages +* sendmessage diff --git a/doc/source/explanations.rst b/doc/source/explanations.rst index 48724bf..15155a0 100644 --- a/doc/source/explanations.rst +++ b/doc/source/explanations.rst @@ -18,8 +18,8 @@ Should I use it? ---------------- * If you need a plain MPD client to manage you MPD server, then stick with - no-ansynio module python-musicpd_ - * If you're building a interactive client, concurrent access to MPD or plugin + non-asyncio module python-musicpd_ + * If you're building an interactive client, concurrent access to MPD or plugin into another asyncio project then use musicpdaio. @@ -63,15 +63,32 @@ learn how to call commands and what kind of arguments they expect. Some examples are provided for the most common cases, see :ref:`tutorial`. -For a list of currently supported commands in this python module see -:ref:`commands`. - **musicpdaio** tries to come with sane defaults, then running -:py:class:`mpdaio.MPDClient` with no explicit argument will try defaults values -to connect to MPD. Cf. :ref:`reference` for more about defaults. +:py:class:`mpdaio.MPDClient` with no explicit argument will try default values +to connect to MPD. Cf. :ref:`reference` for more about +:ref:`defaults<default_settings>`. + +.. _socket_connections: +.. index:: pair: connection pool; socket + +Socket connection +----------------- +**musicpdaio** uses a connection pool internally to keep already opened socket +and reuse it. + +When first instantiated :py:class:`mpdaio.MPDClient` comes with an empty pool, +when the first MPD command is called a connection is opened, saved and +potentially reused later. In case a concurrent MPD command is called while the +connection is still in use a new connection is made and kept in the pool. + +.. code-block:: python -.. _environment_variables: + client = mpdaio.MPDClient() + client.connections # Returns an empty list: [] + client.version # Returns empty string: '' + await client.ping() # A connection is made and kept open + client.connections # Returns a list; [Connection<example.org:6600>] .. _snake case: https://en.wikipedia.org/wiki/Snake_case diff --git a/doc/source/index.rst b/doc/source/index.rst index 0c20408..5b5a51b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -10,9 +10,13 @@ have some specific action to perform or goal to achieve. :maxdepth: 2 tutorial + explanations + reference + changelog + Indices and tables diff --git a/doc/source/reference.rst b/doc/source/reference.rst index f71444c..1459b92 100644 --- a/doc/source/reference.rst +++ b/doc/source/reference.rst @@ -3,6 +3,8 @@ Reference ========= +.. _environment_variables: + Environment variables --------------------- @@ -39,15 +41,15 @@ Default host: * else set host to ``localhost`` Default port: - * use :envvar:`MPD_PORT` environment variable is set + * use :envvar:`MPD_PORT` environment variable if set * else use ``6600`` Default timeout: - * use :envvar:`MPD_TIMEOUT` is set - * else use :py:obj:`mpdaio.CONNECTION_TIMEOUT` + * use :envvar:`MPD_TIMEOUT` if set + * else use :py:obj:`mpdaio.const.CONNECTION_TIMEOUT` -Changelog ---------- +Supported commands +------------------ -No release yet +.. include:: commands.rst diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index 31cbde9..95e6948 100644 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -23,9 +23,68 @@ For now the code is in early development stage. No releases are made. Getting started ---------------- +.. index:: single: command; searchadd + +**Connect, clear the queue, add tracks** + +.. sourcecode:: python + + import mpdaio + + client = mpdaio.MPDClient() + await client.ping() # Plain connection test + print(client.version) # Prints MPD's protocol version + print(await client.clear()) # Clears the current queue + # Add all tracks from artist "Amon Tobin" + print(await client.searchadd('(Artist == "Amon Tobin")')) + await client.play() + await client.setvol(60) + print(await client.currentsong()) + await client.close() # Finally close connection + +.. index:: single: command; password + +**Using a specific host, port and a password.** + +The password is sent when a connection is made, no need to explicitly send the +password command. + +.. sourcecode:: python + + client = mpdaio.MPDClient(host='example.org', port='6601', password='53(237') + await client.ping() + +**Wrapping some commands in a python script** .. literalinclude:: tutorial/tutorial-00.py +.. index:: single: command; albumart + +**Fetch album art for the given track** + +The logic is similar with `readpicture` command. + +.. sourcecode:: python + + client = mpdaio.MPDClient() + # Looking for cover art in 'Tool/2001-Lateralus/' + track = 'Tool/2001-Lateralus/09-Tool - Lateralus.flac' + aart = await cli.albumart(track, 0) + received = int(aart.get('binary')) + size = int(aart.get('size')) + with open('/tmp/cover', 'wb') as cover: + # aart = {'size': 42, 'binary': 2051, data: bytes(...)} + cover.write(aart.get('data')) + while received < size: + aart = await cli.albumart(track, received) + cover.write(aart.get('data')) + received += int(aart.get('binary')) + if received != size: + print('something went wrong') + await cli.close() + +Cf. `MPD protocol documentation`_ for more binary responses. + Concurrency ----------- @@ -33,4 +92,5 @@ Concurrency .. literalinclude:: tutorial/tutorial-01.py +.. _MPD protocol documentation: http://www.musicpd.org/doc/protocol/ .. vim: spell spelllang=en diff --git a/doc/source/tutorial/mpdaio b/doc/source/tutorial/mpdaio deleted file mode 120000 index 7af2e50..0000000 --- a/doc/source/tutorial/mpdaio +++ /dev/null @@ -1 +0,0 @@ -../../../mpdaio \ No newline at end of file diff --git a/doc/source/tutorial/tutorial-00.py b/doc/source/tutorial/tutorial-00.py index dea5826..6445a6e 100644 --- a/doc/source/tutorial/tutorial-00.py +++ b/doc/source/tutorial/tutorial-00.py @@ -1,7 +1,8 @@ +# mpd-client.py import asyncio import logging -from mpdaio.client import MPDClient +from mpdaio import MPDClient # Configure loggers logging.basicConfig(level=logging.INFO, format='%(levelname)-8s %(message)s') @@ -25,6 +26,7 @@ async def run(): # The connection is kept open an reused for later commands await client.ping() + # Get player status status = await client.status() if status.get('state') == 'play': current_song_id = status.get('songid') @@ -37,6 +39,13 @@ async def run(): else: log.info('Not playing') + # Add all songs form artist "The Doors" + await client.searchadd('(Artist == "The Doors")') + # Start playing + if (await client.status()).get('state') != 'play': + await client.play() + + # Closes any remaining connections to MPD server await client.close() diff --git a/doc/source/tutorial/tutorial-01.py b/doc/source/tutorial/tutorial-01.py index 1ef066c..c8197e5 100644 --- a/doc/source/tutorial/tutorial-01.py +++ b/doc/source/tutorial/tutorial-01.py @@ -1,7 +1,7 @@ import asyncio import logging -from mpdaio.client import MPDClient +from mpdaio import MPDClient # Configure loggers logging.basicConfig(level=logging.INFO, format='%(levelname)-8s %(message)s') @@ -11,20 +11,30 @@ log = logging.getLogger('mpdaio.client') log.setLevel(logging.DEBUG) +async def search(fltr): + # Look for and add + await client.searchadd(fltr) + + async def run(): - # Use defaults to access MPD server - client = MPDClient() # Make an initial connection to MPD server # The connection is kept open an reused for later commands await client.ping() - + await client.clear() + filters = [ + '(Artist == "Neurosis")', + '(Artist == "Isis")', + '(Artist == "Cult of Luna")', + ] # Each task gathered here will run with it's own connection - await asyncio.gather(...) + await asyncio.gather(*map(search, filters)) # Closes all connections to MPD server await client.close() if __name__ == '__main__': + # Use defaults to access MPD server + client = MPDClient() asyncio.run(run()) -- 2.39.5