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

Spotify icon
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 an xref:: target, with a generated reftext like Spotify: 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 allow-uri-read as API-only — the document itself can never grant it — so pass -a allow-uri-read (CLI) or attributes: {'allow-uri-read' ⇒ ''} (API) yourself, or PDF cards render without artwork.

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 process() runs synchronously inside Opal’s conversion pipeline — there is no supported way to await a fetch() mid-conversion. Rather than require an Antora-only async pre-pass, the npm package shells out to curl synchronously. curl must be on PATH wherever the site is built.

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

thumbnail=

Optional, boolean, defaults to true. Setting it to false (or a provider simply not resolving a thumbnail) fills that column with the provider’s own logo at full size instead of leaving it empty, and drops the smaller icon that would otherwise sit beside the link text — showing the same mark twice would be redundant.

id=

Optional. Assigns the card an id so xref:: elsewhere in the document may target it.

caption=

Optional, boolean, defaults to false. The card already shows its own title, so a caption line repeating it beneath the card is opt-in. The reftext an xref:: resolves against is generated regardless of this flag.

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:

  • RubyAsciidoctor::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) at require time from your own gem, the way Ruby Asciidoctor extensions commonly self-register on load.

  • Node — export a module with key, displayName, linkText, iconBlock(), and resolve(path, doc, context), and call require('@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 appdetails endpoint 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 (#1DB954); no contrast badge added, per Spotify’s own guidelines requiring none against black or white. Thumbnail corner radius (4px) is likewise Spotify’s own mandated figure, not this project’s choice — see DESIGN-asciidoctor-beautify-uri.adoc.

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 (#FF0000); viewBox cropped to the mark’s own ink bounds (the official artwork only fills about 70% of its declared square) so sizing code that scales the declared box to a target height doesn’t undersize the visible mark.

Everything else — the uri:: macro, provider resolvers, card renderers/converters, and test suite — is original to this project.

Licence

MIT. See LICENSE. Upstream components retain their own licenses as recorded in Sources.