diff --git a/docs/changes.rst b/docs/changes.rst index 4b6f74ca..2c240bfa 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -70,6 +70,9 @@ Please note that 0.5.0 requires some updated dependencies, as listed under - Improve :option:`--list-settings` output. (Fixes: :issue:`91`) + - Added :option:`--interactive` for reading missing local settings from + ``stdin``. (Fixes: :issue:`96`) + - Tag cache generator: - Made it possible to abort :command:`mopidy-scan` with CTRL+C. diff --git a/mopidy/core.py b/mopidy/core.py index ca5b92a1..98575478 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -28,15 +28,15 @@ from mopidy.utils.settings import list_settings_optparse_callback logger = logging.getLogger('mopidy.core') def main(): - options = parse_options() - setup_logging(options.verbosity_level, options.save_debug_log) - setup_settings() - setup_gobject_loop() - setup_gstreamer() - setup_mixer() - setup_backend() - setup_frontends() try: + options = parse_options() + setup_logging(options.verbosity_level, options.save_debug_log) + setup_settings(options.interactive) + setup_gobject_loop() + setup_gstreamer() + setup_mixer() + setup_backend() + setup_frontends() while ActorRegistry.get_all(): time.sleep(1) logger.info(u'No actors left. Exiting...') @@ -49,6 +49,9 @@ def parse_options(): parser.add_option('--help-gst', action='store_true', dest='help_gst', help='show GStreamer help options') + parser.add_option('-i', '--interactive', + action='store_true', dest='interactive', + help='ask interactively for required settings which is missing') parser.add_option('-q', '--quiet', action='store_const', const=0, dest='verbosity_level', help='less output (warning level)') @@ -63,11 +66,11 @@ def parse_options(): help='list current settings') return parser.parse_args(args=mopidy_args)[0] -def setup_settings(): +def setup_settings(interactive): get_or_create_folder('~/.mopidy/') get_or_create_file('~/.mopidy/settings.py') try: - settings.validate() + settings.validate(interactive) except SettingsError, e: logger.error(e.message) sys.exit(1) diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 01fee23d..a10b3a78 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -1,6 +1,7 @@ # Absolute import needed to import ~/.mopidy/settings.py and not ourselves from __future__ import absolute_import from copy import copy +import getpass import logging import os from pprint import pformat @@ -63,12 +64,28 @@ class SettingsProxy(object): else: super(SettingsProxy, self).__setattr__(attr, value) - def validate(self): + def validate(self, interactive): + if interactive: + self._read_missing_settings_from_stdin(self.default, self.local) if self.get_errors(): logger.error(u'Settings validation errors: %s', indent(self.get_errors_as_string())) raise SettingsError(u'Settings validation failed.') + def _read_missing_settings_from_stdin(self, default, local): + for setting, value in default.iteritems(): + if isinstance(value, basestring) and len(value) == 0: + local[setting] = self._read_from_stdin(setting + u': ') + + def _read_from_stdin(self, prompt): + if u'_PASSWORD' in prompt: + return (getpass.getpass(prompt) + .decode(sys.stdin.encoding, 'ignore')) + else: + sys.stdout.write(prompt) + return (sys.stdin.readline().strip() + .decode(sys.stdin.encoding, 'ignore')) + def get_errors(self): return validate_settings(self.default, self.local) diff --git a/tests/help_test.py b/tests/help_test.py index dccccc9c..25f534c2 100644 --- a/tests/help_test.py +++ b/tests/help_test.py @@ -14,6 +14,7 @@ class HelpTest(unittest.TestCase): self.assert_('--version' in output) self.assert_('--help' in output) self.assert_('--help-gst' in output) + self.assert_('--interactive' in output) self.assert_('--quiet' in output) self.assert_('--verbose' in output) self.assert_('--save-debug-log' in output) diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py index 748eae85..d1481ce5 100644 --- a/tests/utils/settings_test.py +++ b/tests/utils/settings_test.py @@ -149,6 +149,12 @@ class SettingsProxyTest(unittest.TestCase): actual = self.settings.TEST self.assertEqual(actual, './test') + def test_interactive_input_of_missing_defaults(self): + self.settings.default['TEST'] = '' + interactive_input = 'input' + self.settings._read_from_stdin = lambda _: interactive_input + self.settings.validate(interactive=True) + self.assertEqual(interactive_input, self.settings.TEST) class FormatSettingListTest(unittest.TestCase): def setUp(self):