From f14dbf4e89ea74d09026da5028e4ecf59192c9bd Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Tue, 24 Feb 2015 14:37:37 +0100 Subject: [PATCH 01/12] SubcommandsOptionParser: use super() --- beets/ui/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index e041db95e..ad1614bad 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -635,7 +635,7 @@ class Subcommand(object): root_parser.get_prog_name().decode('utf8'), self.name) -class SubcommandsOptionParser(optparse.OptionParser): +class SubcommandsOptionParser(optparse.OptionParser, object): """A variant of OptionParser that parses subcommands and their arguments. """ @@ -653,7 +653,7 @@ class SubcommandsOptionParser(optparse.OptionParser): kwargs['add_help_option'] = False # Super constructor. - optparse.OptionParser.__init__(self, *args, **kwargs) + super(SubcommandsOptionParser, self).__init__(*args, **kwargs) # Our root parser needs to stop on the first unrecognized argument. self.disable_interspersed_args() @@ -670,7 +670,7 @@ class SubcommandsOptionParser(optparse.OptionParser): # Add the list of subcommands to the help message. def format_help(self, formatter=None): # Get the original help message, to which we will append. - out = optparse.OptionParser.format_help(self, formatter) + out = super(SubcommandsOptionParser, self).format_help(formatter) if formatter is None: formatter = self.formatter From f14f47f059459118a5d3f9123c5f72a948ac3a4c Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Wed, 4 Mar 2015 16:48:14 +0100 Subject: [PATCH 02/12] Renamed list_format_* into format_* --- beets/config_default.yaml | 4 ++-- beets/library.py | 4 ++-- beets/ui/__init__.py | 11 +++++++++++ docs/changelog.rst | 3 +++ docs/plugins/duplicates.rst | 2 +- docs/plugins/mbsync.rst | 4 ++-- docs/plugins/missing.rst | 2 +- docs/reference/config.rst | 26 ++++++++++++++++++++++---- test/test_library.py | 4 ++-- test/test_mbsync.py | 4 ++-- 10 files changed, 48 insertions(+), 16 deletions(-) diff --git a/beets/config_default.yaml b/beets/config_default.yaml index b98ffbfc6..e0b942d82 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -61,8 +61,8 @@ ui: action_default: turquoise action: blue -list_format_item: $artist - $album - $title -list_format_album: $albumartist - $album +format_item: $artist - $album - $title +format_album: $albumartist - $album time_format: '%Y-%m-%d %H:%M:%S' sort_album: albumartist+ album+ diff --git a/beets/library.py b/beets/library.py index 6e53352d7..333ff4238 100644 --- a/beets/library.py +++ b/beets/library.py @@ -418,7 +418,7 @@ class Item(LibModel): _sorts = {'artist': SmartArtistSort} - _format_config_key = 'list_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 = 'list_format_album' + _format_config_key = 'format_album' @classmethod def _getters(cls): diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index ad1614bad..999067f53 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -871,6 +871,17 @@ def _configure(options): u'See documentation for more info.') config['ui']['color'].set(config['color'].get(bool)) + # Compatibility from list_format_{item,album} to format_{item,album} + for elem in ('item', 'album'): + old_key = 'list_format_{0}'.format(elem) + if config[old_key].exists(): + new_key = 'format_{0}'.format(elem) + log.warning('Warning: configuration uses "{0}" which is deprecated' + ' in favor of "{1}" now that it affects all commands. ' + 'See changelog & documentation.'.format(old_key, + new_key)) + config[new_key].set(config[old_key]) + config_path = config.user_config_path() if os.path.isfile(config_path): log.debug(u'user configuration: {0}', diff --git a/docs/changelog.rst b/docs/changelog.rst index 41266a7dc..527806a9a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -71,6 +71,9 @@ Core changes: now affect (almost) every place where objects are printed and logged. (Previously, they only controlled the :ref:`list-cmd` command and a few other scattered pieces.) :bug:`1269` +* :ref:`list_format_album` and :ref:`list_format_album` have respectively been + renamed :ref:`format_album` and :ref:`format_item`. The old names still work + but each triggers a warning message. :bug:`1271` Fixes: diff --git a/docs/plugins/duplicates.rst b/docs/plugins/duplicates.rst index 18c2a8052..ecbda69cf 100644 --- a/docs/plugins/duplicates.rst +++ b/docs/plugins/duplicates.rst @@ -61,7 +61,7 @@ file. The available options mirror the command-line options: or album. This uses the same template syntax as beets' :doc:`path formats`. The usage is inspired by, and therefore similar to, the :ref:`list ` command. - Default: :ref:`list_format_item` + Default: :ref:`format_item` - **full**: List every track or album that has duplicates, not just the duplicates themselves. Default: ``no``. diff --git a/docs/plugins/mbsync.rst b/docs/plugins/mbsync.rst index cf89974e4..a7633a500 100644 --- a/docs/plugins/mbsync.rst +++ b/docs/plugins/mbsync.rst @@ -34,5 +34,5 @@ The command has a few command-line options: plugin will write new metadata to files' tags. To disable this, use the ``-W`` (``--nowrite``) option. * To customize the output of unrecognized items, use the ``-f`` - (``--format``) option. The default output is ``list_format_item`` or - ``list_format_album`` for items and albums, respectively. + (``--format``) option. The default output is ``format_item`` or + ``format_album`` for items and albums, respectively. diff --git a/docs/plugins/missing.rst b/docs/plugins/missing.rst index ffca94052..aab04e71b 100644 --- a/docs/plugins/missing.rst +++ b/docs/plugins/missing.rst @@ -36,7 +36,7 @@ configuration file. The available options are: track. This uses the same template syntax as beets' :doc:`path formats `. The usage is inspired by, and therefore similar to, the :ref:`list ` command. - Default: :ref:`list_format_item`. + Default: :ref:`format_item`. - **total**: Print a single count of missing tracks in all albums. Default: ``no``. diff --git a/docs/reference/config.rst b/docs/reference/config.rst index a64e73749..b7763c0b4 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -167,20 +167,38 @@ Defaults to ``yes``. list_format_item ~~~~~~~~~~~~~~~~ -Format to use when listing *individual items* with the :ref:`list-cmd` -command and other commands that need to print out items. Defaults to -``$artist - $album - $title``. The ``-f`` command-line option overrides -this setting. +Deprecated option, replaced by :ref:`format_item`. .. _list_format_album: list_format_album ~~~~~~~~~~~~~~~~~ +Deprecated option, replaced by :ref:`format_album`. + +.. _format_item: + +format_item +~~~~~~~~~~~ + +Format to use when listing *individual items* with the :ref:`list-cmd` +command and other commands that need to print out items. Defaults to +``$artist - $album - $title``. The ``-f`` command-line option overrides +this setting. + +It used to be named :ref:`list_format_item`. + +.. _format_album: + +format_album +~~~~~~~~~~~~ + Format to use when listing *albums* with :ref:`list-cmd` and other commands. Defaults to ``$albumartist - $album``. The ``-f`` command-line option overrides this setting. +It used to be named :ref:`list_format_album`. + .. _sort_item: sort_item diff --git a/test/test_library.py b/test/test_library.py index 1a2812b61..a496ef6c0 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -972,7 +972,7 @@ class TemplateTest(_common.LibTestCase): self.assertEqual(self.i.evaluate_template('$foo'), 'baz') def test_album_and_item_format(self): - config['list_format_album'] = u'foö $foo' + config['format_album'] = u'foö $foo' album = beets.library.Album() album.foo = 'bar' album.tagada = 'togodo' @@ -981,7 +981,7 @@ class TemplateTest(_common.LibTestCase): self.assertEqual(unicode(album), u"foö bar") self.assertEqual(str(album), b"fo\xc3\xb6 bar") - config['list_format_item'] = 'bar $foo' + config['format_item'] = 'bar $foo' item = beets.library.Item() item.foo = 'bar' item.tagada = 'togodo' diff --git a/test/test_mbsync.py b/test/test_mbsync.py index fc37fe8c3..701227366 100644 --- a/test/test_mbsync.py +++ b/test/test_mbsync.py @@ -73,8 +73,8 @@ class MbsyncCliTest(unittest.TestCase, TestHelper): self.assertEqual(album.album, 'album info') def test_message_when_skipping(self): - config['list_format_item'] = '$artist - $album - $title' - config['list_format_album'] = '$albumartist - $album' + config['format_item'] = '$artist - $album - $title' + config['format_album'] = '$albumartist - $album' # Test album with no mb_albumid. # The default format for an album include $albumartist so From 6234fee67d82b84fe6cd0acb92a9420ed4da0474 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Thu, 5 Mar 2015 14:48:03 +0100 Subject: [PATCH 03/12] 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. --- beets/library.py | 8 ++-- beets/ui/__init__.py | 106 +++++++++++++++++++++++++++++++++++++++++-- beets/ui/commands.py | 48 ++++---------------- beetsplug/convert.py | 2 +- 4 files changed, 117 insertions(+), 47 deletions(-) diff --git a/beets/library.py b/beets/library.py index 333ff4238..05ef98f48 100644 --- a/beets/library.py +++ b/beets/library.py @@ -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): diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 999067f53..b151f3602 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -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', diff --git a/beets/ui/commands.py b/beets/ui/commands.py index bea5dc91c..5d29093c9 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -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) diff --git a/beetsplug/convert.py b/beetsplug/convert.py index 352c3c94d..d4bc6d32b 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -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 From 5623d26a9179edfb83be6cf91ed201e095a9f4b7 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Thu, 5 Mar 2015 16:07:49 +0100 Subject: [PATCH 04/12] Add tests for the CommonOptionsParser Unit test both the features & do real behaviour tests with the 'list' command. --- test/test_ui.py | 136 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/test/test_ui.py b/test/test_ui.py index d2a2ea80e..90ee43b38 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -1033,6 +1033,142 @@ class CompletionTest(_common.TestCase): self.fail('test/test_completion.sh did not execute properly') +class CommonOptionsParserCliTest(unittest.TestCase, TestHelper): + """Test CommonOptionsParser and formatting LibModel formatting on 'list' + command. + """ + def setUp(self): + self.setup_beets() + self.lib = library.Library(':memory:') + self.item = _common.item() + self.item.path = 'xxx/yyy' + self.lib.add(self.item) + self.lib.add_album([self.item]) + + def tearDown(self): + self.teardown_beets() + + def test_base(self): + l = self.run_with_output('ls') + self.assertEqual(l, 'the artist - the album - the title\n') + + l = self.run_with_output('ls', '-a') + self.assertEqual(l, 'the album artist - the album\n') + + def test_path_option(self): + l = self.run_with_output('ls', '-p') + self.assertEqual(l, 'xxx/yyy\n') + + l = self.run_with_output('ls', '-a', '-p') + self.assertEqual(l, 'xxx\n') + + def test_format_option(self): + l = self.run_with_output('ls', '-f', '$artist') + self.assertEqual(l, 'the artist\n') + + l = self.run_with_output('ls', '-a', '-f', '$albumartist') + self.assertEqual(l, 'the album artist\n') + + def test_root_format_option(self): + l = self.run_with_output('--format-item', '$artist', + '--format-album', 'foo', 'ls') + self.assertEqual(l, 'the artist\n') + + l = self.run_with_output('--format-item', 'foo', + '--format-album', '$albumartist', 'ls', '-a') + self.assertEqual(l, 'the album artist\n') + + +class CommonOptionsParserTest(unittest.TestCase, TestHelper): + def setUp(self): + self.setup_beets() + + def tearDown(self): + self.teardown_beets() + + def test_album_option(self): + parser = ui.CommonOptionsParser() + self.assertFalse(parser._has_album) + parser.add_album_option() + self.assertTrue(parser._has_album) + + self.assertEqual(parser.parse_args([]), ({'album': None}, [])) + self.assertEqual(parser.parse_args(['-a']), ({'album': True}, [])) + self.assertEqual(parser.parse_args(['--album']), ({'album': True}, [])) + + def test_path_option(self): + parser = ui.CommonOptionsParser() + parser.add_path_option() + self.assertFalse(parser._has_album) + + config['format_item'].set('$foo') + self.assertEqual(parser.parse_args([]), ({'path': None}, [])) + self.assertEqual(config['format_item'].get(unicode), u'$foo') + + self.assertEqual(parser.parse_args(['-p']), + ({'path': None, 'format': '$path'}, [])) + self.assertEqual(parser.parse_args(['--path']), + ({'path': None, 'format': '$path'}, [])) + + self.assertEqual(config['format_item'].get(unicode), '$path') + self.assertEqual(config['format_album'].get(unicode), '$path') + + def test_format_option(self): + parser = ui.CommonOptionsParser() + parser.add_format_option() + self.assertFalse(parser._has_album) + + config['format_item'].set('$foo') + self.assertEqual(parser.parse_args([]), ({'format': None}, [])) + self.assertEqual(config['format_item'].get(unicode), u'$foo') + + self.assertEqual(parser.parse_args(['-f', '$bar']), + ({'format': '$bar'}, [])) + self.assertEqual(parser.parse_args(['--format', '$baz']), + ({'format': '$baz'}, [])) + + self.assertEqual(config['format_item'].get(unicode), '$baz') + self.assertEqual(config['format_album'].get(unicode), '$baz') + + def test_format_option_with_target(self): + with self.assertRaises(KeyError): + ui.CommonOptionsParser().add_format_option(target='thingy') + + parser = ui.CommonOptionsParser() + parser.add_format_option(target='item') + + config['format_item'].set('$item') + config['format_album'].set('$album') + + self.assertEqual(parser.parse_args(['-f', '$bar']), + ({'format': '$bar'}, [])) + + self.assertEqual(config['format_item'].get(unicode), '$bar') + self.assertEqual(config['format_album'].get(unicode), '$album') + + def test_format_option_with_album(self): + parser = ui.CommonOptionsParser() + parser.add_album_option() + parser.add_format_option() + + config['format_item'].set('$item') + config['format_album'].set('$album') + + parser.parse_args(['-f', '$bar']) + self.assertEqual(config['format_item'].get(unicode), '$bar') + self.assertEqual(config['format_album'].get(unicode), '$album') + + parser.parse_args(['-a', '-f', '$foo']) + self.assertEqual(config['format_item'].get(unicode), '$bar') + self.assertEqual(config['format_album'].get(unicode), '$foo') + + def test_add_all_common_options(self): + parser = ui.CommonOptionsParser() + parser.add_all_common_options() + self.assertEqual(parser.parse_args([]), + ({'album': None, 'path': None, 'format': None}, [])) + + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 38ca99498d7f23294130005c2d1a93a0f1824f87 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Thu, 5 Mar 2015 16:20:07 +0100 Subject: [PATCH 05/12] Bypass sequential args. parsing for --album When setting format and using --album we *need* to know whether we're in album mode. Naively if --album happens after "--format fmt" then we'll set Item format instead of Album format. By looking forward for -a/--album we bypass that problem. --- beets/ui/__init__.py | 20 ++++++++++++++++---- test/test_ui.py | 11 +++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index b151f3602..dfa7f2e09 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -609,7 +609,10 @@ class CommonOptionsParser(optparse.OptionParser, object): """ def __init__(self, *args, **kwargs): super(CommonOptionsParser, self).__init__(*args, **kwargs) - self._has_album = False + self._album_flags = False + # this serves both as an indicator that we offer the feature AND allows + # us to check whether it has been specified on the CLI - bypassing the + # fact that arguments may be in any order def add_album_option(self, flags=('-a', '--album')): """Add a -a/--album option to match albums instead of tracks. @@ -621,7 +624,7 @@ class CommonOptionsParser(optparse.OptionParser, object): album = optparse.Option(*flags, action='store_true', help='match albums instead of tracks') self.add_option(album) - self._has_album = True + self._album_flags = set(flags) def _set_format(self, option, opt_str, value, parser, target=None, fmt=None): @@ -633,9 +636,18 @@ class CommonOptionsParser(optparse.OptionParser, object): if target: config[target.format_config_key].set(value) else: - if not self._has_album or not parser.values.album: + if self._album_flags: + if parser.values.album: + target = library.Album + else: + # the option is either missing either not parsed yet + if self._album_flags & set(parser.rargs): + target = library.Album + else: + target = library.Item + config[target.format_config_key].set(value) + else: 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')): diff --git a/test/test_ui.py b/test/test_ui.py index 90ee43b38..75905dafb 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -1088,9 +1088,9 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): def test_album_option(self): parser = ui.CommonOptionsParser() - self.assertFalse(parser._has_album) + self.assertFalse(parser._album_flags) parser.add_album_option() - self.assertTrue(parser._has_album) + self.assertTrue(bool(parser._album_flags)) self.assertEqual(parser.parse_args([]), ({'album': None}, [])) self.assertEqual(parser.parse_args(['-a']), ({'album': True}, [])) @@ -1099,7 +1099,7 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): def test_path_option(self): parser = ui.CommonOptionsParser() parser.add_path_option() - self.assertFalse(parser._has_album) + self.assertFalse(parser._album_flags) config['format_item'].set('$foo') self.assertEqual(parser.parse_args([]), ({'path': None}, [])) @@ -1116,7 +1116,7 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): def test_format_option(self): parser = ui.CommonOptionsParser() parser.add_format_option() - self.assertFalse(parser._has_album) + self.assertFalse(parser._album_flags) config['format_item'].set('$foo') self.assertEqual(parser.parse_args([]), ({'format': None}, [])) @@ -1162,6 +1162,9 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): self.assertEqual(config['format_item'].get(unicode), '$bar') self.assertEqual(config['format_album'].get(unicode), '$foo') + parser.parse_args(['-f', '$foo2', '-a']) + self.assertEqual(config['format_album'].get(unicode), '$foo2') + def test_add_all_common_options(self): parser = ui.CommonOptionsParser() parser.add_all_common_options() From 650305c9a184320df1bcfe6ae5e60d4f5a7ad283 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Thu, 5 Mar 2015 17:00:19 +0100 Subject: [PATCH 06/12] All suitable plugins use CommonOptionsParser features --- beetsplug/convert.py | 6 ++---- beetsplug/duplicates.py | 17 +---------------- beetsplug/echonest.py | 5 +---- beetsplug/mbsync.py | 16 +++++++--------- beetsplug/missing.py | 8 +------- beetsplug/play.py | 6 +----- beetsplug/random.py | 7 +------ beetsplug/replaygain.py | 3 +-- test/test_mbsync.py | 4 ++++ 9 files changed, 19 insertions(+), 53 deletions(-) diff --git a/beetsplug/convert.py b/beetsplug/convert.py index d4bc6d32b..11ea1f5ce 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -139,8 +139,6 @@ class ConvertPlugin(BeetsPlugin): cmd = ui.Subcommand('convert', help='convert to external location') cmd.parser.add_option('-p', '--pretend', action='store_true', help='show actions but do nothing') - cmd.parser.add_option('-a', '--album', action='store_true', - help='choose albums instead of tracks') cmd.parser.add_option('-t', '--threads', action='store', type='int', help='change the number of threads, \ defaults to maximum available processors') @@ -149,10 +147,10 @@ class ConvertPlugin(BeetsPlugin): and move the old files') cmd.parser.add_option('-d', '--dest', action='store', help='set the destination directory') - cmd.parser.add_option('-f', '--format', action='store', dest='format', - help='set the destination directory') cmd.parser.add_option('-y', '--yes', action='store_true', dest='yes', help='do not ask for confirmation') + cmd.parser.add_album_option() + cmd.parser.add_format_option(target='item') cmd.func = self.convert_func return [cmd] diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index fb697922b..1739d3530 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -125,17 +125,6 @@ class DuplicatesPlugin(BeetsPlugin): self._command = Subcommand('duplicates', help=__doc__, aliases=['dup']) - - self._command.parser.add_option('-f', '--format', dest='format', - action='store', type='string', - help='print with custom format', - metavar='FMT', default='') - - self._command.parser.add_option('-a', '--album', dest='album', - action='store_true', - help='show duplicate albums instead of' - ' tracks') - self._command.parser.add_option('-c', '--count', dest='count', action='store_true', help='show duplicate counts') @@ -168,15 +157,11 @@ class DuplicatesPlugin(BeetsPlugin): action='store', metavar='DEST', help='copy items to dest') - self._command.parser.add_option('-p', '--path', dest='path', - action='store_true', - help='print paths for matched items or' - ' albums') - self._command.parser.add_option('-t', '--tag', dest='tag', action='store', help='tag matched items with \'k=v\'' ' attribute') + self._command.parser.add_all_common_options() def commands(self): diff --git a/beetsplug/echonest.py b/beetsplug/echonest.py index c8d45f3b0..753bfb5c3 100644 --- a/beetsplug/echonest.py +++ b/beetsplug/echonest.py @@ -486,10 +486,7 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): '-t', '--threshold', dest='threshold', action='store', type='float', default=0.15, help='Set difference threshold' ) - sim_cmd.parser.add_option( - '-f', '--format', action='store', default='${difference}: ${path}', - help='print with custom format' - ) + sim_cmd.parser.add_format_option() def sim_func(lib, opts, args): self.config.set_args(opts) diff --git a/beetsplug/mbsync.py b/beetsplug/mbsync.py index 365d83193..974f7e894 100644 --- a/beetsplug/mbsync.py +++ b/beetsplug/mbsync.py @@ -52,8 +52,7 @@ class MBSyncPlugin(BeetsPlugin): cmd.parser.add_option('-W', '--nowrite', action='store_false', default=config['import']['write'], dest='write', help="don't write updated metadata to files") - cmd.parser.add_option('-f', '--format', action='store', default='', - help='print with custom format') + cmd.parser.add_format_option() cmd.func = self.func return [cmd] @@ -64,17 +63,16 @@ class MBSyncPlugin(BeetsPlugin): pretend = opts.pretend write = opts.write query = ui.decargs(args) - fmt = opts.format - self.singletons(lib, query, move, pretend, write, fmt) - self.albums(lib, query, move, pretend, write, fmt) + self.singletons(lib, query, move, pretend, write) + self.albums(lib, query, move, pretend, write) - def singletons(self, lib, query, move, pretend, write, fmt): + def singletons(self, lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for items matched by query. """ for item in lib.items(query + ['singleton:true']): - item_formatted = format(item, fmt) + item_formatted = format(item) if not item.mb_trackid: self._log.info(u'Skipping singleton with no mb_trackid: {0}', item_formatted) @@ -93,13 +91,13 @@ class MBSyncPlugin(BeetsPlugin): autotag.apply_item_metadata(item, track_info) apply_item_changes(lib, item, move, pretend, write) - def albums(self, lib, query, move, pretend, write, fmt): + def albums(self, lib, query, move, pretend, write): """Retrieve and apply info from the autotagger for albums matched by query and their items. """ # Process matching albums. for a in lib.albums(query): - album_formatted = format(a, fmt) + album_formatted = format(a) if not a.mb_albumid: self._log.info(u'Skipping album with no mb_albumid: {0}', album_formatted) diff --git a/beetsplug/missing.py b/beetsplug/missing.py index 517a20758..7f976f75c 100644 --- a/beetsplug/missing.py +++ b/beetsplug/missing.py @@ -94,19 +94,13 @@ class MissingPlugin(BeetsPlugin): self._command = Subcommand('missing', help=__doc__, aliases=['miss']) - - self._command.parser.add_option('-f', '--format', dest='format', - action='store', type='string', - help='print with custom FORMAT', - metavar='FORMAT', default='') - self._command.parser.add_option('-c', '--count', dest='count', action='store_true', help='count missing tracks per album') - self._command.parser.add_option('-t', '--total', dest='total', action='store_true', help='count total of missing tracks') + self._command.add_format_option() def commands(self): def _miss(lib, opts, args): diff --git a/beetsplug/play.py b/beetsplug/play.py index 8a912505c..65d5b91ec 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -42,11 +42,7 @@ class PlayPlugin(BeetsPlugin): 'play', help='send music to a player as a playlist' ) - play_command.parser.add_option( - '-a', '--album', - action='store_true', default=False, - help='query and load albums rather than tracks' - ) + play_command.parser.add_album_option() play_command.func = self.play_music return [play_command] diff --git a/beetsplug/random.py b/beetsplug/random.py index f6a664a4c..180e954f0 100644 --- a/beetsplug/random.py +++ b/beetsplug/random.py @@ -67,16 +67,11 @@ def random_item(lib, opts, args): random_cmd = Subcommand('random', help='chose a random track or album') -random_cmd.parser.add_option('-a', '--album', action='store_true', - help='choose an album instead of track') -random_cmd.parser.add_option('-p', '--path', action='store_true', - help='print the path of the matched item') -random_cmd.parser.add_option('-f', '--format', action='store', - help='print with custom format', default='') random_cmd.parser.add_option('-n', '--number', action='store', type="int", help='number of objects to choose', default=1) random_cmd.parser.add_option('-e', '--equal-chance', action='store_true', help='each artist has the same chance') +random_cmd.parser.add_all_common_options() random_cmd.func = random_item diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index ce41cad57..d5b6ae8d4 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -760,7 +760,6 @@ class ReplayGainPlugin(BeetsPlugin): self.handle_track(item, write) cmd = ui.Subcommand('replaygain', help='analyze for ReplayGain') - cmd.parser.add_option('-a', '--album', action='store_true', - help='analyze albums instead of tracks') + cmd.parser.add_album_option() cmd.func = func return [cmd] diff --git a/test/test_mbsync.py b/test/test_mbsync.py index 701227366..ff6e01cf3 100644 --- a/test/test_mbsync.py +++ b/test/test_mbsync.py @@ -99,6 +99,10 @@ class MbsyncCliTest(unittest.TestCase, TestHelper): e = "mbsync: Skipping album with no mb_albumid: 'album info'" self.assertEqual(e, logs[0]) + # restore the config + config['format_item'] = '$artist - $album - $title' + config['format_album'] = '$albumartist - $album' + # Test singleton with no mb_trackid. # The default singleton format includes $artist and $album # so we need to stub them here From ea1dc1eb1921302a7be3cfcfa6e2c1569150f450 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Thu, 5 Mar 2015 17:24:57 +0100 Subject: [PATCH 07/12] Plugins conversion cont. --- beetsplug/random.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/beetsplug/random.py b/beetsplug/random.py index 180e954f0..43fb7957c 100644 --- a/beetsplug/random.py +++ b/beetsplug/random.py @@ -26,7 +26,6 @@ from itertools import groupby def random_item(lib, opts, args): query = decargs(args) - fmt = '$path' if opts.path else opts.format if opts.album: objs = list(lib.albums(query)) @@ -63,7 +62,7 @@ def random_item(lib, opts, args): objs = random.sample(objs, number) for item in objs: - print_(format(item, fmt)) + print_(format(item)) random_cmd = Subcommand('random', help='chose a random track or album') From 7798a521b58ac4525f3e121fabca3bd1d6317ac8 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Thu, 5 Mar 2015 17:39:56 +0100 Subject: [PATCH 08/12] Fix convert plugin --- beetsplug/convert.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/beetsplug/convert.py b/beetsplug/convert.py index 11ea1f5ce..0cce93f7a 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -146,11 +146,12 @@ class ConvertPlugin(BeetsPlugin): dest='keep_new', help='keep only the converted \ and move the old files') cmd.parser.add_option('-d', '--dest', action='store', + help='set the target format of the tracks') + cmd.parser.add_option('-f', '--format', action='store', dest='format', help='set the destination directory') cmd.parser.add_option('-y', '--yes', action='store_true', dest='yes', help='do not ask for confirmation') cmd.parser.add_album_option() - cmd.parser.add_format_option(target='item') cmd.func = self.convert_func return [cmd] From 6fda0e23fc0ab4c16c9a0050eb85c26d4e9e74f2 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Thu, 5 Mar 2015 17:13:33 +0100 Subject: [PATCH 09/12] Update docs & changelog --- docs/changelog.rst | 6 ++++++ docs/dev/plugins.rst | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 527806a9a..4da0bb141 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,9 @@ Changelog Features: +* Beets now accept top-level options ``--format-item`` and ``--format-album`` + before any subcommand to control how items and albums are displayed. + :bug:`1271`: * There are now multiple levels of verbosity. On the command line, you can make beets somewhat verbose with ``-v`` or very verbose with ``-vv``. :bug:`1244` @@ -120,6 +123,9 @@ Fixes: For developers: +* the ``OptionParser`` is now a ``CommonOptionsParser`` that offers facilities + for adding usual options (``--album``, ``--path`` and ``--format``). See + :ref:`add_subcommands`. :bug:`1271` * The logging system in beets has been overhauled. Plugins now each have their own logger, which helps by automatically adjusting the verbosity level in import mode and by prefixing the plugin's name. Logging levels are diff --git a/docs/dev/plugins.rst b/docs/dev/plugins.rst index f448b5dfa..9961ad03c 100644 --- a/docs/dev/plugins.rst +++ b/docs/dev/plugins.rst @@ -87,8 +87,11 @@ The function should use any of the utility functions defined in ``beets.ui``. Try running ``pydoc beets.ui`` to see what's available. You can add command-line options to your new command using the ``parser`` member -of the ``Subcommand`` class, which is an ``OptionParser`` instance. Just use it -like you would a normal ``OptionParser`` in an independent script. +of the ``Subcommand`` class, which is a ``CommonOptionParser`` instance. Just +use it like you would a normal ``OptionParser`` in an independent script. Note +that it offers several methods to add common options: ``--album``, ``--path`` +and ``--format``. This feature is versatile and extensively documented, try +``pydoc beets.ui.CommonOptionParser`` for more information. .. _plugin_events: From 167f067961d1fe7ca755225e52cda11642351729 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Thu, 5 Mar 2015 17:23:55 +0100 Subject: [PATCH 10/12] Improve behavior of --path: store_true-like Availability of the 'path' presence in arguments can be important for some plugins such as duplicates, and therefore should be conserved. --- beets/ui/__init__.py | 8 ++++++-- test/test_ui.py | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index dfa7f2e09..85c5e4824 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -627,10 +627,13 @@ class CommonOptionsParser(optparse.OptionParser, object): self._album_flags = set(flags) def _set_format(self, option, opt_str, value, parser, target=None, - fmt=None): + fmt=None, store_true=False): """Internal callback that sets the correct format while parsing CLI arguments. """ + if store_true: + setattr(parser.values, option.dest, True) + value = fmt or value and unicode(value) or '' parser.values.format = value if target: @@ -662,7 +665,8 @@ class CommonOptionsParser(optparse.OptionParser, object): """ path = optparse.Option(*flags, nargs=0, action='callback', callback=self._set_format, - callback_kwargs={'fmt': '$path'}, + callback_kwargs={'fmt': '$path', + 'store_true': True}, help='print paths for matched items or albums') self.add_option(path) diff --git a/test/test_ui.py b/test/test_ui.py index 75905dafb..0f8bb6306 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -1106,9 +1106,9 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): self.assertEqual(config['format_item'].get(unicode), u'$foo') self.assertEqual(parser.parse_args(['-p']), - ({'path': None, 'format': '$path'}, [])) + ({'path': True, 'format': '$path'}, [])) self.assertEqual(parser.parse_args(['--path']), - ({'path': None, 'format': '$path'}, [])) + ({'path': True, 'format': '$path'}, [])) self.assertEqual(config['format_item'].get(unicode), '$path') self.assertEqual(config['format_album'].get(unicode), '$path') From 679b0a586bc82d7ce3d96c7628e97195294f00cd Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Sat, 7 Mar 2015 13:45:58 +0100 Subject: [PATCH 11/12] Remove list_format_{album,item} sections from docs --- docs/changelog.rst | 4 ++-- docs/reference/config.rst | 19 ++++--------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 9af5d0a34..693c4b1d8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -72,11 +72,11 @@ Core changes: ``albumtotal`` computed attribute that provides the total number of tracks on the album. (The :ref:`per_disc_numbering` option has no influence on this field.) -* The :ref:`list_format_album` and :ref:`list_format_item` configuration keys +* The `list_format_album` and `list_format_item` configuration keys now affect (almost) every place where objects are printed and logged. (Previously, they only controlled the :ref:`list-cmd` command and a few other scattered pieces.) :bug:`1269` -* :ref:`list_format_album` and :ref:`list_format_album` have respectively been +* `list_format_album` and `list_format_album` have respectively been renamed :ref:`format_album` and :ref:`format_item`. The old names still work but each triggers a warning message. :bug:`1271` diff --git a/docs/reference/config.rst b/docs/reference/config.rst index b7763c0b4..fc686a8b8 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -162,20 +162,8 @@ Either ``yes`` or ``no``, indicating whether the autotagger should use multiple threads. This makes things faster but may behave strangely. Defaults to ``yes``. + .. _list_format_item: - -list_format_item -~~~~~~~~~~~~~~~~ - -Deprecated option, replaced by :ref:`format_item`. - -.. _list_format_album: - -list_format_album -~~~~~~~~~~~~~~~~~ - -Deprecated option, replaced by :ref:`format_album`. - .. _format_item: format_item @@ -186,8 +174,9 @@ command and other commands that need to print out items. Defaults to ``$artist - $album - $title``. The ``-f`` command-line option overrides this setting. -It used to be named :ref:`list_format_item`. +It used to be named `list_format_item`. +.. _list_format_album: .. _format_album: format_album @@ -197,7 +186,7 @@ Format to use when listing *albums* with :ref:`list-cmd` and other commands. Defaults to ``$albumartist - $album``. The ``-f`` command-line option overrides this setting. -It used to be named :ref:`list_format_album`. +It used to be named `list_format_album`. .. _sort_item: From e789b04c9a201e6672bb8274f09df498d3dc4335 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Sat, 7 Mar 2015 13:51:00 +0100 Subject: [PATCH 12/12] =?UTF-8?q?Rename=20LibModel.format=5Fconfig=5Fkey?= =?UTF-8?q?=20=E2=86=92=20=5Fformat=5Fconfig=5Fkey?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #1346 --- beets/library.py | 8 ++++---- beets/ui/__init__.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/beets/library.py b/beets/library.py index bd88136f1..139cdfec0 100644 --- a/beets/library.py +++ b/beets/library.py @@ -259,7 +259,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. """ @@ -282,7 +282,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 @@ -444,7 +444,7 @@ class Item(LibModel): _sorts = {'artist': SmartArtistSort} - format_config_key = 'format_item' + _format_config_key = 'format_item' @classmethod def _getters(cls): @@ -877,7 +877,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): diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 85c5e4824..335129af2 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -637,7 +637,7 @@ class CommonOptionsParser(optparse.OptionParser, object): value = fmt or value and unicode(value) or '' parser.values.format = value if target: - config[target.format_config_key].set(value) + config[target._format_config_key].set(value) else: if self._album_flags: if parser.values.album: @@ -648,10 +648,10 @@ class CommonOptionsParser(optparse.OptionParser, object): target = library.Album else: target = library.Item - config[target.format_config_key].set(value) + config[target._format_config_key].set(value) else: - config[library.Item.format_config_key].set(value) - config[library.Album.format_config_key].set(value) + config[library.Item._format_config_key].set(value) + 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