diff --git a/.travis.yml b/.travis.yml index 2e635d6f5..4907a7cb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,3 +36,5 @@ notifications: - "irc.freenode.org#beets" use_notice: true skip_join: true + on_success: change + on_failure: always diff --git a/beets/importer.py b/beets/importer.py index a050be27c..296ac99ba 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -950,8 +950,6 @@ def read_tasks(session): .format(displayable_path(dirs))) skipped += 1 continue - print(paths) - print(read_items(paths)) yield ImportTask(toppath, dirs, read_items(paths)) # Indicate the directory is finished. diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 829089200..d235cc758 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -499,14 +499,6 @@ def get_plugin_paths(): The value for "pluginpath" may be a single string or a list of strings. """ - pluginpaths = config['pluginpath'].get() - if isinstance(pluginpaths, basestring): - pluginpaths = [pluginpaths] - if not isinstance(pluginpaths, list): - raise confit.ConfigTypeError( - u'pluginpath must be string or a list of strings' - ) - return map(util.normpath, pluginpaths) def _pick_format(album, fmt=None): @@ -694,6 +686,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() @@ -840,26 +842,34 @@ def vararg_callback(option, opt_str, value, parser): # The main entry point and bootstrapping. -def _load_plugins(): +def _load_plugins(config): """Load the plugins specified in the configuration. """ - # Add plugin paths. + paths = config['pluginpath'].get(confit.EnsureStringList()) + paths = map(util.normpath, paths) + import beetsplug - beetsplug.__path__ = get_plugin_paths() + beetsplug.__path__ - + beetsplug.__path__ = paths + beetsplug.__path__ # For backwards compatibility. - sys.path += get_plugin_paths() + sys.path += paths - # Load requested plugins. plugins.load_plugins(config['plugins'].as_str_seq()) plugins.send("pluginload") + return plugins -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() + + config = _configure(options) + + plugins = _load_plugins(config) + # Temporary: Migrate from 1.0-style configuration. from beets.ui import migrate migrate.automigrate() @@ -867,21 +877,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 +915,48 @@ 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()))) + return config - # 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/beets/util/confit.py b/beets/util/confit.py index 0149f68a7..4ffe68cea 100644 --- a/beets/util/confit.py +++ b/beets/util/confit.py @@ -1073,6 +1073,22 @@ class StrSeq(Template): self.fail('must be a list of strings', view, True) +class EnsureStringList(Template): + """Always return a list of strings. + + The raw value may either be a single string or a list of strings. + Otherwise a type error is raised. For single strings a singleton + list is returned. + """ + def convert(self, paths, view): + if isinstance(paths, basestring): + paths = [paths] + if not isinstance(paths, list) or \ + not all(map(lambda p: isinstance(p, basestring), paths)): + self.fail(u'must be string or a list of strings', view, True) + return paths + + class Filename(Template): """A template that validates strings as filenames. diff --git a/beetsplug/info.py b/beetsplug/info.py index 9fd6f4f12..f7462761a 100644 --- a/beetsplug/info.py +++ b/beetsplug/info.py @@ -60,8 +60,7 @@ def run(lib, opts, args): else: if not first: ui.print_() - else: - print_data(data) + print_data(data) first = False if opts.summarize: diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 01a188616..186e83d6e 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -122,6 +122,7 @@ def strip_cruft(lyrics, wscollapse=True): lyrics = unescape(lyrics) if wscollapse: lyrics = re.sub(r'\s+', ' ', lyrics) # Whitespace collapse. + lyrics = re.sub(r'<(script).*?(?s)', '', lyrics) # Strip script tags. lyrics = BREAK_RE.sub('\n', lyrics) #
newlines. lyrics = re.sub(r'\n +', '\n', lyrics) lyrics = re.sub(r' +\n', '\n', lyrics) diff --git a/beetsplug/play.py b/beetsplug/play.py index 8c04228cc..9a1bc444c 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -19,6 +19,7 @@ from beets.ui import Subcommand from beets import config from beets import ui from beets import util +from os.path import relpath import platform import logging import shlex @@ -33,6 +34,9 @@ def play_music(lib, opts, args): """ command_str = config['play']['command'].get() use_folders = config['play']['use_folders'].get(bool) + relative_to = config['play']['relative_to'].get() + if relative_to: + relative_to = util.normpath(relative_to) if command_str: command = shlex.split(command_str) else: @@ -86,7 +90,10 @@ def play_music(lib, opts, args): # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) for item in paths: - m3u.write(item + '\n') + if relative_to: + m3u.write(relpath(item, relative_to) + '\n') + else: + m3u.write(item + '\n') m3u.close() command.append(m3u.name) @@ -96,7 +103,7 @@ def play_music(lib, opts, args): if output: log.debug(u'Output of {0}: {1}'.format(command[0], output)) - ui.print_(u'Playing {0} {1}.'.format(len(paths), item_type)) + ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) class PlayPlugin(BeetsPlugin): diff --git a/docs/changelog.rst b/docs/changelog.rst index b435dcd41..c67a38472 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -37,6 +37,10 @@ Fixes: data when the two are inconsistent. * Resuming imports and beginning incremental imports should now be much faster when there is a lot of previously-imported music to skip. +* :doc:`/plugins/lyrics`: Remove ``