Docco
Docco is a Ruby gem that transforms your gem's README.md into a static HTML documentation website. It's designed to be simple, fast, and easy to integrate into your Ruby gem's workflow.
Features
- Converts GitHub-flavored Markdown to beautiful HTML documentation
- Automatic navigation sidebar generated from your README headings
- Syntax highlighting for code blocks (using highlight.js)
- Responsive design that works on all devices
- Active section highlighting as you scroll
- GitHub link integration from your gemspec
- GitHub Actions integration for automatic deployment to GitHub Pages
- Zero configuration required - works out of the box
- An ERB-based theming system that can also render multi-page websites, with a page per README section
Installation
Add Docco to your gem's Gemfile:
group :development do
gem 'docco', github: 'ismasan/docco'
endOr install it directly:
gem install doccoUsage
Basic Setup
Add the following to your gem's Rakefile:
require 'docco/tasks'That's it! Now you can generate documentation with:
bundle exec rake docco:docsThis will:
- Read your
README.md - Extract metadata from your
.gemspec - Generate a beautiful HTML website in the
docs/directory - Copy the necessary CSS styles
Programmatic Usage
You can also use Docco programmatically in your Ruby code:
require 'docco'
# Basic usage - auto-detects gemspec
builder = Docco::DocsBuilder.new(
readme_path: 'README.md',
output_dir: 'docs'
)
builder.build
# With custom gemspec path
builder = Docco::DocsBuilder.new(
readme_path: 'README.md',
output_dir: 'public/docs',
gemspec_path: 'my_gem.gemspec'
)
builder.buildAvailable Rake Tasks
Docco provides two rake tasks:
Generate Documentation
# Default: uses README.md and outputs to docs/
bundle exec rake docco:docs
# With custom paths
bundle exec rake docco:docs[path/to/README.md,output/dir,my_gem.gemspec]Generate GitHub Action
Automatically create a GitHub Action that builds and deploys your documentation to GitHub Pages:
bundle exec rake docco:ghThis creates .github/workflows/deploy-docs.yml with a pre-configured workflow that:
- Runs on push to main branch
- Builds your documentation
- Deploys to GitHub Pages
GitHub Actions Integration
After running rake docco:gh, you'll have a GitHub Action that automatically deploys documentation. To complete the setup:
- Go to your GitHub repository settings
- Navigate to Pages section
- Set source to "GitHub Actions"
Now every push to main will automatically rebuild and deploy your docs!
Example workflow (created by docco:gh):
name: Deploy Documentation
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
- run: bundle install
- run: bundle exec rake docco:docs
- uses: actions/upload-pages-artifact@v1
with:
path: docs
- uses: actions/deploy-pages@v1How It Works
Docco analyzes your README.md structure and creates a documentation website with:
-
Navigation Sidebar: Generated from level 2 and 3 headings (
##and###) in your README - Main Content: Your entire README rendered as HTML
- Page Header: Uses your gem name and summary from the gemspec
-
GitHub Link: Automatically extracted from your gemspec's
source_code_urior homepage
README Structure Requirements
For best results, structure your README like this:
# Gem Name
Brief description of your gem.
## Installation
Installation instructions...
## Usage
### Basic Usage
Example code...
### Advanced Usage
More examples...
## Configuration
Configuration options...
## Contributing
Contributing guidelines...- The first
#heading becomes the page title - Level 2 headings (
##) become main navigation items - Level 3 headings (
###) become sub-navigation items
Customization
Custom Styles
Building your docs writes the default stylesheet to docs/styles.css. Edit that file to match your branding — Docco never overwrites a file that already exists, so your changes survive subsequent builds. To start over from the default, delete docs/styles.css and build again.
The stylesheet uses CSS custom properties (variables) for easy theming:
:root {
--primary-color: #2563eb;
--bg-color: #f8fafc;
--text-color: #1e293b;
--code-block-bg: #282c34;
--code-block-text: #abb2bf;
/* ... and many more */
}--code-block-text is the fallback colour for code blocks. Syntax highlighting is applied by highlight.js at runtime, but blocks it can't highlight — an unrecognised language, or a reader with JavaScript disabled — fall back to this colour, so keep it legible against --code-block-bg.
Gemspec Metadata
Docco extracts information from your gemspec. Make sure these fields are set:
Gem::Specification.new do |spec|
spec.name = "my_gem"
spec.summary = "A short description"
spec.description = "A longer description"
spec.homepage = "https://github.com/username/my_gem"
# For the GitHub link, set source_code_uri
spec.metadata["source_code_uri"] = "https://github.com/username/my_gem"
endThemes
The default theme renders your entire README as a single page, but that's just one theme. Docco's templating system can also produce multi-page websites, where any section of your README becomes its own page with its own URL.
How Theming Works
Three pieces cooperate:
-
Docco.parseturns Markdown into a tree of sections (headings, nested by level) and content nodes (everything else). -
Docco::Builderwalks that tree with a theme and collects the result into aHash<path, content>— the whole website in memory. -
Docco::Writer(viaDocco.write) writes that hash to disk. Paths without a file extension getindex.htmlappended, so/usage/basicsbecomesdocs/usage/basics/index.html.
A theme is a class that inherits from Docco::Theme and responds to .call(node). Rendering starts at the root node, and templates create additional pages as they go.
Defining Templates
Docco::Theme.define compiles an ERB string into a template. It also accepts anything that responds to #read, such as a Pathname, which is handy for keeping templates (and CSS) in separate files:
require 'docco/theme'
class MyTheme < Docco::Theme
# From a string
Layout = define <<~HTML
<html>
<head><title><%= slots[:doc_title] || 'Home' %></title></head>
<body><%= slots[:main] %></body>
</html>
HTML
# From a file on disk
Styles = define(Pathname.new(File.join(__dir__, 'styles.css')))
endCalling #define on an existing template produces a new template that fills that layout's named slots. Pass a string to fill just the :main slot, or a block to fill several:
# Fills the :main slot
HomeTemplate = Layout.define <<~HTML
<h1><%= page.root.info.name %></h1>
<p><%= page.root.info.summary %></p>
HTML
# Fills multiple slots
PageTemplate = Layout.define do |tpl|
tpl.slot :doc_title, '<%= page.title %>'
tpl.slot :main, <<~HTML
<h1><%= page.title %></h1>
<% page.nodes.each do |node| %>
<%= node.to_html %>
<% end %>
HTML
endSlots are rendered first, then the layout, so slots[:main] in the layout holds already-rendered HTML.
Creating Pages With build
build is what makes multi-page themes possible. Inside a template, calling build on a node:
- renders the given template for that node,
- registers the result in the site under that node's path,
- and returns the path — so it goes straight into an
href.
<a href="<%= section.build(MyTheme::PageTemplate) %>"><%= section.title %></a>The path is derived from the node's position in the README tree (/my-gem/usage/basic-setup). Pass an explicit path as the first argument when you want to control it — this is also how static assets are emitted:
<link rel="stylesheet" href="<%= page.build('styles.css', MyTheme::Styles) %>">Pages are memoized by path, so templates can link to each other freely. A sidebar that links to every page, rendered on every page, terminates instead of recursing forever.
Note that stylesheets are themselves ERB templates, so they can interpolate values too.
Template API
Each template is evaluated with two locals: page (the node being rendered, also available as node) and slots.
Section nodes respond to:
| Method | Description |
|---|---|
title |
The heading's contents as inline HTML (e.g. This is the <code>title</code>) |
title_html |
The full rendered heading element (<h2 id="usage">Usage</h2>) |
id |
Kramdown's auto-generated anchor id, de-duplicated across the document |
level |
Heading level (1-6) |
nodes |
Child nodes, both sections and content |
sections |
Child nodes that are sections |
section? |
true for sections, false for content nodes |
to_html |
The section and all its descendants rendered as HTML |
to_path |
This node's path within the site |
build(template) / build(path, template)
|
Render a sub-page and return its path |
root |
The Builder, i.e. the site root |
info |
Gem metadata |
Content nodes (paragraphs, code blocks, lists, etc.) are minimal: to_html and section?.
The root node passed to .call is the Builder itself. It has nodes, sections, to_html, build, info and root (which returns itself), but no title or id — pull page titles from page.root.info at that level.
info is a Docco::Info with name, summary, description and repo_url, read from your gemspec.
A Multi-Page Theme
This theme puts every ## and ### section on its own page, with a shared navigation menu:
require 'docco/theme'
class MyTheme < Docco::Theme
Styles = define(Pathname.new(File.join(__dir__, 'styles.css')))
Layout = define <<~HTML
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="<%= page.build('styles.css', MyTheme::Styles) %>">
<title><%= slots[:doc_title] || 'Home' %> / <%= page.root.info.name %></title>
</head>
<body>
<nav>
<a href="/">Home</a>
<% page.root.sections.each do |top| %>
<ul>
<% top.sections.each do |section| %>
<li>
<a href="<%= section.build(MyTheme::PageTemplate) %>"><%= section.title %></a>
</li>
<% end %>
</ul>
<% end %>
</nav>
<main><%= slots[:main] %></main>
</body>
</html>
HTML
PageTemplate = Layout.define do |tpl|
tpl.slot :doc_title, '<%= page.title %>'
tpl.slot :main, <<~HTML
<h1><%= page.title %></h1>
<% page.nodes.each do |node| %>
<% if node.section? %>
<h2><a href="<%= node.build(MyTheme::PageTemplate) %>"><%= node.title %></a></h2>
<% else %>
<%= node.to_html %>
<% end %>
<% end %>
HTML
end
HomeTemplate = Layout.define <<~HTML
<h1><%= page.root.info.name %></h1>
<p><%= page.root.info.summary %></p>
HTML
# Entry point. Rendering starts here, with the site root.
def self.call(node) = HomeTemplate.call(node)
endPageTemplate links to its own subsections using itself, so the site nests as deeply as your headings do.
Rendering a Site With a Custom Theme
rake docco:docs and Docco::DocsBuilder always use Docco::Themes::Default. To use your own theme, drive Builder and Writer directly:
require 'docco'
require_relative 'my_theme'
root = Docco.parse(File.read('README.md'))
info = Docco::Info.new(
name: 'my_gem',
summary: 'A short description',
description: 'A longer description',
repo_url: 'https://github.com/username/my_gem'
)
builder = Docco::Builder.new(nodes: root.nodes, info:)
builder.visit(MyTheme)
# builder.pages is now a Hash<path, content> holding the entire site:
# { '' => '<html>...', 'styles.css' => 'body { ... }',
# '/my-gem/usage' => '<html>...', ... }
Docco.write(builder.pages, output_dir: 'docs', overwrite: true)overwrite defaults to false, which leaves existing files untouched — useful when you hand-edit a generated stylesheet and don't want it clobbered. Pass overwrite: true to regenerate everything.
Because builder.pages is a plain hash, writing to disk is optional. You can serve it straight from memory from a Rack app, or post-process it before writing.
Extending the Default Theme
Docco::Themes::Default is built from the same primitives, and its templates are public constants (Layout, Menu, Section, HomePageTemplate, Styles). Reading lib/docco/themes/default.rb is the quickest way to see a complete theme, and you can reuse individual templates from your own:
<%= Docco::Themes::Default::Menu.(page) %>Example Output
Check out Docco's own documentation (built with Docco, of course!): https://ismasan.github.io/docco
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install.
Testing Your Changes
Generate documentation for Docco itself:
bundle exec rake docco:docsThen open docs/index.html in your browser to see the results.
Requirements
- Ruby >= 3.2.0
- A README.md file
- (Optional) A .gemspec file for metadata
Dependencies
-
kramdown- Markdown parsing -
kramdown-parser-gfm- GitHub-flavored Markdown support
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/ismasan/docco.
License
The gem is available as open source under the terms of the MIT License.
Credits
Created by Ismael Celis