diff --git a/docs/changelog.rst b/docs/changelog.rst index c35cde49..6241f748 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,6 +14,14 @@ v0.15.0 (UNRELEASED) - Mopidy no longer supports Python 2.6. Currently, the only Python version supported by Mopidy is Python 2.7. (Fixes: :issue:`344`) +**Command line options** + +- Converted from the optparse to the argparse library for handling command line + options. + +- :option:`mopidy --show-config` will now take into consideration any + :option:`mopidy --option` arguments appearing later on the command line. + v0.14.1 (2013-04-28) ==================== diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 59063b84..0118395c 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import logging -import optparse import os import signal import sys @@ -24,11 +23,11 @@ sys.path.insert( 0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) -from mopidy import ext +from mopidy import commands, ext from mopidy.audio import Audio from mopidy import config as config_lib from mopidy.core import Core -from mopidy.utils import deps, log, path, process, versioning +from mopidy.utils import log, path, process logger = logging.getLogger('mopidy.main') @@ -37,11 +36,13 @@ def main(): signal.signal(signal.SIGTERM, process.exit_handler) signal.signal(signal.SIGUSR1, pykka.debug.log_thread_tracebacks) - loop = gobject.MainLoop() - options = parse_options() - config_files = options.config.split(b':') - config_overrides = options.overrides + args = commands.parser.parse_args(args=mopidy_args) + if args.show_config: + commands.show_config(args) + if args.show_deps: + commands.show_deps() + loop = gobject.MainLoop() enabled_extensions = [] # Make sure it is defined before the finally block logging_initialized = False @@ -50,17 +51,18 @@ def main(): try: # Initial config without extensions to bootstrap logging. - logging_config, _ = config_lib.load(config_files, [], config_overrides) + logging_config, _ = config_lib.load( + args.config_files, [], args.config_overrides) # TODO: setup_logging needs defaults in-case config values are None log.setup_logging( - logging_config, options.verbosity_level, options.save_debug_log) + logging_config, args.verbosity_level, args.save_debug_log) logging_initialized = True installed_extensions = ext.load_extensions() config, config_errors = config_lib.load( - config_files, installed_extensions, config_overrides) + args.config_files, installed_extensions, args.config_overrides) # Filter out disabled extensions and remove any config errors for them. for extension in installed_extensions: @@ -123,78 +125,6 @@ def check_config_errors(errors): sys.exit(1) -def check_config_override(option, opt, override): - try: - return config_lib.parse_override(override) - except ValueError: - raise optparse.OptionValueError( - 'option %s: must have the format section/key=value' % opt) - - -def parse_options(): - parser = optparse.OptionParser( - version='Mopidy %s' % versioning.get_version()) - - # Ugly extension of optparse type checking magic :/ - optparse.Option.TYPES += ('config_override',) - optparse.Option.TYPE_CHECKER['config_override'] = check_config_override - - parser.add_option( - '-q', '--quiet', - action='store_const', const=0, dest='verbosity_level', - help='less output (warning level)') - parser.add_option( - '-v', '--verbose', - action='count', default=1, dest='verbosity_level', - help='more output (debug level)') - parser.add_option( - '--save-debug-log', - action='store_true', dest='save_debug_log', - help='save debug log to "./mopidy.log"') - parser.add_option( - '--show-config', - action='callback', callback=show_config_callback, - help='show current config') - parser.add_option( - '--show-deps', - action='callback', callback=deps.show_deps_optparse_callback, - help='show dependencies and their versions') - parser.add_option( - '--config', - action='store', dest='config', - default=b'$XDG_CONFIG_DIR/mopidy/mopidy.conf', - help='config files to use, colon seperated, later files override') - parser.add_option( - '-o', '--option', - action='append', dest='overrides', type='config_override', - help='`section/key=value` values to override config options') - return parser.parse_args(args=mopidy_args)[0] - - -def show_config_callback(option, opt, value, parser): - # TODO: don't use callback for this as --config or -o set after - # --show-config will be ignored. - files = getattr(parser.values, 'config', b'').split(b':') - overrides = getattr(parser.values, 'overrides', []) - - extensions = ext.load_extensions() - config, errors = config_lib.load(files, extensions, overrides) - - # Clear out any config for disabled extensions. - for extension in extensions: - if not ext.validate_extension(extension): - config[extension.ext_name] = {b'enabled': False} - errors[extension.ext_name] = { - b'enabled': b'extension disabled its self.'} - elif not config[extension.ext_name]['enabled']: - config[extension.ext_name] = {b'enabled': False} - errors[extension.ext_name] = { - b'enabled': b'extension disabled by config.'} - - print config_lib.format(config, extensions, errors) - sys.exit(0) - - def check_old_locations(): dot_mopidy_dir = path.expand_path(b'~/.mopidy') if os.path.isdir(dot_mopidy_dir): diff --git a/mopidy/commands.py b/mopidy/commands.py new file mode 100644 index 00000000..c5ca0236 --- /dev/null +++ b/mopidy/commands.py @@ -0,0 +1,83 @@ +from __future__ import unicode_literals + +import argparse +import sys + +from mopidy import config as config_lib, ext +from mopidy.utils import deps, versioning + + +def config_files_type(value): + return value.split(b':') + + +def config_override_type(value): + try: + section, remainder = value.split(b'/', 1) + key, value = remainder.split(b'=', 1) + return (section.strip(), key.strip(), value.strip()) + except ValueError: + raise argparse.ArgumentTypeError( + '%s must have the format section/key=value' % value) + + +parser = argparse.ArgumentParser() +parser.add_argument( + '--version', action='version', + version='Mopidy %s' % versioning.get_version()) +parser.add_argument( + '-q', '--quiet', + action='store_const', const=0, dest='verbosity_level', + help='less output (warning level)') +parser.add_argument( + '-v', '--verbose', + action='count', default=1, dest='verbosity_level', + help='more output (debug level)') +parser.add_argument( + '--save-debug-log', + action='store_true', dest='save_debug_log', + help='save debug log to "./mopidy.log"') +parser.add_argument( + '--show-config', + action='store_true', dest='show_config', + help='show current config') +parser.add_argument( + '--show-deps', + action='store_true', dest='show_deps', + help='show dependencies and their versions') +parser.add_argument( + '--config', + action='store', dest='config_files', type=config_files_type, + default=b'$XDG_CONFIG_DIR/mopidy/mopidy.conf', + help='config files to use, colon seperated, later files override') +parser.add_argument( + '-o', '--option', + action='append', dest='config_overrides', type=config_override_type, + help='`section/key=value` values to override config options') + + +def show_config(args): + """Prints the effective config and exits.""" + extensions = ext.load_extensions() + config, errors = config_lib.load( + args.config_files, extensions, args.config_overrides) + + # Clear out any config for disabled extensions. + for extension in extensions: + if not ext.validate_extension(extension): + config[extension.ext_name] = {b'enabled': False} + errors[extension.ext_name] = { + b'enabled': b'extension disabled its self.'} + elif not config[extension.ext_name]['enabled']: + config[extension.ext_name] = {b'enabled': False} + errors[extension.ext_name] = { + b'enabled': b'extension disabled by config.'} + + print config_lib.format(config, extensions, errors) + sys.exit(0) + + +def show_deps(): + """Prints a list of all dependencies and exits.""" + print deps.format_dependency_list() + sys.exit(0) diff --git a/mopidy/config/__init__.py b/mopidy/config/__init__.py index e4723b15..0d9b9e7a 100644 --- a/mopidy/config/__init__.py +++ b/mopidy/config/__init__.py @@ -140,13 +140,6 @@ def _format(config, comments, schemas, display): return b'\n'.join(output) -def parse_override(override): - """Parse ``section/key=value`` command line overrides""" - section, remainder = override.split(b'/', 1) - key, value = remainder.split(b'=', 1) - return (section.strip(), key.strip(), value.strip()) - - class Proxy(collections.Mapping): def __init__(self, data): self._data = data diff --git a/mopidy/scanner.py b/mopidy/scanner.py index 61257add..41b42347 100644 --- a/mopidy/scanner.py +++ b/mopidy/scanner.py @@ -1,8 +1,8 @@ from __future__ import unicode_literals +import argparse import datetime import logging -import optparse import os import sys @@ -33,7 +33,7 @@ from mopidy.utils import log, path, versioning def main(): - options = parse_options() + args = parse_args() # TODO: support config files and overrides (shared from main?) config_files = [b'/etc/mopidy/mopidy.conf', b'$XDG_CONFIG_DIR/mopidy/mopidy.conf'] @@ -43,7 +43,7 @@ def main(): # Initial config without extensions to bootstrap logging. logging_config, _ = config_lib.load(config_files, [], config_overrides) log.setup_root_logger() - log.setup_console_logging(logging_config, options.verbosity_level) + log.setup_console_logging(logging_config, args.verbosity_level) extensions = ext.load_extensions() config, errors = config_lib.load( @@ -87,18 +87,20 @@ def main(): logging.info('Done writing tag cache') -def parse_options(): - parser = optparse.OptionParser( +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--version', action='version', version='Mopidy %s' % versioning.get_version()) - parser.add_option( + parser.add_argument( '-q', '--quiet', action='store_const', const=0, dest='verbosity_level', help='less output (warning level)') - parser.add_option( + parser.add_argument( '-v', '--verbose', action='count', default=1, dest='verbosity_level', help='more output (debug level)') - return parser.parse_args(args=mopidy_args)[0] + return parser.parse_args(args=mopidy_args) def translator(data): diff --git a/mopidy/utils/deps.py b/mopidy/utils/deps.py index 7258d8da..99a22d3c 100644 --- a/mopidy/utils/deps.py +++ b/mopidy/utils/deps.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import functools import os import platform -import sys import pygst pygst.require('0.10') @@ -14,17 +13,6 @@ import pkg_resources from . import formatting -def show_deps_optparse_callback(*args): - """ - Prints a list of all dependencies. - - Called by optparse when Mopidy is run with the :option:`--show-deps` - option. - """ - print format_dependency_list() - sys.exit(0) - - def format_dependency_list(adapters=None): if adapters is None: dist_names = set([ diff --git a/tests/commands_test.py b/tests/commands_test.py new file mode 100644 index 00000000..35fd0de5 --- /dev/null +++ b/tests/commands_test.py @@ -0,0 +1,44 @@ +from __future__ import unicode_literals + +import argparse +import unittest + +from mopidy import commands + + +class ConfigOverrideTypeTest(unittest.TestCase): + def test_valid_override(self): + expected = (b'section', b'key', b'value') + self.assertEqual( + expected, commands.config_override_type(b'section/key=value')) + self.assertEqual( + expected, commands.config_override_type(b'section/key=value ')) + self.assertEqual( + expected, commands.config_override_type(b'section/key =value')) + self.assertEqual( + expected, commands.config_override_type(b'section /key=value')) + + def test_valid_override_is_bytes(self): + section, key, value = commands.config_override_type( + b'section/key=value') + self.assertIsInstance(section, bytes) + self.assertIsInstance(key, bytes) + self.assertIsInstance(value, bytes) + + def test_empty_override(self): + expected = ('section', 'key', '') + self.assertEqual( + expected, commands.config_override_type(b'section/key=')) + self.assertEqual( + expected, commands.config_override_type(b'section/key= ')) + + def test_invalid_override(self): + self.assertRaises( + argparse.ArgumentTypeError, + commands.config_override_type, b'section/key') + self.assertRaises( + argparse.ArgumentTypeError, + commands.config_override_type, b'section=') + self.assertRaises( + argparse.ArgumentTypeError, + commands.config_override_type, b'section') diff --git a/tests/config/config_test.py b/tests/config/config_test.py index a792839a..c40baa87 100644 --- a/tests/config/config_test.py +++ b/tests/config/config_test.py @@ -106,31 +106,3 @@ class ValidateTest(unittest.TestCase): self.assertEqual({'foo': {'bar': 'bad'}}, errors) # TODO: add more tests - - -class ParseOverrideTest(unittest.TestCase): - def test_valid_override(self): - expected = (b'section', b'key', b'value') - self.assertEqual(expected, config.parse_override(b'section/key=value')) - self.assertEqual( - expected, config.parse_override(b'section/key=value ')) - self.assertEqual( - expected, config.parse_override(b'section/key =value')) - self.assertEqual( - expected, config.parse_override(b'section /key=value')) - - def test_valid_override_is_bytes(self): - section, key, value = config.parse_override(b'section/key=value') - self.assertIsInstance(section, bytes) - self.assertIsInstance(key, bytes) - self.assertIsInstance(value, bytes) - - def test_empty_override(self): - expected = ('section', 'key', '') - self.assertEqual(expected, config.parse_override(b'section/key=')) - self.assertEqual(expected, config.parse_override(b'section/key= ')) - - def test_invalid_override(self): - self.assertRaises(ValueError, config.parse_override, b'section/key') - self.assertRaises(ValueError, config.parse_override, b'section=') - self.assertRaises(ValueError, config.parse_override, b'section')