The Great Trailing Whitespace Purge of 2012

What can I say? I used to use TextMate!
This commit is contained in:
Adrian Sampson 2012-05-13 20:22:17 -07:00
parent a9eb249a15
commit b68e87b92c
47 changed files with 438 additions and 438 deletions

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -93,7 +93,7 @@ def albums_in_dir(path, ignore=()):
collapse_root = root
collapse_items = []
continue
# If it's nonempty, yield it.
if items:
yield root, items

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -112,7 +112,7 @@ def string_dist(str1, str2):
"""
str1 = str1.lower()
str2 = str2.lower()
# Don't penalize strings that move certain words to the end. For
# example, "the something" should be considered equal to
# "something, the".
@ -126,7 +126,7 @@ def string_dist(str1, str2):
for pat, repl in SD_REPLACE:
str1 = re.sub(pat, repl, str1)
str2 = re.sub(pat, repl, str2)
# Change the weight for certain string portions matched by a set
# of regular expressions. We gradually change the strings and build
# up penalties associated with parts of the string that were
@ -137,7 +137,7 @@ def string_dist(str1, str2):
# Get strings that drop the pattern.
case_str1 = re.sub(pat, '', str1)
case_str2 = re.sub(pat, '', str2)
if case_str1 != str1 or case_str2 != str2:
# If the pattern was present (i.e., it is deleted in the
# the current case), recalculate the distances for the
@ -146,7 +146,7 @@ def string_dist(str1, str2):
case_delta = max(0.0, base_dist - case_dist)
if case_delta == 0.0:
continue
# Shift our baseline strings down (to avoid rematching the
# same part of the string) and add a scaled distance
# amount to the penalties.
@ -155,7 +155,7 @@ def string_dist(str1, str2):
base_dist = case_dist
penalty += weight * case_delta
dist = base_dist + penalty
return dist
def current_metadata(items):
@ -191,7 +191,7 @@ def order_items(items, trackinfo):
for i, canon_item in enumerate(trackinfo):
row.append(track_distance(cur_item, canon_item, i+1))
costs.append(row)
# Find a minimum-cost bipartite matching.
matching = Munkres().compute(costs)
@ -221,7 +221,7 @@ def track_distance(item, track_info, track_index=None, incl_artist=False):
diff = min(diff, TRACK_LENGTH_MAX)
dist += (diff / TRACK_LENGTH_MAX) * TRACK_LENGTH_WEIGHT
dist_max += TRACK_LENGTH_WEIGHT
# Track title.
dist += string_dist(item.title, track_info.title) * TRACK_TITLE_WEIGHT
dist_max += TRACK_TITLE_WEIGHT
@ -241,7 +241,7 @@ def track_distance(item, track_info, track_index=None, incl_artist=False):
if item.track not in (track_index, track_info.medium_index):
dist += TRACK_INDEX_WEIGHT
dist_max += TRACK_INDEX_WEIGHT
# MusicBrainz track ID.
if item.mb_trackid:
if item.mb_trackid != track_info.track_id:
@ -262,19 +262,19 @@ def distance(items, album_info):
cur_artist, cur_album, _ = current_metadata(items)
cur_artist = cur_artist or ''
cur_album = cur_album or ''
# These accumulate the possible distance components. The final
# distance will be dist/dist_max.
dist = 0.0
dist_max = 0.0
# Artist/album metadata.
if not album_info.va:
dist += string_dist(cur_artist, album_info.artist) * ARTIST_WEIGHT
dist_max += ARTIST_WEIGHT
dist += string_dist(cur_album, album_info.album) * ALBUM_WEIGHT
dist_max += ALBUM_WEIGHT
# Track distances.
for i, (item, track_info) in enumerate(zip(items, album_info.tracks)):
if item:
@ -305,7 +305,7 @@ def match_by_id(items):
if not albumids:
log.debug('No album IDs found.')
return None
# If all album IDs are equal, look up the album.
if bool(reduce(lambda x,y: x if x==y else (), albumids)):
albumid = albumids[0]
@ -314,7 +314,7 @@ def match_by_id(items):
else:
log.debug('No album ID consensus.')
return None
#fixme In the future, at the expense of performance, we could use
# other IDs (i.e., track and artist) in case the album tag isn't
# present, but that event seems very unlikely.
@ -398,11 +398,11 @@ def tag_album(items, timid=False, search_artist=None, search_album=None,
# Get current metadata.
cur_artist, cur_album, artist_consensus = current_metadata(items)
log.debug('Tagging %s - %s' % (cur_artist, cur_album))
# The output result (distance, AlbumInfo) tuples (keyed by MB album
# ID).
candidates = {}
# Try to find album indicated by MusicBrainz IDs.
if search_id:
log.debug('Searching for album ID: ' + search_id)
@ -427,13 +427,13 @@ def tag_album(items, timid=False, search_artist=None, search_album=None,
return cur_artist, cur_album, candidates.values(), rec
else:
return cur_artist, cur_album, [], RECOMMEND_NONE
# Search terms.
if not (search_artist and search_album):
# No explicit search terms -- use current metadata.
search_artist, search_album = cur_artist, cur_album
log.debug(u'Search terms: %s - %s' % (search_artist, search_album))
# Is this album likely to be a "various artist" release?
va_likely = ((not artist_consensus) or
(search_artist.lower() in VA_ARTISTS) or
@ -446,7 +446,7 @@ def tag_album(items, timid=False, search_artist=None, search_album=None,
log.debug(u'Evaluating %i candidates.' % len(search_cands))
for info in search_cands:
validate_candidate(items, candidates, info)
# Sort and get the recommendation.
candidates = sorted(candidates.itervalues())
rec = recommendation(candidates)
@ -456,7 +456,7 @@ def tag_item(item, timid=False, search_artist=None, search_title=None,
search_id=None):
"""Attempts to find metadata for a single track. Returns a
`(candidates, recommendation)` pair where `candidates` is a list
of `(distance, track_info)` pairs. `search_artist` and
of `(distance, track_info)` pairs. `search_artist` and
`search_title` may be used to override the current metadata for
the purposes of the MusicBrainz title; likewise `search_id`.
"""
@ -484,7 +484,7 @@ def tag_item(item, timid=False, search_artist=None, search_title=None,
return candidates.values(), rec
else:
return [], RECOMMEND_NONE
# Search terms.
if not (search_artist and search_title):
search_artist, search_title = item.artist, item.title

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -502,14 +502,14 @@ def read_tasks(config):
if config.incremental:
incremental_skipped = 0
history_dirs = history_get()
for toppath in config.paths:
# Check whether the path is to a file.
if config.singletons and not os.path.isdir(syspath(toppath)):
item = library.Item.from_path(toppath)
yield ImportTask.item_task(item)
continue
# Produce paths under this directory.
if progress:
resume_dir = resume_dirs.get(toppath)
@ -599,7 +599,7 @@ def user_query(config):
task = yield task
if task.sentinel:
continue
# Ask the user for a choice.
choice = config.choose_match_func(task, config)
task.set_choice(choice)
@ -617,7 +617,7 @@ def user_query(config):
while True:
item_task = yield
item_tasks.append(item_task)
ipl = pipeline.Pipeline((emitter(), item_lookup(config),
ipl = pipeline.Pipeline((emitter(), item_lookup(config),
item_query(config), collector()))
ipl.run_sequential()
task = pipeline.multiple(item_tasks)
@ -650,14 +650,14 @@ def show_progress(config):
# Behave as if ASIS were selected.
task.set_null_match()
task.set_choice(action.ASIS)
def apply_choices(config):
"""A coroutine for applying changes to albums during the autotag
process.
"""
lib = _reopen_lib(config.lib)
task = None
while True:
while True:
task = yield task
if task.should_skip():
continue
@ -894,7 +894,7 @@ def run_import(**kwargs):
ImportConfig.
"""
config = ImportConfig(**kwargs)
# Set up the pipeline.
if config.query is None:
stages = [read_tasks(config)]

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -157,7 +157,7 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None,
# Mark the option's shortcut letter for display.
if (default is None and not numrange and first) \
or (isinstance(default, basestring) and
or (isinstance(default, basestring) and
found_letter.lower() == default.lower()):
# The first option is the default; mark it.
show_letter = '[%s]' % found_letter.upper()
@ -187,7 +187,7 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None,
default = numrange[0]
else:
default = display_letters[0].lower()
# Make a prompt if one is not provided.
if not prompt:
prompt_parts = []
@ -249,11 +249,11 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None,
resp = raw_input(prompt + ' ')
while True:
resp = resp.strip().lower()
# Try default option.
if default is not None and not resp:
resp = default
# Try an integer input if available.
if numrange:
try:
@ -266,13 +266,13 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None,
return resp
else:
resp = None
# Try a normal letter input.
if resp:
resp = resp[0]
if resp in letters:
return resp
# Prompt for new input.
resp = raw_input(fallback_prompt + ' ')
@ -293,7 +293,7 @@ def config_val(config, section, name, default, vtype=None):
"""
if not config.has_section(section):
config.add_section(section)
try:
if vtype is bool:
return config.getboolean(section, name)
@ -387,7 +387,7 @@ def colordiff(a, b, highlight='red'):
a_out = []
b_out = []
matcher = SequenceMatcher(lambda x: False, a, b)
for op, a_start, a_end, b_start, b_end in matcher.get_opcodes():
if op == 'equal':
@ -406,7 +406,7 @@ def colordiff(a, b, highlight='red'):
b_out.append(colorize(highlight, b[b_start:b_end]))
else:
assert(False)
return u''.join(a_out), u''.join(b_out)
def default_paths(pathmod=None):
@ -524,7 +524,7 @@ class SubcommandsOptionParser(optparse.OptionParser):
_HelpSubcommand = Subcommand('help', optparse.OptionParser(),
help='give detailed help on a specific sub-command',
aliases=('?',))
def __init__(self, *args, **kwargs):
"""Create a new subcommand-aware option parser. All of the
options to OptionParser.__init__ are supported in addition
@ -533,41 +533,41 @@ class SubcommandsOptionParser(optparse.OptionParser):
# The subcommand array, with the help command included.
self.subcommands = list(kwargs.pop('subcommands', []))
self.subcommands.append(self._HelpSubcommand)
# A more helpful default usage.
if 'usage' not in kwargs:
kwargs['usage'] = """
%prog COMMAND [ARGS...]
%prog help COMMAND"""
# Super constructor.
optparse.OptionParser.__init__(self, *args, **kwargs)
# Adjust the help-visible name of each subcommand.
for subcommand in self.subcommands:
subcommand.parser.prog = '%s %s' % \
(self.get_prog_name(), subcommand.name)
# Our root parser needs to stop on the first unrecognized argument.
# Our root parser needs to stop on the first unrecognized argument.
self.disable_interspersed_args()
def add_subcommand(self, cmd):
"""Adds a Subcommand object to the parser's list of commands.
"""
self.subcommands.append(cmd)
# Add the list of subcommands to the help message.
def format_help(self, formatter=None):
# Get the original help message, to which we will append.
out = optparse.OptionParser.format_help(self, formatter)
if formatter is None:
formatter = self.formatter
# Subcommands header.
result = ["\n"]
result.append(formatter.format_heading('Commands'))
formatter.indent()
# Generate the display names (including aliases).
# Also determine the help position.
disp_names = []
@ -577,12 +577,12 @@ class SubcommandsOptionParser(optparse.OptionParser):
if subcommand.aliases:
name += ' (%s)' % ', '.join(subcommand.aliases)
disp_names.append(name)
# Set the help position based on the max width.
proposed_help_position = len(name) + formatter.current_indent + 2
if proposed_help_position <= formatter.max_help_position:
help_position = max(help_position, proposed_help_position)
help_position = max(help_position, proposed_help_position)
# Add each subcommand to the output.
for subcommand, name in zip(self.subcommands, disp_names):
# Lifted directly from optparse.py.
@ -601,11 +601,11 @@ class SubcommandsOptionParser(optparse.OptionParser):
result.extend(["%*s%s\n" % (help_position, "", line)
for line in help_lines[1:]])
formatter.dedent()
# Concatenate the original help message with the subcommand
# list.
return out + "".join(result)
def _subcommand_for_name(self, name):
"""Return the subcommand in self.subcommands matching the
given name. The name may either be the name of a subcommand or
@ -616,16 +616,16 @@ class SubcommandsOptionParser(optparse.OptionParser):
name in subcommand.aliases:
return subcommand
return None
def parse_args(self, a=None, v=None):
"""Like OptionParser.parse_args, but returns these four items:
- options: the options passed to the root parser
- subcommand: the Subcommand object that was invoked
- suboptions: the options passed to the subcommand parser
- subargs: the positional arguments passed to the subcommand
"""
"""
options, args = optparse.OptionParser.parse_args(self, a, v)
if not args:
# No command given.
self.print_help()
@ -635,7 +635,7 @@ class SubcommandsOptionParser(optparse.OptionParser):
subcommand = self._subcommand_for_name(cmdname)
if not subcommand:
self.error('unknown command ' + cmdname)
suboptions, subargs = subcommand.parser.parse_args(args)
if subcommand is self._HelpSubcommand:
@ -649,7 +649,7 @@ class SubcommandsOptionParser(optparse.OptionParser):
# general
self.print_help()
self.exit()
return options, subcommand, suboptions, subargs
@ -701,10 +701,10 @@ def main(args=None, configfh=None):
help="destination music directory")
parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
help='print debugging information')
# Parse the command-line!
options, subcommand, suboptions, subargs = parser.parse_args(args)
# Open library file.
libpath = options.libpath or \
config_val(config, 'beets', 'library', default_libpath)
@ -729,7 +729,7 @@ def main(args=None, configfh=None):
replacements)
except sqlite3.OperationalError:
raise UserError("database file %s could not be opened" % db_path)
# Configure the logger.
log = logging.getLogger('beets')
if options.verbose:
@ -739,7 +739,7 @@ def main(args=None, configfh=None):
log.debug(u'config file: %s' % util.displayable_path(configpath))
log.debug(u'library database: %s' % util.displayable_path(lib.path))
log.debug(u'library directory: %s' % util.displayable_path(lib.directory))
# Invoke the subcommand.
try:
subcommand.func(lib, config, suboptions, subargs)

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -64,7 +64,7 @@ def _do_query(lib, query, album, also_items=True):
raise ui.UserError('No matching albums found.')
elif not album and not items:
raise ui.UserError('No matching items found.')
return items, albums
FLOAT_EPSILON = 0.01
@ -212,7 +212,7 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True):
if color:
cur_length = ui.colorize('red', cur_length)
new_length = ui.colorize('red', new_length)
# Possibly colorize changes.
if color:
cur_title, new_title = ui.colordiff(cur_title, new_title)
@ -223,7 +223,7 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True):
# Show filename (non-colorized) when title is not set.
if not item.title.strip():
cur_title = displayable_path(os.path.basename(item.path))
if cur_title != new_title:
lhs, rhs = cur_title, new_title
if cur_track != new_track:
@ -345,7 +345,7 @@ def choose_candidate(candidates, singleton, rec, color, timid,
else:
dist, items, info = candidates[0]
bypass_candidates = True
while True:
# Display and choose from candidates.
if not bypass_candidates:
@ -387,7 +387,7 @@ def choose_candidate(candidates, singleton, rec, color, timid,
line += u' %s' % warning
print_(line)
# Ask the user for a choice.
if singleton:
opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id',
@ -416,20 +416,20 @@ def choose_candidate(candidates, singleton, rec, color, timid,
else:
dist, items, info = candidates[sel-1]
bypass_candidates = False
# Show what we're about to do.
if singleton:
show_item_change(item, info, dist, color)
else:
show_change(cur_artist, cur_album, items, info, dist, color)
# Exact match => tag automatically if we're not in timid mode.
if rec == autotag.RECOMMEND_STRONG and not timid:
if singleton:
return info
else:
return info, items
# Ask for confirmation.
if singleton:
opts = ('Apply', 'More candidates', 'Skip', 'Use as-is',
@ -507,10 +507,10 @@ def choose_match(task, config):
candidates, rec = task.candidates, task.rec
while True:
# Ask for a choice from the user.
choice = choose_candidate(candidates, False, rec, config.color,
choice = choose_candidate(candidates, False, rec, config.color,
config.timid, task.cur_artist,
task.cur_album, itemcount=len(task.items))
# Choose which tags to use.
if choice in (importer.action.SKIP, importer.action.ASIS,
importer.action.TRACKS):
@ -687,7 +687,7 @@ def import_files(lib, paths, copy, move, write, autot, logpath, art, threaded,
ignore = ignore,
resolve_duplicate_func = resolve_duplicate,
)
finally:
# If we were logging, close the file.
if logfile:

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -39,11 +39,11 @@ def ancestry(path, pathmod=None):
last_path = None
while path:
path = pathmod.dirname(path)
if path == last_path:
break
last_path = path
if path: # don't yield ''
out.insert(0, path)
return out
@ -151,11 +151,11 @@ def components(path, pathmod=None):
comps.append(comp)
else: # root
comps.append(anc)
last = pathmod.basename(path)
if last:
comps.append(last)
return comps
def bytestring_path(path):
@ -361,7 +361,7 @@ def levenshtein(s1, s2):
return levenshtein(s2, s1)
if not s1:
return len(s2)
previous_row = xrange(len(s2) + 1)
for i, c1 in enumerate(s1):
current_row = [i + 1]
@ -371,7 +371,7 @@ def levenshtein(s1, s2):
substitutions = previous_row[j] + (c1 != c2)
current_row.append(min(insertions, deletions, substitutions))
previous_row = current_row
return previous_row[-1]
def plurality(objs):

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -35,7 +35,7 @@ how you would expect them to.
'west'
>>> Direction.north < Direction.west
True
Enumerations are classes; their instances represent the possible values
of the enumeration. Because Python classes must have names, you may
provide a `name` parameter to `enum`; if you don't, a meaningless one
@ -45,31 +45,31 @@ import random
class Enumeration(type):
"""A metaclass whose classes are enumerations.
The `values` attribute of the class is used to populate the
enumeration. Values may either be a list of enumerated names or a
string containing a space-separated list of names. When the class
is created, it is instantiated for each name value in `values`.
Each such instance is the name of the enumerated item as the sole
argument.
The `Enumerated` class is a good choice for a superclass.
"""
def __init__(cls, name, bases, dic):
super(Enumeration, cls).__init__(name, bases, dic)
if 'values' not in dic:
# Do nothing if no values are provided (i.e., with
# Enumerated itself).
return
# May be called with a single string, in which case we split on
# whitespace for convenience.
values = dic['values']
if isinstance(values, basestring):
values = values.split()
# Create the Enumerated instances for each value. We have to use
# super's __setattr__ here because we disallow setattr below.
super(Enumeration, cls).__setattr__('_items_dict', {})
@ -78,44 +78,44 @@ class Enumeration(type):
item = cls(value, len(cls._items_list))
cls._items_dict[value] = item
cls._items_list.append(item)
def __getattr__(cls, key):
try:
return cls._items_dict[key]
except KeyError:
raise AttributeError("enumeration '" + cls.__name__ +
"' has no item '" + key + "'")
def __setattr__(cls, key, val):
raise TypeError("enumerations do not support attribute assignment")
def __getitem__(cls, key):
if isinstance(key, int):
return cls._items_list[key]
else:
return getattr(cls, key)
def __len__(cls):
return len(cls._items_list)
def __iter__(cls):
return iter(cls._items_list)
def __nonzero__(cls):
# Ensures that __len__ doesn't get called before __init__ by
# pydoc.
return True
class Enumerated(object):
"""An item in an enumeration.
Contains instance methods inherited by enumerated objects. The
metaclass is preset to `Enumeration` for your convenience.
Instance attributes:
Instance attributes:
name -- The name of the item.
index -- The index of the item in its enumeration.
>>> from enumeration import Enumerated
>>> class Garment(Enumerated):
... values = 'hat glove belt poncho lederhosen suspenders'
@ -125,9 +125,9 @@ class Enumerated(object):
>>> Garment.poncho.wear()
now wearing a poncho
"""
__metaclass__ = Enumeration
def __init__(self, name, index):
self.name = name
self.index = index
@ -149,18 +149,18 @@ class Enumerated(object):
def enum(*values, **kwargs):
"""Shorthand for creating a new Enumeration class.
Call with enumeration values as a list, a space-delimited string, or
just an argument list. To give the class a name, pass it as the
`name` keyword argument. Otherwise, a name will be chosen for you.
The following are all equivalent:
enum('pinkie ring middle index thumb')
enum('pinkie', 'ring', 'middle', 'index', 'thumb')
enum(['pinkie', 'ring', 'middle', 'index', 'thumb'])
"""
if ('name' not in kwargs) or kwargs['name'] is None:
# Create a probably-unique name. It doesn't really have to be
# unique, but getting distinct names each time helps with
@ -168,11 +168,11 @@ def enum(*values, **kwargs):
name = 'Enumeration' + hex(random.randint(0,0xfffffff))[2:].upper()
else:
name = kwargs['name']
if len(values) == 1:
# If there's only one value, we have a couple of alternate calling
# styles.
if isinstance(values[0], basestring) or hasattr(values[0], '__iter__'):
values = values[0]
return type(name, (Enumerated,), {'values': values})

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -409,7 +409,7 @@ class Parser(object):
# No function name.
self.parts.append(FUNC_DELIM)
return
if self.pos >= len(self.string):
# Identifier terminates string.
self.parts.append(self.string[start_pos:self.pos])
@ -447,7 +447,7 @@ class Parser(object):
# Extract and advance past the parsed expression.
expressions.append(Expression(subparser.parts))
self.pos += subparser.pos
self.pos += subparser.pos
if self.pos >= len(self.string) or \
self.string[self.pos] == GROUP_CLOSE:

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -177,23 +177,23 @@ class FirstPipelineThread(PipelineThread):
self.coro = coro
self.out_queue = out_queue
self.out_queue.acquire()
self.abort_lock = Lock()
self.abort_flag = False
def run(self):
try:
while True:
with self.abort_lock:
if self.abort_flag:
return
# Get the value from the generator.
try:
msg = self.coro.next()
except StopIteration:
break
# Send messages to the next stage.
for msg in _allmsgs(msg):
with self.abort_lock:
@ -207,7 +207,7 @@ class FirstPipelineThread(PipelineThread):
# Generator finished; shut down the pipeline.
self.out_queue.release()
class MiddlePipelineThread(PipelineThread):
"""A thread running any stage in the pipeline except the first or
last.
@ -223,7 +223,7 @@ class MiddlePipelineThread(PipelineThread):
try:
# Prime the coroutine.
self.coro.next()
while True:
with self.abort_lock:
if self.abort_flag:
@ -233,14 +233,14 @@ class MiddlePipelineThread(PipelineThread):
msg = self.in_queue.get()
if msg is POISON:
break
with self.abort_lock:
if self.abort_flag:
return
# Invoke the current stage.
out = self.coro.send(msg)
# Send messages to next stage.
for msg in _allmsgs(out):
with self.abort_lock:
@ -251,7 +251,7 @@ class MiddlePipelineThread(PipelineThread):
except:
self.abort_all(sys.exc_info())
return
# Pipeline is shutting down normally.
self.out_queue.release()
@ -273,12 +273,12 @@ class LastPipelineThread(PipelineThread):
with self.abort_lock:
if self.abort_flag:
return
# Get the message from the previous stage.
msg = self.in_queue.get()
if msg is POISON:
break
with self.abort_lock:
if self.abort_flag:
return
@ -308,7 +308,7 @@ class Pipeline(object):
self.stages.append((stage,))
else:
self.stages.append(stage)
def run_sequential(self):
"""Run the pipeline sequentially in the current thread. The
stages are run one after the other. Only the first coroutine
@ -319,7 +319,7 @@ class Pipeline(object):
# "Prime" the coroutines.
for coro in coros[1:]:
coro.next()
# Begin the pipeline.
for out in coros[0]:
msgs = _allmsgs(out)
@ -329,7 +329,7 @@ class Pipeline(object):
out = coro.send(msg)
next_msgs.extend(_allmsgs(out))
msgs = next_msgs
def run_parallel(self, queue_size=DEFAULT_QUEUE_SIZE):
"""Run the pipeline in parallel using one thread per stage. The
messages between the stages are stored in queues of the given
@ -354,11 +354,11 @@ class Pipeline(object):
threads.append(
LastPipelineThread(coro, queues[-1], threads)
)
# Start threads.
for thread in threads:
thread.start()
# Wait for termination. The final thread lasts the longest.
try:
# Using a timeout allows us to receive KeyboardInterrupt
@ -371,7 +371,7 @@ class Pipeline(object):
for thread in threads:
thread.abort()
raise
finally:
# Make completely sure that all the threads have finished
# before we return. They should already be either finished,
@ -388,7 +388,7 @@ class Pipeline(object):
# Smoke test.
if __name__ == '__main__':
import time
# Test a normally-terminating pipeline both in sequence and
# in parallel.
def produce():

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -89,7 +89,7 @@ class BPDError(Exception):
self.message = message
self.cmd_name = cmd_name
self.index = index
template = Template(u'$resp [$code@$index] {$cmd_name} $message')
def response(self):
"""Returns a string to be used as the response code for the
@ -120,7 +120,7 @@ ArgumentNotFoundError = make_bpd_error(ERROR_NO_EXIST, 'argument not found')
def cast_arg(t, val):
"""Attempts to call t on val, raising a CommandArgumentError
on ValueError.
If 't' is the special string 'intbool', attempts to cast first
to an int and then to a bool (i.e., 1=True, 0=False).
"""
@ -142,17 +142,17 @@ class BPDClose(Exception):
class BaseServer(object):
"""A MPD-compatible music player server.
The functions with the `cmd_` prefix are invoked in response to
client commands. For instance, if the client says `status`,
`cmd_status` will be invoked. The arguments to the client's commands
are used as function arguments following the connection issuing the
command. The functions may send data on the connection. They may
also raise BPDError exceptions to report errors.
This is a generic superclass and doesn't support many commands.
"""
def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT,
password=DEFAULT_PASSWORD):
"""Create a new server bound to address `host` and listening
@ -160,7 +160,7 @@ class BaseServer(object):
anything significant on the server.
"""
self.host, self.port, self.password = host, port, password
# Default server values.
self.random = False
self.repeat = False
@ -174,7 +174,7 @@ class BaseServer(object):
# Object for random numbers generation
self.random_obj = random.Random()
def run(self):
"""Block and start listening for connections from clients. An
interrupt (^C) closes the server.
@ -188,7 +188,7 @@ class BaseServer(object):
single song's metadata.
"""
raise NotImplementedError
def _item_id(self, item):
"""An abstract method returning the integer id for an item.
"""
@ -242,16 +242,16 @@ class BaseServer(object):
def cmd_ping(self, conn):
"""Succeeds."""
pass
def cmd_kill(self, conn):
"""Exits the server process."""
self.listener.close()
exit(0)
def cmd_close(self, conn):
"""Closes the connection."""
raise BPDClose()
def cmd_password(self, conn, password):
"""Attempts password authentication."""
if password == self.password:
@ -259,20 +259,20 @@ class BaseServer(object):
else:
conn.authenticated = False
raise BPDError(ERROR_PASSWORD, 'incorrect password')
def cmd_commands(self, conn):
"""Lists the commands available to the user."""
if self.password and not conn.authenticated:
# Not authenticated. Show limited list of commands.
for cmd in SAFE_COMMANDS:
yield u'command: ' + cmd
else:
# Authenticated. Show all commands.
for func in dir(self):
if func.startswith('cmd_'):
yield u'command: ' + func[4:]
def cmd_notcommands(self, conn):
"""Lists all unavailable commands."""
if self.password and not conn.authenticated:
@ -282,15 +282,15 @@ class BaseServer(object):
cmd = func[4:]
if cmd not in SAFE_COMMANDS:
yield u'command: ' + cmd
else:
# Authenticated. No commands are unavailable.
pass
def cmd_status(self, conn):
"""Returns some status information for use with an
implementation of cmd_status.
Gives a list of response-lines for: volume, repeat, random,
playlist, playlistlength, and xfade.
"""
@ -301,7 +301,7 @@ class BaseServer(object):
u'playlistlength: ' + unicode(len(self.playlist)),
u'xfade: ' + unicode(self.crossfade),
)
if self.current_index == -1:
state = u'stop'
elif self.paused:
@ -309,7 +309,7 @@ class BaseServer(object):
else:
state = u'play'
yield u'state: ' + state
if self.current_index != -1: # i.e., paused or playing
current_id = self._item_id(self.playlist[self.current_index])
yield u'song: ' + unicode(self.current_index)
@ -324,34 +324,34 @@ class BaseServer(object):
command (for instance, when playing a file).
"""
self.error = None
def cmd_random(self, conn, state):
"""Set or unset random (shuffle) mode."""
self.random = cast_arg('intbool', state)
def cmd_repeat(self, conn, state):
"""Set or unset repeat mode."""
self.repeat = cast_arg('intbool', state)
def cmd_setvol(self, conn, vol):
"""Set the player's volume level (0-100)."""
vol = cast_arg(int, vol)
if vol < VOLUME_MIN or vol > VOLUME_MAX:
raise BPDError(ERROR_ARG, u'volume out of range')
self.volume = vol
def cmd_crossfade(self, conn, crossfade):
"""Set the number of seconds of crossfading."""
crossfade = cast_arg(int, crossfade)
if crossfade < 0:
if crossfade < 0:
raise BPDError(ERROR_ARG, u'crossfade time must be nonnegative')
def cmd_clear(self, conn):
"""Clear the playlist."""
self.playlist = []
self.playlist_version += 1
self.cmd_stop(conn)
def cmd_delete(self, conn, index):
"""Remove the song at index from the playlist."""
index = cast_arg(int, index)
@ -369,7 +369,7 @@ class BaseServer(object):
def cmd_deleteid(self, conn, track_id):
self.cmd_delete(conn, self._id_to_index(track_id))
def cmd_move(self, conn, idx_from, idx_to):
"""Move a track in the playlist."""
idx_from = cast_arg(int, idx_from)
@ -379,7 +379,7 @@ class BaseServer(object):
self.playlist.insert(idx_to, track)
except IndexError:
raise ArgumentIndexError()
# Update currently-playing song.
if idx_from == self.current_index:
self.current_index = idx_to
@ -387,13 +387,13 @@ class BaseServer(object):
self.current_index -= 1
elif idx_from > self.current_index >= idx_to:
self.current_index += 1
self.playlist_version += 1
def cmd_moveid(self, conn, id_from, idx_to):
idx_from = self._id_to_index(idx_from)
return self.cmd_move(conn, idx_from, idx_to)
def cmd_swap(self, conn, i, j):
"""Swaps two tracks in the playlist."""
i = cast_arg(int, i)
@ -403,27 +403,27 @@ class BaseServer(object):
track_j = self.playlist[j]
except IndexError:
raise ArgumentIndexError()
self.playlist[j] = track_i
self.playlist[i] = track_j
# Update currently-playing song.
if self.current_index == i:
self.current_index = j
elif self.current_index == j:
self.current_index = i
self.playlist_version += 1
def cmd_swapid(self, conn, i_id, j_id):
i = self._id_to_index(i_id)
j = self._id_to_index(j_id)
return self.cmd_swap(conn, i, j)
def cmd_urlhandlers(self, conn):
"""Indicates supported URL schemes. None by default."""
pass
def cmd_playlistinfo(self, conn, index=-1):
"""Gives metadata information about the entire playlist or a
single track, given by its index.
@ -440,25 +440,25 @@ class BaseServer(object):
yield self._item_info(track)
def cmd_playlistid(self, conn, track_id=-1):
return self.cmd_playlistinfo(conn, self._id_to_index(track_id))
def cmd_plchanges(self, conn, version):
"""Sends playlist changes since the given version.
This is a "fake" implementation that ignores the version and
just returns the entire playlist (rather like version=0). This
seems to satisfy many clients.
"""
return self.cmd_playlistinfo(conn)
def cmd_plchangesposid(self, conn, version):
"""Like plchanges, but only sends position and id.
Also a dummy implementation.
"""
for idx, track in enumerate(self.playlist):
yield u'cpos: ' + unicode(idx)
yield u'Id: ' + unicode(track.id)
def cmd_currentsong(self, conn):
"""Sends information about the currently-playing song.
"""
@ -474,7 +474,7 @@ class BaseServer(object):
return self.cmd_stop(conn)
else:
return self.cmd_play(conn)
def cmd_previous(self, conn):
"""Step back to the last song."""
self.current_index = self._prev_idx()
@ -482,33 +482,33 @@ class BaseServer(object):
return self.cmd_stop(conn)
else:
return self.cmd_play(conn)
def cmd_pause(self, conn, state=None):
"""Set the pause state playback."""
if state is None:
self.paused = not self.paused # Toggle.
else:
self.paused = cast_arg('intbool', state)
def cmd_play(self, conn, index=-1):
"""Begin playback, possibly at a specified playlist index."""
index = cast_arg(int, index)
if index < -1 or index > len(self.playlist):
raise ArgumentIndexError()
if index == -1: # No index specified: start where we are.
if not self.playlist: # Empty playlist: stop immediately.
return self.cmd_stop(conn)
if self.current_index == -1: # No current song.
self.current_index = 0 # Start at the beginning.
# If we have a current song, just stay there.
else: # Start with the specified index.
self.current_index = index
self.paused = False
def cmd_playid(self, conn, track_id=0):
track_id = cast_arg(int, track_id)
if track_id == -1:
@ -516,12 +516,12 @@ class BaseServer(object):
else:
index = self._id_to_index(track_id)
return self.cmd_play(conn, index)
def cmd_stop(self, conn):
"""Stop playback."""
self.current_index = -1
self.paused = False
def cmd_seek(self, conn, index, pos):
"""Seek to a specified point in a specified song."""
index = cast_arg(int, index)
@ -548,7 +548,7 @@ class Connection(object):
self.server = server
self.sock = sock
self.authenticated = False
def send(self, lines):
"""Send lines, which which is either a single string or an
iterable consisting of strings, to the client. A newline is
@ -562,7 +562,7 @@ class Connection(object):
if isinstance(out, unicode):
out = out.encode('utf8')
return self.sock.sendall(out)
def do_command(self, command):
"""A coroutine that runs the given command and sends an
appropriate response."""
@ -574,20 +574,20 @@ class Connection(object):
else:
# Send success code.
yield self.send(RESP_OK)
def run(self):
"""Send a greeting to the client and begin processing commands
as they arrive.
"""
yield self.send(HELLO)
clist = None # Initially, no command list is being constructed.
while True:
line = (yield self.sock.readline()).strip()
if not line:
break
log.debug(line)
if clist is not None:
# Command list already opened.
if line == CLIST_END:
@ -595,11 +595,11 @@ class Connection(object):
clist = None # Clear the command list.
else:
clist.append(Command(line))
elif line == CLIST_BEGIN or line == CLIST_VERBOSE_BEGIN:
# Begin a command list.
clist = CommandList([], line == CLIST_VERBOSE_BEGIN)
else:
# Ordinary command.
try:
@ -608,7 +608,7 @@ class Connection(object):
# Command indicates that the conn should close.
self.sock.close()
return
@classmethod
def handler(cls, server):
def _handle(sock):
@ -620,10 +620,10 @@ class Connection(object):
class Command(object):
"""A command issued by the client for processing by the server.
"""
command_re = re.compile(r'^([^ \t]+)[ \t]*')
arg_re = re.compile(r'"((?:\\"|[^"])+)"|([^ \t"]+)')
def __init__(self, s):
"""Creates a new `Command` from the given string, `s`, parsing
the string for command name and arguments.
@ -643,7 +643,7 @@ class Command(object):
arg = match[1]
arg = arg.decode('utf8')
self.args.append(arg)
def run(self, conn):
"""A coroutine that executes the command on the given
connection.
@ -653,36 +653,36 @@ class Command(object):
if not hasattr(conn.server, func_name):
raise BPDError(ERROR_UNKNOWN, u'unknown command', self.name)
func = getattr(conn.server, func_name)
# Ensure we have permission for this command.
if conn.server.password and \
not conn.authenticated and \
self.name not in SAFE_COMMANDS:
raise BPDError(ERROR_PERMISSION, u'insufficient privileges')
try:
args = [conn] + self.args
results = func(*args)
if results:
for data in results:
yield conn.send(data)
except BPDError, e:
# An exposed error. Set the command name and then let
# the Connection handle it.
e.cmd_name = self.name
raise e
except BPDClose:
# An indication that the connection should close. Send
# it on the Connection.
raise
except Exception, e:
# An "unintentional" error. Hide it from the client.
log.error(traceback.format_exc(e))
raise BPDError(ERROR_SYSTEM, u'server error', self.name)
class CommandList(list):
"""A list of commands issued by the client for processing by the
@ -737,7 +737,7 @@ class Server(BaseServer):
self.lib = library
self.player = gstplayer.GstPlayer(self.play_finished)
self.cmd_update(None)
def run(self):
self.player.run()
super(Server, self).run()
@ -747,10 +747,10 @@ class Server(BaseServer):
track.
"""
self.cmd_next(None)
# Metadata helper functions.
def _item_info(self, item):
info_lines = [u'file: ' + self.lib.destination(item, fragment=True),
u'Time: ' + unicode(int(item.length)),
@ -759,25 +759,25 @@ class Server(BaseServer):
u'Album: ' + item.album,
u'Genre: ' + item.genre,
]
track = unicode(item.track)
if item.tracktotal:
track += u'/' + unicode(item.tracktotal)
info_lines.append(u'Track: ' + track)
info_lines.append(u'Date: ' + unicode(item.year))
try:
pos = self._id_to_index(item.id)
info_lines.append(u'Pos: ' + unicode(pos))
except ArgumentNotFoundError:
# Don't include position if not in playlist.
pass
info_lines.append(u'Id: ' + unicode(item.id))
return info_lines
def _item_id(self, item):
return item.id
@ -799,7 +799,7 @@ class Server(BaseServer):
def _resolve_path(self, path):
"""Returns a VFS node or an item ID located at the path given.
If the path does not exist, raises a
If the path does not exist, raises a
"""
components = path.split(u'/')
node = self.tree
@ -820,12 +820,12 @@ class Server(BaseServer):
raise ArgumentNotFoundError()
return node
def _path_join(self, p1, p2):
"""Smashes together two BPD paths."""
out = p1 + u'/' + p2
return out.replace(u'//', u'/').replace(u'//', u'/')
def cmd_lsinfo(self, conn, path=u"/"):
"""Sends info on all the items in the path."""
node = self._resolve_path(path)
@ -842,7 +842,7 @@ class Server(BaseServer):
# Strip leading slash (libmpc rejects this).
dirpath = dirpath[1:]
yield u'directory: %s' % dirpath
def _listall(self, basepath, node, info=False):
"""Helper function for recursive listing. If info, show
tracks' complete info; otherwise, just show items' paths.
@ -864,15 +864,15 @@ class Server(BaseServer):
newpath = self._path_join(basepath, name)
yield u'directory: ' + newpath
for v in self._listall(newpath, subdir, info): yield v
def cmd_listall(self, conn, path=u"/"):
"""Send the paths all items in the directory, recursively."""
return self._listall(path, self._resolve_path(path), False)
def cmd_listallinfo(self, conn, path=u"/"):
"""Send info on all the items in the directory, recursively."""
return self._listall(path, self._resolve_path(path), True)
# Playlist manipulation.
def _all_items(self, node):
@ -889,7 +889,7 @@ class Server(BaseServer):
for v in self._all_items(itemid): yield v
for name, subdir in sorted(node.dirs.iteritems()):
for v in self._all_items(subdir): yield v
def _add(self, path, send_id=False):
"""Adds a track or directory to the playlist, specified by the
path. If `send_id`, write each item's id to the client.
@ -899,13 +899,13 @@ class Server(BaseServer):
if send_id:
yield u'Id: ' + unicode(item.id)
self.playlist_version += 1
def cmd_add(self, conn, path):
"""Adds a track or directory to the playlist, specified by a
path.
"""
return self._add(path, False)
def cmd_addid(self, conn, path):
"""Same as `cmd_add` but sends an id back to the client."""
return self._add(path, True)
@ -918,13 +918,13 @@ class Server(BaseServer):
yield line
if self.current_index > -1:
item = self.playlist[self.current_index]
yield u'bitrate: ' + unicode(item.bitrate/1000)
#fixme: missing 'audio'
(pos, total) = self.player.time()
yield u'time: ' + unicode(pos) + u':' + unicode(total)
#fixme: also missing 'updating_db'
@ -970,7 +970,7 @@ class Server(BaseServer):
"""
for tag in self.tagtype_map:
yield u'tagtype: ' + tag
def _tagtype_lookup(self, tag):
"""Uses `tagtype_map` to look up the beets column name for an
MPD tagtype (or throw an appropriate exception). Returns both
@ -1005,7 +1005,7 @@ class Server(BaseServer):
return beets.library.AndQuery(queries)
else: # No key-value pairs.
return beets.library.TrueQuery()
def cmd_search(self, conn, *kv):
"""Perform a substring match for items."""
query = self._metadata_query(beets.library.SubstringQuery,
@ -1013,7 +1013,7 @@ class Server(BaseServer):
kv)
for item in self.lib.items(query):
yield self._item_info(item)
def cmd_find(self, conn, *kv):
"""Perform an exact match for items."""
query = self._metadata_query(beets.library.MatchQuery,
@ -1021,24 +1021,24 @@ class Server(BaseServer):
kv)
for item in self.lib.items(query):
yield self._item_info(item)
def cmd_list(self, conn, show_tag, *kv):
"""List distinct metadata values for show_tag, possibly
filtered by matching match_tag to match_term.
"""
show_tag_canon, show_key = self._tagtype_lookup(show_tag)
query = self._metadata_query(beets.library.MatchQuery, None, kv)
clause, subvals = query.clause()
statement = 'SELECT DISTINCT ' + show_key + \
' FROM items WHERE ' + clause + \
' ORDER BY ' + show_key
with self.lib.transaction() as tx:
rows = tx.query(statement, subvals)
for row in rows:
yield show_tag_canon + u': ' + unicode(row[0])
def cmd_count(self, conn, tag, value):
"""Returns the number and total time of songs matching the
tag/value query.
@ -1048,31 +1048,31 @@ class Server(BaseServer):
songs, playtime = query.count(self.lib)
yield u'songs: ' + unicode(songs)
yield u'playtime: ' + unicode(int(playtime))
# "Outputs." Just a dummy implementation because we don't control
# any outputs.
def cmd_outputs(self, conn):
"""List the available outputs."""
yield (u'outputid: 0',
u'outputname: gstreamer',
u'outputenabled: 1',
)
def cmd_enableoutput(self, conn, output_id):
output_id = cast_arg(int, output_id)
if output_id != 0:
raise ArgumentIndexError()
def cmd_disableoutput(self, conn, output_id):
output_id = cast_arg(int, output_id)
if output_id == 0:
raise BPDError(ERROR_ARG, u'cannot disable this output')
else:
raise ArgumentIndexError()
# Playback control. The functions below hook into the
# half-implementations provided by the base class. Together, they're
# enough to implement all normal playback functionality.
@ -1081,7 +1081,7 @@ class Server(BaseServer):
new_index = index != -1 and index != self.current_index
was_paused = self.paused
super(Server, self).cmd_play(conn, index)
if self.current_index > -1: # Not stopped.
if was_paused and not new_index:
# Just unpause.
@ -1099,7 +1099,7 @@ class Server(BaseServer):
def cmd_stop(self, conn):
super(Server, self).cmd_stop(conn)
self.player.stop()
def cmd_seek(self, conn, index, pos):
"""Seeks to the specified position in the specified song."""
index = cast_arg(int, index)
@ -1152,6 +1152,6 @@ class BPDPlugin(BeetsPlugin):
DEFAULT_PASSWORD)
debug = opts.debug or False
self.start_bpd(lib, host, int(port), password, debug)
cmd.func = func
return [cmd]

View file

@ -123,7 +123,7 @@ class ThreadException(Exception):
self.exc_info = exc_info
def reraise(self):
raise self.exc_info[0], self.exc_info[1], self.exc_info[2]
def run(root_coro):
"""Schedules a coroutine, running it to completion. This
encapsulates the Bluelet scheduler, which the root coroutine can
@ -138,7 +138,7 @@ def run(root_coro):
# suspended until the delegate completes. This dictionary keeps
# track of each running delegate's delegator.
delegators = {}
def advance_thread(coro, value, is_exc=False):
"""After an event is fired, run a given coroutine associated with
it in the threads dict until it yields again. If the coroutine
@ -222,7 +222,7 @@ def run(root_coro):
threads[event2coro[event]] = ReturnEvent(None)
else:
advance_thread(event2coro[event], value)
except ThreadException, te:
# Exception raised from inside a thread.
event = ExceptionEvent(te.exc_info)
@ -235,7 +235,7 @@ def run(root_coro):
# The thread is root-level. Raise in client code.
exit_te = te
break
except:
# For instance, KeyboardInterrupt during select(). Raise
# into root thread and terminate others.
@ -414,7 +414,7 @@ def server(host, port, func):
yield func(conn)
finally:
conn.close()
listener = Listener(host, port)
try:
while True:

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -27,27 +27,27 @@ import urllib
class GstPlayer(object):
"""A music player abstracting GStreamer's Playbin element.
Create a player object, then call run() to start a thread with a
runloop. Then call play_file to play music. Use player.playing
to check whether music is currently playing.
A basic play queue is also implemented (just a Python list,
player.queue, whose last element is next to play). To use it,
just call enqueue() and then play(). When a track finishes and
another is available on the queue, it is played automatically.
"""
def __init__(self, finished_callback=None):
"""Initialize a player.
If a finished_callback is provided, it is called every time a
track started with play_file finishes.
Once the player has been created, call run() to begin the main
runloop in a separate thread.
"""
# Set up the Gstreamer player. From the pygst tutorial:
# http://pygstdocs.berlios.de/pygst-tutorial/playbin.html
self.player = gst.element_factory_make("playbin2", "player")
@ -56,7 +56,7 @@ class GstPlayer(object):
bus = self.player.get_bus()
bus.add_signal_watch()
bus.connect("message", self._handle_message)
# Set up our own stuff.
self.playing = False
self.finished_callback = finished_callback
@ -68,7 +68,7 @@ class GstPlayer(object):
# gst's get_state function returns a 3-tuple; we just want the
# status flag in position 1.
return self.player.get_state()[1]
def _handle_message(self, bus, message):
"""Callback for status updates from GStreamer."""
if message.type == gst.MESSAGE_EOS:
@ -115,7 +115,7 @@ class GstPlayer(object):
if self._get_state() == gst.STATE_PAUSED:
self.player.set_state(gst.STATE_PLAYING)
self.playing = True
def pause(self):
"""Pause playback."""
self.player.set_state(gst.STATE_PAUSED)
@ -128,7 +128,7 @@ class GstPlayer(object):
def run(self):
"""Start a new thread for the player.
Call this function before trying to play any music with
play_file() or play().
"""
@ -150,7 +150,7 @@ class GstPlayer(object):
length = self.player.query_duration(fmt, None)[0]/(10**9)
self.cached_time = (pos, length)
return (pos, length)
except gst.QueryError:
# Stream not ready. For small gaps of time, for instance
# after seeking, the time values are unavailable. For this
@ -166,14 +166,14 @@ class GstPlayer(object):
if position > cur_len:
self.stop()
return
fmt = gst.Format(gst.FORMAT_TIME)
ns = position * 10**9 # convert to nanoseconds
self.player.seek_simple(fmt, gst.SEEK_FLAG_FLUSH, ns)
# save new cached time
self.cached_time = (position, cur_len)
def block(self):
"""Block until playing finishes."""
while self.playing:

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -63,11 +63,11 @@ def acoustid_match(path):
res = acoustid.lookup(API_KEY, fp, duration,
meta='recordings releases')
except acoustid.AcoustidError, exc:
log.debug('fingerprint matching %s failed: %s' %
log.debug('fingerprint matching %s failed: %s' %
(repr(path), str(exc)))
return None
log.debug('chroma: fingerprinted %s' % repr(path))
# Ensure the response is usable and parse it.
if res['status'] != 'ok' or not res.get('results'):
log.debug('chroma: no match found')

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -38,9 +38,9 @@ class ImportFeedsPlugin(BeetsPlugin):
_feeds_dir = ui.config_val(config, 'importfeeds', 'feeds_dir', None)
_feeds_dir = os.path.expanduser(_feeds_dir)
_m3u_name = ui.config_val(config, 'importfeeds', 'm3u_name',
_m3u_name = ui.config_val(config, 'importfeeds', 'm3u_name',
M3U_DEFAULT_NAME)
if _feeds_dir and not os.path.exists(_feeds_dir):
os.makedirs(_feeds_dir)
@ -58,7 +58,7 @@ def _get_feeds_dir(lib):
return dirpath
def _build_m3u_filename(basename):
"""Builds unique m3u filename by appending given basename to current
"""Builds unique m3u filename by appending given basename to current
date."""
basename = re.sub(r"[\s,'\"]", '_', basename)
@ -81,15 +81,15 @@ def _record_items(lib, basename, items):
_feeds_dir = _get_feeds_dir(lib)
paths = []
for item in items:
for item in items:
paths.append(os.path.relpath(item.path, _feeds_dir))
if 'm3u' in _feeds_formats:
m3u_path = os.path.join(_feeds_dir, _m3u_name)
m3u_path = os.path.join(_feeds_dir, _m3u_name)
_write_m3u(m3u_path, paths)
if 'm3u_multi' in _feeds_formats:
m3u_path = _build_m3u_filename(basename)
m3u_path = _build_m3u_filename(basename)
_write_m3u(m3u_path, paths)
if 'link' in _feeds_formats:

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -67,13 +67,13 @@ def _tags_for(obj):
def _tags_to_genre(tags):
"""Given a tag list, returns a genre. Returns the first tag that is
present in the genre whitelist or None if no tag is suitable.
present in the genre whitelist or None if no tag is suitable.
"""
if not tags:
return None
elif not options['whitelist']:
return tags[0].title()
if options.get('c14n'):
# Use the canonicalization tree.
for tag in tags:
@ -157,13 +157,13 @@ class LastGenrePlugin(plugins.BeetsPlugin):
from yaml import load
genres_tree = load(open(c14n_filename, 'r'))
branches = []
flatten_tree(genres_tree, [], branches)
options['branches'] = branches
flatten_tree(genres_tree, [], branches)
options['branches'] = branches
options['c14n'] = True
fallback_str = ui.config_val(config, 'lastgenre', 'fallback_str', None)
@LastGenrePlugin.listen('album_imported')
def album_imported(lib, album, config):
global fallback_str

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -68,7 +68,7 @@ def update_mpd(host='localhost', port=6600, password=None):
if 'OK MPD' not in resp:
print 'MPD connection failed:', repr(resp)
return
if password:
s.send('password "%s"\n' % password)
resp = s.readline()

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,9 +8,9 @@ body {
right: 0;
top: 0;
height: 36px;
color: white;
cursor: default;
/* shadowy border */
@ -33,15 +33,15 @@ body {
#entities {
width: 17em;
position: fixed;
top: 36px;
left: 0;
bottom: 0;
margin: 0;
z-index: 1;
background: #dde4eb;
background: #dde4eb;
/* shadowy border */
box-shadow: 0 0 20px #666;
@ -59,7 +59,7 @@ body {
}
#entities ul {
width: 17em;
position: fixed;
top: 36px;
left: 0;

View file

@ -133,7 +133,7 @@ $.fn.player = function(debug) {
$.fn.disableSelection = function() {
$(this).attr('unselectable', 'on')
.css('-moz-user-select', 'none')
.each(function() {
.each(function() {
this.onselectstart = function() { return false; };
});
};

View file

@ -4,7 +4,7 @@
<title>beets</title>
<link rel="stylesheet"
href="{{ url_for('static', filename='beets.css') }}" type="text/css">
href="{{ url_for('static', filename='beets.css') }}" type="text/css">
<script src="{{ url_for('static', filename='jquery.js') }}"></script>
<script src="{{ url_for('static', filename='underscore.js') }}">
@ -52,7 +52,7 @@
<span class="year">(<%= year %>)</span>
</span>
<span class="title"><%= title %></span>
<button class="play">&#9654;</button>
<dl>

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -113,7 +113,7 @@ class Timecop(object):
def time(self):
return self.now
def sleep(self, amount):
self.now += amount
@ -179,7 +179,7 @@ class DummyIO(object):
res = self.stdout.get()
self.stdout.clear()
return res
def readcount(self):
return self.stdin.reads

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -82,7 +82,7 @@ def _make_trackinfo():
ti.append(TrackInfo('two', None, 'some artist', length=1))
ti.append(TrackInfo('three', None, 'some artist', length=1))
return ti
class TrackDistanceTest(unittest.TestCase):
def test_identical_tracks(self):
item = _make_item('one', 1)
@ -258,13 +258,13 @@ class AlbumsInDirTest(unittest.TestCase):
# create a directory structure for testing
self.base = os.path.abspath(os.path.join(_common.RSRC, 'tempdir'))
os.mkdir(self.base)
os.mkdir(os.path.join(self.base, 'album1'))
os.mkdir(os.path.join(self.base, 'album2'))
os.mkdir(os.path.join(self.base, 'more'))
os.mkdir(os.path.join(self.base, 'more', 'album3'))
os.mkdir(os.path.join(self.base, 'more', 'album4'))
_mkmp3(os.path.join(self.base, 'album1', 'album1song1.mp3'))
_mkmp3(os.path.join(self.base, 'album1', 'album1song2.mp3'))
_mkmp3(os.path.join(self.base, 'album2', 'album2song.mp3'))
@ -272,11 +272,11 @@ class AlbumsInDirTest(unittest.TestCase):
_mkmp3(os.path.join(self.base, 'more', 'album4', 'album4song.mp3'))
def tearDown(self):
shutil.rmtree(self.base)
def test_finds_all_albums(self):
albums = list(autotag.albums_in_dir(self.base))
self.assertEqual(len(albums), 4)
def test_separates_contents(self):
found = []
for _, album in autotag.albums_in_dir(self.base):
@ -285,7 +285,7 @@ class AlbumsInDirTest(unittest.TestCase):
self.assertTrue('2' in found)
self.assertTrue('3' in found)
self.assertTrue('4' in found)
def test_finds_multiple_songs(self):
for _, album in autotag.albums_in_dir(self.base):
n = re.search(r'album(.)song', album[0].path).group(1)
@ -351,7 +351,7 @@ class OrderingTest(unittest.TestCase):
'title': title, 'track': track,
'mb_trackid': '', 'mb_albumid': '', 'mb_artistid': '',
})
def test_order_corrects_metadata(self):
items = []
items.append(self.item('one', 1))
@ -482,46 +482,46 @@ class ApplyTest(unittest.TestCase):
va = False,
mediums = 2,
)
def test_titles_applied(self):
autotag.apply_metadata(self.items, self.info)
self.assertEqual(self.items[0].title, 'oneNew')
self.assertEqual(self.items[1].title, 'twoNew')
def test_album_and_artist_applied_to_all(self):
autotag.apply_metadata(self.items, self.info)
self.assertEqual(self.items[0].album, 'albumNew')
self.assertEqual(self.items[1].album, 'albumNew')
self.assertEqual(self.items[0].artist, 'artistNew')
self.assertEqual(self.items[1].artist, 'artistNew')
def test_track_index_applied(self):
autotag.apply_metadata(self.items, self.info)
self.assertEqual(self.items[0].track, 1)
self.assertEqual(self.items[1].track, 2)
def test_track_total_applied(self):
autotag.apply_metadata(self.items, self.info)
self.assertEqual(self.items[0].tracktotal, 2)
self.assertEqual(self.items[1].tracktotal, 2)
def test_disc_index_applied(self):
autotag.apply_metadata(self.items, self.info)
self.assertEqual(self.items[0].disc, 1)
self.assertEqual(self.items[1].disc, 2)
def test_disc_total_applied(self):
autotag.apply_metadata(self.items, self.info)
self.assertEqual(self.items[0].disctotal, 2)
self.assertEqual(self.items[1].disctotal, 2)
def test_mb_trackid_applied(self):
autotag.apply_metadata(self.items, self.info)
self.assertEqual(self.items[0].mb_trackid,
'dfa939ec-118c-4d0f-84a0-60f3d1e6522c')
self.assertEqual(self.items[1].mb_trackid,
'40130ed1-a27c-42fd-a328-1ebefb6caef4')
def test_mb_albumid_and_artistid_applied(self):
autotag.apply_metadata(self.items, self.info)
for item in self.items:
@ -611,59 +611,59 @@ class StringDistanceTest(unittest.TestCase):
def test_equal_strings(self):
dist = match.string_dist('Some String', 'Some String')
self.assertEqual(dist, 0.0)
def test_different_strings(self):
dist = match.string_dist('Some String', 'Totally Different')
self.assertNotEqual(dist, 0.0)
def test_punctuation_ignored(self):
dist = match.string_dist('Some String', 'Some.String!')
self.assertEqual(dist, 0.0)
def test_case_ignored(self):
dist = match.string_dist('Some String', 'sOME sTring')
self.assertEqual(dist, 0.0)
def test_leading_the_has_lower_weight(self):
def test_leading_the_has_lower_weight(self):
dist1 = match.string_dist('XXX Band Name', 'Band Name')
dist2 = match.string_dist('The Band Name', 'Band Name')
self.assert_(dist2 < dist1)
def test_parens_have_lower_weight(self):
def test_parens_have_lower_weight(self):
dist1 = match.string_dist('One .Two.', 'One')
dist2 = match.string_dist('One (Two)', 'One')
self.assert_(dist2 < dist1)
def test_brackets_have_lower_weight(self):
def test_brackets_have_lower_weight(self):
dist1 = match.string_dist('One .Two.', 'One')
dist2 = match.string_dist('One [Two]', 'One')
self.assert_(dist2 < dist1)
def test_ep_label_has_zero_weight(self):
def test_ep_label_has_zero_weight(self):
dist = match.string_dist('My Song (EP)', 'My Song')
self.assertEqual(dist, 0.0)
def test_featured_has_lower_weight(self):
def test_featured_has_lower_weight(self):
dist1 = match.string_dist('My Song blah Someone', 'My Song')
dist2 = match.string_dist('My Song feat Someone', 'My Song')
self.assert_(dist2 < dist1)
def test_postfix_the(self):
def test_postfix_the(self):
dist = match.string_dist('The Song Title', 'Song Title, The')
self.assertEqual(dist, 0.0)
def test_postfix_a(self):
def test_postfix_a(self):
dist = match.string_dist('A Song Title', 'Song Title, A')
self.assertEqual(dist, 0.0)
def test_postfix_an(self):
def test_postfix_an(self):
dist = match.string_dist('An Album Title', 'Album Title, An')
self.assertEqual(dist, 0.0)
def test_empty_strings(self):
dist = match.string_dist('', '')
self.assertEqual(dist, 0.0)
def test_solo_pattern(self):
# Just make sure these don't crash.
match.string_dist('The ', '')

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -30,12 +30,12 @@ class MoveTest(unittest.TestCase, _common.ExtraAsserts):
# make a temporary file
self.path = join(_common.RSRC, 'temp.mp3')
shutil.copy(join(_common.RSRC, 'full.mp3'), self.path)
# add it to a temporary library
self.lib = beets.library.Library(':memory:')
self.i = beets.library.Item.from_path(self.path)
self.lib.add(self.i)
# set up the destination
self.libdir = join(_common.RSRC, 'testlibdir')
self.lib.directory = self.libdir
@ -47,7 +47,7 @@ class MoveTest(unittest.TestCase, _common.ExtraAsserts):
self.dest = join(self.libdir, 'one', 'two', 'three.mp3')
self.otherdir = join(_common.RSRC, 'testotherdir')
def tearDown(self):
if os.path.exists(self.path):
os.remove(self.path)
@ -55,15 +55,15 @@ class MoveTest(unittest.TestCase, _common.ExtraAsserts):
shutil.rmtree(self.libdir)
if os.path.exists(self.otherdir):
shutil.rmtree(self.otherdir)
def test_move_arrives(self):
self.lib.move(self.i)
self.assertExists(self.dest)
def test_move_to_custom_dir(self):
self.lib.move(self.i, basedir=self.otherdir)
self.assertExists(join(self.otherdir, 'one', 'two', 'three.mp3'))
def test_move_departs(self):
self.lib.move(self.i)
self.assertNotExists(self.path)
@ -77,15 +77,15 @@ class MoveTest(unittest.TestCase, _common.ExtraAsserts):
self.lib.move(self.i)
self.assertNotExists(old_path)
self.assertNotExists(os.path.dirname(old_path))
def test_copy_arrives(self):
self.lib.move(self.i, copy=True)
self.assertExists(self.dest)
def test_copy_does_not_depart(self):
self.lib.move(self.i, copy=True)
self.assertExists(self.path)
def test_move_changes_path(self):
self.lib.move(self.i)
self.assertEqual(self.i.path, util.normpath(self.dest))
@ -124,7 +124,7 @@ class MoveTest(unittest.TestCase, _common.ExtraAsserts):
self.assertNotEqual(self.i.path, dest)
self.assertEqual(os.path.dirname(self.i.path),
os.path.dirname(dest))
class HelperTest(unittest.TestCase):
def test_ancestry_works_on_file(self):
p = '/a/b/c'
@ -138,7 +138,7 @@ class HelperTest(unittest.TestCase):
p = 'a/b/c'
a = ['a', 'a/b']
self.assertEqual(util.ancestry(p), a)
def test_components_works_on_file(self):
p = '/a/b/c'
a = ['/', 'a', 'b', 'c']
@ -268,11 +268,11 @@ class ArtFileTest(unittest.TestCase, _common.ExtraAsserts):
i2.artist = 'someArtist'
ai = self.lib.add_album((i2,))
self.lib.move(i2, True)
self.assertEqual(ai.artpath, None)
ai.set_art(newart)
self.assertTrue(os.path.exists(ai.artpath))
def test_setart_to_existing_art_works(self):
os.remove(self.art)
@ -332,7 +332,7 @@ class ArtFileTest(unittest.TestCase, _common.ExtraAsserts):
newart = os.path.join(self.libdir, 'newart.jpg')
touch(newart)
os.chmod(newart, 0400) # read-only
try:
i2 = item()
i2.path = self.i.path
@ -340,11 +340,11 @@ class ArtFileTest(unittest.TestCase, _common.ExtraAsserts):
ai = self.lib.add_album((i2,))
self.lib.move(i2, True)
ai.set_art(newart)
mode = stat.S_IMODE(os.stat(ai.artpath).st_mode)
self.assertTrue(mode & stat.S_IRGRP)
self.assertTrue(os.access(ai.artpath, os.W_OK))
finally:
# Make everything writable so it can be cleaned up.
os.chmod(newart, 0777)

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -700,22 +700,22 @@ class DuplicateCheckTest(unittest.TestCase):
self.assertTrue(res)
def test_duplicate_item_apply(self):
res = importer._item_duplicate_check(self.lib,
res = importer._item_duplicate_check(self.lib,
self._item_task(False))
self.assertTrue(res)
def test_different_item_apply(self):
res = importer._item_duplicate_check(self.lib,
res = importer._item_duplicate_check(self.lib,
self._item_task(False, 'xxx', 'yyy'))
self.assertFalse(res)
def test_duplicate_item_asis(self):
res = importer._item_duplicate_check(self.lib,
res = importer._item_duplicate_check(self.lib,
self._item_task(True))
self.assertTrue(res)
def test_different_item_asis(self):
res = importer._item_duplicate_check(self.lib,
res = importer._item_duplicate_check(self.lib,
self._item_task(True, 'xxx', 'yyy'))
self.assertFalse(res)
@ -725,7 +725,7 @@ class DuplicateCheckTest(unittest.TestCase):
self.assertFalse(res)
def test_duplicate_item_existing(self):
res = importer._item_duplicate_check(self.lib,
res = importer._item_duplicate_check(self.lib,
self._item_task(False, existing=True))
self.assertFalse(res)

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -156,7 +156,7 @@ class MBAlbumInfoTest(unittest.TestCase):
d = mb.album_info(release)
self.assertEqual(d.year, 1987)
self.assertEqual(d.month, 3)
def test_no_durations(self):
tracks = [self._make_track('TITLE', 'ID', None)]
release = self._make_release(tracks=tracks)

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -54,7 +54,7 @@ class EdgeTest(unittest.TestCase):
# rather than just a number.
f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'bpm.mp3'))
self.assertEqual(f.bpm, 128)
def test_discc_alternate_field(self):
# Different taggers use different vorbis comments to reflect
# the disc and disc count fields: ensure that the alternative
@ -119,27 +119,27 @@ class SafetyTest(unittest.TestCase):
self.assertRaises(exc, beets.mediafile.MediaFile, fn)
finally:
os.unlink(fn) # delete the temporary file
def test_corrupt_mp3_raises_unreadablefileerror(self):
# Make sure we catch Mutagen reading errors appropriately.
self._exccheck('corrupt.mp3', beets.mediafile.UnreadableFileError)
def test_corrupt_mp4_raises_unreadablefileerror(self):
self._exccheck('corrupt.m4a', beets.mediafile.UnreadableFileError)
def test_corrupt_flac_raises_unreadablefileerror(self):
self._exccheck('corrupt.flac', beets.mediafile.UnreadableFileError)
def test_corrupt_ogg_raises_unreadablefileerror(self):
self._exccheck('corrupt.ogg', beets.mediafile.UnreadableFileError)
def test_invalid_ogg_header_raises_unreadablefileerror(self):
self._exccheck('corrupt.ogg', beets.mediafile.UnreadableFileError,
'OggS\x01vorbis')
def test_corrupt_monkeys_raises_unreadablefileerror(self):
self._exccheck('corrupt.ape', beets.mediafile.UnreadableFileError)
def test_invalid_extension_raises_filetypeerror(self):
self._exccheck('something.unknown', beets.mediafile.FileTypeError)

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -56,14 +56,14 @@ def MakeReadOnlyTest(path, field, value):
return ReadOnlyTest
def MakeWritingTest(path, correct_dict, field, testsuffix='_test'):
class WritingTest(unittest.TestCase):
def setUp(self):
# make a copy of the file we'll work on
root, ext = os.path.splitext(path)
self.tpath = root + testsuffix + ext
shutil.copy(path, self.tpath)
# generate the new value we'll try storing
if field == 'art':
self.value = 'xxx'
@ -82,18 +82,18 @@ def MakeWritingTest(path, correct_dict, field, testsuffix='_test'):
else:
raise ValueError('unknown field type ' + \
str(type(correct_dict[field])))
def runTest(self):
def runTest(self):
# write new tag
a = beets.mediafile.MediaFile(self.tpath)
setattr(a, field, self.value)
a.save()
# verify ALL tags are correct with modification
b = beets.mediafile.MediaFile(self.tpath)
for readfield in correct_dict.keys():
got = getattr(b, readfield)
# Make sure the modified field was changed correctly...
if readfield == field:
message = field + ' modified incorrectly (changed to ' + \
@ -103,7 +103,7 @@ def MakeWritingTest(path, correct_dict, field, testsuffix='_test'):
self.assertAlmostEqual(got, self.value, msg=message)
else:
self.assertEqual(got, self.value, message)
# ... and that no other field was changed.
else:
# MPEG-4: ReplayGain not implented.
@ -113,7 +113,7 @@ def MakeWritingTest(path, correct_dict, field, testsuffix='_test'):
# The value should be what it was originally most of the
# time.
correct = correct_dict[readfield]
# The date field, however, is modified when its components
# change.
if readfield=='date' and field in ('year', 'month', 'day'):
@ -128,7 +128,7 @@ def MakeWritingTest(path, correct_dict, field, testsuffix='_test'):
# And vice-versa.
if field=='date' and readfield in ('year', 'month', 'day'):
correct = getattr(self.value, readfield)
message = readfield + ' changed when it should not have' \
' (expected ' + repr(correct) + ', got ' + \
repr(got) + ') when modifying ' + field + \
@ -137,11 +137,11 @@ def MakeWritingTest(path, correct_dict, field, testsuffix='_test'):
self.assertAlmostEqual(got, correct, msg=message)
else:
self.assertEqual(got, correct, message)
def tearDown(self):
if os.path.exists(self.tpath):
os.remove(self.tpath)
return WritingTest
correct_dicts = {
@ -278,7 +278,7 @@ read_only_correct_dicts = {
'bitdepth': 16,
'channels': 2,
},
'full.ogg': {
'length': 1.0,
'bitrate': 48000,
@ -287,7 +287,7 @@ read_only_correct_dicts = {
'bitdepth': 0,
'channels': 1,
},
'full.ape': {
'length': 1.0,
'bitrate': 112040,
@ -342,7 +342,7 @@ test_files = {
def suite():
s = unittest.TestSuite()
# General tests.
for kind, tagsets in test_files.items():
for tagset in tagsets:
@ -350,13 +350,13 @@ def suite():
correct_dict = correct_dicts[tagset]
for test in suite_for_file(path, correct_dict):
s.addTest(test)
# Special test for missing ID3 tag.
for test in suite_for_file(os.path.join(_common.RSRC, 'empty.mp3'),
correct_dicts['empty'],
writing=False):
s.addTest(test)
# Special test for advanced release date.
for test in suite_for_file(os.path.join(_common.RSRC, 'date.mp3'),
correct_dicts['date']):
@ -367,7 +367,7 @@ def suite():
path = os.path.join(_common.RSRC, fname)
for field, value in correct_dict.iteritems():
s.addTest(MakeReadOnlyTest(path, field, value)())
return s
def test_nose_suite():

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -57,7 +57,7 @@ class CommandParseTest(unittest.TestCase):
s = ur'command "hello \\ there"'
c = bpd.Command(s)
self.assertEqual(c.args, [u'hello \ there'])
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -121,52 +121,52 @@ class GetTest(unittest.TestCase, AssertsMixin):
q = ''
results = self.lib.items(q)
self.assert_matched_all(results)
def test_get_none(self):
q = None
results = self.lib.items(q)
self.assert_matched_all(results)
def test_get_one_keyed_term(self):
q = 'artist:Lil'
results = self.lib.items(q)
self.assert_matched(results, 'Littlest Things')
self.assert_done(results)
def test_get_one_keyed_regexp(self):
q = r'artist::L.+y'
results = self.lib.items(q)
self.assert_matched(results, 'Littlest Things')
self.assert_done(results)
def test_get_one_unkeyed_term(self):
q = 'Terry'
results = self.lib.items(q)
self.assert_matched(results, 'Boracay')
self.assert_done(results)
def test_get_one_unkeyed_regexp(self):
q = r':y$'
results = self.lib.items(q)
self.assert_matched(results, 'Boracay')
self.assert_done(results)
def test_get_no_matches(self):
q = 'popebear'
results = self.lib.items(q)
self.assert_done(results)
def test_invalid_key(self):
q = 'pope:bear'
results = self.lib.items(q)
self.assert_matched_all(results)
def test_term_case_insensitive(self):
q = 'UNCoVER'
results = self.lib.items(q)
self.assert_matched(results, 'Lovers Who Uncover')
self.assert_done(results)
def test_regexp_case_sensitive(self):
q = r':UNCoVER'
results = self.lib.items(q)
@ -175,19 +175,19 @@ class GetTest(unittest.TestCase, AssertsMixin):
results = self.lib.items(q)
self.assert_matched(results, 'Lovers Who Uncover')
self.assert_done(results)
def test_term_case_insensitive_with_key(self):
q = 'album:stiLL'
results = self.lib.items(q)
self.assert_matched(results, 'Littlest Things')
self.assert_done(results)
def test_key_case_insensitive(self):
q = 'ArTiST:Allen'
results = self.lib.items(q)
self.assert_matched(results, 'Littlest Things')
self.assert_done(results)
def test_unkeyed_term_matches_multiple_columns(self):
q = 'little'
results = self.lib.items(q)
@ -195,7 +195,7 @@ class GetTest(unittest.TestCase, AssertsMixin):
self.assert_matched(results, 'Lovers Who Uncover')
self.assert_matched(results, 'Boracay')
self.assert_done(results)
def test_unkeyed_regexp_matches_multiple_columns(self):
q = r':^T'
results = self.lib.items(q)
@ -203,21 +203,21 @@ class GetTest(unittest.TestCase, AssertsMixin):
self.assert_matched(results, 'Lovers Who Uncover')
self.assert_matched(results, 'Boracay')
self.assert_done(results)
def test_keyed_term_matches_only_one_column(self):
q = 'artist:little'
results = self.lib.items(q)
self.assert_matched(results, 'Lovers Who Uncover')
self.assert_matched(results, 'Boracay')
self.assert_done(results)
def test_keyed_regexp_matches_only_one_column(self):
q = r'album::\sS'
results = self.lib.items(q)
self.assert_matched(results, 'Littlest Things')
self.assert_matched(results, 'Lovers Who Uncover')
self.assert_done(results)
def test_multiple_terms_narrow_search(self):
q = 'little ones'
results = self.lib.items(q)
@ -333,7 +333,7 @@ class PathQueryTest(unittest.TestCase, AssertsMixin):
q = 'path:/xyzzy/'
results = self.lib.items(q)
self.assert_done(results)
def test_fragment_no_match(self):
q = 'path:/b/'
results = self.lib.items(q)
@ -390,7 +390,7 @@ class BrowseTest(unittest.TestCase, AssertsMixin):
def test_albums_matches_albumartist(self):
albums = list(self.lib.albums('panda'))
self.assertEqual(len(albums), 1)
def test_items_matches_title(self):
items = self.lib.items('boracay')
self.assert_matched(items, 'Boracay')
@ -401,7 +401,7 @@ class BrowseTest(unittest.TestCase, AssertsMixin):
self.assert_done(items)
#FIXME Haven't tested explicit (non-query) criteria.
class CountTest(unittest.TestCase):
def setUp(self):
self.lib = beets.library.Library(':memory:')
@ -420,7 +420,7 @@ class CountTest(unittest.TestCase):
songs, totaltime = beets.library.TrueQuery().count(tx)
self.assertEqual(songs, 0)
self.assertEqual(totaltime, 0.0)
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -128,19 +128,19 @@ class ParseTest(unittest.TestCase):
def test_unclosed_braces_symbol(self):
self.assertEqual(list(_normparse(u'a ${ b')), [u'a ${ b'])
def test_empty_braces_symbol(self):
self.assertEqual(list(_normparse(u'a ${} b')), [u'a ${} b'])
def test_call_without_args_at_end(self):
self.assertEqual(list(_normparse(u'foo %bar')), [u'foo %bar'])
def test_call_without_args(self):
self.assertEqual(list(_normparse(u'foo %bar baz')), [u'foo %bar baz'])
def test_call_with_unclosed_args(self):
self.assertEqual(list(_normparse(u'foo %bar{ baz')), [u'foo %bar{ baz'])
def test_call_with_unclosed_multiple_args(self):
self.assertEqual(list(_normparse(u'foo %bar{bar,bar baz')),
[u'foo %bar{bar,bar baz'])
@ -150,26 +150,26 @@ class ParseTest(unittest.TestCase):
self.assertEqual(len(parts), 1)
self._assert_call(parts[0], u"foo", 1)
self.assertEqual(list(_normexpr(parts[0].args[0])), [])
def test_call_single_arg(self):
parts = list(_normparse(u'%foo{bar}'))
self.assertEqual(len(parts), 1)
self._assert_call(parts[0], u"foo", 1)
self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar'])
def test_call_two_args(self):
parts = list(_normparse(u'%foo{bar,baz}'))
self.assertEqual(len(parts), 1)
self._assert_call(parts[0], u"foo", 2)
self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar'])
self.assertEqual(list(_normexpr(parts[0].args[1])), [u'baz'])
def test_call_with_escaped_sep(self):
parts = list(_normparse(u'%foo{bar$,baz}'))
self.assertEqual(len(parts), 1)
self._assert_call(parts[0], u"foo", 1)
self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar,baz'])
def test_call_with_escaped_close(self):
parts = list(_normparse(u'%foo{bar$}baz}'))
self.assertEqual(len(parts), 1)
@ -217,10 +217,10 @@ class EvalTest(unittest.TestCase):
def test_plain_text(self):
self.assertEqual(self._eval(u"foo"), u"foo")
def test_subtitute_value(self):
self.assertEqual(self._eval(u"$foo"), u"bar")
def test_subtitute_value_in_text(self):
self.assertEqual(self._eval(u"hello $foo world"), u"hello bar world")
@ -242,7 +242,7 @@ class EvalTest(unittest.TestCase):
def test_function_call_exception(self):
res = self._eval(u"%lower{a,b,c,d,e}")
self.assertTrue(isinstance(res, basestring))
def test_function_returning_integer(self):
self.assertEqual(self._eval(u"%len{foo}"), u"3")

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -45,7 +45,7 @@ class ListTest(unittest.TestCase):
def tearDown(self):
self.io.restore()
def test_list_outputs_item(self):
commands.list_items(self.lib, '', False, False, None)
out = self.io.getoutput()
@ -74,7 +74,7 @@ class ListTest(unittest.TestCase):
commands.list_items(self.lib, '', True, True, None)
out = self.io.getoutput()
self.assertEqual(out.strip(), u'xxx')
def test_list_album_omits_title(self):
commands.list_items(self.lib, '', True, False, None)
out = self.io.getoutput()
@ -85,7 +85,7 @@ class ListTest(unittest.TestCase):
out = self.io.getoutput()
self.assertTrue(u'the artist' in out)
self.assertTrue(u'the album artist' not in out)
def test_list_album_uses_album_artist(self):
commands.list_items(self.lib, '', True, False, None)
out = self.io.getoutput()
@ -407,7 +407,7 @@ class PrintTest(unittest.TestCase):
self.io.install()
def tearDown(self):
self.io.restore()
def test_print_without_locale(self):
lang = os.environ.get('LANG')
if lang:

View file

@ -8,7 +8,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
@ -27,7 +27,7 @@ class VFSTest(unittest.TestCase):
self.lib.add(_common.item())
self.lib.add_album([_common.item()])
self.tree = vfs.libtree(self.lib)
def test_singleton_item(self):
self.assertEqual(self.tree.dirs['tracks'].dirs['the artist'].
files['the title'], 1)

View file

@ -10,7 +10,7 @@
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.