From 273874f3a22032aa5da0c1035adf37199ea8273a Mon Sep 17 00:00:00 2001 From: Thomas Scholtes Date: Fri, 5 Sep 2014 19:05:06 +0200 Subject: [PATCH] Refactor cli main function The logic of setting up beets and running a command was scattered across the `_raw_main` and `_configure` functions. This is an attempt to bring more structure to the process and to diminish the dependency on global state. --- beets/ui/__init__.py | 117 +++++++++++++++++++++++-------------------- test/test_ui.py | 2 +- 2 files changed, 64 insertions(+), 55 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 829089200..1aaa87013 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -694,6 +694,16 @@ class SubcommandsOptionParser(optparse.OptionParser): # Super constructor. optparse.OptionParser.__init__(self, *args, **kwargs) + self.add_option('-l', '--library', dest='library', + help='library database file to use') + self.add_option('-d', '--directory', dest='directory', + help="destination music directory") + self.add_option('-v', '--verbose', dest='verbose', action='store_true', + help='print debugging information') + self.add_option('-c', '--config', dest='config', + help='path to configuration file') + self.add_option('-h', '--help', dest='help', action='store_true', + help='how this help message and exit') # Our root parser needs to stop on the first unrecognized argument. self.disable_interspersed_args() @@ -855,11 +865,18 @@ def _load_plugins(): plugins.send("pluginload") -def _configure(args): - """Parse the command line, load configuration files (including - loading any indicated plugins), and return the invoked subcomand, - the subcommand options, and the subcommand arguments. +def _setup(options, lib=None): + """Prepare and global state and updates it with command line options. + + Returns a list of subcommands, a list of plugins, and a library instance. """ + # Configure the MusicBrainz API. + mb.configure() + + _configure(options) + + _load_plugins() + # Temporary: Migrate from 1.0-style configuration. from beets.ui import migrate migrate.automigrate() @@ -867,21 +884,20 @@ def _configure(args): # Get the default subcommands. from beets.ui.commands import default_commands - # Construct the root parser. - parser = SubcommandsOptionParser() - parser.add_option('-l', '--library', dest='library', - help='library database file to use') - parser.add_option('-d', '--directory', dest='directory', - help="destination music directory") - parser.add_option('-v', '--verbose', dest='verbose', action='store_true', - help='print debugging information') - parser.add_option('-c', '--config', dest='config', - help='path to configuration file') - parser.add_option('-h', '--help', dest='help', action='store_true', - help='how this help message and exit') + subcommands = list(default_commands) + subcommands.append(migrate.migrate_cmd) + subcommands.extend(plugins.commands()) - # Parse the command-line! - options, subargs = parser.parse_global_options(args) + if lib is None: + lib = _open_library(config) + plugins.send("library_opened", lib=lib) + + return subcommands, plugins, lib + + +def _configure(options): + """Amend the global configuration object with command line options. + """ # Add any additional config files specified with --config. This # special handling lets specified plugins get loaded before we @@ -906,54 +922,47 @@ def _configure(args): log.debug('no user configuration found at {0}'.format( util.displayable_path(config_path))) - # Add builtin subcommands - parser.add_subcommand(*default_commands) - parser.add_subcommand(migrate.migrate_cmd) + log.debug(u'data directory: {0}' + .format(util.displayable_path(config.config_dir()))) - # Now add the plugin commands to the parser. - _load_plugins() - for cmd in plugins.commands(): - parser.add_subcommand(cmd) - # Parse the remainder of the command line with loaded plugins. - return parser.parse_subcommand(subargs) +def _open_library(config): + """Create a new library instance from the configuration. + """ + dbpath = config['library'].as_filename() + try: + lib = library.Library( + dbpath, + config['directory'].as_filename(), + get_path_formats(), + get_replacements(), + ) + except sqlite3.OperationalError: + raise UserError(u"database file {0} could not be opened".format( + util.displayable_path(dbpath) + )) + log.debug(u'library database: {0}\n' + u'library directory: {1}' + .format( + util.displayable_path(lib.path), + util.displayable_path(lib.directory), + )) + return lib def _raw_main(args, lib=None): """A helper function for `main` without top-level exception handling. """ - subcommand, suboptions, subargs = _configure(args) - if lib is None: - # Open library file. - dbpath = config['library'].as_filename() - try: - lib = library.Library( - dbpath, - config['directory'].as_filename(), - get_path_formats(), - get_replacements(), - ) - except sqlite3.OperationalError: - raise UserError(u"database file {0} could not be opened".format( - util.displayable_path(dbpath) - )) - plugins.send("library_opened", lib=lib) + parser = SubcommandsOptionParser() + options, subargs = parser.parse_global_options(args) - log.debug(u'data directory: {0}\n' - u'library database: {1}\n' - u'library directory: {2}' - .format( - util.displayable_path(config.config_dir()), - util.displayable_path(lib.path), - util.displayable_path(lib.directory), - )) + subcommands, plugins, lib = _setup(options, lib) - # Configure the MusicBrainz API. - mb.configure() + parser.add_subcommand(*subcommands) + subcommand, suboptions, subargs = parser.parse_subcommand(subargs) - # Invoke the subcommand. subcommand.func(lib, suboptions, subargs) plugins.send('cli_exit', lib=lib) diff --git a/test/test_ui.py b/test/test_ui.py index 7e718550b..76abc20bf 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -740,7 +740,7 @@ class ConfigTest(unittest.TestCase, TestHelper): beetsdir = os.path.join(self.temp_dir, 'beetsfile') open(beetsdir, 'a').close() os.environ['BEETSDIR'] = beetsdir - self.assertRaises(ConfigError, ui._raw_main, 'test') + self.assertRaises(ConfigError, ui._raw_main, ['test']) def test_beetsdir_config_does_not_load_default_user_config(self): os.environ['BEETSDIR'] = self.beetsdir