From 377a2a6964caaaff0e8d8a8059d57d65d838a517 Mon Sep 17 00:00:00 2001 From: diomekes Date: Thu, 19 Jan 2017 15:09:12 -0500 Subject: [PATCH 1/6] add bracket argument to aunique --- beets/library.py | 15 ++++++++++++--- docs/reference/pathformat.rst | 25 +++++++++++++++---------- test/test_library.py | 4 ++-- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/beets/library.py b/beets/library.py index e3ac1bd409..1cfe87f0c3 100644 --- a/beets/library.py +++ b/beets/library.py @@ -1447,7 +1447,7 @@ def tmpl_time(s, fmt): cur_fmt = beets.config['time_format'].as_str() return time.strftime(fmt, time.strptime(s, cur_fmt)) - def tmpl_aunique(self, keys=None, disam=None): + def tmpl_aunique(self, keys=None, disam=None, bracket=None): """Generate a string that is guaranteed to be unique among all albums in the library who share the same set of keys. A fields from "disam" is used in the string if one is sufficient to @@ -1467,8 +1467,10 @@ def tmpl_aunique(self, keys=None, disam=None): keys = keys or 'albumartist album' disam = disam or 'albumtype year label catalognum albumdisambig' + bracket = bracket or '[]' keys = keys.split() disam = disam.split() + bracket = None if bracket is ' ' else list(bracket) album = self.lib.get_album(self.item) if not album: @@ -1502,13 +1504,20 @@ def tmpl_aunique(self, keys=None, disam=None): else: # No disambiguator distinguished all fields. - res = u' {0}'.format(album.id) + res = u' {1}{0}{2}'.format(album.id, bracket[0] if bracket else + u'', bracket[1] if bracket else u'') self.lib._memotable[memokey] = res return res # Flatten disambiguation value into a string. disam_value = album.formatted(True).get(disambiguator) - res = u' [{0}]'.format(disam_value) + res = u' {1}{0}{2}'.format(disam_value, bracket[0] if bracket else u'', + bracket[1] if bracket else u'') + + # Remove space and/or brackets if disambiguation value is empty + if bracket and res == u' ' + ''.join(bracket) or res.isspace(): + res = u'' + self.lib._memotable[memokey] = res return res diff --git a/docs/reference/pathformat.rst b/docs/reference/pathformat.rst index 72453a6e53..de1185ae06 100644 --- a/docs/reference/pathformat.rst +++ b/docs/reference/pathformat.rst @@ -71,8 +71,8 @@ These functions are built in to beets: For example, "café" becomes "cafe". Uses the mapping provided by the `unidecode module`_. See the :ref:`asciify-paths` configuration option. -* ``%aunique{identifiers,disambiguators}``: Provides a unique string to - disambiguate similar albums in the database. See :ref:`aunique`, below. +* ``%aunique{identifiers,disambiguators,brackets}``: Provides a unique string + to disambiguate similar albums in the database. See :ref:`aunique`, below. * ``%time{date_time,format}``: Return the date and time in any format accepted by `strftime`_. For example, to get the year some music was added to your library, use ``%time{$added,%Y}``. @@ -112,14 +112,16 @@ will expand to "[2008]" for one album and "[2010]" for the other. The function detects that you have two albums with the same artist and title but that they have different release years. -For full flexibility, the ``%aunique`` function takes two arguments, each of -which are whitespace-separated lists of album field names: a set of -*identifiers* and a set of *disambiguators*. Any group of albums with identical -values for all the identifiers will be considered "duplicates". Then, the -function tries each disambiguator field, looking for one that distinguishes each -of the duplicate albums from each other. The first such field is used as the -result for ``%aunique``. If no field suffices, an arbitrary number is used to -distinguish the two albums. +For full flexibility, the ``%aunique`` function takes three arguments. The +first two are whitespace-separated lists of album field names: a set of +*identifiers* and a set of *disambiguators*. The third argument is a pair of +characters used to surround the disambiguator. + +Any group of albums with identical values for all the identifiers will be +considered "duplicates". Then, the function tries each disambiguator field, +looking for one that distinguishes each of the duplicate albums from each +other. The first such field is used as the result for ``%aunique``. If no field +suffices, an arbitrary number is used to distinguish the two albums. The default identifiers are ``albumartist album`` and the default disambiguators are ``albumtype year label catalognum albumdisambig``. So you can get reasonable @@ -127,6 +129,9 @@ disambiguation behavior if you just use ``%aunique{}`` with no parameters in your path forms (as in the default path formats), but you can customize the disambiguation if, for example, you include the year by default in path formats. +The default characters used as brackets are ``[]``. If a single blank space is +used, then the disambiguator will not be surrounded by anything. + One caveat: When you import an album that is named identically to one already in your library, the *first* album—the one already in your library— will not consider itself a duplicate at import time. This means that ``%aunique{}`` will diff --git a/test/test_library.py b/test/test_library.py index 7aa88e0640..d2c21d5f63 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -713,8 +713,8 @@ def test_use_fallback_numbers_when_identical(self): album2.year = 2001 album2.store() - self._assert_dest(b'/base/foo 1/the title', self.i1) - self._assert_dest(b'/base/foo 2/the title', self.i2) + self._assert_dest(b'/base/foo [1]/the title', self.i1) + self._assert_dest(b'/base/foo [2]/the title', self.i2) def test_unique_falls_back_to_second_distinguishing_field(self): self._setf(u'foo%aunique{albumartist album,month year}/$title') From 3a967df3961022f3787a99b19b963a623c3ba3fa Mon Sep 17 00:00:00 2001 From: diomekes Date: Thu, 19 Jan 2017 20:30:13 -0500 Subject: [PATCH 2/6] simplify check for empty disam_val, update changelog and docs, add change bracket test --- beets/library.py | 25 +++++++++++++++++-------- docs/changelog.rst | 5 +++++ docs/reference/pathformat.rst | 6 ++++-- test/test_library.py | 10 ++++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/beets/library.py b/beets/library.py index 1cfe87f0c3..86fcf7c208 100644 --- a/beets/library.py +++ b/beets/library.py @@ -1453,7 +1453,9 @@ def tmpl_aunique(self, keys=None, disam=None, bracket=None): from "disam" is used in the string if one is sufficient to disambiguate the albums. Otherwise, a fallback opaque value is used. Both "keys" and "disam" should be given as - whitespace-separated lists of field names. + whitespace-separated lists of field names, while "bracket" is a + pair of characters to be used as brackets surrounding the + disambiguator or a white space to have no brackets. """ # Fast paths: no album, no item or library, or memoized value. if not self.item or not self.lib: @@ -1470,7 +1472,15 @@ def tmpl_aunique(self, keys=None, disam=None, bracket=None): bracket = bracket or '[]' keys = keys.split() disam = disam.split() - bracket = None if bracket is ' ' else list(bracket) + bracket = None if bracket == ' ' else list(bracket) + + # Assign a left and right bracket from bracket list. + if bracket: + bracket_l = bracket[0] + bracket_r = bracket[1] + else: + bracket_l = u'' + bracket_r = u'' album = self.lib.get_album(self.item) if not album: @@ -1504,18 +1514,17 @@ def tmpl_aunique(self, keys=None, disam=None, bracket=None): else: # No disambiguator distinguished all fields. - res = u' {1}{0}{2}'.format(album.id, bracket[0] if bracket else - u'', bracket[1] if bracket else u'') + res = u' {1}{0}{2}'.format(album.id, bracket_l, bracket_r) self.lib._memotable[memokey] = res return res # Flatten disambiguation value into a string. disam_value = album.formatted(True).get(disambiguator) - res = u' {1}{0}{2}'.format(disam_value, bracket[0] if bracket else u'', - bracket[1] if bracket else u'') - # Remove space and/or brackets if disambiguation value is empty - if bracket and res == u' ' + ''.join(bracket) or res.isspace(): + # Return empty string if disambiguator is empty. + if disam_value: + res = u' {1}{0}{2}'.format(disam_value, bracket_l, bracket_r) + else: res = u'' self.lib._memotable[memokey] = res diff --git a/docs/changelog.rst b/docs/changelog.rst index 4fa8aa50fe..e2849c22d2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -17,6 +17,11 @@ New features: return the item info for the file at the given path, or 404. * The :doc:`/plugins/web` also has a new config option, ``include_paths``, which will cause paths to be included in item API responses if set to true. +* The ``%aunique`` template function for :ref:`aunique` now takes a third + argument that specifies which brackets to use around the disambiguator + value. The argument can be any two characters that represent the left and + right brackets. It defaults to `[]` and can also be a white space to turn off + bracketing. :bug:`2397` :bug:`2399` Fixes: diff --git a/docs/reference/pathformat.rst b/docs/reference/pathformat.rst index de1185ae06..873a51c5ea 100644 --- a/docs/reference/pathformat.rst +++ b/docs/reference/pathformat.rst @@ -129,8 +129,10 @@ disambiguation behavior if you just use ``%aunique{}`` with no parameters in your path forms (as in the default path formats), but you can customize the disambiguation if, for example, you include the year by default in path formats. -The default characters used as brackets are ``[]``. If a single blank space is -used, then the disambiguator will not be surrounded by anything. +The default characters used as brackets are ``[]``. To change this, provide a +third argument to the ``%aunique`` function consisting of two characters: the left +and right brackets. Or, to turn off bracketing entirely, use a single blank +space. One caveat: When you import an album that is named identically to one already in your library, the *first* album—the one already in your library— will not diff --git a/test/test_library.py b/test/test_library.py index d2c21d5f63..9e13018808 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -730,6 +730,16 @@ def test_unique_sanitized(self): self._setf(u'foo%aunique{albumartist album,albumtype}/$title') self._assert_dest(b'/base/foo [foo_bar]/the title', self.i1) + def test_drop_empty_disam_string(self): + album1 = self.lib.get_album(self.i1) + album1.year = None + album1.store() + self._assert_dest(b'/base/foo/the title', self.i1) + + def test_change_brackets(self): + self._setf(u'foo%aunique{albumartist album,year,()}/$title') + self._assert_dest(b'/base/foo (2001)/the title', self.i1) + class PluginDestinationTest(_common.TestCase): def setUp(self): From d10df34c65bbdcff39c9c7660ae4b9f5593cd26c Mon Sep 17 00:00:00 2001 From: diomekes Date: Fri, 20 Jan 2017 09:06:38 -0500 Subject: [PATCH 3/6] add test for aunique without brackets --- test/test_library.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_library.py b/test/test_library.py index 9e13018808..263254f6b9 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -740,6 +740,10 @@ def test_change_brackets(self): self._setf(u'foo%aunique{albumartist album,year,()}/$title') self._assert_dest(b'/base/foo (2001)/the title', self.i1) + def test_remove_brackets(self): + self._setf(u'foo%aunique{albumartist album,year, }/$title') + self._assert_dest(b'/base/foo 2001/the title', self.i1) + class PluginDestinationTest(_common.TestCase): def setUp(self): From eaa2161a903ba8ec5d33f3497100e1900fa468a5 Mon Sep 17 00:00:00 2001 From: diomekes Date: Fri, 20 Jan 2017 19:40:09 -0500 Subject: [PATCH 4/6] fix empty disambig string test --- test/test_library.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_library.py b/test/test_library.py index 263254f6b9..34e5437e5a 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -730,10 +730,14 @@ def test_unique_sanitized(self): self._setf(u'foo%aunique{albumartist album,albumtype}/$title') self._assert_dest(b'/base/foo [foo_bar]/the title', self.i1) - def test_drop_empty_disam_string(self): + def test_drop_empty_disambig_string(self): album1 = self.lib.get_album(self.i1) - album1.year = None + album1.albumdisambig = None + album2 = self.lib.get_album(self.i2) + album2.albumdisambig = u'foo' album1.store() + album2.store() + self._setf(u'foo%aunique{albumartist album,albumdisambig}/$title') self._assert_dest(b'/base/foo/the title', self.i1) def test_change_brackets(self): From 04f7915d41c246c7d8305d079fd1b130389b5a61 Mon Sep 17 00:00:00 2001 From: diomekes Date: Fri, 20 Jan 2017 22:47:47 -0500 Subject: [PATCH 5/6] change no-bracket argument from white space to empty --- beets/library.py | 9 +++++---- docs/changelog.rst | 2 +- docs/reference/pathformat.rst | 3 +-- test/test_library.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/beets/library.py b/beets/library.py index 86fcf7c208..6f68e74349 100644 --- a/beets/library.py +++ b/beets/library.py @@ -1469,13 +1469,14 @@ def tmpl_aunique(self, keys=None, disam=None, bracket=None): keys = keys or 'albumartist album' disam = disam or 'albumtype year label catalognum albumdisambig' - bracket = bracket or '[]' + if bracket is None: + bracket = '[]' keys = keys.split() disam = disam.split() - bracket = None if bracket == ' ' else list(bracket) - # Assign a left and right bracket from bracket list. - if bracket: + # Assign a left and right bracket or leave blank if argument is empty. + if len(bracket) == 2: + bracket = list(bracket) bracket_l = bracket[0] bracket_r = bracket[1] else: diff --git a/docs/changelog.rst b/docs/changelog.rst index e2849c22d2..0a32752d15 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -20,7 +20,7 @@ New features: * The ``%aunique`` template function for :ref:`aunique` now takes a third argument that specifies which brackets to use around the disambiguator value. The argument can be any two characters that represent the left and - right brackets. It defaults to `[]` and can also be a white space to turn off + right brackets. It defaults to `[]` and can also be blank to turn off bracketing. :bug:`2397` :bug:`2399` Fixes: diff --git a/docs/reference/pathformat.rst b/docs/reference/pathformat.rst index 873a51c5ea..667be3150b 100644 --- a/docs/reference/pathformat.rst +++ b/docs/reference/pathformat.rst @@ -131,8 +131,7 @@ disambiguation if, for example, you include the year by default in path formats. The default characters used as brackets are ``[]``. To change this, provide a third argument to the ``%aunique`` function consisting of two characters: the left -and right brackets. Or, to turn off bracketing entirely, use a single blank -space. +and right brackets. Or, to turn off bracketing entirely, leave argument blank. One caveat: When you import an album that is named identically to one already in your library, the *first* album—the one already in your library— will not diff --git a/test/test_library.py b/test/test_library.py index 34e5437e5a..aaab6fe035 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -745,7 +745,7 @@ def test_change_brackets(self): self._assert_dest(b'/base/foo (2001)/the title', self.i1) def test_remove_brackets(self): - self._setf(u'foo%aunique{albumartist album,year, }/$title') + self._setf(u'foo%aunique{albumartist album,year,}/$title') self._assert_dest(b'/base/foo 2001/the title', self.i1) From 672fc36cc8b3c51d7332a4dbba2a9613e36d17d4 Mon Sep 17 00:00:00 2001 From: diomekes Date: Fri, 20 Jan 2017 23:08:36 -0500 Subject: [PATCH 6/6] fix docstring for aunique empty bracket --- beets/library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/library.py b/beets/library.py index 6f68e74349..a636948044 100644 --- a/beets/library.py +++ b/beets/library.py @@ -1455,7 +1455,7 @@ def tmpl_aunique(self, keys=None, disam=None, bracket=None): used. Both "keys" and "disam" should be given as whitespace-separated lists of field names, while "bracket" is a pair of characters to be used as brackets surrounding the - disambiguator or a white space to have no brackets. + disambiguator or empty to have no brackets. """ # Fast paths: no album, no item or library, or memoized value. if not self.item or not self.lib: