mirror of
https://github.com/beetbox/beets.git
synced 2026-09-16 04:19:28 -05:00
For a flexible attribute query, replace the `col_name` property with a function call that extracts that attribute from the `field_attrs` field introduced in the earlier commit. Additionally, for boolean, numeric and date queries CAST the value to NUMERIC SQLite affinity to ensure that our queries like 'flex:1..5' and 'flex:true' continue working fine. This removes the concept of 'slow query', since every query for any field now has an SQL clause.
93 lines
2.5 KiB
Python
93 lines
2.5 KiB
Python
"""Adds head/tail functionality to list/ls.
|
|
|
|
1. Implemented as `lslimit` command with `--head` and `--tail` options. This is
|
|
the idiomatic way to use this plugin.
|
|
2. Implemented as query prefix `<` for head functionality only. This is the
|
|
composable way to use the plugin (plays nicely with anything that uses the
|
|
query language).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import deque
|
|
from itertools import islice
|
|
from typing import TYPE_CHECKING, Protocol
|
|
|
|
from beets.dbcore import FieldQuery
|
|
from beets.plugins import BeetsPlugin
|
|
from beets.ui import Subcommand, print_
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterable
|
|
|
|
from beets.library import LibModel, Library
|
|
|
|
|
|
class LsLimitCLIOpts(Protocol):
|
|
album: bool
|
|
head: int | None
|
|
tail: int | None
|
|
|
|
|
|
def lslimit(lib: Library, opts: LsLimitCLIOpts, args: list[str]) -> None:
|
|
"""Query command with head/tail."""
|
|
|
|
if (opts.head is not None) and (opts.tail is not None):
|
|
raise ValueError("Only use one of --head and --tail")
|
|
if (opts.head or opts.tail or 0) < 0:
|
|
raise ValueError("Limit value must be non-negative")
|
|
|
|
objs: Iterable[LibModel]
|
|
if opts.album:
|
|
objs = lib.albums(args)
|
|
else:
|
|
objs = lib.items(args)
|
|
|
|
if opts.head is not None:
|
|
objs = islice(objs, opts.head)
|
|
elif opts.tail is not None:
|
|
objs = deque(objs, opts.tail)
|
|
|
|
for obj in objs:
|
|
print_(format(obj))
|
|
|
|
|
|
lslimit_cmd = Subcommand("lslimit", help="query with optional head or tail")
|
|
|
|
lslimit_cmd.parser.add_option(
|
|
"--head", action="store", type="int", default=None
|
|
)
|
|
|
|
lslimit_cmd.parser.add_option(
|
|
"--tail", action="store", type="int", default=None
|
|
)
|
|
|
|
lslimit_cmd.parser.add_all_common_options()
|
|
lslimit_cmd.func = lslimit
|
|
|
|
|
|
class LimitPlugin(BeetsPlugin):
|
|
"""Query limit functionality via command and query prefix."""
|
|
|
|
def commands(self):
|
|
"""Expose `lslimit` subcommand."""
|
|
return [lslimit_cmd]
|
|
|
|
def queries(self):
|
|
class HeadQuery(FieldQuery):
|
|
"""This inner class pattern allows the query to track state."""
|
|
|
|
n = 0
|
|
N = None
|
|
|
|
@classmethod
|
|
def value_match(cls, pattern, value):
|
|
if cls.N is None:
|
|
cls.N = int(pattern)
|
|
if cls.N < 0:
|
|
raise ValueError("Limit value must be non-negative")
|
|
cls.n += 1
|
|
return cls.n <= cls.N
|
|
|
|
return {"<": HeadQuery}
|