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
` 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("\nDIVIDER
\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