A uri:: block macro that resolves a Spotify or YouTube URI live, at build time and renders a branded card in place of the bare link: the resolved title, artist or channel, a thumbnail, and a plain link out.
Resolution happens fresh on every build — nothing is cached or persisted between builds, and the macro accepts no attribute for supplying title, artist, or channel manually. If a link stops resolving, that’s between the author and whatever removed the content; the extension shows a visible placeholder and moves on.
See DESIGN-asciidoctor-beautify-uri.adoc for the full design rationale.
Preview
uri::spotify:track/2takcwOaAZWiXQijPHIx7B[]
uri::youtube:watch?v=dQw4w9WgXcQ[]
A Spotify card renders a thumbnail, the resolved title, and a plain "Play on Spotify" link beside the brand icon — no pill button, no fill on the frame, so it sits comfortably on a white PDF page or a dark Antora theme.
A YouTube card’s HTML5 output defers to Asciidoctor’s own built-in video::[] block (an embedded player) rather than a static card; its PDF output gets a static card like Spotify’s, since there is no video playback in a PDF.
Features
-
Two built-in providers — Spotify and YouTube, both resolved through their free oEmbed endpoints
-
Live resolution only — no cache, no manual override attribute; a broken link becomes a visible placeholder, not silent stale data
-
Cross-referencing — an
id=attribute makes a card anxref::target, with a generated reftext likeSpotify: Roygbiv - Boards of Canada -
Dual coverage — a RubyGem (HTML5 +
asciidoctor-pdf) and an npm package (Asciidoctor.js + Antora), sharing one card template -
Third-party providers — register an additional provider without modifying this repository (see Provider Architecture)
-
Optional Spotify Web API — supply your own client credentials to add the artist name, which Spotify’s unauthenticated oEmbed endpoint does not return for a track
Installation
Ruby (PDF + HTML5)
gem install asciidoctor-beautify-uri
Add to a Gemfile:
gem 'asciidoctor-beautify-uri'
Require on the command line:
asciidoctor-pdf -r asciidoctor-beautify-uri document.adoc -a allow-uri-read
asciidoctor -r asciidoctor-beautify-uri document.adoc
|
Important
|
PDF thumbnails are always remote images (the provider’s own oEmbed CDN URL).
Asciidoctor hardcodes |
Node.js / npm (HTML5, Antora)
npm install @baiyibai-asciidoctor/beautify-uri
Add to the Antora site’s package.json dependencies, then reference in the playbook under asciidoc: — not under antora: (those are pipeline hooks, not Asciidoctor.js extensions):
asciidoc:
extensions:
- '@baiyibai-asciidoctor/beautify-uri'
|
Note
|
Asciidoctor.js’s block macro |
There is no PDF backend in the Asciidoctor.js/npm ecosystem — the npm package only covers HTML5.
Macro Syntax
uri::spotify:track/<id>[]
uri::youtube:watch?v=<id>[]
The target carries the provider name and that provider’s own path within it, after the block macro’s double colon. The macro accepts no attribute for supplying title, artist, or channel manually — all identity metadata comes from live resolution, every time.
| Attribute | Effect |
|---|---|
|
Optional, boolean, defaults to |
|
Optional. Assigns the card an id so |
|
Optional, boolean, defaults to |
Optional Spotify Web API credentials
Spotify’s oEmbed endpoint returns only a track’s title, with no artist field. Supplying your own Spotify Web API client-credentials pair adds the artist name via a live (still uncached) lookup:
:spotify-client-id: {SPOTIFY_CLIENT_ID}
:spotify-client-secret: {SPOTIFY_CLIENT_SECRET}
Or via environment variables, SPOTIFY_CLIENT_ID / SPOTIFY_CLIENT_SECRET — checked when the document attributes are absent.
Without credentials, resolution falls back to the free, unauthenticated oEmbed endpoint (title only).
Using this path binds you to Spotify’s own Developer Terms of Service; this project never holds or ships credentials of its own.
Cross-Referencing
uri::spotify:track/2takcwOaAZWiXQijPHIx7B[id=roygbiv-track]
See <<roygbiv-track>>.
A plain xref::/[id] with no explicit link text resolves to the generated reftext automatically, e.g. Spotify: Roygbiv - Boards of Canada — the provider name, unnumbered, followed by the resolved title (and subtitle, when there is one). This works whether or not caption=true is also set — suppressing (or never showing) the caption removes only the visible line beneath the card, never the underlying reftext.
Logging
The macro logs what it resolved (or why it didn’t) through the host tool’s own logger, not a bespoke attribute:
-
Ruby —
Asciidoctor::LoggerManager.logger, gated by Asciidoctor’s own verbosity (-v). -
Node/Antora — the Antora pipeline extension context’s logger when available, falling back to Asciidoctor.js’s own
LoggerManager, which Antora wires to its own--log-level.
Provider Architecture
A provider is defined by three responsibilities: a matcher (recognizes which URIs belong to it), a resolver (fetches metadata and maps it onto title/artist-or-channel/thumbnail), and a renderer (produces the card markup following that provider’s own brand rules).
Spotify and YouTube ship as the two built-in providers. A third party registers an additional provider without modifying this repository:
-
Ruby — call
Asciidoctor::BeautifyUri::ProviderRegistry.register(YourProvider)atrequiretime from your own gem, the way Ruby Asciidoctor extensions commonly self-register on load. -
Node — export a module with
key,displayName,linkText,iconBlock(), andresolve(path, doc, context), and callrequire('@baiyibai-asciidoctor/beautify-uri/lib/provider-registry').register(yourProvider).
A provider’s card template — templates/card.html, filled by simple {{TOKEN}} string substitution — is one language-agnostic file shared as-is between the Ruby gem and the npm package.
PDF has no HTML/CSS renderer to pass that template through to, so the PDF card is drawn directly with Prawn: lib/asciidoctor/beautify_uri/pdf_card.rb builds a plain :uri_card node carrying the already-resolved data (title, subtitle, thumbnail URL/dimensions, link text/target, icon path) as attributes, and lib/asciidoctor/beautify_uri/pdf_converter.rb’s `convert_uri_card lays out and inks the frame, thumbnail (clipped to a rounded rect), and title/subtitle/link text itself — the same side-by-side shape as the HTML card, but with every position chosen explicitly rather than delegated to `asciidoctor-pdf’s table converter. Genuinely Ruby-only since there is no PDF backend in the Asciidoctor.js ecosystem at all.
Each provider’s brand compliance is that provider’s own concern, verified against that provider’s published guidelines before its renderer is written — see templates/icons/ and DESIGN-asciidoctor-beautify-uri.adoc, "Card Rendering".
The PDF card’s spacing, border, corner radii, and font styles all read from a uri_card theme namespace with sensible defaults — see doc/modules/ROOT/pages/pdf-theming.adoc for the full list of keys.
Running the Tests
ruby test/run_html.rb
ruby test/run_pdf.rb # requires asciidoctor-pdf; see Gemfile
node js/test/run.js # requires @asciidoctor/core; npm install --prefix js
All three exercise the same fixture, test/fixtures/basic.adoc, and require live network access — resolution is never stubbed, by design.
Output PDFs are written to test/out/.
Future Ideas
Researched and confirmed as good future candidates for additional providers; none started.
itms(Apple Music, App Store)-
Free iTunes Lookup API for title, artist, and artwork. Needs its own renderer design, since Apple’s only sanctioned brand asset is a fixed generic badge, not a per-item card.
lastfm-
Comparable public API to Spotify’s, notably without a strict brand-badge requirement, useful precisely because its renderer would look structurally different from Spotify’s.
doi-
Crossref and DataCite both resolve a DOI to full bibliographic metadata for free. Flagged as the likely highest-value addition after Spotify and YouTube for the teaching-material use case, letting a document cite the musicological source behind a claim with the same id and reftext machinery as a recording.
steam-
Valve’s
appdetailsendpoint fits the same pattern. Explicitly deferred at the requester’s instruction; not to be designed further until asked for. market,swh-
Real but narrower fits, noted without further design work.
MusicBrainz and IMSLP were identified as the strongest conceptual fit for classical and historical-period teaching material specifically, though neither has an IANA-registered URI scheme — a distinction that does not appear to matter for this design, since YouTube itself has none either.
See DESIGN-asciidoctor-beautify-uri.adoc, "Provider Roadmap", for the full reasoning behind each.
Sources
This project bundles two provider brand marks as static assets under templates/icons/, traced from Simple Icons rather than hand-drawn approximations or a vendored copy of that library itself.
| Component | Upstream | License | Modifications |
|---|---|---|---|
Spotify mark |
https://simpleicons.org (Simple Icons) |
Icon: CC0 1.0. Spotify’s own trademark guidelines govern the mark’s use. |
Colored to Spotify’s own published brand green ( |
YouTube mark |
https://simpleicons.org (Simple Icons) |
Icon: CC0 1.0. YouTube’s/Google’s own trademark guidelines govern the mark’s use. |
Colored to YouTube red ( |
Everything else — the uri:: macro, provider resolvers, card renderers/converters, and test suite — is original to this project.