shortcode

High-performance, CommonMark AST-safe positional shortcode tokenizer and recursive Markdown expander for Crystal.
Shortcode Logo

CommonMark AST-Safe Positional Shortcode Tokenizer & Recursive Markdown Expander for Crystal

CI Status Crystal Version License: GPL-3.0 SHA-256 Repository Ameba Verified Liberapay Support

Donate using Liberapay


Overview

shortcode enables Hugo-style shortcode tags (such as {{< badge text="Beta" />}} and {{< alert type="info" >}}Inner Markdown{{< /alert >}}) within Markdown content pipelines without corrupting the underlying CommonMark AST hierarchy.

Most naive shortcode implementations use regular expressions directly on rendered HTML or raw Markdown, breaking paragraph wrapping, corrupting code blocks, or mangling HTML attributes. shortcode solves this via a two-pass AST preservation engine.


How It Works (Two-Pass AST Architecture)

 Raw Source (with {{< ... >}})
               │
               ▼
   [ 1. Shortcode::Engine.tokenize ] ──► Extracts shortcodes & masks code blocks
               │                         Replaces with deterministic HTML comments:
               │                         <!--SHORTCODE:INLINE:hash--> & <!--SHORTCODE:BLOCK:hash-->
               ▼
   [ 2. CommonMark Engine (e.g. Markd) ] ──► Compiles Markdown to HTML unharmed
               │
               ▼
   [ 3. Shortcode::Engine.expand ] ────► Recursively evaluates inner Markdown bodies
               │                         and expands placeholders into final HTML
               ▼
          Final HTML
  1. Tokenization Pass: Before Markdown compilation, raw shortcodes outside code fences and inline backticks are replaced with deterministic HTML comment placeholders (<!--SHORTCODE:INLINE:hash--> and <!--SHORTCODE:BLOCK:hash-->). Code blocks and tilde fences are strictly masked to prevent accidental evaluation of code examples.
  2. CommonMark AST Pass: The Markdown parser compiles the document into HTML, passing HTML comment placeholders through unharmed without creating unwanted <p> wrapper corruptions.
  3. Expansion Pass: Placeholders are recursively substituted with rendered HTML structures. Inner Markdown bodies inside block shortcodes are compiled via a consumer-provided MarkdownCompiler interface.

Installation

Add shortcode to your shard.yml:

dependencies:
  shortcode:
    gitlab: renich/shortcode
    version: ~> 0.1.0

Run shards install.


Quick Start

require "shortcode"
require "markd"

# 1. Define your Markdown compiler adapter
class MarkdCompiler < Shortcode::MarkdownCompiler
  def compile_to_html(markdown : String) : String
    Markd.to_html(markdown)
  end
end

compiler = MarkdCompiler.new

source = <<-MD
# Modern Architecture

Here is an inline tag: {{< badge text="v1.0" color="green" />}}

{{< alert type="warning" >}}
**Warning**: This block contains *markdown* and {{< badge text="Nested" />}}!
{{< /alert >}}
MD

# 2. Tokenize shortcodes before Markdown compilation
tokenized_markdown, shortcodes = Shortcode::Engine.tokenize(source)

# 3. Compile to HTML via standard Markdown engine
compiled_html = compiler.compile_to_html(tokenized_markdown)

# 4. Expand shortcode placeholders recursively
final_html = Shortcode::Engine.expand(compiled_html, shortcodes, compiler)

puts final_html

Custom Renderers

Provide custom handlers for project-specific shortcodes:

expanded = Shortcode::Engine.expand(compiled_html, shortcodes, compiler) do |inv, inner_html|
  case inv.name
  when "youtube"
    video_id = inv.params["id"]? || "dQw4w9WgXcQ"
    %(<div class="video-wrapper"><iframe src="https://www.youtube.com/embed/#{video_id}" allowfullscreen></iframe></div>)
  when "badge"
    %(<span class="badge badge-#{inv.params["color"]? || "info"}">#{inv.params["text"]?}</span>)
  else
    Shortcode::Engine.render_shortcode(inv, inner_html)
  end
end

API Reference

Shortcode::Engine

Method Description
tokenize(source : String, ...) Scans source, masks code blocks, extracts shortcodes, and injects comment tokens
expand(html, shortcodes, compiler, &block) Recursively substitutes comment tokens with rendered HTML components
render_shortcode(inv, inner_html) Default semantic HTML fallback renderer for built-in shortcodes

Development & Testing

make spec
make lint
make format

Support & Donation

If you find shortcode or other tools in this ecosystem valuable, consider supporting continued FOSS development:

Donate using Liberapay


License

GNU General Public License v3.0 or later (GPL-3.0-or-later).

Copyright © 2026 Rénich Bon Ćirić and Contributors.

Repository

shortcode

Owner
Statistic
  • 0
  • 0
  • 0
  • 1
  • 1
  • about 3 hours ago
  • August 26, 2026
License

GNU General Public License v3.0 only

Links
Synced at

Wed, 26 Aug 2026 04:03:59 GMT

Languages