Option parser: add common options with a method

Add a new OptionParser subclass: CommonOptionsOptionParser, which
provides facilities for adding --album, --path and --format options. The
last one is quite versatile.

Update base commands (from beets.ui.commands) to use those.
This commit is contained in:
Bruno Cauet 2015-03-05 14:48:03 +01:00
parent f14f47f059
commit 6234fee67d
4 changed files with 117 additions and 47 deletions

View file

@ -233,7 +233,7 @@ class LibModel(dbcore.Model):
"""Shared concrete functionality for Items and Albums.
"""
_format_config_key = None
format_config_key = None
"""Config key that specifies how an instance should be formatted.
"""
@ -256,7 +256,7 @@ class LibModel(dbcore.Model):
def __format__(self, spec):
if not spec:
spec = beets.config[self._format_config_key].get(unicode)
spec = beets.config[self.format_config_key].get(unicode)
result = self.evaluate_template(spec)
if isinstance(spec, bytes):
# if spec is a byte string then we must return a one as well
@ -418,7 +418,7 @@ class Item(LibModel):
_sorts = {'artist': SmartArtistSort}
_format_config_key = 'format_item'
format_config_key = 'format_item'
@classmethod
def _getters(cls):
@ -851,7 +851,7 @@ class Album(LibModel):
"""List of keys that are set on an album's items.
"""
_format_config_key = 'format_album'
format_config_key = 'format_album'
@classmethod
def _getters(cls):

View file

@ -592,6 +592,103 @@ def show_model_changes(new, old=None, fields=None, always=False):
return bool(changes)
class CommonOptionsParser(optparse.OptionParser, object):
"""Offers a simple way to add common formatting options.
Options available include:
- matching albums instead of tracks: add_album_option()
- showing paths instead of items/albums: add_path_option()
- changing the format of displayed items/albums: add_format_option()
The last one can have several behaviors:
- against a special target
- with a certain format
- autodetected target with the album option
Each method is fully documented in the related method.
"""
def __init__(self, *args, **kwargs):
super(CommonOptionsParser, self).__init__(*args, **kwargs)
self._has_album = False
def add_album_option(self, flags=('-a', '--album')):
"""Add a -a/--album option to match albums instead of tracks.
If used then the format option can auto-detect whether we're setting
the format for items or albums.
Sets the album property on the options extracted from the CLI.
"""
album = optparse.Option(*flags, action='store_true',
help='match albums instead of tracks')
self.add_option(album)
self._has_album = True
def _set_format(self, option, opt_str, value, parser, target=None,
fmt=None):
"""Internal callback that sets the correct format while parsing CLI
arguments.
"""
value = fmt or value and unicode(value) or ''
parser.values.format = value
if target:
config[target.format_config_key].set(value)
else:
if not self._has_album or not parser.values.album:
config[library.Item.format_config_key].set(value)
if not self._has_album or parser.values.album:
config[library.Album.format_config_key].set(value)
def add_path_option(self, flags=('-p', '--path')):
"""Add a -p/--path option to display the path instead of the default
format.
By default this affects both items and albums. If add_album_option()
is used then the target will be autodetected.
Sets the format property to u'$path' on the options extracted from the
CLI.
"""
path = optparse.Option(*flags, nargs=0, action='callback',
callback=self._set_format,
callback_kwargs={'fmt': '$path'},
help='print paths for matched items or albums')
self.add_option(path)
def add_format_option(self, flags=('-f', '--format'), target=None):
"""Add -f/--format option to print some LibModel instances with a
custom format.
`target` is optional and can be one of ``library.Item``, 'item',
``library.Album`` and 'album'.
Several behaviors are available:
- if `target` is given then the format is only applied to that
LibModel
- if the album option is used then the target will be autodetected
- otherwise the format is applied to both items and albums.
Sets the format property on the options extracted from the CLI.
"""
kwargs = {}
if target:
if isinstance(target, basestring):
target = {'item': library.Item,
'album': library.Album}[target]
kwargs['target'] = target
opt = optparse.Option(*flags, action='callback',
callback=self._set_format,
callback_kwargs=kwargs,
help='print with custom format')
self.add_option(opt)
def add_all_common_options(self):
"""Add album, path and format options.
"""
self.add_album_option()
self.add_path_option()
self.add_format_option()
# Subcommand parsing infrastructure.
#
# This is a fairly generic subcommand parser for optparse. It is
@ -600,6 +697,7 @@ def show_model_changes(new, old=None, fields=None, always=False):
# There you will also find a better description of the code and a more
# succinct example program.
class Subcommand(object):
"""A subcommand of a root command-line application that may be
invoked by a SubcommandOptionParser.
@ -609,10 +707,10 @@ class Subcommand(object):
the subcommand; aliases are alternate names. parser is an
OptionParser responsible for parsing the subcommand's options.
help is a short description of the command. If no parser is
given, it defaults to a new, empty OptionParser.
given, it defaults to a new, empty CommonOptionsParser.
"""
self.name = name
self.parser = parser or optparse.OptionParser()
self.parser = parser or CommonOptionsParser()
self.aliases = aliases
self.help = help
self.hide = hide
@ -635,7 +733,7 @@ class Subcommand(object):
root_parser.get_prog_name().decode('utf8'), self.name)
class SubcommandsOptionParser(optparse.OptionParser, object):
class SubcommandsOptionParser(CommonOptionsParser):
"""A variant of OptionParser that parses subcommands and their
arguments.
"""
@ -924,6 +1022,8 @@ def _raw_main(args, lib=None):
handling.
"""
parser = SubcommandsOptionParser()
parser.add_format_option(flags=('--format-item',), target=library.Item)
parser.add_format_option(flags=('--format-album',), target=library.Album)
parser.add_option('-l', '--library', dest='library',
help='library database file to use')
parser.add_option('-d', '--directory', dest='directory',

View file

@ -957,7 +957,7 @@ default_commands.append(import_cmd)
# list: Query and show library contents.
def list_items(lib, query, album, fmt):
def list_items(lib, query, album, fmt=''):
"""Print out items in lib matching query. If album, then search for
albums instead of single items.
"""
@ -970,23 +970,11 @@ def list_items(lib, query, album, fmt):
def list_func(lib, opts, args):
fmt = '$path' if opts.path else opts.format
list_items(lib, decargs(args), opts.album, fmt)
list_items(lib, decargs(args), opts.album)
list_cmd = ui.Subcommand('list', help='query the library', aliases=('ls',))
list_cmd.parser.add_option(
'-a', '--album', action='store_true',
help='show matching albums instead of tracks'
)
list_cmd.parser.add_option(
'-p', '--path', action='store_true',
help='print paths for matched items or albums'
)
list_cmd.parser.add_option(
'-f', '--format', action='store',
help='print with custom format', default=''
)
list_cmd.parser.add_all_common_options()
list_cmd.func = list_func
default_commands.append(list_cmd)
@ -1087,10 +1075,8 @@ def update_func(lib, opts, args):
update_cmd = ui.Subcommand(
'update', help='update the library', aliases=('upd', 'up',)
)
update_cmd.parser.add_option(
'-a', '--album', action='store_true',
help='match albums instead of tracks'
)
update_cmd.parser.add_album_option()
update_cmd.parser.add_format_option()
update_cmd.parser.add_option(
'-M', '--nomove', action='store_false', default=True, dest='move',
help="don't move files in library"
@ -1099,10 +1085,6 @@ update_cmd.parser.add_option(
'-p', '--pretend', action='store_true',
help="show all changes but do nothing"
)
update_cmd.parser.add_option(
'-f', '--format', action='store',
help='print with custom format', default=''
)
update_cmd.func = update_func
default_commands.append(update_cmd)
@ -1151,10 +1133,7 @@ remove_cmd.parser.add_option(
"-d", "--delete", action="store_true",
help="also remove files from disk"
)
remove_cmd.parser.add_option(
'-a', '--album', action='store_true',
help='match albums instead of tracks'
)
remove_cmd.parser.add_album_option()
remove_cmd.func = remove_func
default_commands.append(remove_cmd)
@ -1348,18 +1327,12 @@ modify_cmd.parser.add_option(
'-W', '--nowrite', action='store_false', dest='write',
help="don't write metadata (opposite of -w)"
)
modify_cmd.parser.add_option(
'-a', '--album', action='store_true',
help='modify whole albums instead of tracks'
)
modify_cmd.parser.add_album_option()
modify_cmd.parser.add_format_option(target='item')
modify_cmd.parser.add_option(
'-y', '--yes', action='store_true',
help='skip confirmation'
)
modify_cmd.parser.add_option(
'-f', '--format', action='store',
help='print with custom format', default=''
)
modify_cmd.func = modify_func
default_commands.append(modify_cmd)
@ -1405,10 +1378,7 @@ move_cmd.parser.add_option(
'-c', '--copy', default=False, action='store_true',
help='copy instead of moving'
)
move_cmd.parser.add_option(
'-a', '--album', default=False, action='store_true',
help='match whole albums instead of tracks'
)
move_cmd.parser.add_album_option()
move_cmd.func = move_func
default_commands.append(move_cmd)

View file

@ -359,7 +359,7 @@ class ConvertPlugin(BeetsPlugin):
self.config['pretend'].get(bool)
if not pretend:
ui.commands.list_items(lib, ui.decargs(args), opts.album, '')
ui.commands.list_items(lib, ui.decargs(args), opts.album)
if not (opts.yes or ui.input_yn("Convert? (Y/n)")):
return