plugin the: version 1.0

This commit is contained in:
Blemjhoo Tezoulbr 2012-09-16 04:42:39 +03:00
parent 749b19955e
commit 16aa842ccf
4 changed files with 225 additions and 0 deletions

124
beetsplug/the.py Normal file
View 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))

View file

@ -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
View 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
View 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')