mirror of
https://github.com/beetbox/beets.git
synced 2026-01-06 07:53:40 +01:00
Fix encoding for hook plugin
- Add a AutoFieldCountFormatter formatter for auto field incrementation. - Add a CodingFormatter for formatting encoded strings. - Fix encoding for hook plugin using CodingFormatter.
This commit is contained in:
parent
b9464d1ea4
commit
576ec92992
1 changed files with 96 additions and 9 deletions
|
|
@ -15,6 +15,7 @@
|
|||
"""Allows custom commands to be run when an event is emitted by beets"""
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
import string
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
|
|
@ -22,6 +23,88 @@ from beets.plugins import BeetsPlugin
|
|||
from beets.ui import _arg_encoding
|
||||
|
||||
|
||||
# Sadly we need this class for {} support due to issue 13598
|
||||
# https://bugs.python.org/issue13598
|
||||
# https://bugs.python.org/file25816/issue13598.diff
|
||||
class AutoFieldCountFormatter(string.Formatter):
|
||||
def _vformat(self, format_string, args, kwargs, used_args,
|
||||
recursion_depth):
|
||||
if recursion_depth < 0:
|
||||
raise ValueError('Max string recursion exceeded')
|
||||
auto_field_count = 0
|
||||
# manual numbering
|
||||
manual = None
|
||||
result = []
|
||||
for literal_text, field_name, format_spec, conversion in \
|
||||
self.parse(format_string):
|
||||
|
||||
# output the literal text
|
||||
if literal_text:
|
||||
result.append(literal_text)
|
||||
|
||||
# if there's a field, output it
|
||||
if field_name is not None:
|
||||
# this is some markup, find the object and do
|
||||
# the formatting
|
||||
|
||||
# ensure we are consistent with numbering
|
||||
if (field_name == "" and manual) or manual is False:
|
||||
raise ValueError("cannot switch from manual field " +
|
||||
"specification to automatic field " +
|
||||
"numbering")
|
||||
|
||||
# automatic numbering
|
||||
if field_name == "":
|
||||
manual = False
|
||||
field_name = str(auto_field_count)
|
||||
auto_field_count += 1
|
||||
|
||||
# manual numbering
|
||||
else:
|
||||
manual = True
|
||||
|
||||
# given the field_name, find the object it references
|
||||
# and the argument it came from
|
||||
obj, arg_used = self.get_field(field_name, args, kwargs)
|
||||
used_args.add(arg_used)
|
||||
|
||||
# do any conversion on the resulting object
|
||||
obj = self.convert_field(obj, conversion)
|
||||
|
||||
# expand the format spec, if needed
|
||||
format_spec = self._vformat(format_spec, args, kwargs,
|
||||
used_args, recursion_depth - 1)
|
||||
|
||||
# format the object and append to the result
|
||||
result.append(self.format_field(obj, format_spec))
|
||||
|
||||
return ''.join(result)
|
||||
|
||||
|
||||
class CodingFormatter(AutoFieldCountFormatter):
|
||||
def __init__(self, coding):
|
||||
self._coding = coding
|
||||
|
||||
def format(self, format_string, *args, **kwargs):
|
||||
try:
|
||||
format_string = format_string.decode(self._coding)
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
|
||||
return super(CodingFormatter, self).format(format_string, *args,
|
||||
**kwargs)
|
||||
|
||||
def convert_field(self, value, conversion):
|
||||
converted = super(CodingFormatter, self).convert_field(value,
|
||||
conversion)
|
||||
try:
|
||||
converted = converted.decode(self._coding)
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
|
||||
return converted
|
||||
|
||||
|
||||
class HookPlugin(BeetsPlugin):
|
||||
"""Allows custom commands to be run when an event is emitted by beets"""
|
||||
def __init__(self):
|
||||
|
|
@ -47,18 +130,22 @@ class HookPlugin(BeetsPlugin):
|
|||
self._log.error('invalid command "{0}"', command)
|
||||
return
|
||||
|
||||
unicode_command = command.decode('utf-8')
|
||||
formatted_command = unicode_command.format(event=event,
|
||||
**kwargs)
|
||||
encoded_command = formatted_command.decode(_arg_encoding())
|
||||
command_pieces = shlex.split(encoded_command)
|
||||
encoding = _arg_encoding()
|
||||
formatter = CodingFormatter(encoding)
|
||||
formatted_command = formatter.format(command, event=event,
|
||||
**kwargs)
|
||||
encoded_formatted_command = formatted_command.encode(encoding)
|
||||
command_pieces = shlex.split(encoded_formatted_command)
|
||||
decoded_command_pieces = map(lambda piece:
|
||||
piece.decode(encoding),
|
||||
command_pieces)
|
||||
|
||||
self._log.debug('Running command "{0}" for event {1}',
|
||||
encoded_command, event)
|
||||
self._log.debug(u'running command "{0}" for event {1}',
|
||||
formatted_command, event)
|
||||
|
||||
try:
|
||||
subprocess.Popen(command_pieces).wait()
|
||||
subprocess.Popen(decoded_command_pieces).wait()
|
||||
except OSError as exc:
|
||||
self._log.error('hook for {0} failed: {1}', event, exc)
|
||||
self._log.error(u'hook for {0} failed: {1}', event, exc)
|
||||
|
||||
self.register_listener(event, hook_function)
|
||||
|
|
|
|||
Loading…
Reference in a new issue