Files
scrbblr/AGENTS.md
2026-03-22 08:46:09 +00:00

6.3 KiB

AGENTS.md — Coding Agent Guidelines

Project Overview

scrbblr is a Rust CLI tool that scrobbles music from MPRIS-compatible players and MPD on Linux. It stores data in SQLite, generates terminal/JSON/HTML reports, and enriches album metadata from MusicBrainz/Cover Art Archive.

Single binary, no async, flat module structure. Rust 2024 edition.

Build / Test / Lint

cargo build                          # debug build
cargo build --release                # release build
cargo test                           # run all tests (currently 63)
cargo test <name>                    # single test, e.g. cargo test test_top_artists
cargo test --lib db::tests           # all tests in one module
cargo test -- --nocapture            # show println/eprintln output
cargo fmt                            # format (default rustfmt, no config file)
cargo clippy                         # lint (no clippy.toml)

Always run cargo fmt, cargo test, and cargo build before committing. There is no CI pipeline — local verification is the only gate.

Module Structure

All source lives in src/. Flat layout, no nested modules.

File Role
main.rs CLI (clap derive), subcommand dispatch, process management
db.rs SQLite schema, all queries, data structs
watcher.rs playerctl event parsing, ScrobbleTracker state machine
report.rs Terminal tables, JSON output, HTML report + CSS generation
enrich.rs MusicBrainz/CAA API, cover downloads, genre extraction

Database access goes through db.rs public functions only — no raw SQL elsewhere.

Language & Spelling

Use British English everywhere: comments, docs, README, variable names.

  • normalise not normalize
  • behaviour not behavior
  • licence not license
  • colour not color (in prose; CSS property names stay American per spec)

Code Style

Formatting

Default rustfmt (no rustfmt.toml). 4-space indent, trailing commas in multi-line expressions. Run cargo fmt before every commit.

Naming

  • Functions: snake_caseopen_memory_db, parse_metadata_line
  • Types/Structs: PascalCaseScrobbleTracker, TopArtist
  • Enums: PascalCase type + variants — Event::Metadata, PlayerStatus::Playing
  • Constants: SCREAMING_SNAKE_CASEDEFAULT_PLAYER, RATE_LIMIT_DELAY
  • Tests: test_ prefix — test_top_artists, test_schema_creation

Imports

No strict grouping enforced. General pattern: crate:: imports, external crates, then std. Let cargo fmt handle ordering within groups.

use crate::db;
use rusqlite::Connection;
use serde::Serialize;
use std::fmt::Write as _;

Section Separators

Use box-drawing comment blocks between major sections:

// ---------------------------------------------------------------------------
// Section Name
// ---------------------------------------------------------------------------

Documentation

  • Module-level //! doc comments at top of every .rs file.
  • /// doc comments on all public functions and important private functions.
  • Inline // comments explaining "why" not just "what".
  • Comments should be thorough — this codebase favours extensive documentation.

Error Handling

Layered approach — no panics in production code paths:

  1. Result<T> + ? in library functions (all db.rs queries, gather_report, etc.)
  2. eprintln! + std::process::exit(1) for fatal CLI errors in main.rs
  3. .expect("descriptive message") only for truly unrecoverable cases (missing HOME, failed dir creation)
  4. .unwrap() only in test code — never in production paths
  5. match on Result with eprintln! for non-fatal errors (failed cache writes, etc.)

All user-facing progress/error output goes to stderr via eprintln!. Only report content (tables, JSON, HTML) goes to stdout.

Logging prefixes: [scrobbled], [error], [warn], or unprefixed for progress.

Rust 2024 Edition Features

The codebase uses edition 2024 features freely:

  • Let chains: if let Some(x) = foo && condition { ... }
  • is_none_or() method
  • Other recently stabilised APIs

Test Patterns

Every source file has #[cfg(test)] mod tests at the bottom.

Key test helpers

  • db::open_memory_db() — in-memory SQLite with schema. Used by all modules.
  • db::tests::seed_db(conn) — populates 5 test scrobbles across two artists/days.
  • watcher::TestableTracker — simulated clock (advance_time(secs)) + scrobble collection for unit-testing the state machine without real time.

Test conventions

  • Each test creates its own open_memory_db() — no shared state.
  • .unwrap() is fine in tests (panic = test failure).
  • Use .into() for string literals in test data: artist: "Deftones".into().
  • Descriptive names: test_top_genres_merges_hyphen_and_space_variants.

Key Architectural Notes

  • No async — uses reqwest::blocking and std::thread.
  • No logging crate — direct eprintln! calls throughout.
  • No template engine — HTML built via custom HtmlWriter helper in report.rs.
  • No unsafe code except one libc::ioctl for terminal width (#[cfg(unix)]).
  • XDG paths — DB at $XDG_DATA_HOME/scrbblr/scrobbles.db, covers in covers/ subdirectory. State marker for publish in $XDG_STATE_HOME.
  • MusicBrainz rate limiting: 1-second delay between API calls (RATE_LIMIT_DELAY).
  • HTML report is fully self-contained (relative covers/ paths, no external JS).
  • Font loaded from Google Fonts CDN with monospace fallback chain for offline use.

Files Outside src/

File Purpose
install.sh Interactive installer (binary + service)
uninstall.sh Interactive uninstaller
scrbblr-publish.sh Incremental report publish (rsync)
contrib/examples/publish.conf.example Config template for publish
contrib/systemd/user/ systemd user service unit
FUTURE.md Parking lot for future feature ideas