diff --git a/lib/rocco.rb b/lib/rocco.rb index 7bd1ca0..929320a 100644 --- a/lib/rocco.rb +++ b/lib/rocco.rb @@ -1,18 +1,45 @@ -# Rocco -# ===== +# **Rocco** is a Ruby port of [Docco][do], the quick-and-dirty, +# hundred-line-long, literate-programming-style documentation generator. # -# Rocco is a quick-and-dirty literate-programming-style documentation -# generator based *heavily* on [Docco](http://jashkenas.github.com/docco/). +# Rocco reads Ruby source files and produces annotated source documentation +# in HTML format. Comments are formatted with [Markdown][md] 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][no] 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][co] and may be a bit easier to obtain and install in +# existing Ruby environments or where node doesn't run yet. +# +# Rocco can be installed 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. +# +# [no]: http://nodejs.org/ +# [do]: http://jashkenas.github.com/docco/ +# [co]: http://coffeescript.org/ +# [md]: http://daringfireball.net/projects/markdown/ +#### Prerequisites # The RDiscount library is required for Markdown processing. require 'rdiscount' +#### 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 - # `Rocco.new` takes a source `filename` and an optional `block` used to - # read the file's contents. 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. def initialize(filename, &block) @file = filename @data = @@ -21,103 +48,93 @@ class Rocco else File.read(filename) end - - # Jump right into the parsing and highlighting phase. + # Jump right into parsing and highlighting. @sections = highlight(parse(@data)) end - # The source filename. + # 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]` and represents a - # single section. - # - # Both `docs_html` and `code_html` are strings containing the - # documentation and source code HTML, respectively. + # 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 - # Internal Parsing and Highlighting - # --------------------------------- - protected - - # Parse the raw file data into a list of two-tuples. - 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 - when /^\s*$/ - if code.any? - code << line - else - docs << line - end - else - code << line - end - end - sections << [docs, code] if docs.any? || code.any? - sections - end - - # Take the raw section data and apply markdown formatting and syntax - # highlighting. - def highlight(sections) - # Start by splitting the docs and codes blocks into two separate lists. - 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 - - # Combine all docs blocks into a single big markdown document and run - # through RDiscount. Then split it back out into separate sections. - markdown = docs_blocks.join("\n##### DIVIDER\n") - docs_html = Markdown.new(markdown, :smart). - to_html. - split("\n
DIVIDER
\n") - - # Combine all code blocks into a single big stream and run through - # pygments. We `popen` a pygmentize process and then fork off a - # writer process. - code_html = nil - open("|pygmentize -l ruby -f html", 'r+') do |fd| - fork { - fd.close_read - fd.write code_blocks.join("\n# DIVIDER\n") - fd.close_write - exit! - } - - fd.close_write - code_html = fd.read - fd.close_read - end - - # Do some post-processing on the pygments output to remove - # partial `
` blocks. We'll add these back when we build to main
-      # document.
-      code_html = code_html.
-        split(/\n*# DIVIDER<\/span>\n*/m).
-        map { |code| code.sub(/\n?
/m, '') }.
-        map { |code| code.sub(/\n?<\/pre><\/div>\n/m, '') }
-
-      # Combine the docs and code lists into the same sections style list we
-      # started with.
-      docs_html.zip(code_html)
-    end
-
-public
+  # 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.
+  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
+      when /^\s*$/
+        code << line
+      else
+        code << line
+      end
+    end
+    sections << [docs, code] if docs.any? || code.any?
+    sections
+  end
+
+  # Take the raw section data and apply markdown formatting and syntax
+  # highlighting.
+  def highlight(sections)
+    # Start by splitting the docs and codes blocks into two separate lists.
+    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
+
+    # Combine all docs blocks into a single big markdown document and run
+    # through RDiscount. Then split it back out into separate sections.
+    markdown = docs_blocks.join("\n##### DIVIDER\n")
+    docs_html = Markdown.new(markdown, :smart).
+      to_html.
+      split("\n
DIVIDER
\n") + + # Combine all code blocks into a single big stream and run through + # pygments. We `popen` a pygmentize process and then fork off a + # writer process. + code_html = nil + open("|pygmentize -l ruby -f html", 'r+') do |fd| + fork { + fd.close_read + fd.write code_blocks.join("\n# DIVIDER\n") + fd.close_write + exit! + } + + fd.close_write + code_html = fd.read + fd.close_read + end + + # Do some post-processing on the pygments output to remove + # partial `
` blocks. We'll add these back when we build to main
+    # document.
+    code_html = code_html.
+      split(/\n?# DIVIDER<\/span>\n?/m).
+      map { |code| code.sub(/\n?
/m, '') }.
+      map { |code| code.sub(/\n?<\/pre><\/div>\n/m, '') }
+
+    # Combine the docs and code lists into the same sections style list we
+    # started with.
+    docs_html.zip(code_html)
+  end
 end
diff --git a/lib/rocco/layout.rb b/lib/rocco/layout.rb
index 9711ee4..bea5511 100644
--- a/lib/rocco/layout.rb
+++ b/lib/rocco/layout.rb
@@ -8,7 +8,7 @@ class Rocco::Layout < Mustache
   end
 
   def title
-    @doc.file
+    File.basename(@doc.file)
   end
 
   def sections