diff --git a/beetsplug/bucket.py b/beetsplug/bucket.py index 60ceeb119..49a7cbbaa 100644 --- a/beetsplug/bucket.py +++ b/beetsplug/bucket.py @@ -152,22 +152,30 @@ def extract_modes(spans): return deflen, deffmt -def build_alpha_spans(alpha_spans_str): +def build_alpha_spans(alpha_spans_str, alpha_regexs): """Extract alphanumerics from string and return sorted list of chars [from...to] """ spans = [] ASCII_DIGITS = string.digits + string.ascii_lowercase for elem in alpha_spans_str: - bucket = sorted([x for x in elem.lower() if x.isalnum()]) - if bucket: - beginIdx = ASCII_DIGITS.index(bucket[0]) - endIdx = ASCII_DIGITS.index(bucket[-1]) + if elem in alpha_regexs: + spans.append(re.compile(alpha_regexs[elem])) else: - raise ui.UserError("invalid range defined for alpha bucket '%s'" - " : no alphanumeric character found" % - elem) - spans.append(ASCII_DIGITS[beginIdx:endIdx + 1]) + bucket = sorted([x for x in elem.lower() if x.isalnum()]) + if bucket: + beginIdx = ASCII_DIGITS.index(bucket[0]) + endIdx = ASCII_DIGITS.index(bucket[-1]) + else: + raise ui.UserError("invalid range defined for alpha bucket " + "'%s': no alphanumeric character found" % + elem) + spans.append( + re.compile( + "^[" + ASCII_DIGITS[beginIdx:endIdx + 1] + + ASCII_DIGITS[beginIdx:endIdx + 1].upper() + "]" + ) + ) return spans @@ -179,6 +187,7 @@ class BucketPlugin(plugins.BeetsPlugin): self.config.add({ 'bucket_year': [], 'bucket_alpha': [], + 'bucket_alpha_regex': {}, 'extrapolate': False }) self.setup() @@ -193,7 +202,10 @@ class BucketPlugin(plugins.BeetsPlugin): self.year_spans = extend_year_spans(self.year_spans, self.ys_len_mode) - self.alpha_spans = build_alpha_spans(self.config['bucket_alpha'].get()) + self.alpha_spans = build_alpha_spans( + self.config['bucket_alpha'].get(), + self.config['bucket_alpha_regex'].get() + ) def find_bucket_year(self, year): """Return bucket that matches given year or return the year @@ -215,12 +227,12 @@ class BucketPlugin(plugins.BeetsPlugin): string initial if no matching bucket. """ for (i, span) in enumerate(self.alpha_spans): - if s.lower()[0] in span: + if span.match(s): return self.config['bucket_alpha'].get()[i] return s[0].upper() def _tmpl_bucket(self, text, field=None): - if not field and text.isdigit(): + if not field and len(text) == 4 and text.isdigit(): field = 'year' if field == 'year': diff --git a/docs/plugins/bucket.rst b/docs/plugins/bucket.rst index a278d4208..f869ea024 100644 --- a/docs/plugins/bucket.rst +++ b/docs/plugins/bucket.rst @@ -38,3 +38,12 @@ of declared buckets:: extrapolate: true The above configuration creates five-year ranges for any input year. + +If the automatic range of an alpha bucket is not sufficient an overriding regular expression can be used:: + + bucket: + bucket_alpha: ['A - D', 'E - L', 'M - R', 'S - Z'] + bucket_alpha_regex: + 'A - D': ^[0-9a-dA-D…äÄ] + +The *A - D* bucket now matches also all artists starting with ä or Ä and 0 to 9 and … (three dots). The other buckets work as ranges (see above). diff --git a/test/test_bucket.py b/test/test_bucket.py index f8b86bf6d..5ff834d52 100644 --- a/test/test_bucket.py +++ b/test/test_bucket.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2014, Fabrice Laporte. # @@ -31,9 +32,10 @@ class BucketPluginTest(unittest.TestCase, TestHelper): self.teardown_beets() def _setup_config(self, bucket_year=[], bucket_alpha=[], - extrapolate=False): + bucket_alpha_regex = {}, extrapolate=False): config['bucket']['bucket_year'] = bucket_year config['bucket']['bucket_alpha'] = bucket_alpha + config['bucket']['bucket_alpha_regex'] = bucket_alpha_regex config['bucket']['extrapolate'] = extrapolate self.plugin.setup() @@ -107,6 +109,26 @@ class BucketPluginTest(unittest.TestCase, TestHelper): self._setup_config(bucket_alpha=[]) self.assertEqual(self.plugin._tmpl_bucket('errol'), 'E') + def test_alpha_regex(self): + """Check regex is used""" + self._setup_config(bucket_alpha=['foo', 'bar'], + bucket_alpha_regex={'foo': '^[a-d]', + 'bar': '^[e-z]'}) + self.assertEqual(self.plugin._tmpl_bucket('alpha'), 'foo') + self.assertEqual(self.plugin._tmpl_bucket('delta'), 'foo') + self.assertEqual(self.plugin._tmpl_bucket('zeta'), 'bar') + self.assertEqual(self.plugin._tmpl_bucket('Alpha'), 'A') + + def test_alpha_regex_mix(self): + """Check mixing regex and non-regex is possible""" + self._setup_config(bucket_alpha=['A - D', 'E - L'], + bucket_alpha_regex={'A - D': '^[0-9a-dA-D…äÄ]'}) + self.assertEqual(self.plugin._tmpl_bucket('alpha'), 'A - D') + self.assertEqual(self.plugin._tmpl_bucket('Ärzte'), 'A - D') + self.assertEqual(self.plugin._tmpl_bucket('112'), 'A - D') + self.assertEqual(self.plugin._tmpl_bucket('…and Oceans'), 'A - D') + self.assertEqual(self.plugin._tmpl_bucket('Eagles'), 'E - L') + @raises(ui.UserError) def test_bad_alpha_range_def(self): """If bad alpha range definition, a UserError is raised"""