diff --git a/bin/rocco b/bin/rocco
index 5ba062c..5f03958 100755
--- a/bin/rocco
+++ b/bin/rocco
@@ -1,11 +1,14 @@
#!/usr/bin/env ruby
-#/ Usage: rocco [-o
] ...
+#/ Usage: rocco [-l ] [-c ] [-o ] ...
#/ Generate literate-programming-style documentation for Ruby source s.
#/
#/ Options:
-#/ -o, --output= Directory where generated HTML files are written
+#/ -l, --language= The Pygments lexer to use to highlight code
+#/ -c, --comment-chars=
+#/ The string to recognize as a comment marker
+#/ -o, --output= Directory where generated HTML files are written
#/
-#/ --help Show this help message
+#/ --help Show this help message
require 'optparse'
@@ -28,9 +31,12 @@ end
# Parse command line options, aborting if anything goes wrong.
output_dir = '.'
sources = []
+options = {}
ARGV.options { |o|
o.program_name = File.basename($0)
o.on("-o", "--output=DIR") { |dir| output_dir = dir }
+ o.on("-l", "--language=LANG") { |lang| options[:language] = lang }
+ o.on("-c", "--comment-chars=CHARS") { |chars| options[:comment_chars] = Regexp.escape(chars) }
o.on_tail("-h", "--help") { usage($stdout, 0) }
o.parse!
} or abort_with_note
@@ -76,8 +82,8 @@ Dir.mkdir output_dir if !File.directory?(output_dir)
# Run each file through Rocco and write output.
sources.each do |filename|
- rocco = Rocco.new(filename, sources)
- dest = File.join(output_dir, File.basename(filename, '.rb') + '.html')
+ rocco = Rocco.new(filename, sources, options)
+ dest = filename.split('.')[0..-2].join('.') + '.html'
puts "rocco: #{filename} -> #{dest}"
File.open(dest, 'wb') { |fd| fd.write(rocco.to_html) }
end
diff --git a/lib/rocco.rb b/lib/rocco.rb
index cef51ce..a472d04 100644
--- a/lib/rocco.rb
+++ b/lib/rocco.rb
@@ -57,13 +57,18 @@ end
#### Public Interface
# `Rocco.new` takes a source `filename`, an optional list of source filenames
-# for other documentation sources, 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.
+# for other documentation sources, an `options` hash, and an optional `block`.
+# The `options` hash respects two members: `:language`, which specifies which
+# Pygments lexer to use; and `:comment_chars`, which specifies the comment
+# characters of the target language. The options default to `'ruby'` and `'#'`,
+# respectively.
+# 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.3'
- def initialize(filename, sources=[], &block)
+ def initialize(filename, sources=[], options={}, &block)
@file = filename
@data =
if block_given?
@@ -71,7 +76,10 @@ class Rocco
else
File.read(filename)
end
+ defaults = { :language => 'ruby', :comment_chars => '#' }
+ @options = defaults.merge(options)
@sources = sources
+ @comment_pattern = Regexp.new("^\\s*#{@options[:comment_chars]}")
@sections = highlight(split(parse(@data)))
end
@@ -98,13 +106,16 @@ class Rocco
# 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.
+ # raw lines parsed from the input file. The first line is ignored if it
+ # is a shebang line.
def parse(data)
sections = []
docs, code = [], []
- data.split("\n").each do |line|
+ lines = data.split("\n")
+ lines.shift if lines[0] =~ /^\#\!/
+ lines.each do |line|
case line
- when /^\s*#(?:\s+|$)/
+ when @comment_pattern
if code.any?
sections << [docs, code]
docs, code = [], []
@@ -124,8 +135,11 @@ class Rocco
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")
+ docs_blocks << docs.map { |line| line.sub(@comment_pattern, '') }.join("\n")
+ code_blocks << code.map do |line|
+ tabs = line.match(/^(\t+)/)
+ tabs ? line.sub(/^\t+/, ' ' * tabs.captures[0].length) : line
+ end.join("\n")
end
[docs_blocks, code_blocks]
end
@@ -147,7 +161,7 @@ class Rocco
# 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|
+ open("|pygmentize -l #{@options[:language]} -f html", 'r+') do |fd|
pid =
fork {
fd.close_read
diff --git a/lib/rocco/layout.rb b/lib/rocco/layout.rb
index a2d7a80..5ad61dd 100644
--- a/lib/rocco/layout.rb
+++ b/lib/rocco/layout.rb
@@ -31,7 +31,7 @@ class Rocco::Layout < Mustache
{
:path => source,
:basename => File.basename(source),
- :url => File.basename(source, '.rb') + '.html'
+ :url => File.basename(source).split('.')[0..-2].join('.') + '.html'
}
end
end
diff --git a/lib/rocco/tasks.rb b/lib/rocco/tasks.rb
index 286eb54..7c0ac6c 100644
--- a/lib/rocco/tasks.rb
+++ b/lib/rocco/tasks.rb
@@ -33,6 +33,15 @@
#
# Rocco::make 'html/', ['lib/thing.rb', 'lib/thing/*.rb']
#
+# Finally, it is also possible to specify which Pygments language you would
+# like to use to highlight the code, as well as the comment characters for the
+# language in the `options` hash:
+#
+# Rocco::make 'html/', 'lib/thing/**/*.rb', {
+# :language => 'io',
+# :comment_chars => '#'
+# }
+#
# Might be nice to defer this until we actually need to build docs but this
# will have to do for now.
@@ -42,17 +51,18 @@ require 'rocco'
# 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)
+ def self.make(dest='docs/', source_files='lib/**/*.rb', options={})
+ Task.new(:rocco, dest, source_files, options)
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')
+ def initialize(task_name, dest='docs/', sources='lib/**/*.rb', options={})
@name = task_name
@dest = dest[-1] == ?/ ? dest : "#{dest}/"
@sources = FileList[sources]
+ @options = options
# Make sure there's a `directory` task defined for our destination.
define_directory_task @dest
@@ -60,7 +70,7 @@ class Rocco
# 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'
+ dest_file = File.basename(source_file).split('.')[0..-2].join('.') + '.html'
define_file_task source_file, "#{@dest}#{dest_file}"
# If `rake/clean` was required, add the generated files to the list.
@@ -92,7 +102,7 @@ class Rocco
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, @sources.to_a)
+ rocco = Rocco.new(source_file, @sources.to_a, @options)
File.open(dest_file, 'wb') { |fd| fd.write(rocco.to_html) }
end
task @name => dest_file