diff --git a/groove/cli.py b/groove/cli.py index 840794e..ff6e059 100644 --- a/groove/cli.py +++ b/groove/cli.py @@ -1,21 +1,36 @@ import logging import os +import sys import typer from pathlib import Path from typing import Optional +from textwrap import dedent + from dotenv import load_dotenv +from rich import print from rich.logging import RichHandler +import groove.path + from groove.shell import interactive_shell from groove.db.manager import database_manager from groove.webserver import webserver +from groove.exceptions import ConfigurationError +from groove.console import Console app = typer.Typer() -def initialize(): - load_dotenv() +@app.callback() +def main( + context: typer.Context, + env: Optional[Path] = typer.Option( + Path('~/.groove'), + help="Path to the Groove on Demand environment", + ) +): + load_dotenv(env.expanduser()) debug = os.getenv('DEBUG', None) logging.basicConfig( format='%(message)s', @@ -26,13 +41,76 @@ def initialize(): ) logging.getLogger('asyncio').setLevel(logging.ERROR) + try: + groove.path.media_root() + groove.path.static_root() + groove.path.themes_root() + groove.path.database() + except ConfigurationError as e: + sys.stderr.write(f'{e}\n') + sys.exit(1) + @app.command() -def list(): +def setup(context: typer.Context): """ - List all Playlists + (Re)Initialize Groove on Demand. + """ + print(dedent( + """ + Interactive setup is not yet available. Sorry! + + In the mean time, please make sure you set MEDIA_ROOT and SECRET_KEY + in your environment. By default, Groove on Demand will attempt to load + these variables from ~/.groove, which may contain the following + variables as well. See also the --env paramter. + + # Set this one. The path containing your media files + MEDIA_ROOT= + + # the kinds of files to import + # MEDIA_GLOB=*.mp3,*.flac,*.m4a + + # where to store the groove_on_demand.db sqlite database. + # DATABASE_PATH=~ + + # Try 'groove themes' to see a list of available themes. + # DEFAULT_THEME=blue_train + + # Web interface configuration + # HOST=127.0.0.1 + # PORT=2323 + + # Set this to a suitably random string. + SECRET_KEY=much secret very private + + # Console configuration + # EDITOR= + # CONSOLE_WIDTH=auto + """ + )) + + +@app.command() +def themes(context: typer.Context): + """ + List the available themes. + """ + print("Available themes:") + themes = [theme for theme in groove.path.themes_root().iterdir()] + tags = ('artist', 'title', 'bold', 'dim', 'link', 'prompt', 'bright', 'text', 'help') + for theme in themes: + text = '' + for tag in tags: + text += f'[{tag}]◼◼◼◼◼◼' + Console(theme=theme.name).print(f' ▪ [title]{theme.name}[/title] {text}') + + +@app.command() +def playlists(context: typer.Context): + """ + List all playlists """ - initialize() with database_manager() as manager: shell = interactive_shell.InteractiveShell(manager) shell.list(None) @@ -40,6 +118,7 @@ def list(): @app.command() def scan( + context: typer.Context, path: Optional[Path] = typer.Option( '', help="A path to scan, relative to your MEDIA_ROOT. " @@ -49,7 +128,6 @@ def scan( """ Scan the filesystem and create track entries in the database. """ - initialize() with database_manager() as manager: shell = interactive_shell.InteractiveShell(manager) shell.console.print("Starting the Groove on Demand scanner...") @@ -57,13 +135,17 @@ def scan( @app.command() -def shell(): - initialize() - interactive_shell.start() +def shell(context: typer.Context): + """ + Start the Groove on Demand interactive shell. + """ + with database_manager() as manager: + interactive_shell.InteractiveShell(manager).start() @app.command() def server( + context: typer.Context, host: str = typer.Argument( '0.0.0.0', help="bind address", @@ -80,7 +162,6 @@ def server( """ Start the Groove on Demand playlsit server. """ - initialize() with database_manager() as manager: manager.import_from_filesystem() webserver.start(host=host, port=port, debug=debug) diff --git a/groove/console.py b/groove/console.py index 3782fb8..595fdce 100644 --- a/groove/console.py +++ b/groove/console.py @@ -26,7 +26,7 @@ BASE_STYLE = { } -def console_theme(theme_name: Union[str, None] = None) -> dict: +def console_theme(theme_name: Union[str, None] = 'blue_train') -> dict: """ Return a console theme as a dictionary. @@ -36,8 +36,8 @@ def console_theme(theme_name: Union[str, None] = None) -> dict: cfg = ConfigParser() cfg.read_dict({'styles': BASE_STYLE}) cfg.read(theme( - theme_name or os.environ['DEFAULT_THEME']) / Path('console.cfg') - ) + Path(theme_name or os.environ['DEFAULT_THEME']) / Path('console.cfg') + )) return cfg['styles'] diff --git a/groove/db/scanner.py b/groove/db/scanner.py index f44429f..afdcd39 100644 --- a/groove/db/scanner.py +++ b/groove/db/scanner.py @@ -69,7 +69,7 @@ class MediaScanner: console: Union[Console, None] = None, ) -> None: self._db = db - self._glob = tuple((glob or os.environ.get('MEDIA_GLOB')).split(',')) + self._glob = tuple((glob or os.environ.get('MEDIA_GLOB', '*.mp3,*.flac,*.m4a')).split(',')) self._root = groove.path.media_root() self._console = console or Console() self._scanned = 0 diff --git a/groove/path.py b/groove/path.py index 59e1370..1035d75 100644 --- a/groove/path.py +++ b/groove/path.py @@ -4,14 +4,14 @@ import os from pathlib import Path from groove.exceptions import ConfigurationError, ThemeMissingException, ThemeConfigurationError -_setup_hint = "You may be able to solve this error by running 'groove setup'." +_setup_hint = "You may be able to solve this error by running 'groove setup' or specifying the --env parameter." _reinstall_hint = "You might need to reinstall Groove On Demand to fix this error." def root(): path = os.environ.get('GROOVE_ON_DEMAND_ROOT', None) if not path: - raise ConfigurationError(f"GROOVE_ON_DEMAND_ROOT is not defined in your environment.\n\n{_setup_hint}") + raise ConfigurationError(f"GROOVE_ON_DEMAND_ROOT is not defined in your environment.\n{_setup_hint}") path = Path(path).expanduser() if not path.exists() or not path.is_dir(): raise ConfigurationError( @@ -83,7 +83,7 @@ def themes_root(): def theme(name): path = themes_root() / Path(name) - if not path.exists() or not path.is_dir(): + if not path.exists(): available = ','.join(available_themes()) raise ThemeMissingException( f"A theme directory named {name} does not exist or isn't a directory. " @@ -102,13 +102,9 @@ def available_themes(): def database(): - path = os.environ.get('DATABASE_PATH', None) - if not path: - path = root() - else: # pragma: no cover - path = Path(path).expanduser() - if not path.exists() or not path.is_dir(): - raise ConfigurationError( - "DATABASE_PATH doesn't exist or isn't a directory.\n\n{_setup_hint}" - ) + path = Path(os.environ.get('DATABASE_PATH', '~')).expanduser() + if not path.exists() or not path.is_dir(): + raise ConfigurationError( + "DATABASE_PATH doesn't exist or isn't a directory.\n\n{_setup_hint}" + ) return path / Path('groove_on_demand.db') diff --git a/groove/webserver/webserver.py b/groove/webserver/webserver.py index 6e8ad51..b361b85 100644 --- a/groove/webserver/webserver.py +++ b/groove/webserver/webserver.py @@ -16,7 +16,7 @@ from groove.webserver import requests, themes server = bottle.Bottle() -def start(host: str, port: int, debug: bool) -> None: # pragma: no cover +def start(host: str = '127.0.0.1', port: int = 2323, debug: bool = False) -> None: # pragma: no cover """ Start the Bottle app. """ diff --git a/test/test_path.py b/test/test_path.py index 9fbb0e4..ca0fe64 100644 --- a/test/test_path.py +++ b/test/test_path.py @@ -35,7 +35,7 @@ def test_theme_no_path(): def test_database_default(env): - assert path.database().relative_to(path.root()) + assert path.database() def test_database(env):