diff --git a/README.md b/README.md index d80cde9..6a3821d 100644 --- a/README.md +++ b/README.md @@ -115,11 +115,9 @@ Options: Commands: cache Manage the read cache. fs Manage the virtual library. + print Print cached library data (JSON-encoded). ``` -The virtual filesystem is mounted and unmounted by `rose fs mount` and -`rose fs unmount` respectively. - ## Configuration Rosé must be configured prior to use. Rosé is configured via a TOML file @@ -162,6 +160,24 @@ Rosé also supports JPEG and PNG cover art. The supported cover art file stems are `cover`, `folder`, and `art`. The supported cover art file extensions are `.jpg`, `.jpeg`, and `.png`. +## Virtual Filesystem + +The virtual filesystem is mounted and unmounted by `rose fs mount` and +`rose fs unmount` respectively. + +TODO + +## Metadata Management + +TODO + +## Data Querying + +The `rose print` family of commands (e.g. `rose print albums`) prints out data +in the read cache in a JSON-encoded format. The output of this command can be +piped into tools like `jq`, `fx`, and others in order to further process the +output. + ## Tagging Conventions Rosé is lenient in the tags it ingests, but has opinionated conventions for the @@ -187,10 +203,6 @@ remixer ::= ' remixed by ' name name ::= string ';' name | string ``` -## Metadata Management - -TODO - ## New Releases TODO @@ -225,7 +237,7 @@ TODO; example unit files to schedule Rosé with systemd. ## Logging -Logs are written to stdout and to `${XDG_STATE_HOME:-$HOME/.local/state}/rose/rose.log`. +Logs are written to stderr and to `${XDG_STATE_HOME:-$HOME/.local/state}/rose/rose.log`. # Architecture diff --git a/rose/__init__.py b/rose/__init__.py index 82226a7..9411eb7 100644 --- a/rose/__init__.py +++ b/rose/__init__.py @@ -17,7 +17,7 @@ "[%(asctime)s] %(levelname)s: %(message)s", datefmt="%H:%M:%S", ) - stream_handler = logging.StreamHandler(sys.stdout) + stream_handler = logging.StreamHandler(sys.stderr) stream_handler.setFormatter(stream_formatter) logger.addHandler(stream_handler) diff --git a/rose/__main__.py b/rose/__main__.py index 94dfbe2..009b2b7 100644 --- a/rose/__main__.py +++ b/rose/__main__.py @@ -7,6 +7,7 @@ from rose.cache import migrate_database, update_cache_for_all_releases from rose.config import Config +from rose.print import print_releases from rose.virtualfs import mount_virtualfs, unmount_virtualfs @@ -75,5 +76,17 @@ def unmount(ctx: Context) -> None: unmount_virtualfs(ctx.config) +@cli.group() +def print() -> None: + """Print cached library data (JSON-encoded).""" + + +@print.command() +@click.pass_obj +def albums(ctx: Context) -> None: + """Print albums.""" + print_releases(ctx.config) + + if __name__ == "__main__": cli() diff --git a/rose/cache.py b/rose/cache.py index 1a11f96..5daac9c 100644 --- a/rose/cache.py +++ b/rose/cache.py @@ -865,6 +865,7 @@ def list_releases( ) """ args.append(sanitized_label_filter) + query += " ORDER BY r.source_path" cursor = conn.execute(query, args) for row in cursor: diff --git a/rose/print.py b/rose/print.py new file mode 100644 index 0000000..e0fade7 --- /dev/null +++ b/rose/print.py @@ -0,0 +1,19 @@ +import json +from dataclasses import asdict +from pathlib import Path +from typing import Any + +from rose.cache import list_releases +from rose.config import Config + + +class CustomJSONEncoder(json.JSONEncoder): + def default(self, obj: Any) -> Any: + if isinstance(obj, Path): + return str(obj) + return super().default(obj) + + +def print_releases(c: Config) -> None: + releases = [asdict(r) for r in list_releases(c)] + print(json.dumps(releases, cls=CustomJSONEncoder))