mpd: Update docs
This commit is contained in:
parent
2e6f716b72
commit
a7f4ffb124
@ -7,6 +7,13 @@ For details on how to use Mopidy's MPD server, see :ref:`ext-mpd`.
|
|||||||
.. automodule:: mopidy.mpd
|
.. automodule:: mopidy.mpd
|
||||||
:synopsis: MPD server frontend
|
:synopsis: MPD server frontend
|
||||||
|
|
||||||
|
MPD tokenizer
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. automodule:: mopidy.mpd.tokenize
|
||||||
|
:synopsis: MPD request tokenizer
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
MPD dispatcher
|
MPD dispatcher
|
||||||
==============
|
==============
|
||||||
|
|||||||
@ -37,6 +37,7 @@ def load_protocol_modules():
|
|||||||
|
|
||||||
|
|
||||||
def INT(value):
|
def INT(value):
|
||||||
|
"""Converts a value that matches [+-]?\d+ into and integer."""
|
||||||
if value is None:
|
if value is None:
|
||||||
raise ValueError('None is not a valid integer')
|
raise ValueError('None is not a valid integer')
|
||||||
# TODO: check for whitespace via value != value.strip()?
|
# TODO: check for whitespace via value != value.strip()?
|
||||||
@ -44,6 +45,7 @@ def INT(value):
|
|||||||
|
|
||||||
|
|
||||||
def UINT(value):
|
def UINT(value):
|
||||||
|
"""Converts a value that matches \d+ into and integer."""
|
||||||
if value is None:
|
if value is None:
|
||||||
raise ValueError('None is not a valid integer')
|
raise ValueError('None is not a valid integer')
|
||||||
if not value.isdigit():
|
if not value.isdigit():
|
||||||
@ -52,13 +54,19 @@ def UINT(value):
|
|||||||
|
|
||||||
|
|
||||||
def BOOL(value):
|
def BOOL(value):
|
||||||
|
"""Convert the values 0 and 1 into booleans."""
|
||||||
if value in ('1', '0'):
|
if value in ('1', '0'):
|
||||||
return bool(int(value))
|
return bool(int(value))
|
||||||
raise ValueError('%r is not 0 or 1' % value)
|
raise ValueError('%r is not 0 or 1' % value)
|
||||||
|
|
||||||
|
|
||||||
def RANGE(value):
|
def RANGE(value):
|
||||||
# TODO: test and check that values are positive
|
"""Convert a single integer or range spec into a slice
|
||||||
|
|
||||||
|
`n` should become `slice(n, n+1)`
|
||||||
|
`n:` should become `slice(n, None)`
|
||||||
|
`n:m` should become `slice(n, m)` and `m > n` must hold
|
||||||
|
"""
|
||||||
if ':' in value:
|
if ':' in value:
|
||||||
start, stop = value.split(':', 1)
|
start, stop = value.split(':', 1)
|
||||||
start = UINT(start)
|
start = UINT(start)
|
||||||
@ -75,10 +83,38 @@ def RANGE(value):
|
|||||||
|
|
||||||
|
|
||||||
class Commands(object):
|
class Commands(object):
|
||||||
|
"""Collection of MPD commands to expose to users.
|
||||||
|
|
||||||
|
Normally used through the global instance which command handlers have been
|
||||||
|
installed into.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.handlers = {}
|
self.handlers = {}
|
||||||
|
|
||||||
|
# TODO: consider removing auth_required and list_command in favour of
|
||||||
|
# additional command instances to register in?
|
||||||
def add(self, name, auth_required=True, list_command=True, **validators):
|
def add(self, name, auth_required=True, list_command=True, **validators):
|
||||||
|
"""Create a decorator that registers a handler + validation rules.
|
||||||
|
|
||||||
|
Additional keyword arguments are treated as converts/validators to
|
||||||
|
apply to tokens converting them to proper python types.
|
||||||
|
|
||||||
|
Requirements for valid handlers:
|
||||||
|
|
||||||
|
- must accept a context argument as the first arg.
|
||||||
|
- may not use variable keyword arguments, ``**kwargs``.
|
||||||
|
- may use variable arguments ``*args`` *or* a mix of required and
|
||||||
|
optional arguments.
|
||||||
|
|
||||||
|
Decorator returns the unwrapped function so that tests etc can use the
|
||||||
|
functions with values with correct python types instead of strings.
|
||||||
|
|
||||||
|
:param string name: Name of the command being registered.
|
||||||
|
:param bool auth_required: If authorization is required.
|
||||||
|
:param bool list_command: If command should be listed in reflection.
|
||||||
|
"""
|
||||||
|
|
||||||
def wrapper(func):
|
def wrapper(func):
|
||||||
if name in self.handlers:
|
if name in self.handlers:
|
||||||
raise ValueError('%s already registered' % name)
|
raise ValueError('%s already registered' % name)
|
||||||
@ -118,12 +154,21 @@ class Commands(object):
|
|||||||
return func
|
return func
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def call(self, args, context=None):
|
def call(self, tokens, context=None):
|
||||||
if not args:
|
"""Find and run the handler registered for the given command.
|
||||||
|
|
||||||
|
If the handler was registered with any converters/validators there will
|
||||||
|
be run before calling the real handler.
|
||||||
|
|
||||||
|
:param list tokens: List of tokens to process
|
||||||
|
:param context: MPD context.
|
||||||
|
:type context: :class:`~mopidy.mpd.dispatcher.MpdContext`
|
||||||
|
"""
|
||||||
|
if not tokens:
|
||||||
raise exceptions.MpdNoCommand()
|
raise exceptions.MpdNoCommand()
|
||||||
if args[0] not in self.handlers:
|
if tokens[0] not in self.handlers:
|
||||||
raise exceptions.MpdUnknownCommand(command=args[0])
|
raise exceptions.MpdUnknownCommand(command=tokens[0])
|
||||||
return self.handlers[args[0]](context, *args[1:])
|
return self.handlers[tokens[0]](context, *tokens[1:])
|
||||||
|
|
||||||
|
|
||||||
#: Global instance to install commands into
|
#: Global instance to install commands into
|
||||||
|
|||||||
@ -39,6 +39,23 @@ UNESCAPE_RE = re.compile(r'\\(.)') # Backslash escapes any following char.
|
|||||||
|
|
||||||
|
|
||||||
def split(line):
|
def split(line):
|
||||||
|
"""Splits a line into tokens using same rules as MPD.
|
||||||
|
|
||||||
|
- Lines may not start with whitespace
|
||||||
|
- Tokens are split by arbitrary amount of spaces or tabs
|
||||||
|
- First token must match `[a-z][a-z0-9_]*`
|
||||||
|
- Remaining tokens can be unquoted or quoted tokens.
|
||||||
|
- Unquoted tokens consist of all printable characters except double quotes,
|
||||||
|
single quotes, spaces and tabs.
|
||||||
|
- Quoted tokens are surrounded by a matching pair of double quotes.
|
||||||
|
- The closing quote must be followed by space, tab or end of line.
|
||||||
|
- Any value is allowed inside a quoted token. Including double quotes,
|
||||||
|
assuming it is correctly escaped.
|
||||||
|
- Backslash inside a quoted token is used to escape the following
|
||||||
|
character.
|
||||||
|
|
||||||
|
For examples see the tests for this function.
|
||||||
|
"""
|
||||||
if not line.strip():
|
if not line.strip():
|
||||||
raise exceptions.MpdNoCommand('No command given')
|
raise exceptions.MpdNoCommand('No command given')
|
||||||
match = WORD_RE.match(line)
|
match = WORD_RE.match(line)
|
||||||
@ -60,6 +77,7 @@ def split(line):
|
|||||||
|
|
||||||
|
|
||||||
def _determine_error_message(remainder):
|
def _determine_error_message(remainder):
|
||||||
|
"""Helper to emulate MPD errors."""
|
||||||
# Following checks are simply to match MPD error messages:
|
# Following checks are simply to match MPD error messages:
|
||||||
match = BAD_QUOTED_PARAM_RE.match(remainder)
|
match = BAD_QUOTED_PARAM_RE.match(remainder)
|
||||||
if match:
|
if match:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user