From cdf2dd0c1021639a4aebae4eac44fbb09a224877 Mon Sep 17 00:00:00 2001 From: kaliko Date: Tue, 5 Mar 2024 17:35:07 +0100 Subject: [PATCH] Add Sphinx documentation --- doc/Makefile | 20 ++++++++ doc/source/conf.py | 42 +++++++++++++++ doc/source/explanations.rst | 82 ++++++++++++++++++++++++++++++ doc/source/index.rst | 25 +++++++++ doc/source/reference.rst | 53 +++++++++++++++++++ doc/source/tutorial.rst | 36 +++++++++++++ doc/source/tutorial/mpdaio | 1 + doc/source/tutorial/tutorial-00.py | 45 ++++++++++++++++ doc/source/tutorial/tutorial-01.py | 30 +++++++++++ 9 files changed, 334 insertions(+) create mode 100644 doc/Makefile create mode 100644 doc/source/conf.py create mode 100644 doc/source/explanations.rst create mode 100644 doc/source/index.rst create mode 100644 doc/source/reference.rst create mode 100644 doc/source/tutorial.rst create mode 120000 doc/source/tutorial/mpdaio create mode 100644 doc/source/tutorial/tutorial-00.py create mode 100644 doc/source/tutorial/tutorial-01.py diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..6377b03 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,42 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'musicpdaio' +copyright = '2024, kaliko' +author = 'kaliko' +release = '0.0.1' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', + 'sphinx.ext.todo', +] + +templates_path = ['_templates'] +exclude_patterns = [] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'alabaster' +html_static_path = ['_static'] +html_theme_options = { + 'page_width' : '1024px', + 'fixed_sidebar': 'true', + } + +# -- Options for intersphinx extension --------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration + +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), +} diff --git a/doc/source/explanations.rst b/doc/source/explanations.rst new file mode 100644 index 0000000..48724bf --- /dev/null +++ b/doc/source/explanations.rst @@ -0,0 +1,82 @@ +.. _explanations: + +Explanations +============ + +What is musicpdaio? +------------------- + + | « Concurrency is about dealing with lots of things at once. » + | Concurrency is not Parallelism -- Rob Pike + +**musicpdaio** is the asyncio_ port of python-musicpd_. + +The goal of this project is to keep python-musicpd_ simplicity and provide +asyncio_ support. + +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 + into another asyncio project then use musicpdaio. + + +Using the client library +------------------------- + +The client library can be used as follows: + +.. code-block:: python + + client = mpdaio.MPDClient() # create client object + await client.ping() # test the connection, using default host/port, cf. Reference + print(client.version) # print the MPD protocol version + await client.setvol('42') # sets the volume + await client.close() # disconnect from the server + +The MPD command protocol exchanges line-based text records. The client emits a +command with optional arguments. In the example above the client sends a +`setvol` command with the string argument `42`. + +MPD commands are exposed as :py:class:`mpdaio.MPDClient` methods. Methods +**arguments are python strings**. Some commands are composed of more than one word +(ie "**tagtypes [disable|enable|all]**"), for these use a `snake case`_ style to +access the method. Then **"tagtypes enable"** command is called with +**"tagtypes_enable"**. + +Remember MPD protocol is text based, then all MPD command arguments are UTF-8 +strings. In the example above, an integer can be used as argument for the +`setvol` command, but it is then evaluated as a string when the command is +written to the socket. To avoid confusion use regular string instead of relying +on object string representation. + +:py:class:`mpdaio.MPDClient` methods returns different kinds of objects +depending on the command. Could be :py:obj:`None`, a single object as a +:py:obj:`str` or a :py:obj:`dict`, a list of :py:obj:`dict`. + +Then :py:class:`mpdaio.MPDClient` **methods signatures** are not hard coded +within this module since the protocol is handled on the server side. Please +refer to the protocol and MPD commands in `MPD protocol documentation`_ to +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. + + +.. _environment_variables: + + +.. _snake case: https://en.wikipedia.org/wiki/Snake_case +.. _MPD protocol documentation: http://www.musicpd.org/doc/protocol/ +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _python-musicpd: https://kaliko.gitlab.io/python-musicpd + +.. vim: spell spelllang=en diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..0c20408 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,25 @@ +Welcome to musicpdaio's documentation! +====================================== + +The documentation is structured by following the `Diátaxis`_ principles: +tutorials and explanation are mainly useful to discover and learn, howtos and +reference are more useful when you are familiar with musicpdaio already and you +have some specific action to perform or goal to achieve. + +.. toctree:: + :maxdepth: 2 + + tutorial + explanations + reference + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +.. _Diátaxis: https://diataxis.fr/ diff --git a/doc/source/reference.rst b/doc/source/reference.rst new file mode 100644 index 0000000..f71444c --- /dev/null +++ b/doc/source/reference.rst @@ -0,0 +1,53 @@ +.. _reference: + +Reference +========= + +Environment variables +--------------------- + +:py:class:`mpdaio.MPDClient` honors the following environment variables: + +.. envvar:: MPD_HOST + + MPD host (:abbr:`FQDN (fully qualified domain name)`, IP, socket path or abstract socket) and password. + + | To define a **password** set :envvar:`MPD_HOST` to "*password@host*" (password only "*password@*") + | For **abstract socket** use "@" as prefix : "*@socket*" and then with a password "*pass@@socket*" + | Regular **unix socket** are set with an absolute path: "*/run/mpd/socket*" + +.. envvar:: MPD_PORT + + MPD port, relevant for TCP socket only + +.. envvar:: MPD_TIMEOUT + + socket timeout when connecting to MPD and waiting for MPD’s response (in seconds) + +.. envvar:: XDG_RUNTIME_DIR + + path to look for potential socket + +.. _default_settings: + +Default settings +---------------- + +Default host: + * use :envvar:`MPD_HOST` environment variable if set, extract password if present, + * else looks for an existing file in :envvar:`${XDG_RUNTIME_DIR:-/run/}/mpd/socket` + * else set host to ``localhost`` + +Default port: + * use :envvar:`MPD_PORT` environment variable is set + * else use ``6600`` + +Default timeout: + * use :envvar:`MPD_TIMEOUT` is set + * else use :py:obj:`mpdaio.CONNECTION_TIMEOUT` + + +Changelog +--------- + +No release yet diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst new file mode 100644 index 0000000..31cbde9 --- /dev/null +++ b/doc/source/tutorial.rst @@ -0,0 +1,36 @@ +.. _tutorial: + +Tutorials +========= + +Install +------- + +For now the code is in early development stage. No releases are made. + +.. code:: bash + + # Use a virtualenv + python -m venv .../path/to/my/venv + . .../path/to/my/venv/bin/activate + pip install git+https://codeberg.org/MusicPlayerDaemon/python-musicpdaio.git@main + + +.. todo:: + + Updating a previous installation via `pip install git+https`. + +Getting started +---------------- + + +.. literalinclude:: tutorial/tutorial-00.py + + +Concurrency +----------- + +.. literalinclude:: tutorial/tutorial-01.py + + +.. vim: spell spelllang=en diff --git a/doc/source/tutorial/mpdaio b/doc/source/tutorial/mpdaio new file mode 120000 index 0000000..7af2e50 --- /dev/null +++ b/doc/source/tutorial/mpdaio @@ -0,0 +1 @@ +../../../mpdaio \ No newline at end of file diff --git a/doc/source/tutorial/tutorial-00.py b/doc/source/tutorial/tutorial-00.py new file mode 100644 index 0000000..dea5826 --- /dev/null +++ b/doc/source/tutorial/tutorial-00.py @@ -0,0 +1,45 @@ +import asyncio +import logging + +from mpdaio.client import MPDClient + +# Configure loggers +logging.basicConfig(level=logging.INFO, format='%(levelname)-8s %(message)s') +logging.getLogger("asyncio").setLevel(logging.WARNING) +# debug level level will show where defaults settings come from +log = logging.getLogger('mpdaio.client') +log.setLevel(logging.DEBUG) + + +async def run(): + # Explicit host declaration + #client = MPDClient(host='example.org', port='6601') + + # Use defaults + client = MPDClient() + # MPDClient use MPD_HOST/MPD_PORT env var if set + # else test ${XDG_RUNTIME_DIR}/mpd/socket for existence + # finnally fallback to localhost:6600 + + # Make an initial connection to MPD server + # The connection is kept open an reused for later commands + await client.ping() + + status = await client.status() + if status.get('state') == 'play': + current_song_id = status.get('songid') + current_song = await client.playlistid(current_song_id) + log.info(f'Playing : {current_song[0].get("file")}') + next_song_id = status.get('nextsongid', None) + if next_song_id: + next_song = await client.playlistid(next_song_id) + log.info(f'Next song : {next_song[0].get("file")}') + else: + log.info('Not playing') + + # Closes any remaining connections to MPD server + await client.close() + + +if __name__ == '__main__': + asyncio.run(run()) diff --git a/doc/source/tutorial/tutorial-01.py b/doc/source/tutorial/tutorial-01.py new file mode 100644 index 0000000..1ef066c --- /dev/null +++ b/doc/source/tutorial/tutorial-01.py @@ -0,0 +1,30 @@ +import asyncio +import logging + +from mpdaio.client import MPDClient + +# Configure loggers +logging.basicConfig(level=logging.INFO, format='%(levelname)-8s %(message)s') +logging.getLogger("asyncio").setLevel(logging.WARNING) +# debug level level will show where defaults settings come from +log = logging.getLogger('mpdaio.client') +log.setLevel(logging.DEBUG) + + +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() + + # Each task gathered here will run with it's own connection + await asyncio.gather(...) + + # Closes all connections to MPD server + await client.close() + + +if __name__ == '__main__': + asyncio.run(run()) -- 2.39.2