Markdown Composer
Markdown Composer is a headless Ruby gem for selecting, composing, validating, and transforming Markdown-first document fragments.
It is framework-independent. It does not depend on Rails, an ORM, an authorization system, a template engine, a component framework, or a UI. Host applications pass plain Markdown/HTML sources into the gem and own source lookup, permissions, sanitization, asset handling, and presentation.
Requirements
- Ruby
>= 3.1 commonmarker >= 0.23.10, < 3nokogiri >= 1.13.10, < 2
Development dependencies are limited to test and benchmark tooling.
Installation
From RubyGems:
# Gemfile
gem "markdown_composer"
Then run:
bundle install
For a standalone Ruby script:
gem install markdown_composer
From a Git repository before a RubyGems release:
# Gemfile
gem "markdown_composer", git: "https://github.com/SocIt2Em/markdown_composer.git"
If installing from source before a RubyGems release, point Bundler at the standalone repository:
# Gemfile
gem "markdown_composer", git: "https://github.com/SocIt2Em/markdown_composer.git"
Quick Start
require "markdown_composer"
sources = MarkdownComposer.source_list do
current File.read("source_current.md")
explicit :guide, File.read("guide.md")
end
plan = MarkdownComposer.plan do
from :current
select "h2_section[first:1]"
include "all"
set
from({ type: "explicit", key: "guide" })
select "h2_section"
include "all"
append
end
result = MarkdownComposer.compose(sources: sources, plan: plan)
puts result.markdown
MarkdownComposer.compose always receives source content through the sources: keyword. Plan-row inline sources are the exception: they embed small source content directly in the plan.
Sources
Runtime sources are plain hashes or values produced by MarkdownComposer.source_list.
sources = MarkdownComposer.source_list do
current current_markdown
explicit :guide, guide_markdown
explicit :faq, faq_markdown
inherited :site_intro, inherited_markdown
end
This returns the same shape as:
[
{ "key" => "current", "type" => "current", "markdown" => current_markdown },
{ "key" => "guide", "type" => "explicit", "markdown" => guide_markdown },
{ "key" => "faq", "type" => "explicit", "markdown" => faq_markdown },
{ "key" => "site_intro", "type" => "inherited", "markdown" => inherited_markdown }
]
Supported runtime source types:
| Type | Purpose |
|---|---|
current |
Main document supplied by the caller. |
explicit |
Named source selected by key. |
inherited |
Named source resolved by the host application. |
Plan-only source references such as previous, buffer, and inline are not runtime source records.
Plans
Plans can be authored with the Ruby DSL:
plan = MarkdownComposer.plan do
output :markdown
from :current
select 'heading_2_section where none(title:equals("Table of Contents"))'
include "heading_title"
set
from :buffer
select 'heading_2 where text:starts_with("1.1")'
remove_buffer_target
end
Structured config is also supported for storage, import, and export:
config = {
"output" => "markdown",
"compose" => [
{
"source" => "current",
"select" => "h2_section[first:1]",
"include" => "all",
"action" => "set"
}
]
}
result = MarkdownComposer.compose(sources: sources, config: config)
YAML and JSON helpers are available:
plan = MarkdownComposer.parse_yaml(yaml)
yaml = MarkdownComposer.to_yaml(plan)
plan = MarkdownComposer.parse_json(json)
json = MarkdownComposer.to_json(plan)
Row-style hashes are accepted for spreadsheet or GUI workflows:
plan = MarkdownComposer.parse_rows([
{ "Source" => "current", "Select" => "h2_section[first:1]", "Include" => "all", "Action" => "set" }
])
Public API
Main entry points:
MarkdownComposer.compose(sources:, config: nil, plan: nil, options: {})
MarkdownComposer.validate(config:, sources: [], options: {})
MarkdownComposer.normalise(config_or_rows)
MarkdownComposer.normalize(config_or_rows)
MarkdownComposer.capabilities(options: {})
MarkdownComposer.plan(&block)
MarkdownComposer.source_list(&block)
MarkdownComposer.parse_yaml(yaml_string)
MarkdownComposer.parse_json(json_string)
MarkdownComposer.parse_rows(row_hashes)
MarkdownComposer.to_yaml(plan)
MarkdownComposer.to_json(plan)
MarkdownComposer.to_rows(plan)
validate returns valid, the normalized plan, diagnostics, and errors. capabilities returns JSON-compatible metadata for building host UI controls.
Actions And Transforms
Compose actions place selected content into the composition buffer:
set,append,prependinsert_before,insert_after,insert_betweenreplace,copy,movemodifyremove_buffer_target,transform_buffer_target
Transforms run after composition or inside modify/transform_buffer_target rows. Supported transform families include heading numbering, heading levels, text replacement, link handling, content insertion/removal, dedupe, ordering, sanitise, and adapter-owned transforms.
Use MarkdownComposer.capabilities as the source of truth for enabled actions, tokens, transform modes, required options, aliases, support status, and policy gates.
Framework Integration
Markdown Composer does not ship Rails, ViewComponent, React, Vue, or other framework-specific integrations.
Host applications are responsible for:
- resolving documents into plain Markdown/HTML source hashes;
- enforcing authorization, tenant, visibility, and workflow rules;
- preparing source Markdown when templates or variables are involved;
- sanitizing output and resolving assets;
- rendering previews and mapping diagnostics into host UI.
Rails, Hotwire, React, Vue, CLIs, and scripts should wrap the same headless API.
Validation And Diagnostics
Validation covers portable Composer configuration: source references, selectors, include rules, targets, actions, transforms, output formats, diagnostics, and policy-gated features.
Example diagnostic:
{
code: "transform.option_missing",
message: "Missing required option from",
path: "transform[0].options.from",
severity: :error,
details: nil
}
Current diagnostic families include:
action.*json.*output.*source.*take.*target.*token.*transform.*where.*yaml.*
Format Support
Markdown is the source-fidelity format. HTML input is accepted through a best-effort HTML-to-Markdown path and may lose fidelity for structures that do not map cleanly to Markdown.
| From | To | Status | Notes |
|---|---|---|---|
| Markdown | Markdown | Supported | Source-fidelity selection and transforms. |
| Markdown | HTML | Supported | Rendered via CommonMarker when available, fallback renderer otherwise. |
| HTML | Markdown | Best effort | Uses Nokogiri-backed conversion. |
| HTML | HTML | Best effort | Converts through Markdown, then renders back to HTML. |
Host-specific sanitization, custom attributes, and asset policies should live outside the gem.
Development
Run the test suite:
bundle exec rake test
Build the gem package:
gem build markdown_composer.gemspec
The release test suite covers public helper APIs, diagnostics, transform capability coverage, Markdown/HTML conversion fixtures, syntax checks, and coupling guards.
License
Markdown Composer is released under the MIT License. See LICENSE.txt.