From bb32ab6d2752c072969d3078cc6364a34cf59694 Mon Sep 17 00:00:00 2001 From: Ryan Tomayko Date: Thu, 11 Mar 2010 03:25:35 -0800 Subject: [PATCH] rebuild pages for 0.2 release --- index.html | 345 ++++++++++++++++++++++++++++++++++++++++++++++++++++ layout.html | 56 +++++++++ rocco.html | 345 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tasks.html | 219 +++++++++++++++++++++++++++++++++ 4 files changed, 965 insertions(+) create mode 100644 index.html create mode 100644 layout.html create mode 100644 rocco.html create mode 100644 tasks.html diff --git a/index.html b/index.html new file mode 100644 index 0000000..ad58300 --- /dev/null +++ b/index.html @@ -0,0 +1,345 @@ + + + + + rocco.rb + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

rocco.rb

+
+ # +
+

Rocco is a Ruby port of Docco, the quick-and-dirty, +hundred-line-long, literate-programming-style documentation generator.

+ +

Rocco reads Ruby source files and produces annotated source documentation +in HTML format. Comments are formatted with Markdown and presented +alongside syntax highlighted code so as to give an annotation effect. +This page is the result of running Rocco against its own source file.

+ +

Most of this was written while waiting for node.js to build (so I +could use Docco!). Docco’s gorgeous HTML and CSS are taken verbatim. +The main difference is that Rocco is written in Ruby instead of +CoffeeScript and may be a bit easier to obtain and install in +existing Ruby environments or where node doesn’t run yet.

+ +

Install Rocco with Rubygems:

+ +
gem install rocco
+
+ +

Once installed, the rocco command can be used to generate documentation +for a set of Ruby source files:

+ +
rocco lib/*.rb
+
+ +

The HTML files are written to the current working directory.

+
+
+
+
+ # +
+

Prerequisites

+
+
+
+
+ # +
+

We’ll need a Markdown library. RDiscount, if we’re lucky. Otherwise, +issue a warning and fall back on using BlueCloth.

+
+
begin
+  require 'rdiscount'
+rescue LoadError => boom
+  warn "warn: #{boom}. trying bluecloth"
+  require 'bluecloth'
+  Markdown = BlueCloth
+end
+
+
+ # +
+

We use {{ mustache }} for +HTML templating.

+
+
require 'mustache'
+
+
+ # +
+

Code is run through Pygments for syntax +highlighting. Fail fast right here if we can’t find the pygmentize +program on PATH.

+
+
if ! ENV['PATH'].split(':').any? { |dir| File.exist?("#{dir}/pygmentize") }
+  fail "Pygments is required for syntax highlighting"
+end
+
+
+ # +
+

Public Interface

+
+
+
+
+ # +
+

Rocco.new takes a source filename and an optional block. +When block is given, it must read the contents of the file using +whatever means necessary and return it as a string. With no block, the +file is read to retrieve data.

+
+
class Rocco
+  VERSION = '0.2'
+
+  def initialize(filename, &block)
+    @file = filename
+    @data =
+      if block_given?
+        yield
+      else
+        File.read(filename)
+      end
+    @sections = highlight(split(parse(@data)))
+  end
+
+
+ # +
+

The filename as given to Rocco.new.

+
+
  attr_reader :file
+
+
+ # +
+

A list of two-tuples representing each section of the source file. Each +item in the list has the form: [docs_html, code_html], where both +elements are strings containing the documentation and source code HTML, +respectively.

+
+
  attr_reader :sections
+
+
+ # +
+

Generate HTML output for the entire document.

+
+
  require 'rocco/layout'
+  def to_html
+    Rocco::Layout.new(self).render
+  end
+
+
+ # +
+

Internal Parsing and Highlighting

+
+
+
+
+ # +
+

Parse the raw file data into a list of two-tuples. Each tuple has the +form [docs, code] where both elements are arrays containing the +raw lines parsed from the input file.

+
+
  def parse(data)
+    sections = []
+    docs, code = [], []
+    data.split("\n").each do |line|
+      case line
+      when /^\s*#/
+        if code.any?
+          sections << [docs, code]
+          docs, code = [], []
+        end
+        docs << line
+      else
+        code << line
+      end
+    end
+    sections << [docs, code] if docs.any? || code.any?
+    sections
+  end
+
+
+ # +
+

Take the list of paired sections two-tuples and split into two +separate lists: one holding the comments with leaders removed and +one with the code blocks.

+
+
  def split(sections)
+    docs_blocks, code_blocks = [], []
+    sections.each do |docs,code|
+      docs_blocks << docs.map { |line| line.sub(/^\s*#\s?/, '') }.join("\n")
+      code_blocks << code.join("\n")
+    end
+    [docs_blocks, code_blocks]
+  end
+
+
+ # +
+

Take the result of split and apply Markdown formatting to comments and +syntax highlighting to source code.

+
+
  def highlight(blocks)
+    docs_blocks, code_blocks = blocks
+
+
+ # +
+

Combine all docs blocks into a single big markdown document with section +dividers and run through the Markdown processor. Then split it back out +into separate sections.

+
+
    markdown = docs_blocks.join("\n\n##### DIVIDER\n\n")
+    docs_html = Markdown.new(markdown, :smart).
+      to_html.
+      split(/\n*<h5>DIVIDER<\/h5>\n*/m)
+
+
+ # +
+

Combine all code blocks into a single big stream and run through +Pygments. We popen a read/write pygmentize process in the parent and +then fork off a child process to write the input.

+
+
    code_html = nil
+    open("|pygmentize -l ruby -f html", 'r+') do |fd|
+      pid =
+        fork {
+          fd.close_read
+          fd.write code_blocks.join("\n\n# DIVIDER\n\n")
+          fd.close_write
+          exit!
+        }
+      fd.close_write
+      code_html = fd.read
+      fd.close_read
+      Process.wait(pid)
+    end
+
+
+ # +
+

Do some post-processing on the pygments output to split things back +into sections and remove partial <pre> blocks.

+
+
    code_html = code_html.
+      split(/\n*<span class="c1"># DIVIDER<\/span>\n*/m).
+      map { |code| code.sub(/\n?<div class="highlight"><pre>/m, '') }.
+      map { |code| code.sub(/\n?<\/pre><\/div>\n/m, '') }
+
+
+ # +
+

Lastly, combine the docs and code lists back into a list of two-tuples.

+
+
    docs_html.zip(code_html)
+  end
+end
+
+
+ # +
+

And that’s it.

+ +
+
+
+
+ diff --git a/layout.html b/layout.html new file mode 100644 index 0000000..e2de612 --- /dev/null +++ b/layout.html @@ -0,0 +1,56 @@ + + + + + layout.rb + + + +
+
+ + + + + + + + + + + + +

layout.rb

+
+ # +
+ + +
+
require 'mustache'
+
+class Rocco::Layout < Mustache
+  self.template_path = File.dirname(__FILE__)
+
+  def initialize(doc)
+    @doc = doc
+  end
+
+  def title
+    File.basename(@doc.file)
+  end
+
+  def sections
+    num = 0
+    @doc.sections.map do |docs,code|
+      {
+        :docs  => docs,
+        :code  => code,
+        :num   => (num += 1)
+      }
+    end
+  end
+end
+
+
+ diff --git a/rocco.html b/rocco.html new file mode 100644 index 0000000..ad58300 --- /dev/null +++ b/rocco.html @@ -0,0 +1,345 @@ + + + + + rocco.rb + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

rocco.rb

+
+ # +
+

Rocco is a Ruby port of Docco, the quick-and-dirty, +hundred-line-long, literate-programming-style documentation generator.

+ +

Rocco reads Ruby source files and produces annotated source documentation +in HTML format. Comments are formatted with Markdown and presented +alongside syntax highlighted code so as to give an annotation effect. +This page is the result of running Rocco against its own source file.

+ +

Most of this was written while waiting for node.js to build (so I +could use Docco!). Docco’s gorgeous HTML and CSS are taken verbatim. +The main difference is that Rocco is written in Ruby instead of +CoffeeScript and may be a bit easier to obtain and install in +existing Ruby environments or where node doesn’t run yet.

+ +

Install Rocco with Rubygems:

+ +
gem install rocco
+
+ +

Once installed, the rocco command can be used to generate documentation +for a set of Ruby source files:

+ +
rocco lib/*.rb
+
+ +

The HTML files are written to the current working directory.

+
+
+
+
+ # +
+

Prerequisites

+
+
+
+
+ # +
+

We’ll need a Markdown library. RDiscount, if we’re lucky. Otherwise, +issue a warning and fall back on using BlueCloth.

+
+
begin
+  require 'rdiscount'
+rescue LoadError => boom
+  warn "warn: #{boom}. trying bluecloth"
+  require 'bluecloth'
+  Markdown = BlueCloth
+end
+
+
+ # +
+

We use {{ mustache }} for +HTML templating.

+
+
require 'mustache'
+
+
+ # +
+

Code is run through Pygments for syntax +highlighting. Fail fast right here if we can’t find the pygmentize +program on PATH.

+
+
if ! ENV['PATH'].split(':').any? { |dir| File.exist?("#{dir}/pygmentize") }
+  fail "Pygments is required for syntax highlighting"
+end
+
+
+ # +
+

Public Interface

+
+
+
+
+ # +
+

Rocco.new takes a source filename and an optional block. +When block is given, it must read the contents of the file using +whatever means necessary and return it as a string. With no block, the +file is read to retrieve data.

+
+
class Rocco
+  VERSION = '0.2'
+
+  def initialize(filename, &block)
+    @file = filename
+    @data =
+      if block_given?
+        yield
+      else
+        File.read(filename)
+      end
+    @sections = highlight(split(parse(@data)))
+  end
+
+
+ # +
+

The filename as given to Rocco.new.

+
+
  attr_reader :file
+
+
+ # +
+

A list of two-tuples representing each section of the source file. Each +item in the list has the form: [docs_html, code_html], where both +elements are strings containing the documentation and source code HTML, +respectively.

+
+
  attr_reader :sections
+
+
+ # +
+

Generate HTML output for the entire document.

+
+
  require 'rocco/layout'
+  def to_html
+    Rocco::Layout.new(self).render
+  end
+
+
+ # +
+

Internal Parsing and Highlighting

+
+
+
+
+ # +
+

Parse the raw file data into a list of two-tuples. Each tuple has the +form [docs, code] where both elements are arrays containing the +raw lines parsed from the input file.

+
+
  def parse(data)
+    sections = []
+    docs, code = [], []
+    data.split("\n").each do |line|
+      case line
+      when /^\s*#/
+        if code.any?
+          sections << [docs, code]
+          docs, code = [], []
+        end
+        docs << line
+      else
+        code << line
+      end
+    end
+    sections << [docs, code] if docs.any? || code.any?
+    sections
+  end
+
+
+ # +
+

Take the list of paired sections two-tuples and split into two +separate lists: one holding the comments with leaders removed and +one with the code blocks.

+
+
  def split(sections)
+    docs_blocks, code_blocks = [], []
+    sections.each do |docs,code|
+      docs_blocks << docs.map { |line| line.sub(/^\s*#\s?/, '') }.join("\n")
+      code_blocks << code.join("\n")
+    end
+    [docs_blocks, code_blocks]
+  end
+
+
+ # +
+

Take the result of split and apply Markdown formatting to comments and +syntax highlighting to source code.

+
+
  def highlight(blocks)
+    docs_blocks, code_blocks = blocks
+
+
+ # +
+

Combine all docs blocks into a single big markdown document with section +dividers and run through the Markdown processor. Then split it back out +into separate sections.

+
+
    markdown = docs_blocks.join("\n\n##### DIVIDER\n\n")
+    docs_html = Markdown.new(markdown, :smart).
+      to_html.
+      split(/\n*<h5>DIVIDER<\/h5>\n*/m)
+
+
+ # +
+

Combine all code blocks into a single big stream and run through +Pygments. We popen a read/write pygmentize process in the parent and +then fork off a child process to write the input.

+
+
    code_html = nil
+    open("|pygmentize -l ruby -f html", 'r+') do |fd|
+      pid =
+        fork {
+          fd.close_read
+          fd.write code_blocks.join("\n\n# DIVIDER\n\n")
+          fd.close_write
+          exit!
+        }
+      fd.close_write
+      code_html = fd.read
+      fd.close_read
+      Process.wait(pid)
+    end
+
+
+ # +
+

Do some post-processing on the pygments output to split things back +into sections and remove partial <pre> blocks.

+
+
    code_html = code_html.
+      split(/\n*<span class="c1"># DIVIDER<\/span>\n*/m).
+      map { |code| code.sub(/\n?<div class="highlight"><pre>/m, '') }.
+      map { |code| code.sub(/\n?<\/pre><\/div>\n/m, '') }
+
+
+ # +
+

Lastly, combine the docs and code lists back into a list of two-tuples.

+
+
    docs_html.zip(code_html)
+  end
+end
+
+
+ # +
+

And that’s it.

+ +
+
+
+
+ diff --git a/tasks.html b/tasks.html new file mode 100644 index 0000000..9165939 --- /dev/null +++ b/tasks.html @@ -0,0 +1,219 @@ + + + + + tasks.rb + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

tasks.rb

+
+ # +
+

Rocco Rake Tasks

+ +

To use the Rocco Rake tasks, require rocco/tasks in your Rakefile +and define a Rake task with rocco_task. In its simplest form, rocco_task +takes the path to a destination directory where HTML docs should be built:

+ +
require 'rocco/tasks'
+
+desc "Build Rocco Docs"
+Rocco::make 'docs/'
+
+ +

This creates a :rocco rake task, which can then be run with:

+ +
rake rocco
+
+ +

It’s a good idea to guard against Rocco not being available, since your +Rakefile will fail to load otherwise. Consider doing something like this, +so that your Rakefile will still work

+ +
begin
+  require 'rocco/tasks'
+  Rocco::make 'docs/'
+rescue LoadError
+  warn "#$! -- rocco tasks not loaded."
+  task :rocco
+end
+
+ +

It’s also possible to pass a glob pattern:

+ +
Rocco::make 'html/', 'lib/thing/**/*.rb'
+
+ +

Or a list of glob patterns:

+ +
Rocco::make 'html/', ['lib/thing.rb', 'lib/thing/*.rb']
+
+
+
+
+
+ # +
+

Might be nice to defer this until we actually need to build docs but this +will have to do for now.

+
+
require 'rocco'
+
+
+ # +
+

Reopen the Rocco class and add a make class method. This is a simple bit +of sugar over Rocco::Task.new. If you want your Rake task to be named +something other than :rocco, you can use Rocco::Task directly.

+
+
class Rocco
+  def self.make(dest='docs/', source_files='lib/**/*.rb')
+    Task.new(:rocco, dest, source_files)
+  end
+
+
+ # +
+

Rocco::Task.new takes a task name, the destination directory docs +should be built under, and a source file pattern or file list.

+
+
  class Task
+    def initialize(task_name, dest='docs/', sources='lib/**/*.rb')
+      @name = task_name
+      @dest = dest[-1] == ?/ ? dest : "#{dest}/"
+      @sources = FileList[sources]
+
+
+ # +
+

Make sure there’s a directory task defined for our destination.

+
+
      define_directory_task @dest
+
+
+ # +
+

Run over the source file list, constructing destination filenames +and defining file tasks.

+
+
      @sources.each do |source_file|
+        dest_file = File.basename(source_file, '.rb') + '.html'
+        define_file_task source_file, "#{@dest}#{dest_file}"
+
+
+ # +
+

If rake/clean was required, add the generated files to the list. +That way all Rocco generated are removed when running rake clean.

+
+
        CLEAN.include "#{@dest}#{dest_file}" if defined? CLEAN
+      end
+    end
+
+
+ # +
+

Define the destination directory task and make the :rocco task depend +on it. This causes the destination directory to be created if it doesn’t +already exist.

+
+
    def define_directory_task(path)
+      directory path
+      task @name => path
+    end
+
+
+ # +
+

Setup a file task for a single Rocco output file (dest_file). It +depends on the source file, the destination directory, and all of Rocco’s +internal source code, so that the destination file is rebuilt when any of +those changes.

+ +

You can run these tasks directly with Rake:

+ +
rake docs/foo.html docs/bar.html
+
+ +

… would generate the foo.html and bar.html files but only if they +don’t already exist or one of their dependencies was changed.

+
+
    def define_file_task(source_file, dest_file)
+      prerequisites = [@dest, source_file] + rocco_source_files
+      file dest_file => prerequisites do |f|
+        verbose { puts "rocco: #{source_file} -> #{dest_file}" }
+        rocco = Rocco.new(source_file)
+        File.open(dest_file, 'wb') { |fd| fd.write(rocco.to_html) }
+      end
+      task @name => dest_file
+    end
+
+
+ # +
+

Return a FileList that includes all of Roccos source files. This causes +output files to be regenerated properly when someone upgrades the Rocco +library.

+ +
+
    def rocco_source_files
+      libdir = File.expand_path('../..', __FILE__)
+      FileList["#{libdir}/rocco.rb", "#{libdir}/rocco/**"]
+    end
+
+  end
+end
+
+
+