mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
plugin the: version 1.0
This commit is contained in:
parent
749b19955e
commit
16aa842ccf
4 changed files with 225 additions and 0 deletions
124
beetsplug/the.py
Normal file
124
beetsplug/the.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#!/usr/bin/env python2
|
||||
#
|
||||
# This is a plugin for beets music organizer.
|
||||
# Copyright (c) 2012 Blemjhoo Tezoulbr <baobab@heresiarch.info>
|
||||
# Licensed under the same terms as beets itself.
|
||||
#
|
||||
|
||||
"""Moves patterns in path formats (suitable for moving articles)."""
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import re
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets import ui
|
||||
|
||||
|
||||
__author__ = 'baobab@heresiarch.info'
|
||||
__version__ = '1.0'
|
||||
|
||||
PATTERN_THE = u'^[the]{3}\s'
|
||||
PATTERN_A = u'^[a][n]?\s'
|
||||
FORMAT = u'{0}, {1}'
|
||||
|
||||
the_options = {
|
||||
'debug': False,
|
||||
'the': True,
|
||||
'a': True,
|
||||
'format': FORMAT,
|
||||
'strip': False,
|
||||
'silent': False,
|
||||
'patterns': [PATTERN_THE, PATTERN_A],
|
||||
}
|
||||
|
||||
|
||||
class ThePlugin(BeetsPlugin):
|
||||
|
||||
def configure(self, config):
|
||||
if not config.has_section('the'):
|
||||
print('[the] plugin is not configured, using defaults',
|
||||
file=sys.stderr)
|
||||
return
|
||||
self.in_config = True
|
||||
the_options['debug'] = ui.config_val(config, 'the', 'debug', False,
|
||||
bool)
|
||||
the_options['the'] = ui.config_val(config, 'the', 'the', True, bool)
|
||||
the_options['a'] = ui.config_val(config, 'the', 'a', True, bool)
|
||||
the_options['format'] = ui.config_val(config, 'the', 'format',
|
||||
FORMAT)
|
||||
the_options['strip'] = ui.config_val(config, 'the', 'strip', False,
|
||||
bool)
|
||||
the_options['silent'] = ui.config_val(config, 'the', 'silent', False,
|
||||
bool)
|
||||
the_options['patterns'] = ui.config_val(config, 'the', 'patterns',
|
||||
'').split()
|
||||
for p in the_options['patterns']:
|
||||
if p:
|
||||
try:
|
||||
re.compile(p)
|
||||
except re.error:
|
||||
print(u'[the] invalid pattern: {}'.format(p),
|
||||
file=sys.stderr)
|
||||
else:
|
||||
if not (p.startswith('^') or p.endswith('$')):
|
||||
if not the_options['silent']:
|
||||
print(u'[the] warning: pattern \"{}\" will not '
|
||||
'match string start/end'.format(p),
|
||||
file=sys.stderr)
|
||||
if the_options['a']:
|
||||
the_options['patterns'] = [PATTERN_A] + the_options['patterns']
|
||||
if the_options['the']:
|
||||
the_options['patterns'] = [PATTERN_THE] + the_options['patterns']
|
||||
if not the_options['patterns'] and not the_options['silent']:
|
||||
print('[the] no patterns defined!')
|
||||
if the_options['debug']:
|
||||
print(u'[the] patterns: {}'
|
||||
.format(' '.join(the_options['patterns'])), file=sys.stderr)
|
||||
|
||||
|
||||
def unthe(text, pattern, strip=False):
|
||||
"""Moves pattern in the path format string or strips it
|
||||
|
||||
text -- text to handle
|
||||
pattern -- regexp pattern (case ignore is already on)
|
||||
strip -- if True, pattern will be removed
|
||||
|
||||
"""
|
||||
if text:
|
||||
r = re.compile(pattern, flags=re.IGNORECASE)
|
||||
try:
|
||||
t = r.findall(text)[0]
|
||||
except IndexError:
|
||||
return text
|
||||
else:
|
||||
r = re.sub(r, '', text).strip()
|
||||
if strip:
|
||||
return r
|
||||
else:
|
||||
return the_options['format'].format(r, t.strip()).strip()
|
||||
else:
|
||||
return u''
|
||||
|
||||
|
||||
@ThePlugin.template_func('the')
|
||||
def func_the(text):
|
||||
"""Provides beets template function %the"""
|
||||
if not the_options['patterns']:
|
||||
return text
|
||||
if text:
|
||||
for p in the_options['patterns']:
|
||||
r = unthe(text, p, the_options['strip'])
|
||||
if r != text:
|
||||
break
|
||||
if the_options['debug']:
|
||||
print(u'[the] \"{}\" -> \"{}\"'.format(text, r), file=sys.stderr)
|
||||
return r
|
||||
else:
|
||||
return u''
|
||||
|
||||
|
||||
# simple tests
|
||||
if __name__ == '__main__':
|
||||
print(unthe('The The', PATTERN_THE))
|
||||
print(unthe('An Apple', PATTERN_A))
|
||||
print(unthe('A Girl', PATTERN_A, strip=True))
|
||||
|
|
@ -50,6 +50,7 @@ disabled by default, but you can turn them on as described above.
|
|||
rdm
|
||||
mbcollection
|
||||
importfeeds
|
||||
the
|
||||
|
||||
Autotagger Extensions
|
||||
''''''''''''''''''''''
|
||||
|
|
@ -72,6 +73,7 @@ Path Formats
|
|||
|
||||
* :doc:`inline`: Use Python snippets to customize path format strings.
|
||||
* :doc:`rewrite`: Substitute values in path formats.
|
||||
* :doc:`the`: Moves patterns in path formats (suitable for moving articles).
|
||||
|
||||
Interoperability
|
||||
''''''''''''''''
|
||||
|
|
|
|||
47
docs/plugins/the.rst
Normal file
47
docs/plugins/the.rst
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
The Plugin
|
||||
==========
|
||||
|
||||
The ``the`` plugin allows you to move patterns in path formats. It's suitable,
|
||||
for example, for moving articles from string start to the end. This is useful
|
||||
for quick search on filesystems and generally looks good. Plugin DOES NOT
|
||||
change tags. By default plugin supports English "the, a, an", but custom
|
||||
regexp patterns can be added by user. How it works::
|
||||
|
||||
The Something -> Something, The
|
||||
A Band -> Band, A
|
||||
An Orchestra -> Orchestra, An
|
||||
|
||||
To use plugin, enable it by including ``the`` into ``plugins`` line of
|
||||
your beets config::
|
||||
|
||||
[beets]
|
||||
plugins = the
|
||||
|
||||
Plugin provides template function %the, so you can use it on $albumartist or $artist::
|
||||
|
||||
[paths]
|
||||
default: %the{$albumartist}/($year) $album/$track $title
|
||||
|
||||
Default options are acceptable (moves all English articles to the end), but you
|
||||
can add plugin section into config file::
|
||||
|
||||
[the]
|
||||
# handle The, default is on
|
||||
the=yes
|
||||
# handle A/An, default is on
|
||||
a=yes
|
||||
# format string, {0} - part w/o article, {1} - article
|
||||
# spaces already trimmed from ends of both parts
|
||||
# default is '{0}, {1}'
|
||||
format={0}, {1}
|
||||
# strip instead of moving to the end, default is off
|
||||
strip=no
|
||||
# do not print warnings, default is off
|
||||
silent=no
|
||||
# custom regexp patterns, separated by space
|
||||
patterns=
|
||||
|
||||
Custom patterns are usual regular expressions. Ignore case is turned on, but ^ is not added
|
||||
automatically, so be careful. Actually, you can swap arguments in format option and write
|
||||
regexp to match end of the string, so things will be moved from the end of the string to
|
||||
start.
|
||||
52
test/test_the.py
Normal file
52
test/test_the.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"""Tests for the 'the' plugin"""
|
||||
|
||||
from _common import unittest
|
||||
from beetsplug import the
|
||||
|
||||
|
||||
class ThePluginTest(unittest.TestCase):
|
||||
|
||||
|
||||
def test_unthe_with_default_patterns(self):
|
||||
self.assertEqual(the.unthe('', the.PATTERN_THE), '')
|
||||
self.assertEqual(the.unthe('The Something', the.PATTERN_THE),
|
||||
'Something, The')
|
||||
self.assertEqual(the.unthe('The The', the.PATTERN_THE), 'The, The')
|
||||
self.assertEqual(the.unthe('The The', the.PATTERN_THE), 'The, The')
|
||||
self.assertEqual(the.unthe('The The X', the.PATTERN_THE),
|
||||
u'The X, The')
|
||||
self.assertEqual(the.unthe('the The', the.PATTERN_THE), 'The, the')
|
||||
self.assertEqual(the.unthe('Protected The', the.PATTERN_THE),
|
||||
'Protected The')
|
||||
self.assertEqual(the.unthe('A Boy', the.PATTERN_A), 'Boy, A')
|
||||
self.assertEqual(the.unthe('a girl', the.PATTERN_A), 'girl, a')
|
||||
self.assertEqual(the.unthe('An Apple', the.PATTERN_A), 'Apple, An')
|
||||
self.assertEqual(the.unthe('An A Thing', the.PATTERN_A), 'A Thing, An')
|
||||
self.assertEqual(the.unthe('the An Arse', the.PATTERN_A),
|
||||
'the An Arse')
|
||||
self.assertEqual(the.unthe('The Something', the.PATTERN_THE,
|
||||
strip=True), 'Something')
|
||||
self.assertEqual(the.unthe('An A', the.PATTERN_A, strip=True), 'A')
|
||||
|
||||
def test_template_function_with_defaults(self):
|
||||
the.the_options['patterns'] = [the.PATTERN_THE, the.PATTERN_A]
|
||||
the.the_options['format'] = the.FORMAT
|
||||
self.assertEqual(the.func_the('The The'), 'The, The')
|
||||
self.assertEqual(the.func_the('An A'), 'A, An')
|
||||
|
||||
def test_custom_pattern(self):
|
||||
the.the_options['patterns'] = [ u'^test\s']
|
||||
the.the_options['format'] = the.FORMAT
|
||||
self.assertEqual(the.func_the('test passed'), 'passed, test')
|
||||
|
||||
def test_custom_format(self):
|
||||
the.the_options['patterns'] = [the.PATTERN_THE, the.PATTERN_A]
|
||||
the.the_options['format'] = '{1} ({0})'
|
||||
self.assertEqual(the.func_the('The A'), 'The (A)')
|
||||
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='suite')
|
||||
Loading…
Reference in a new issue