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.
normalisenotnormalizebehaviournotbehaviorlicencenotlicensecolournotcolor(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_case—open_memory_db,parse_metadata_line - Types/Structs:
PascalCase—ScrobbleTracker,TopArtist - Enums:
PascalCasetype + variants —Event::Metadata,PlayerStatus::Playing - Constants:
SCREAMING_SNAKE_CASE—DEFAULT_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.rsfile. ///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:
Result<T>+?in library functions (alldb.rsqueries,gather_report, etc.)eprintln!+std::process::exit(1)for fatal CLI errors inmain.rs.expect("descriptive message")only for truly unrecoverable cases (missingHOME, failed dir creation).unwrap()only in test code — never in production pathsmatchonResultwitheprintln!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::blockingandstd::thread. - No logging crate — direct
eprintln!calls throughout. - No template engine — HTML built via custom
HtmlWriterhelper inreport.rs. - No unsafe code except one
libc::ioctlfor terminal width (#[cfg(unix)]). - XDG paths — DB at
$XDG_DATA_HOME/scrbblr/scrobbles.db, covers incovers/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 |