chore: add README, Makefile, CI, lint config and --version

The repo had no README, Makefile, CI, linter config or version string.

- README.md: requirements (ffmpeg/ffprobe, plus yt-dlp for SoundCloud), build,
  the per-source credentials table, usage, the quality scale and the download
  database's migration behaviour. Every claim was checked against the code
  rather than written from memory, which is how the quality-range bug below was
  found. It also records the two deferred refactors (no shared HTTP layer, and
  metadata as map[string]any) so they are documented rather than lore, and
  notes that there is no LICENSE file.
- Makefile: build, install, test, race, cover, fmt, fmt-check, vet, lint,
  check, clean, help. `make build` stamps version/commit/date via -ldflags.
- .github/workflows/ci.yml: gofmt, vet, build, test and test -race, plus a
  lint job. The linter is intentionally unpinned, with a comment: a
  golangci-lint built against an older Go cannot analyse a go1.25 module, which
  is the same reason a locally installed staticcheck built with go1.24 cannot
  run here.
- .golangci.yml: standard set plus bodyclose, errorlint, nilerr and
  govet/copylocks. copylocks matters now that config.Config carries a mutex;
  bodyclose and errorlint target defects this codebase actually has.
- `rip --version` / `-V`, documented in the help output, falling back to the
  VCS revision the toolchain embeds for `go install` builds.

Fixes a bug introduced in the previous commit: Validate capped deezer quality
at 2 and soundcloud at 1, but config.toml.example documents deezer as 2/3/4 and
the global -q flag accepts 0-4, so a documented config would have failed to
load. deezer's buildFormatPriority treats anything >= 2 as FLAC and soundcloud
ignores the value entirely, so the real ceiling is 4 everywhere; only the Qobuz
and Beatport floor of 1 is a genuine constraint. Pinned with a test that walks
the whole documented range.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
JorenandClaude Opus 5 committed 2026-10-03 17:14:59 +02:00
1 parent 384f32cd67
commit ff5fb1cdb5
10 files changed
+433 -6

No files matched your search

+9 -4
View File
@@ -286,13 +286,18 @@ func applyVendorDefaults(data *ConfigData) {
}
}
// qualityRange is the accepted quality id range per source. Qobuz and Beatport
// have no "lowest" tier, so their floor is 1.
// qualityRange is the accepted quality id range per source on the global 0-4
// scale.
//
// The ceiling is 4 everywhere because each provider maps the scale onto its own
// ladder and saturates at the top: deezer's buildFormatPriority treats anything
// >= 2 as FLAC, and soundcloud ignores the value entirely. Only the floor
// differs: Qobuz and Beatport have no equivalent of tier 0.
var qualityRange = map[string][2]int{
"qobuz": {1, 4},
"tidal": {0, 4},
"deezer": {0, 2},
"soundcloud": {0, 1},
"deezer": {0, 4},
"soundcloud": {0, 4},
"yandex": {0, 4},
"beatport": {1, 4},
}
+28 -2
View File
@@ -154,7 +154,6 @@ func TestValidateRejectsBadValues(t *testing.T) {
{"qobuz quality 5", func(d *ConfigData) { d.Qobuz.Quality = 5 }, "qobuz.quality"},
{"beatport quality 0", func(d *ConfigData) { d.Beatport.Quality = 0 }, "beatport.quality"},
{"tidal quality 5", func(d *ConfigData) { d.Tidal.Quality = 5 }, "tidal.quality"},
{"deezer quality 3", func(d *ConfigData) { d.Deezer.Quality = 3 }, "deezer.quality"},
{"negative quality", func(d *ConfigData) { d.Tidal.Quality = -1 }, "tidal.quality"},
{"max_connections 0", func(d *ConfigData) { d.Downloads.MaxConnections = 0 }, "max_connections"},
{"max_connections negative", func(d *ConfigData) { d.Downloads.MaxConnections = -5 }, "max_connections"},
@@ -182,13 +181,40 @@ func TestValidateRejectsBadValues(t *testing.T) {
}
}
// TestValidateAcceptsDocumentedQualityValues pins the ranges documented in
// config.toml.example. An earlier draft capped deezer at 2 and soundcloud at 1,
// which would have made a config the example itself documents fail to load.
func TestValidateAcceptsDocumentedQualityValues(t *testing.T) {
for q := 0; q <= 4; q++ {
d := DefaultConfigData()
applyRuntimeDefaults(&d)
d.Deezer.Quality = q
d.Soundcloud.Quality = q
d.Tidal.Quality = q
d.Yandex.Quality = q
if err := Validate(&d); err != nil {
t.Errorf("quality %d rejected: %v", q, err)
}
}
// Qobuz and Beatport have no tier 0.
for q := 1; q <= 4; q++ {
d := DefaultConfigData()
applyRuntimeDefaults(&d)
d.Qobuz.Quality = q
d.Beatport.Quality = q
if err := Validate(&d); err != nil {
t.Errorf("qobuz/beatport quality %d rejected: %v", q, err)
}
}
}
func TestValidateAcceptsValidValues(t *testing.T) {
d := DefaultConfigData()
applyRuntimeDefaults(&d)
// Negative requests_per_minute disables throttling, which is legitimate.
d.Downloads.RequestsPerMinute = -1
d.Soundcloud.Quality = 1
d.Deezer.Quality = 2
d.Deezer.Quality = 4
d.Qobuz.Quality = 4
if err := Validate(&d); err != nil {
t.Fatalf("Validate() = %v, want nil", err)