mirror of
https://github.com/beetbox/beets.git
synced 2026-01-09 09:22:55 +01:00
singleton: queries
This commit is contained in:
parent
7f1e4c2407
commit
d63a9fd188
3 changed files with 52 additions and 12 deletions
3
NEWS
3
NEWS
|
|
@ -3,7 +3,8 @@
|
|||
* Better support for singleton (non-album) tracks. The "singleton" path
|
||||
format can be used to customize where these tracks are stored. While
|
||||
importing, you can choose the "as Tracks" (T) option to add
|
||||
singletons to your library.
|
||||
singletons to your library. The query "singleton:true" matches only
|
||||
singleton tracks; "singleton:false" matches only album tracks.
|
||||
* The "distance" number, which quantifies how different an album's
|
||||
current and proposed metadata are, is now displayed as "similarity"
|
||||
instead. This should be less noisy and confusing; you'll now see
|
||||
|
|
|
|||
|
|
@ -279,6 +279,13 @@ def _sanitize_for_path(value, pathmod, key=None):
|
|||
value = str(value)
|
||||
return value
|
||||
|
||||
def _bool(value):
|
||||
"""Returns a boolean reflecting a human-entered string."""
|
||||
if value.lower() in ('yes', '1', 'true', 't', 'y'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
# Library items (songs).
|
||||
|
||||
|
|
@ -453,7 +460,9 @@ class Query(object):
|
|||
ResultIterator.
|
||||
"""
|
||||
c = library.conn.cursor()
|
||||
c.execute(*self.statement())
|
||||
stmt, subs = self.statement()
|
||||
log.debug('Executing query: %s' % stmt)
|
||||
c.execute(stmt, subs)
|
||||
return ResultIterator(c, library)
|
||||
|
||||
class FieldQuery(Query):
|
||||
|
|
@ -489,6 +498,20 @@ class SubstringQuery(FieldQuery):
|
|||
def match(self, item):
|
||||
return self.pattern.lower() in getattr(item, self.field).lower()
|
||||
|
||||
class SingletonQuery(Query):
|
||||
"""Matches either singleton or non-singleton items."""
|
||||
def __init__(self, sense):
|
||||
self.sense = sense
|
||||
|
||||
def clause(self):
|
||||
if self.sense:
|
||||
return "album_id ISNULL", ()
|
||||
else:
|
||||
return "NOT album_id ISNULL", ()
|
||||
|
||||
def match(self, item):
|
||||
return (not item.album_id) == self.sense
|
||||
|
||||
class CollectionQuery(Query):
|
||||
"""An abstract query class that aggregates other queries. Can be
|
||||
indexed like a list to access the sub-queries.
|
||||
|
|
@ -515,16 +538,6 @@ class CollectionQuery(Query):
|
|||
clause = (' ' + joiner + ' ').join(clause_parts)
|
||||
return clause, subvals
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, matches):
|
||||
"""Construct a query from a dictionary, matches, whose keys are
|
||||
item field names and whose values are substring patterns.
|
||||
"""
|
||||
subqueries = []
|
||||
for key, pattern in matches.iteritems():
|
||||
subqueries.append(SubstringQuery(key, pattern))
|
||||
return cls(subqueries)
|
||||
|
||||
# regular expression for _parse_query, below
|
||||
_pq_regex = re.compile(r'(?:^|(?<=\s))' # zero-width match for whitespace
|
||||
# or beginning of string
|
||||
|
|
@ -569,6 +582,8 @@ class CollectionQuery(Query):
|
|||
subqueries.append(AnySubstringQuery(pattern, default_fields))
|
||||
elif key.lower() in ITEM_KEYS: # ignore unrecognized keys
|
||||
subqueries.append(SubstringQuery(key.lower(), pattern))
|
||||
elif key.lower() == 'singleton':
|
||||
subqueries.append(SingletonQuery(_bool(pattern)))
|
||||
if not subqueries: # no terms in query
|
||||
subqueries = [TrueQuery()]
|
||||
return cls(subqueries)
|
||||
|
|
@ -1094,6 +1109,7 @@ class Library(BaseLibrary):
|
|||
sql = "SELECT * FROM items " + \
|
||||
"WHERE " + where + \
|
||||
" ORDER BY artist, album, disc, track"
|
||||
log.debug('Getting items with SQL: %s' % sql)
|
||||
c = self.conn.execute(sql, subvals)
|
||||
return ResultIterator(c, self)
|
||||
|
||||
|
|
|
|||
|
|
@ -168,6 +168,29 @@ class GetTest(unittest.TestCase, AssertsMixin):
|
|||
self.assert_matched(results, 'Boracay')
|
||||
self.assert_done(results)
|
||||
|
||||
class MemoryGetTest(unittest.TestCase, AssertsMixin):
|
||||
def setUp(self):
|
||||
self.album_item = _common.item()
|
||||
self.album_item.title = 'album item'
|
||||
self.single_item = _common.item()
|
||||
self.single_item.title = 'singleton item'
|
||||
|
||||
self.lib = beets.library.Library(':memory:')
|
||||
self.lib.add(self.single_item)
|
||||
self.lib.add_album([self.album_item])
|
||||
|
||||
def test_singleton_true(self):
|
||||
q = 'singleton:true'
|
||||
results = self.lib.get(q)
|
||||
self.assert_matched(results, 'singleton item')
|
||||
self.assert_done(results)
|
||||
|
||||
def test_singleton_false(self):
|
||||
q = 'singleton:false'
|
||||
results = self.lib.get(q)
|
||||
self.assert_matched(results, 'album item')
|
||||
self.assert_done(results)
|
||||
|
||||
class BrowseTest(unittest.TestCase, AssertsMixin):
|
||||
def setUp(self):
|
||||
self.lib = beets.library.Library('rsrc' + os.sep + 'test.blb')
|
||||
|
|
|
|||
Loading…
Reference in a new issue