106 lines
3.2 KiB
Python
106 lines
3.2 KiB
Python
from prompt_toolkit import prompt
|
|
from prompt_toolkit.completion import Completion, FuzzyCompleter
|
|
from slugify import slugify
|
|
from sqlalchemy import func
|
|
from rich import print
|
|
|
|
from groove import db
|
|
from groove.playlist import Playlist
|
|
|
|
|
|
class FuzzyTableCompleter(FuzzyCompleter):
|
|
|
|
def __init__(self, table, column, formatter, session):
|
|
super(FuzzyTableCompleter).__init__()
|
|
self._table = table
|
|
self._column = column
|
|
self._formatter = formatter
|
|
self._session = session
|
|
|
|
def get_completions(self, document, complete_event):
|
|
word = document.get_word_before_cursor()
|
|
query = self._session.query(self._table).filter(self._column.ilike(f"%{word}%"))
|
|
for row in query.all():
|
|
yield Completion(
|
|
self._formatter(row),
|
|
start_position=-len(word)
|
|
)
|
|
|
|
|
|
class Command:
|
|
def __init__(self, processor):
|
|
self._processor = processor
|
|
|
|
def handle(self, *parts):
|
|
raise NotImplementedError()
|
|
|
|
|
|
class help(Command):
|
|
"""Display the help documentation."""
|
|
def handle(self, *parts):
|
|
print("Available commands:")
|
|
for handler in Command.__subclasses__():
|
|
print(f"{handler.__name__}: {handler.__doc__}")
|
|
|
|
|
|
class add(Command):
|
|
"""Add a track to the current playlist."""
|
|
def handle(self, *parts):
|
|
if not self._processor.playlist:
|
|
print("Please select a playlist first, using the 'playlist' command.")
|
|
return
|
|
text = prompt(
|
|
'Add which track? > ',
|
|
completer=FuzzyTableCompleter(db.track, db.track.c.relpath, self._track_to_string, self._processor.session),
|
|
complete_in_thread=True, complete_while_typing=True
|
|
)
|
|
return text
|
|
|
|
def _track_to_string(row):
|
|
return f"{row.artist} - {row.title}"
|
|
|
|
|
|
class list(Command):
|
|
"""Display the current playlist."""
|
|
def handle(self, *parts):
|
|
if not self._processor.playlist:
|
|
print("Please select a playlist first, using the 'playlist' command.")
|
|
return
|
|
print(self._processor.playlist.as_dict)
|
|
|
|
|
|
class stats(Command):
|
|
"""Display database statistics."""
|
|
def handle(self, *parts):
|
|
sess = self._processor.session
|
|
playlists = sess.query(func.count(db.playlist.c.id)).scalar()
|
|
entries = sess.query(func.count(db.entry.c.track)).scalar()
|
|
tracks = sess.query(func.count(db.track.c.relpath)).scalar()
|
|
print(f"Database contains {playlists} playlists with a total of {entries} entries, from {tracks} known tracks.")
|
|
|
|
|
|
class quit(Command):
|
|
"""Exit the interactive shell."""
|
|
def handle(self, *parts):
|
|
raise SystemExit()
|
|
|
|
|
|
class playlist(Command):
|
|
"""Create or load a playlist."""
|
|
def handle(self, *parts):
|
|
name = ' '.join(parts)
|
|
slug = slugify(name)
|
|
self._processor.playlist = Playlist(
|
|
slug=slug,
|
|
name=name,
|
|
session=self._processor.session,
|
|
create_if_not_exists=True
|
|
)
|
|
self._processor.prompt = slug
|
|
print(f"Loaded playlist with slug {self._processor.playlist.record.slug}.")
|
|
|
|
|
|
def load(processor):
|
|
for handler in Command.__subclasses__():
|
|
yield handler.__name__, handler(processor)
|