Compare commits
139 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1ce551581a | ||
|
b1bf8add92 | ||
|
dde147a171 | ||
|
a175735f0c | ||
|
57be0b7695 | ||
|
4f81074ad4 | ||
|
4c4ef24222 | ||
|
97a971897f | ||
|
d149a9bbf4 | ||
|
c4da54408a | ||
|
d98793dc7e | ||
|
9b65c1cd99 | ||
|
7ba75f0503 | ||
|
c1a5017468 | ||
|
58e7e4f7d8 | ||
|
3e9a14e896 | ||
|
9e88a3925c | ||
|
ba393d1d8c | ||
|
61d84c85c2 | ||
|
e727446de7 | ||
|
d4f527f2d6 | ||
|
6736281302 | ||
|
b0582c9deb | ||
|
6193734ee1 | ||
|
3ebbb82f14 | ||
|
c1afb8a0d4 | ||
|
fe09cbe0e3 | ||
|
c5d0f3ba43 | ||
|
d0690e6269 | ||
|
6c96fcb681 | ||
|
c39dbd7816 | ||
|
8dd30853aa | ||
|
e5e0d53f18 | ||
|
d495074320 | ||
|
b993200fc1 | ||
|
66e8f9a319 | ||
|
f9dd29eafa | ||
|
3f1a8dfef3 | ||
|
19506b13db | ||
|
3224735671 | ||
|
a841026e52 | ||
|
f40e57baae | ||
|
a2d316b20d | ||
|
66723da96f | ||
|
b2a76fac29 | ||
|
6874288c8a | ||
|
5271a2ba31 | ||
|
12b26fe704 | ||
|
9d49139090 | ||
|
1fa99adbda | ||
|
cffe49a813 | ||
|
1262d50857 | ||
|
b11543d382 | ||
|
d0211ecc99 | ||
|
77dff765b6 | ||
|
f177a9d7e2 | ||
|
d067210faa | ||
|
7609e1a624 | ||
|
ba93d23634 | ||
|
515966dcc9 | ||
|
6aa2217706 | ||
|
e506c5172a | ||
|
a4d0e41413 | ||
|
b87f4a63e3 | ||
|
185da24fc3 | ||
|
020e8050bc | ||
|
0b392c1094 | ||
|
a43f0fc584 | ||
|
1b211bcc08 | ||
|
38683a8cc2 | ||
|
94b3fd4e51 | ||
|
f144e7e3b2 | ||
|
236fb2731c | ||
|
6266408828 | ||
|
6cf8de0a02 | ||
|
bb9b167b13 | ||
|
bb8fcb9ef0 | ||
|
b9b69d98fb | ||
|
198be61e7c | ||
|
939e7f0e8a | ||
|
e57b208570 | ||
|
6aa7bd6a33 | ||
|
889fcb286b | ||
|
eed5d48981 | ||
|
957e5cf197 | ||
|
feb22ad147 | ||
|
367437fec5 | ||
|
c1a4dd756a | ||
|
3dc4f87c12 | ||
|
6595d5f885 | ||
|
8c948bbb95 | ||
|
55700ff584 | ||
|
2f54f4c424 | ||
|
17eeb9e75f | ||
|
68b1529714 | ||
|
fb4b5404ae | ||
|
c1837853d6 | ||
|
4fa61ea14a | ||
|
bc8bdccb7d | ||
|
545fd53b88 | ||
|
2f8337f49c | ||
|
1978a5fe98 | ||
|
3af16f3afe | ||
|
3b48e38cba | ||
|
bda3a62e42 | ||
|
b6ece339b9 | ||
|
37aeba2247 | ||
|
bf401ef38b | ||
|
cf12978037 | ||
|
02dc9c6c4c | ||
|
832048d946 | ||
|
27b2ee03db | ||
|
73c433f484 | ||
|
459b492390 | ||
|
895bf7a759 | ||
|
a7282e4606 | ||
|
ef7dbdcda0 | ||
|
8559a79837 | ||
|
e467978b69 | ||
|
cc09e0ff1e | ||
|
399a7ebff0 | ||
|
2250694c32 | ||
|
94eaea6339 | ||
|
580d0ef7fc | ||
|
606087986e | ||
|
5a544ca5de | ||
|
84cff4945e | ||
|
39a4c00fc2 | ||
|
cf10e21b30 | ||
|
3234f7013f | ||
|
c3cfde7ebd | ||
|
8dba9306bc | ||
|
4a0dfe7421 | ||
|
50fb900e5e | ||
|
5eca6cdc9b | ||
|
7b0f7d31fd | ||
|
1b4389ef72 | ||
|
b69e02c802 | ||
|
9f3826d8e0 |
63
CHANGES.md
Normal file
63
CHANGES.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
CHANGES
|
||||||
|
=======
|
||||||
|
|
||||||
|
0.6 (2011-03-05)
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This release brought to you almost entirely
|
||||||
|
by [mikewest](http://github.com/mikewest).
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Added `-t`/`--template` CLI option that allows you to specify a Mustache
|
||||||
|
template that ought be used when rendering the final documentation.
|
||||||
|
(Issue #16)
|
||||||
|
|
||||||
|
* More variables in templates:
|
||||||
|
* `docs?`: True if `docs` contains text of any sort, False if it's empty.
|
||||||
|
* `code?`: True if `code` contains text of any sort, False if it's empty.
|
||||||
|
* `empty?`: True if both `code` and `docs` are empty. False otherwise.
|
||||||
|
* `header?`: True if `docs` contains _only_ a HTML header. False otherwise.
|
||||||
|
|
||||||
|
* Test suite! (Run `rake test`)
|
||||||
|
|
||||||
|
* Autodetect file's language if Pygments is installed locally (Issue #19)
|
||||||
|
|
||||||
|
* Autopopulate comment characters for known languages (Issue #20)
|
||||||
|
|
||||||
|
* Correctly parse block comments (Issue #22)
|
||||||
|
|
||||||
|
* Stripping encoding definitions from Ruby and Python files in the same
|
||||||
|
way we strip shebang lines (Issue #21)
|
||||||
|
|
||||||
|
* Adjusting section IDs to contain descriptive test from headers. A header
|
||||||
|
section's ID might be `section-Header_text_goes_here` for friendlier URLs.
|
||||||
|
Other section IDs will remain the same (`section-2` will stay
|
||||||
|
`section-2`). (Issue #28)
|
||||||
|
|
||||||
|
### Bugs Fixed
|
||||||
|
|
||||||
|
* Docco's CSS changed: we updated Rocco's HTML accordingly, and pinned
|
||||||
|
the CSS file to Docco's 0.3.0 tag. (Issues #12 and #23)
|
||||||
|
|
||||||
|
* Fixed code highlighting for shell scripts (among others) (Issue #13)
|
||||||
|
|
||||||
|
* Fixed buggy regex for comment char stripping (Issue #15)
|
||||||
|
|
||||||
|
* Specifying UTF-8 encoding for Pygments (Issue #10)
|
||||||
|
|
||||||
|
* Extensionless file support (thanks to [Vasily Polovnyov][vast] for the
|
||||||
|
fix!) (Issue #24)
|
||||||
|
|
||||||
|
* Fixing language support for Pygments webservice (Issue #11)
|
||||||
|
|
||||||
|
* The source jumplist now generates correctly relative URLs (Issue #26)
|
||||||
|
|
||||||
|
* Fixed an issue with using mustache's `template_path=` incorrectly.
|
||||||
|
|
||||||
|
[vast]: https://github.com/vast
|
||||||
|
|
||||||
|
0.5
|
||||||
|
---
|
||||||
|
|
||||||
|
Rocco 0.5 emerged from the hazy mists, complete and unfettered by history.
|
18
COPYING
Normal file
18
COPYING
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
Copyright (c) 2010 Ryan Tomayko <http://tomayko.com/about>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to
|
||||||
|
deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
README
Normal file
23
README
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
|
||||||
|
___ ___ ___ ___ ___
|
||||||
|
/\ \ /\ \ /\ \ /\ \ /\ \
|
||||||
|
/::\ \ /::\ \ /::\ \ /::\ \ /::\ \
|
||||||
|
/::\:\__\ /:/\:\__\ /:/\:\__\ /:/\:\__\ /:/\:\__\
|
||||||
|
\;:::/ / \:\/:/ / \:\ \/__/ \:\ \/__/ \:\/:/ /
|
||||||
|
|:\/__/ \::/ / \:\__\ \:\__\ \::/ /
|
||||||
|
\|__| \/__/ \/__/ \/__/ \/__/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Rocco is a quick-and-dirty, literate-programming-style documentation
|
||||||
|
generator for Ruby. See the Rocco generated docs for more information:
|
||||||
|
|
||||||
|
<http://rtomayko.github.com/rocco/>
|
||||||
|
|
||||||
|
|
||||||
|
Rocco is a port of, and borrows heavily from, Docco -- the original
|
||||||
|
quick-and-dirty, hundred-line-long, literate-programming-style
|
||||||
|
documentation generator in CoffeeScript:
|
||||||
|
|
||||||
|
<http://jashkenas.github.com/docco/>
|
115
Rakefile
Normal file
115
Rakefile
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
$LOAD_PATH.unshift 'lib'
|
||||||
|
|
||||||
|
require 'rake/testtask'
|
||||||
|
require 'rake/clean'
|
||||||
|
|
||||||
|
task :default => [:sup, :docs, :test]
|
||||||
|
|
||||||
|
desc 'Holla'
|
||||||
|
task :sup do
|
||||||
|
verbose do
|
||||||
|
lines = File.read('README').split("\n")[0,12]
|
||||||
|
lines.map! { |line| line[15..-1] }
|
||||||
|
puts lines.join("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Run tests (default)'
|
||||||
|
Rake::TestTask.new(:test) do |t|
|
||||||
|
t.test_files = FileList['test/suite.rb']
|
||||||
|
t.ruby_opts = ['-rubygems'] if defined? Gem
|
||||||
|
end
|
||||||
|
|
||||||
|
# Bring in Rocco tasks
|
||||||
|
require 'rocco/tasks'
|
||||||
|
Rocco::make 'docs/'
|
||||||
|
|
||||||
|
desc 'Build rocco docs'
|
||||||
|
task :docs => :rocco
|
||||||
|
directory 'docs/'
|
||||||
|
|
||||||
|
desc 'Build docs and open in browser for the reading'
|
||||||
|
task :read => :docs do
|
||||||
|
sh 'open docs/lib/rocco.html'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Make index.html a copy of rocco.html
|
||||||
|
file 'docs/index.html' => 'docs/lib/rocco.html' do |f|
|
||||||
|
cp 'docs/lib/rocco.html', 'docs/index.html', :preserve => true
|
||||||
|
end
|
||||||
|
task :docs => 'docs/index.html'
|
||||||
|
CLEAN.include 'docs/index.html'
|
||||||
|
|
||||||
|
# Alias for docs task
|
||||||
|
task :doc => :docs
|
||||||
|
|
||||||
|
# GITHUB PAGES ===============================================================
|
||||||
|
|
||||||
|
desc 'Update gh-pages branch'
|
||||||
|
task :pages => ['docs/.git', :docs] do
|
||||||
|
rev = `git rev-parse --short HEAD`.strip
|
||||||
|
Dir.chdir 'docs' do
|
||||||
|
sh "git add *.html"
|
||||||
|
sh "git commit -m 'rebuild pages from #{rev}'" do |ok,res|
|
||||||
|
if ok
|
||||||
|
verbose { puts "gh-pages updated" }
|
||||||
|
sh "git push -q o HEAD:gh-pages"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update the pages/ directory clone
|
||||||
|
file 'docs/.git' => ['docs/', '.git/refs/heads/gh-pages'] do |f|
|
||||||
|
sh "cd docs && git init -q && git remote add o ../.git" if !File.exist?(f.name)
|
||||||
|
sh "cd docs && git fetch -q o && git reset -q --hard o/gh-pages && touch ."
|
||||||
|
end
|
||||||
|
CLOBBER.include 'docs/.git'
|
||||||
|
|
||||||
|
# PACKAGING =================================================================
|
||||||
|
|
||||||
|
if defined?(Gem)
|
||||||
|
SPEC = eval(File.read('rocco.gemspec'))
|
||||||
|
|
||||||
|
def package(ext='')
|
||||||
|
"pkg/rocco-#{SPEC.version}" + ext
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Build packages'
|
||||||
|
task :package => %w[.gem .tar.gz].map {|e| package(e)}
|
||||||
|
|
||||||
|
desc 'Build and install as local gem'
|
||||||
|
task :install => package('.gem') do
|
||||||
|
sh "gem install #{package('.gem')}"
|
||||||
|
end
|
||||||
|
|
||||||
|
directory 'pkg/'
|
||||||
|
|
||||||
|
file package('.gem') => %w[pkg/ rocco.gemspec] + SPEC.files do |f|
|
||||||
|
sh "gem build rocco.gemspec"
|
||||||
|
mv File.basename(f.name), f.name
|
||||||
|
end
|
||||||
|
|
||||||
|
file package('.tar.gz') => %w[pkg/] + SPEC.files do |f|
|
||||||
|
sh "git archive --format=tar HEAD | gzip > #{f.name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GEMSPEC ===================================================================
|
||||||
|
|
||||||
|
file 'rocco.gemspec' => FileList['{lib,test,bin}/**','Rakefile'] do |f|
|
||||||
|
version = File.read('lib/rocco.rb')[/VERSION = '(.*)'/] && $1
|
||||||
|
date = Time.now.strftime("%Y-%m-%d")
|
||||||
|
spec = File.
|
||||||
|
read(f.name).
|
||||||
|
sub(/s\.version\s*=\s*'.*'/, "s.version = '#{version}'")
|
||||||
|
parts = spec.split(" # = MANIFEST =\n")
|
||||||
|
files = `git ls-files`.
|
||||||
|
split("\n").sort.reject{ |file| file =~ /^\./ }.
|
||||||
|
map{ |file| " #{file}" }.join("\n")
|
||||||
|
parts[1] = " s.files = %w[\n#{files}\n ]\n"
|
||||||
|
spec = parts.join(" # = MANIFEST =\n")
|
||||||
|
spec.sub!(/s.date = '.*'/, "s.date = '#{date}'")
|
||||||
|
File.open(f.name, 'w') { |io| io.write(spec) }
|
||||||
|
puts "#{f.name} #{version} (#{date})"
|
||||||
|
end
|
100
bin/rocco
Executable file
100
bin/rocco
Executable file
@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
#/ Usage: rocco [-l <lang>] [-c <chars>] [-o <dir>] <file>...
|
||||||
|
#/ Generate literate-programming-style documentation for Ruby source <file>s.
|
||||||
|
#/
|
||||||
|
#/ Options:
|
||||||
|
#/ -l, --language=<lang> The Pygments lexer to use to highlight code
|
||||||
|
#/ -c, --comment-chars=<chars>
|
||||||
|
#/ The string to recognize as a comment marker
|
||||||
|
#/ -o, --output=<dir> Directory where generated HTML files are written
|
||||||
|
#/ -t, --template=<path> The file to use as template when rendering HTML
|
||||||
|
#/ -d, --docblocks Parse Docblock @annotations in comments
|
||||||
|
#/ --help Show this help message
|
||||||
|
|
||||||
|
require 'optparse'
|
||||||
|
require 'fileutils'
|
||||||
|
|
||||||
|
# Write usage message to stdout and exit.
|
||||||
|
def usage(stream=$stderr, status=1)
|
||||||
|
stream.puts File.readlines(__FILE__).
|
||||||
|
grep(/^#\//).
|
||||||
|
map { |line| line.sub(/^#. ?/, '') }.
|
||||||
|
join
|
||||||
|
exit status
|
||||||
|
end
|
||||||
|
|
||||||
|
# Like `Kernel#abort` but writes a note encouraging the user to consult
|
||||||
|
# `rocco --help` for more information.
|
||||||
|
def abort_with_note(message=nil)
|
||||||
|
$stderr.puts message if message
|
||||||
|
abort "See `rocco --help' for usage information."
|
||||||
|
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("-t", "--template=TEMPLATE") { |template| options[:template_file] = template }
|
||||||
|
o.on("-d", "--docblocks") { options[:docblocks] = true }
|
||||||
|
o.on_tail("-h", "--help") { usage($stdout, 0) }
|
||||||
|
o.parse!
|
||||||
|
} or abort_with_note
|
||||||
|
|
||||||
|
# Use http://pygments.appspot.com in case `pygmentize(1)` isn't available.
|
||||||
|
if ! ENV['PATH'].split(':').any? { |dir| File.exist?("#{dir}/pygmentize") }
|
||||||
|
unless options[:webservice]
|
||||||
|
$stderr.puts "pygmentize not in PATH; using pygments.appspot.com instead"
|
||||||
|
options[:webservice] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Eat sources from ARGV.
|
||||||
|
sources << ARGV.shift while ARGV.any?
|
||||||
|
|
||||||
|
# Make sure we have some files to work with.
|
||||||
|
if sources.empty?
|
||||||
|
abort_with_note "#{File.basename($0)}: no input <file>s given"
|
||||||
|
end
|
||||||
|
|
||||||
|
# What a fucking mess. Most of this is duplicated in rocco.rb too.
|
||||||
|
libdir = File.expand_path('../../lib', __FILE__).sub(/^#{Dir.pwd}\//, '')
|
||||||
|
begin
|
||||||
|
require 'rdiscount'
|
||||||
|
require 'rocco'
|
||||||
|
rescue LoadError
|
||||||
|
case $!.to_s
|
||||||
|
when /rdiscount/
|
||||||
|
if !defined?(Gem)
|
||||||
|
warn "warn: #$!. trying again with rubygems"
|
||||||
|
require 'rubygems'
|
||||||
|
retry
|
||||||
|
else
|
||||||
|
require 'bluecloth'
|
||||||
|
Markdown = BlueCloth
|
||||||
|
$LOADED_FEATURES << 'rdiscount.rb'
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
when /rocco/
|
||||||
|
if !$:.include?(libdir)
|
||||||
|
warn "warn: #$!. trying again with #{libdir} on load path"
|
||||||
|
$:.unshift(libdir)
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run each file through Rocco and write output.
|
||||||
|
sources.each do |filename|
|
||||||
|
rocco = Rocco.new(filename, sources, options)
|
||||||
|
dest = filename.sub(Regexp.new("#{File.extname(filename)}$"),".html")
|
||||||
|
dest = File.join(output_dir, dest) if output_dir != '.'
|
||||||
|
puts "rocco: #{filename} -> #{dest}"
|
||||||
|
FileUtils.mkdir_p File.dirname(dest)
|
||||||
|
File.open(dest, 'wb') { |fd| fd.write(rocco.to_html) }
|
||||||
|
end
|
531
lib/rocco.rb
Normal file
531
lib/rocco.rb
Normal file
@ -0,0 +1,531 @@
|
|||||||
|
# **Rocco** is a Ruby port of [Docco][do], 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][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][so].
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# [no]: http://nodejs.org/
|
||||||
|
# [do]: http://jashkenas.github.com/docco/
|
||||||
|
# [co]: http://coffeescript.org/
|
||||||
|
# [md]: http://daringfireball.net/projects/markdown/
|
||||||
|
# [so]: http://github.com/rtomayko/rocco/blob/master/lib/rocco.rb#commit
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
# We'll need a Markdown library. [RDiscount][rd], if we're lucky. Otherwise,
|
||||||
|
# issue a warning and fall back on using BlueCloth.
|
||||||
|
#
|
||||||
|
# [rd]: http://github.com/rtomayko/rdiscount
|
||||||
|
begin
|
||||||
|
require 'rdiscount'
|
||||||
|
rescue LoadError => boom
|
||||||
|
warn "WARNING: #{boom}. Trying bluecloth."
|
||||||
|
require 'bluecloth'
|
||||||
|
Markdown = BlueCloth
|
||||||
|
end
|
||||||
|
|
||||||
|
# We use [{{ mustache }}](http://defunkt.github.com/mustache/) for
|
||||||
|
# HTML templating.
|
||||||
|
require 'mustache'
|
||||||
|
|
||||||
|
# We use `Net::HTTP` to highlight code via <http://pygments.appspot.com>
|
||||||
|
require 'net/http'
|
||||||
|
|
||||||
|
# Code is run through [Pygments](http://pygments.org/) for syntax
|
||||||
|
# highlighting. If it's not installed, locally, use a webservice.
|
||||||
|
if !ENV['PATH'].split(':').any? { |dir| File.executable?("#{dir}/pygmentize") }
|
||||||
|
warn "WARNING: Pygments not found. Using webservice."
|
||||||
|
end
|
||||||
|
|
||||||
|
#### Public Interface
|
||||||
|
|
||||||
|
# `Rocco.new` takes a source `filename`, an optional list of source filenames
|
||||||
|
# for other documentation sources, an `options` hash, and an optional `block`.
|
||||||
|
# The `options` hash respects three members:
|
||||||
|
#
|
||||||
|
# * `:language`: specifies which Pygments lexer to use if one can't be
|
||||||
|
# auto-detected from the filename. _Defaults to `ruby`_.
|
||||||
|
#
|
||||||
|
# * `:comment_chars`, which specifies the comment characters of the
|
||||||
|
# target language. _Defaults to `#`_.
|
||||||
|
#
|
||||||
|
# * `:template_file`, which specifies a external template file to use
|
||||||
|
# when rendering the final, highlighted file via Mustache. _Defaults
|
||||||
|
# to `nil` (that is, Mustache will use `./lib/rocco/layout.mustache`)_.
|
||||||
|
#
|
||||||
|
class Rocco
|
||||||
|
VERSION = '0.7'
|
||||||
|
|
||||||
|
def initialize(filename, sources=[], options={}, &block)
|
||||||
|
@file = filename
|
||||||
|
@sources = sources
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
@data =
|
||||||
|
if block_given?
|
||||||
|
yield
|
||||||
|
else
|
||||||
|
File.read(filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
:language => 'ruby',
|
||||||
|
:comment_chars => '#',
|
||||||
|
:template_file => nil
|
||||||
|
}
|
||||||
|
@options = defaults.merge(options)
|
||||||
|
|
||||||
|
# If we detect a language
|
||||||
|
if detect_language() != "text"
|
||||||
|
# then assign the detected language to `:language`, and look for
|
||||||
|
# comment characters based on that language
|
||||||
|
@options[:language] = detect_language()
|
||||||
|
@options[:comment_chars] = generate_comment_chars()
|
||||||
|
|
||||||
|
# If we didn't detect a language, but the user provided one, use it
|
||||||
|
# to look around for comment characters to override the default.
|
||||||
|
elsif @options[:language] != defaults[:language]
|
||||||
|
@options[:comment_chars] = generate_comment_chars()
|
||||||
|
|
||||||
|
# If neither is true, then convert the default comment character string
|
||||||
|
# into the comment_char syntax (we'll discuss that syntax in detail when
|
||||||
|
# we get to `generate_comment_chars()` in a moment.
|
||||||
|
else
|
||||||
|
@options[:comment_chars] = {
|
||||||
|
:single => @options[:comment_chars],
|
||||||
|
:multi => nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Turn `:comment_chars` into a regex matching a series of spaces, the
|
||||||
|
# `:comment_chars` string, and the an optional space. We'll use that
|
||||||
|
# to detect single-line comments.
|
||||||
|
@comment_pattern =
|
||||||
|
Regexp.new("^\\s*#{@options[:comment_chars][:single]}\s?")
|
||||||
|
|
||||||
|
# `parse()` the file contents stored in `@data`. Run the result through
|
||||||
|
# `split()` and that result through `highlight()` to generate the final
|
||||||
|
# section list.
|
||||||
|
@sections = highlight(split(parse(@data)))
|
||||||
|
end
|
||||||
|
|
||||||
|
# The filename as given to `Rocco.new`.
|
||||||
|
attr_reader :file
|
||||||
|
|
||||||
|
# The merged options array
|
||||||
|
attr_reader :options
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# A list of all source filenames included in the documentation set. Useful
|
||||||
|
# for building an index of other files.
|
||||||
|
attr_reader :sources
|
||||||
|
|
||||||
|
# Generate HTML output for the entire document.
|
||||||
|
require 'rocco/layout'
|
||||||
|
def to_html
|
||||||
|
Rocco::Layout.new(self, @options[:template_file]).render
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helper Functions
|
||||||
|
# ----------------
|
||||||
|
|
||||||
|
# Returns `true` if `pygmentize` is available locally, `false` otherwise.
|
||||||
|
def pygmentize?
|
||||||
|
@_pygmentize ||= ENV['PATH'].split(':').
|
||||||
|
any? { |dir| File.executable?("#{dir}/pygmentize") }
|
||||||
|
end
|
||||||
|
|
||||||
|
# If `pygmentize` is available, we can use it to autodetect a file's
|
||||||
|
# language based on its filename. Filenames without extensions, or with
|
||||||
|
# extensions that `pygmentize` doesn't understand will return `text`.
|
||||||
|
# We'll also return `text` if `pygmentize` isn't available.
|
||||||
|
#
|
||||||
|
# We'll memoize the result, as we'll call this a few times.
|
||||||
|
def detect_language
|
||||||
|
@_language ||=
|
||||||
|
if pygmentize?
|
||||||
|
%x[pygmentize -N #{@file}].strip.split('+').first
|
||||||
|
else
|
||||||
|
"text"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Given a file's language, we should be able to autopopulate the
|
||||||
|
# `comment_chars` variables for single-line comments. If we don't
|
||||||
|
# have comment characters on record for a given language, we'll
|
||||||
|
# use the user-provided `:comment_char` option (which defaults to
|
||||||
|
# `#`).
|
||||||
|
#
|
||||||
|
# Comment characters are listed as:
|
||||||
|
#
|
||||||
|
# { :single => "//",
|
||||||
|
# :multi_start => "/**",
|
||||||
|
# :multi_middle => "*",
|
||||||
|
# :multi_end => "*/" }
|
||||||
|
#
|
||||||
|
# `:single` denotes the leading character of a single-line comment.
|
||||||
|
# `:multi_start` denotes the string that should appear alone on a
|
||||||
|
# line of code to begin a block of documentation. `:multi_middle`
|
||||||
|
# denotes the leading character of block comment content, and
|
||||||
|
# `:multi_end` is the string that ought appear alone on a line to
|
||||||
|
# close a block of documentation. That is:
|
||||||
|
#
|
||||||
|
# /** [:multi][:start]
|
||||||
|
# * [:multi][:middle]
|
||||||
|
# ...
|
||||||
|
# * [:multi][:middle]
|
||||||
|
# */ [:multi][:end]
|
||||||
|
#
|
||||||
|
# If a language only has one type of comment, the missing type
|
||||||
|
# should be assigned `nil`.
|
||||||
|
#
|
||||||
|
# At the moment, we're only returning `:single`. Consider this
|
||||||
|
# groundwork for block comment parsing.
|
||||||
|
C_STYLE_COMMENTS = {
|
||||||
|
:single => "//",
|
||||||
|
:multi => { :start => "/**", :middle => "*", :end => "*/" },
|
||||||
|
:heredoc => nil
|
||||||
|
}
|
||||||
|
COMMENT_STYLES = {
|
||||||
|
"bash" => { :single => "#", :multi => nil },
|
||||||
|
"c" => C_STYLE_COMMENTS,
|
||||||
|
"coffee-script" => {
|
||||||
|
:single => "#",
|
||||||
|
:multi => { :start => "###", :middle => nil, :end => "###" },
|
||||||
|
:heredoc => nil
|
||||||
|
},
|
||||||
|
"cpp" => C_STYLE_COMMENTS,
|
||||||
|
"csharp" => C_STYLE_COMMENTS,
|
||||||
|
"css" => {
|
||||||
|
:single => nil,
|
||||||
|
:multi => { :start => "/**", :middle => "*", :end => "*/" },
|
||||||
|
:heredoc => nil
|
||||||
|
},
|
||||||
|
"html" => {
|
||||||
|
:single => nil,
|
||||||
|
:multi => { :start => '<!--', :middle => nil, :end => '-->' },
|
||||||
|
:heredoc => nil
|
||||||
|
},
|
||||||
|
"java" => C_STYLE_COMMENTS,
|
||||||
|
"js" => C_STYLE_COMMENTS,
|
||||||
|
"lua" => {
|
||||||
|
:single => "--",
|
||||||
|
:multi => nil,
|
||||||
|
:heredoc => nil
|
||||||
|
},
|
||||||
|
"php" => C_STYLE_COMMENTS,
|
||||||
|
"python" => {
|
||||||
|
:single => "#",
|
||||||
|
:multi => { :start => '"""', :middle => nil, :end => '"""' },
|
||||||
|
:heredoc => nil
|
||||||
|
},
|
||||||
|
"rb" => {
|
||||||
|
:single => "#",
|
||||||
|
:multi => { :start => '=begin', :middle => nil, :end => '=end' },
|
||||||
|
:heredoc => "<<-"
|
||||||
|
},
|
||||||
|
"scala" => C_STYLE_COMMENTS,
|
||||||
|
"scheme" => { :single => ";;", :multi => nil, :heredoc => nil },
|
||||||
|
"xml" => {
|
||||||
|
:single => nil,
|
||||||
|
:multi => { :start => '<!--', :middle => nil, :end => '-->' },
|
||||||
|
:heredoc => nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_comment_chars
|
||||||
|
@_commentchar ||=
|
||||||
|
if COMMENT_STYLES[@options[:language]]
|
||||||
|
COMMENT_STYLES[@options[:language]]
|
||||||
|
else
|
||||||
|
{ :single => @options[:comment_chars], :multi => nil, :heredoc => nil }
|
||||||
|
end
|
||||||
|
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, comment characters stripped.
|
||||||
|
def parse(data)
|
||||||
|
sections = []
|
||||||
|
docs, code = [], []
|
||||||
|
lines = data.split("\n")
|
||||||
|
|
||||||
|
# The first line is ignored if it is a shebang line. We also ignore the
|
||||||
|
# PEP 263 encoding information in python sourcefiles, and the similar ruby
|
||||||
|
# 1.9 syntax.
|
||||||
|
lines.shift if lines[0] =~ /^\#\!/
|
||||||
|
lines.shift if lines[0] =~ /coding[:=]\s*[-\w.]+/ &&
|
||||||
|
[ "python", "rb" ].include?(@options[:language])
|
||||||
|
|
||||||
|
# To detect both block comments and single-line comments, we'll set
|
||||||
|
# up a tiny state machine, and loop through each line of the file.
|
||||||
|
# This requires an `in_comment_block` boolean, and a few regular
|
||||||
|
# expressions for line tests. We'll do the same for fake heredoc parsing.
|
||||||
|
in_comment_block = false
|
||||||
|
in_heredoc = false
|
||||||
|
single_line_comment, block_comment_start, block_comment_mid, block_comment_end =
|
||||||
|
nil, nil, nil, nil
|
||||||
|
if not @options[:comment_chars][:single].nil?
|
||||||
|
single_line_comment = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:single])}\\s?")
|
||||||
|
end
|
||||||
|
if not @options[:comment_chars][:multi].nil?
|
||||||
|
block_comment_start = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*$")
|
||||||
|
block_comment_end = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
|
||||||
|
block_comment_one_liner = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
|
||||||
|
block_comment_start_with = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)$")
|
||||||
|
block_comment_end_with = Regexp.new("\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
|
||||||
|
if @options[:comment_chars][:multi][:middle]
|
||||||
|
block_comment_mid = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:middle])}\\s?")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not @options[:comment_chars][:heredoc].nil?
|
||||||
|
heredoc_start = Regexp.new("#{Regexp.escape(@options[:comment_chars][:heredoc])}(\\S+)$")
|
||||||
|
end
|
||||||
|
lines.each do |line|
|
||||||
|
# If we're currently in a comment block, check whether the line matches
|
||||||
|
# the _end_ of a comment block or the _end_ of a comment block with a
|
||||||
|
# comment.
|
||||||
|
if in_comment_block
|
||||||
|
if block_comment_end && line.match(block_comment_end)
|
||||||
|
in_comment_block = false
|
||||||
|
elsif block_comment_end_with && line.match(block_comment_end_with)
|
||||||
|
in_comment_block = false
|
||||||
|
docs << line.match(block_comment_end_with).captures.first.
|
||||||
|
sub(block_comment_mid || '', '')
|
||||||
|
else
|
||||||
|
docs << line.sub(block_comment_mid || '', '')
|
||||||
|
end
|
||||||
|
# If we're currently in a heredoc, we're looking for the end of the
|
||||||
|
# heredoc, and everything it contains is code.
|
||||||
|
elsif in_heredoc
|
||||||
|
if line.match(Regexp.new("^#{Regexp.escape(in_heredoc)}$"))
|
||||||
|
in_heredoc = false
|
||||||
|
end
|
||||||
|
code << line
|
||||||
|
# Otherwise, check whether the line starts a heredoc. If so, note the end
|
||||||
|
# pattern, and the line is code. Otherwise check whether the line matches
|
||||||
|
# the beginning of a block, or a single-line comment all on it's lonesome.
|
||||||
|
# In either case, if there's code, start a new section.
|
||||||
|
else
|
||||||
|
if heredoc_start && line.match(heredoc_start)
|
||||||
|
in_heredoc = $1
|
||||||
|
code << line
|
||||||
|
elsif block_comment_one_liner && line.match(block_comment_one_liner)
|
||||||
|
if code.any?
|
||||||
|
sections << [docs, code]
|
||||||
|
docs, code = [], []
|
||||||
|
end
|
||||||
|
docs << line.match(block_comment_one_liner).captures.first
|
||||||
|
elsif block_comment_start && line.match(block_comment_start)
|
||||||
|
in_comment_block = true
|
||||||
|
if code.any?
|
||||||
|
sections << [docs, code]
|
||||||
|
docs, code = [], []
|
||||||
|
end
|
||||||
|
elsif block_comment_start_with && line.match(block_comment_start_with)
|
||||||
|
in_comment_block = true
|
||||||
|
if code.any?
|
||||||
|
sections << [docs, code]
|
||||||
|
docs, code = [], []
|
||||||
|
end
|
||||||
|
docs << line.match(block_comment_start_with).captures.first
|
||||||
|
elsif single_line_comment && line.match(single_line_comment)
|
||||||
|
if code.any?
|
||||||
|
sections << [docs, code]
|
||||||
|
docs, code = [], []
|
||||||
|
end
|
||||||
|
docs << line.sub(single_line_comment || '', '')
|
||||||
|
else
|
||||||
|
code << line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
sections << [docs, code] if docs.any? || code.any?
|
||||||
|
normalize_leading_spaces(sections)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Normalizes documentation whitespace by checking for leading whitespace,
|
||||||
|
# removing it, and then removing the same amount of whitespace from each
|
||||||
|
# succeeding line. That is:
|
||||||
|
#
|
||||||
|
# def func():
|
||||||
|
# """
|
||||||
|
# Comment 1
|
||||||
|
# Comment 2
|
||||||
|
# """
|
||||||
|
# print "omg!"
|
||||||
|
#
|
||||||
|
# should yield a comment block of `Comment 1\nComment 2` and code of
|
||||||
|
# `def func():\n print "omg!"`
|
||||||
|
def normalize_leading_spaces(sections)
|
||||||
|
sections.map do |section|
|
||||||
|
if section.any? && section[0].any?
|
||||||
|
leading_space = section[0][0].match("^\s+")
|
||||||
|
if leading_space
|
||||||
|
section[0] =
|
||||||
|
section[0].map{ |line| line.sub(/^#{leading_space.to_s}/, '') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
section
|
||||||
|
end
|
||||||
|
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.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
|
||||||
|
|
||||||
|
# Take a list of block comments and convert Docblock @annotations to
|
||||||
|
# Markdown syntax.
|
||||||
|
def docblock(docs)
|
||||||
|
docs.map do |doc|
|
||||||
|
doc.split("\n").map do |line|
|
||||||
|
line.match(/^@\w+/) ? line.sub(/^@(\w+)\s+/, '> **\1** ')+" " : line
|
||||||
|
end.join("\n")
|
||||||
|
end
|
||||||
|
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
|
||||||
|
|
||||||
|
# Pre-process Docblock @annotations.
|
||||||
|
if @options[:docblocks]
|
||||||
|
docs_blocks = docblock(docs_blocks)
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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 = process_markdown(markdown).
|
||||||
|
split(/\n*<h5>DIVIDER<\/h5>\n*/m)
|
||||||
|
|
||||||
|
# Combine all code blocks into a single big stream with section dividers and
|
||||||
|
# run through either `pygmentize(1)` or <http://pygments.appspot.com>
|
||||||
|
span, espan = '<span class="c.?">', '</span>'
|
||||||
|
if @options[:comment_chars][:single]
|
||||||
|
front = @options[:comment_chars][:single]
|
||||||
|
divider_input = "\n\n#{front} DIVIDER\n\n"
|
||||||
|
divider_output = Regexp.new(
|
||||||
|
[ "\\n*",
|
||||||
|
span,
|
||||||
|
Regexp.escape(CGI.escapeHTML(front)),
|
||||||
|
' DIVIDER',
|
||||||
|
espan,
|
||||||
|
"\\n*"
|
||||||
|
].join, Regexp::MULTILINE
|
||||||
|
)
|
||||||
|
else
|
||||||
|
front = @options[:comment_chars][:multi][:start]
|
||||||
|
back = @options[:comment_chars][:multi][:end]
|
||||||
|
divider_input = "\n\n#{front}\nDIVIDER\n#{back}\n\n"
|
||||||
|
divider_output = Regexp.new(
|
||||||
|
[ "\\n*",
|
||||||
|
span, Regexp.escape(CGI.escapeHTML(front)), espan,
|
||||||
|
"\\n",
|
||||||
|
span, "DIVIDER", espan,
|
||||||
|
"\\n",
|
||||||
|
span, Regexp.escape(CGI.escapeHTML(back)), espan,
|
||||||
|
"\\n*"
|
||||||
|
].join, Regexp::MULTILINE
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
code_stream = code_blocks.join(divider_input)
|
||||||
|
|
||||||
|
code_html =
|
||||||
|
if pygmentize?
|
||||||
|
highlight_pygmentize(code_stream)
|
||||||
|
else
|
||||||
|
highlight_webservice(code_stream)
|
||||||
|
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(divider_output).
|
||||||
|
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
|
||||||
|
|
||||||
|
# Convert Markdown to classy HTML.
|
||||||
|
def process_markdown(text)
|
||||||
|
Markdown.new(text, :smart).to_html
|
||||||
|
end
|
||||||
|
|
||||||
|
# We `popen` a read/write pygmentize process in the parent and
|
||||||
|
# then fork off a child process to write the input.
|
||||||
|
def highlight_pygmentize(code)
|
||||||
|
code_html = nil
|
||||||
|
open("|pygmentize -l #{@options[:language]} -O encoding=utf-8 -f html", 'r+') do |fd|
|
||||||
|
pid =
|
||||||
|
fork {
|
||||||
|
fd.close_read
|
||||||
|
fd.write code
|
||||||
|
fd.close_write
|
||||||
|
exit!
|
||||||
|
}
|
||||||
|
fd.close_write
|
||||||
|
code_html = fd.read
|
||||||
|
fd.close_read
|
||||||
|
Process.wait(pid)
|
||||||
|
end
|
||||||
|
|
||||||
|
code_html
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pygments is not one of those things that's trivial for a ruby user to install,
|
||||||
|
# so we'll fall back on a webservice to highlight the code if it isn't available.
|
||||||
|
def highlight_webservice(code)
|
||||||
|
Net::HTTP.post_form(
|
||||||
|
URI.parse('http://pygments.appspot.com/'),
|
||||||
|
{'lang' => @options[:language], 'code' => code}
|
||||||
|
).body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# And that's it.
|
190
lib/rocco/docco.css
Normal file
190
lib/rocco/docco.css
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/*--------------------- Layout and Typography ----------------------------*/
|
||||||
|
body {
|
||||||
|
font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 22px;
|
||||||
|
color: #252519;
|
||||||
|
margin: 0; padding: 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #261a3b;
|
||||||
|
}
|
||||||
|
a:visited {
|
||||||
|
color: #261a3b;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: 0px 0 15px 0;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
#container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#background {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 525px; right: 0; bottom: 0;
|
||||||
|
background: #f5f5ff;
|
||||||
|
border-left: 1px solid #e5e5ee;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
#jump_to, #jump_page {
|
||||||
|
background: white;
|
||||||
|
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
|
||||||
|
-webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
|
||||||
|
font: 10px Arial;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
#jump_to, #jump_wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; right: 0;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jump_wrapper {
|
||||||
|
padding: 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#jump_to:hover #jump_wrapper {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#jump_page {
|
||||||
|
padding: 5px 0 3px;
|
||||||
|
margin: 0 0 25px 25px;
|
||||||
|
overflow: hidden;
|
||||||
|
zoom: 1;
|
||||||
|
}
|
||||||
|
#jump_page .source {
|
||||||
|
float: left;
|
||||||
|
display: inline;
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
#jump_page .source:hover {
|
||||||
|
background: #f5f5ff;
|
||||||
|
}
|
||||||
|
#jump_page .source:first-child {
|
||||||
|
}
|
||||||
|
table td {
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
td.docs, th.docs {
|
||||||
|
max-width: 450px;
|
||||||
|
min-width: 450px;
|
||||||
|
min-height: 5px;
|
||||||
|
padding: 10px 25px 1px 50px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
vertical-align: top;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.docs pre {
|
||||||
|
margin: 15px 0 15px;
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
.docs p tt, .docs p code {
|
||||||
|
background: #f8f8ff;
|
||||||
|
border: 1px solid #dedede;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 0 0.2em;
|
||||||
|
}
|
||||||
|
.pilwrap {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.pilcrow {
|
||||||
|
font: 12px Arial;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #454545;
|
||||||
|
position: absolute;
|
||||||
|
top: 3px; left: -20px;
|
||||||
|
padding: 1px 2px;
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transition: opacity 0.2s linear;
|
||||||
|
}
|
||||||
|
td.docs:hover .pilcrow {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
td.code, th.code {
|
||||||
|
padding: 14px 15px 16px 25px;
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: top;
|
||||||
|
background: #f5f5ff;
|
||||||
|
border-left: 1px solid #e5e5ee;
|
||||||
|
}
|
||||||
|
pre, tt, code {
|
||||||
|
font-size: 12px; line-height: 18px;
|
||||||
|
font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
|
||||||
|
margin: 0; padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*---------------------- Syntax Highlighting -----------------------------*/
|
||||||
|
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
|
||||||
|
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
|
||||||
|
body .hll { background-color: #ffffcc }
|
||||||
|
body .c { color: #408080; font-style: italic } /* Comment */
|
||||||
|
body .err { border: 1px solid #FF0000 } /* Error */
|
||||||
|
body .k { color: #954121 } /* Keyword */
|
||||||
|
body .o { color: #666666 } /* Operator */
|
||||||
|
body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
|
||||||
|
body .cp { color: #BC7A00 } /* Comment.Preproc */
|
||||||
|
body .c1 { color: #408080; font-style: italic } /* Comment.Single */
|
||||||
|
body .cs { color: #408080; font-style: italic } /* Comment.Special */
|
||||||
|
body .gd { color: #A00000 } /* Generic.Deleted */
|
||||||
|
body .ge { font-style: italic } /* Generic.Emph */
|
||||||
|
body .gr { color: #FF0000 } /* Generic.Error */
|
||||||
|
body .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||||
|
body .gi { color: #00A000 } /* Generic.Inserted */
|
||||||
|
body .go { color: #808080 } /* Generic.Output */
|
||||||
|
body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
|
||||||
|
body .gs { font-weight: bold } /* Generic.Strong */
|
||||||
|
body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||||
|
body .gt { color: #0040D0 } /* Generic.Traceback */
|
||||||
|
body .kc { color: #954121 } /* Keyword.Constant */
|
||||||
|
body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */
|
||||||
|
body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */
|
||||||
|
body .kp { color: #954121 } /* Keyword.Pseudo */
|
||||||
|
body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */
|
||||||
|
body .kt { color: #B00040 } /* Keyword.Type */
|
||||||
|
body .m { color: #666666 } /* Literal.Number */
|
||||||
|
body .s { color: #219161 } /* Literal.String */
|
||||||
|
body .na { color: #7D9029 } /* Name.Attribute */
|
||||||
|
body .nb { color: #954121 } /* Name.Builtin */
|
||||||
|
body .nc { color: #0000FF; font-weight: bold } /* Name.Class */
|
||||||
|
body .no { color: #880000 } /* Name.Constant */
|
||||||
|
body .nd { color: #AA22FF } /* Name.Decorator */
|
||||||
|
body .ni { color: #999999; font-weight: bold } /* Name.Entity */
|
||||||
|
body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
|
||||||
|
body .nf { color: #0000FF } /* Name.Function */
|
||||||
|
body .nl { color: #A0A000 } /* Name.Label */
|
||||||
|
body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
|
||||||
|
body .nt { color: #954121; font-weight: bold } /* Name.Tag */
|
||||||
|
body .nv { color: #19469D } /* Name.Variable */
|
||||||
|
body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
|
||||||
|
body .w { color: #bbbbbb } /* Text.Whitespace */
|
||||||
|
body .mf { color: #666666 } /* Literal.Number.Float */
|
||||||
|
body .mh { color: #666666 } /* Literal.Number.Hex */
|
||||||
|
body .mi { color: #666666 } /* Literal.Number.Integer */
|
||||||
|
body .mo { color: #666666 } /* Literal.Number.Oct */
|
||||||
|
body .sb { color: #219161 } /* Literal.String.Backtick */
|
||||||
|
body .sc { color: #219161 } /* Literal.String.Char */
|
||||||
|
body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
|
||||||
|
body .s2 { color: #219161 } /* Literal.String.Double */
|
||||||
|
body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
|
||||||
|
body .sh { color: #219161 } /* Literal.String.Heredoc */
|
||||||
|
body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
|
||||||
|
body .sx { color: #954121 } /* Literal.String.Other */
|
||||||
|
body .sr { color: #BB6688 } /* Literal.String.Regex */
|
||||||
|
body .s1 { color: #219161 } /* Literal.String.Single */
|
||||||
|
body .ss { color: #19469D } /* Literal.String.Symbol */
|
||||||
|
body .bp { color: #954121 } /* Name.Builtin.Pseudo */
|
||||||
|
body .vc { color: #19469D } /* Name.Variable.Class */
|
||||||
|
body .vg { color: #19469D } /* Name.Variable.Global */
|
||||||
|
body .vi { color: #19469D } /* Name.Variable.Instance */
|
||||||
|
body .il { color: #666666 } /* Literal.Number.Integer.Long */
|
46
lib/rocco/layout.mustache
Normal file
46
lib/rocco/layout.mustache
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
<style type="text/css">{{{ styles }}}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='container'>
|
||||||
|
<div id="background"></div>
|
||||||
|
{{#sources?}}
|
||||||
|
<div id="jump_to">
|
||||||
|
Jump To …
|
||||||
|
<div id="jump_wrapper">
|
||||||
|
<div id="jump_page">
|
||||||
|
{{#sources}}
|
||||||
|
<a class="source" href="{{ url }}">{{ basename }}</a>
|
||||||
|
{{/sources}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/sources?}}
|
||||||
|
<table cellspacing=0 cellpadding=0>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class=docs><h1>{{ title }}</h1></th>
|
||||||
|
<th class=code></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#sections}}
|
||||||
|
<tr id='section-{{ section_id }}'>
|
||||||
|
<td class=docs>
|
||||||
|
<div class="pilwrap">
|
||||||
|
<a class="pilcrow" href="#section-{{ section_id }}">¶</a>
|
||||||
|
</div>
|
||||||
|
{{{ docs }}}
|
||||||
|
</td>
|
||||||
|
<td class=code>
|
||||||
|
<div class='highlight'><pre>{{{ code }}}</pre></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/sections}}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
63
lib/rocco/layout.rb
Normal file
63
lib/rocco/layout.rb
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
require 'mustache'
|
||||||
|
require 'pathname'
|
||||||
|
|
||||||
|
class Rocco::Layout < Mustache
|
||||||
|
self.template_path = "#{File.dirname(__FILE__)}/.."
|
||||||
|
|
||||||
|
def initialize(doc, file=nil)
|
||||||
|
@doc = doc
|
||||||
|
if not file.nil?
|
||||||
|
Rocco::Layout.template_file = file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def title
|
||||||
|
File.basename(@doc.file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def file
|
||||||
|
@doc.file
|
||||||
|
end
|
||||||
|
|
||||||
|
def styles
|
||||||
|
File.read(File.expand_path('../docco.css', __FILE__))
|
||||||
|
end
|
||||||
|
|
||||||
|
def sections
|
||||||
|
num = 0
|
||||||
|
@doc.sections.map do |docs,code|
|
||||||
|
code ||= ''
|
||||||
|
is_header = /^<h.>(.+)<\/h.>$/.match( docs )
|
||||||
|
header_text = is_header && is_header[1].split.join("_")
|
||||||
|
num += 1
|
||||||
|
{
|
||||||
|
:docs => docs,
|
||||||
|
:docs? => !docs.empty?,
|
||||||
|
:header? => is_header,
|
||||||
|
|
||||||
|
:code => code,
|
||||||
|
:code? => !code.empty?,
|
||||||
|
|
||||||
|
:empty? => ( code.empty? && docs.empty? ),
|
||||||
|
:section_id => is_header ? header_text : num
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def sources?
|
||||||
|
@doc.sources.length > 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def sources
|
||||||
|
currentpath = Pathname.new( File.dirname( @doc.file ) )
|
||||||
|
@doc.sources.sort { |left, right| File.split(left).last <=> File.split(right).last }.map do |source|
|
||||||
|
htmlpath = Pathname.new( source.sub( Regexp.new( "#{File.extname(source)}$"), ".html" ) )
|
||||||
|
|
||||||
|
{
|
||||||
|
:path => source,
|
||||||
|
:basename => File.basename(source),
|
||||||
|
:url => htmlpath.relative_path_from( currentpath )
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
123
lib/rocco/tasks.rb
Normal file
123
lib/rocco/tasks.rb
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#### 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']
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
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', 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
|
||||||
|
include Rake::DSL if defined?(Rake::DSL)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Run over the source file list, constructing destination filenames
|
||||||
|
# and defining file tasks.
|
||||||
|
@sources.each do |source_file|
|
||||||
|
dest_file = source_file.sub(Regexp.new("#{File.extname(source_file)}$"), ".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, @sources.to_a, @options)
|
||||||
|
FileUtils.mkdir_p(File.dirname(dest_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
|
56
rocco.gemspec
Normal file
56
rocco.gemspec
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
$LOAD_PATH.unshift 'lib'
|
||||||
|
require "rocco"
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.specification_version = 2 if s.respond_to? :specification_version=
|
||||||
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||||
|
|
||||||
|
s.name = 'rocco'
|
||||||
|
s.version = Rocco::VERSION
|
||||||
|
s.date = Time.now.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
s.description = "Docco in Ruby"
|
||||||
|
s.summary = s.description
|
||||||
|
|
||||||
|
s.authors = ["Ryan Tomayko", "Mike West"]
|
||||||
|
s.email = ["r@tomayko.com", "<mike@mikewest.org>"]
|
||||||
|
|
||||||
|
# = MANIFEST =
|
||||||
|
s.files = %w[
|
||||||
|
CHANGES.md
|
||||||
|
COPYING
|
||||||
|
README
|
||||||
|
Rakefile
|
||||||
|
bin/rocco
|
||||||
|
lib/rocco.rb
|
||||||
|
lib/rocco/layout.mustache
|
||||||
|
lib/rocco/layout.rb
|
||||||
|
lib/rocco/tasks.rb
|
||||||
|
rocco.gemspec
|
||||||
|
test/fixtures/issue10.iso-8859-1.rb
|
||||||
|
test/fixtures/issue10.utf-8.rb
|
||||||
|
test/helper.rb
|
||||||
|
test/suite.rb
|
||||||
|
test/test_basics.rb
|
||||||
|
test/test_block_comments.rb
|
||||||
|
test/test_comment_normalization.rb
|
||||||
|
test/test_commentchar_detection.rb
|
||||||
|
test/test_descriptive_section_names.rb
|
||||||
|
test/test_language_detection.rb
|
||||||
|
test/test_reported_issues.rb
|
||||||
|
test/test_skippable_lines.rb
|
||||||
|
test/test_source_list.rb
|
||||||
|
]
|
||||||
|
# = MANIFEST =
|
||||||
|
|
||||||
|
s.executables = ["rocco"]
|
||||||
|
|
||||||
|
s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
|
||||||
|
s.add_dependency 'rdiscount'
|
||||||
|
s.add_dependency 'mustache'
|
||||||
|
|
||||||
|
s.has_rdoc = false
|
||||||
|
s.homepage = "http://rtomayko.github.com/rocco/"
|
||||||
|
s.require_paths = %w[lib]
|
||||||
|
s.rubygems_version = '1.1.1'
|
||||||
|
end
|
1
test/fixtures/issue10.iso-8859-1.rb
vendored
Normal file
1
test/fixtures/issue10.iso-8859-1.rb
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
# hello wörld
|
1
test/fixtures/issue10.utf-8.rb
vendored
Normal file
1
test/fixtures/issue10.utf-8.rb
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
# hello ąćęłńóśźż
|
20
test/helper.rb
Normal file
20
test/helper.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
rootdir = File.expand_path('../../lib', __FILE__)
|
||||||
|
$LOAD_PATH.unshift "#{rootdir}/lib"
|
||||||
|
|
||||||
|
require 'test/unit'
|
||||||
|
begin; require 'turn'; rescue LoadError; end
|
||||||
|
begin
|
||||||
|
require 'rdiscount'
|
||||||
|
rescue LoadError
|
||||||
|
if !defined?(Gem)
|
||||||
|
require 'rubygems'
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
require 'rocco'
|
||||||
|
|
||||||
|
def roccoize( filename, contents, options = {} )
|
||||||
|
Rocco.new( filename, [ filename ], options ) {
|
||||||
|
contents
|
||||||
|
}
|
||||||
|
end
|
5
test/suite.rb
Normal file
5
test/suite.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
require 'test/unit'
|
||||||
|
|
||||||
|
Dir[File.expand_path('../test_*.rb', __FILE__)].
|
||||||
|
each { |file| require file }
|
63
test/test_basics.rb
Normal file
63
test/test_basics.rb
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoBasicTests < Test::Unit::TestCase
|
||||||
|
def test_rocco_exists_and_is_instancable
|
||||||
|
roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_filename
|
||||||
|
r = roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
|
||||||
|
assert_equal "filename.rb", r.file
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sources
|
||||||
|
r = roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
|
||||||
|
assert_equal [ "filename.rb" ], r.sources
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sections
|
||||||
|
r = roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
|
||||||
|
assert_equal 1, r.sections.length
|
||||||
|
assert_equal 2, r.sections[ 0 ].length
|
||||||
|
assert_equal "<p>Comment 1</p>\n", r.sections[ 0 ][ 0 ]
|
||||||
|
assert_equal "<span class=\"k\">def</span> <span class=\"nf\">codeblock</span>\n<span class=\"k\">end</span>", r.sections[ 0 ][ 1 ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_parsing
|
||||||
|
r = Rocco.new( 'test' ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "# Comment 1\ndef codeblock\nend\n" )
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "# Comment 1\ndef codeblock\n# Comment 2\nend\n" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_splitting
|
||||||
|
r = Rocco.new( 'test' ) { "" } # Generate throwaway instance so I can test `split`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ "Comment 1" ],
|
||||||
|
[ "def codeblock\nend" ]
|
||||||
|
],
|
||||||
|
r.split([ [ [ "Comment 1" ], [ "def codeblock", "end" ] ] ])
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ "Comment 1", "Comment 2" ],
|
||||||
|
[ "def codeblock", "end" ]
|
||||||
|
],
|
||||||
|
r.split( [
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "end" ] ]
|
||||||
|
] )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
64
test/test_block_comment_styles.rb
Normal file
64
test/test_block_comment_styles.rb
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoBlockCommentTest < Test::Unit::TestCase
|
||||||
|
def test_one_liner
|
||||||
|
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "/** Comment 1 */\ndef codeblock\nend\n" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_block_start_with_comment
|
||||||
|
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "/** Comment 1a\n * Comment 1b\n */\ndef codeblock\nend\n" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_block_end_with_comment
|
||||||
|
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "/**\n * Comment 1a\n Comment 1b */\ndef codeblock\nend\n" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_block_end_with_comment_and_middle
|
||||||
|
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "/**\n * Comment 1a\n * Comment 1b */\ndef codeblock\nend\n" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_block_start_with_comment_and_end_with_comment
|
||||||
|
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "/** Comment 1a\n Comment 1b */\ndef codeblock\nend\n" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_block_start_with_comment_and_end_with_comment_and_middle
|
||||||
|
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "/** Comment 1a\n * Comment 1b */\ndef codeblock\nend\n" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
101
test/test_block_comments.rb
Normal file
101
test/test_block_comments.rb
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoBlockCommentTest < Test::Unit::TestCase
|
||||||
|
def test_basics
|
||||||
|
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n" )
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "/**\n * Comment 1a\n * Comment 1b\n */\ndef codeblock\nend\n" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_multiple_blocks
|
||||||
|
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
||||||
|
[ [ "Comment 2" ], [] ]
|
||||||
|
],
|
||||||
|
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n/**\n * Comment 2\n */\n" )
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "if false", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n/**\n * Comment 2\n */\nif false\nend" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_block_without_middle_character
|
||||||
|
r = Rocco.new( 'test', '', { :language => "python" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
||||||
|
[ [ "Comment 2" ], [] ]
|
||||||
|
],
|
||||||
|
r.parse( "\"\"\"\n Comment 1\n\"\"\"\ndef codeblock\nend\n\"\"\"\n Comment 2\n\"\"\"\n" )
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "if false", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "\"\"\"\n Comment 1\n\"\"\"\ndef codeblock\nend\n\"\"\"\n Comment 2\n\"\"\"\nif false\nend" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_language_without_single_line_comments_parse
|
||||||
|
r = Rocco.new( 'test', '', { :language => "css" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "if false", "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n/**\n * Comment 2\n */\nif false\nend" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_language_without_single_line_comments_split
|
||||||
|
r = Rocco.new( 'test', '', { :language => "css" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ "Comment 1", "Comment 2" ],
|
||||||
|
[ "def codeblock\nend", "if false\nend" ]
|
||||||
|
],
|
||||||
|
r.split( [
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "if false", "end" ] ]
|
||||||
|
] )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_language_without_single_line_comments_highlight
|
||||||
|
r = Rocco.new( 'test', '', { :language => "css" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
|
||||||
|
highlighted = r.highlight( r.split( r.parse( "/**\n * This is a comment!\n */\n.rule { goes: here; }\n/**\n * Comment 2\n */\n.rule2 { goes: here; }" ) ) )
|
||||||
|
assert_equal(
|
||||||
|
"<p>This is a comment!</p>",
|
||||||
|
highlighted[0][0]
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
"<p>Comment 2</p>\n",
|
||||||
|
highlighted[1][0]
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
!highlighted[0][1].include?("DIVIDER") &&
|
||||||
|
!highlighted[1][1].include?("DIVIDER"),
|
||||||
|
"`DIVIDER` stripped successfully."
|
||||||
|
)
|
||||||
|
assert( true )
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
25
test/test_comment_normalization.rb
Normal file
25
test/test_comment_normalization.rb
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoCommentNormalization < Test::Unit::TestCase
|
||||||
|
def test_normal_comments
|
||||||
|
r = Rocco.new( 'test', '', { :language => "python" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ],
|
||||||
|
[ [ "Comment 2a", " Comment 2b" ], [] ]
|
||||||
|
],
|
||||||
|
r.parse( "\"\"\"\n Comment 1a\n Comment 1b\n\"\"\"\ndef codeblock\nend\n\"\"\"\n Comment 2a\n Comment 2b\n\"\"\"\n" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_single_line_comments
|
||||||
|
r = Rocco.new( 'test', '', { :language => "python" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ],
|
||||||
|
[ [ "Comment 2a", " Comment 2b" ], [] ]
|
||||||
|
],
|
||||||
|
r.parse( "# Comment 1a\n# Comment 1b\ndef codeblock\nend\n# Comment 2a\n# Comment 2b\n" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
28
test/test_commentchar_detection.rb
Normal file
28
test/test_commentchar_detection.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoAutomaticCommentChars < Test::Unit::TestCase
|
||||||
|
def test_basic_detection
|
||||||
|
r = Rocco.new( 'filename.js' ) { "" }
|
||||||
|
assert_equal "//", r.options[:comment_chars][:single]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fallback_language
|
||||||
|
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :language => "js" } ) { "" }
|
||||||
|
assert_equal "//", r.options[:comment_chars][:single]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fallback_default
|
||||||
|
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever' ) { "" }
|
||||||
|
assert_equal "#", r.options[:comment_chars][:single], "`:comment_chars` should be `#` when falling back to defaults."
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fallback_user
|
||||||
|
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :comment_chars => "user" } ) { "" }
|
||||||
|
assert_equal "user", r.options[:comment_chars][:single], "`:comment_chars` should be the user's default when falling back to user-provided settings."
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fallback_user_with_unknown_language
|
||||||
|
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :language => "not-a-language", :comment_chars => "user" } ) { "" }
|
||||||
|
assert_equal "user", r.options[:comment_chars][:single], "`:comment_chars` should be the user's default when falling back to user-provided settings."
|
||||||
|
end
|
||||||
|
end
|
30
test/test_descriptive_section_names.rb
Normal file
30
test/test_descriptive_section_names.rb
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoDescriptiveSectionNamesTests < Test::Unit::TestCase
|
||||||
|
def test_section_name
|
||||||
|
r = roccoize( "filename.rb", "# # Comment 1\ndef codeblock\nend\n" )
|
||||||
|
html = r.to_html
|
||||||
|
assert(
|
||||||
|
html.include?( "<tr id='section-Comment_1'>" ),
|
||||||
|
"The first section should be named"
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
html.include?( '<a class="pilcrow" href="#section-Comment_1">' ),
|
||||||
|
"The rendered HTML should link to a named section"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_section_numbering
|
||||||
|
r = roccoize( "filename.rb", "# # Header 1\ndef codeblock\nend\n# Comment 1\ndef codeblock1\nend\n# # Header 2\ndef codeblock2\nend" )
|
||||||
|
html = r.to_html
|
||||||
|
assert(
|
||||||
|
html.include?( '<a class="pilcrow" href="#section-Header_1">' ) &&
|
||||||
|
html.include?( '<a class="pilcrow" href="#section-Header_2">' ),
|
||||||
|
"First and second headers should be named sections"
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
html.include?( '<a class="pilcrow" href="#section-2">' ),
|
||||||
|
"Sections should continue numbering as though headers were counted."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
22
test/test_docblock_annotations.rb
Normal file
22
test/test_docblock_annotations.rb
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoDocblockAnnotationsTest < Test::Unit::TestCase
|
||||||
|
def test_basics
|
||||||
|
r = Rocco.new( 'test', '', { :language => "c", :docblocks => true } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
"Comment\n\n> **param** mixed foo \n> **return** void "
|
||||||
|
],
|
||||||
|
r.docblock( ["Comment\n\n@param mixed foo\n@return void"] )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
def test_highlighted_in_blocks
|
||||||
|
r = Rocco.new( 'test', '', { :language => "c", :docblocks => true } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
highlighted = r.highlight( r.split( r.parse( "/**\n * Comment\n * @param type name\n */\ndef codeblock\nend\n" ) ) )
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
"<p>Comment</p>\n\n<blockquote><p><strong>param</strong> type name</p></blockquote>\n",
|
||||||
|
highlighted[0][0]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
13
test/test_heredoc.rb
Normal file
13
test/test_heredoc.rb
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoHeredocTest < Test::Unit::TestCase
|
||||||
|
def test_basics
|
||||||
|
r = Rocco.new( 'test', '', { :language => "rb" } ) { "" } # Generate throwaway instance so I can test `parse`
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "heredoc <<-EOH", "#comment", "code", "EOH" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "# Comment 1\nheredoc <<-EOH\n#comment\ncode\nEOH" )
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
27
test/test_language_detection.rb
Normal file
27
test/test_language_detection.rb
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoLanguageDetection < Test::Unit::TestCase
|
||||||
|
def test_basic_detection
|
||||||
|
r = Rocco.new( 'filename.py' ) { "" }
|
||||||
|
if r.pygmentize?
|
||||||
|
assert_equal "python", r.detect_language(), "`detect_language()` should return the correct language"
|
||||||
|
assert_equal "python", r.options[:language], "`@options[:language]` should be set to the correct language"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fallback_default
|
||||||
|
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever' ) { "" }
|
||||||
|
if r.pygmentize?
|
||||||
|
assert_equal "text", r.detect_language(), "`detect_language()` should return `text` when nothing else is detected"
|
||||||
|
assert_equal "ruby", r.options[:language], "`@options[:language]` should be set to `ruby` when nothing else is detected"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fallback_user
|
||||||
|
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :language => "c" } ) { "" }
|
||||||
|
if r.pygmentize?
|
||||||
|
assert_equal "text", r.detect_language(), "`detect_language()` should return `text` nothing else is detected"
|
||||||
|
assert_equal "c", r.options[:language], "`@options[:language]` should be set to the user's setting when nothing else is detected"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
86
test/test_reported_issues.rb
Normal file
86
test/test_reported_issues.rb
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoIssueTests < Test::Unit::TestCase
|
||||||
|
def test_issue07_incorrect_parsing_in_c_mode
|
||||||
|
# Precursor to issue #13 below, Rocco incorrectly parsed C/C++
|
||||||
|
# http://github.com/rtomayko/rocco/issues#issue/7
|
||||||
|
r = Rocco.new( 'issue7.c', [], { :language => 'c' } ) {
|
||||||
|
"// *stdio.h* declares *puts*\n#include <stdio.h>\n\n//### code hello world\n\n// every C program contains function *main*.\nint main (int argc, char *argv[]) {\n puts('hello world');\n return 0;\n}\n\n// that's it!"
|
||||||
|
}
|
||||||
|
r.sections.each do | section |
|
||||||
|
if not section[1].nil?
|
||||||
|
assert(
|
||||||
|
!section[1].include?("<span class=\"c\"># DIVIDER</span>"),
|
||||||
|
"`# DIVIDER` present in code text, which means the highligher screwed up somewhere."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_issue10_utf8_processing
|
||||||
|
# Rocco has issues with strange UTF-8 characters: need to explicitly set the encoding for Pygments
|
||||||
|
# http://github.com/rtomayko/rocco/issues#issue/10
|
||||||
|
r = Rocco.new( File.dirname(__FILE__) + "/fixtures/issue10.utf-8.rb" )
|
||||||
|
assert_equal(
|
||||||
|
"<p>hello ąćęłńóśźż</p>\n",
|
||||||
|
r.sections[0][0],
|
||||||
|
"UTF-8 input files ought behave correctly."
|
||||||
|
)
|
||||||
|
# and, just for grins, ensure that iso-8859-1 works too.
|
||||||
|
# @TODO: Is this really the correct behavior? Converting text
|
||||||
|
# to UTF-8 on the way out is probably preferable.
|
||||||
|
r = Rocco.new( File.dirname(__FILE__) + "/fixtures/issue10.iso-8859-1.rb" )
|
||||||
|
assert_equal(
|
||||||
|
"<p>hello w\366rld</p>\n",
|
||||||
|
r.sections[0][0],
|
||||||
|
"ISO-8859-1 input should probably also behave correctly."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_issue12_css_octothorpe_classname_change
|
||||||
|
# Docco changed some CSS classes. Rocco needs to update its default template.
|
||||||
|
# http://github.com/rtomayko/rocco/issues#issue/12
|
||||||
|
r = Rocco.new( 'issue12.sh' ) {
|
||||||
|
"# Comment 1\n# Comment 1\nprint 'omg!'"
|
||||||
|
}
|
||||||
|
html = r.to_html
|
||||||
|
assert(
|
||||||
|
!html.include?( "<div class=\"octowrap\">" ),
|
||||||
|
"`octowrap` wrapper is present in rendered HTML. This ought be replaced with `pilwrap`."
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
!html.include?( "<a class=\"octothorpe\" href=\"#section-1\">" ),
|
||||||
|
"`octothorpe` link is present in rendered HTML. This ought be replaced with `pilcrow`."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_issue13_incorrect_code_divider_parsing
|
||||||
|
# In `bash` mode (among others), the comment class is `c`, not `c1`.
|
||||||
|
# http://github.com/rtomayko/rocco/issues#issue/13
|
||||||
|
r = Rocco.new( 'issue13.sh', [], { :language => 'bash' } ) {
|
||||||
|
"# Comment 1\necho 'code';\n# Comment 2\necho 'code';\n# Comment 3\necho 'code';\n"
|
||||||
|
}
|
||||||
|
r.sections.each do | section |
|
||||||
|
if not section[1].nil?
|
||||||
|
assert(
|
||||||
|
!section[1].include?("<span class=\"c\"># DIVIDER</span>"),
|
||||||
|
"`# DIVIDER` present in code text, which means the highligher screwed up somewhere."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_issue15_extra_space_after_comment_character_remains
|
||||||
|
# After the comment character, a single space should be removed.
|
||||||
|
# http://github.com/rtomayko/rocco/issues#issue/15
|
||||||
|
r = Rocco.new( 'issue15.sh') {
|
||||||
|
"# Comment 1\n# ---------\necho 'code';"
|
||||||
|
}
|
||||||
|
assert(
|
||||||
|
!r.sections[0][0].include?( "<hr />" ),
|
||||||
|
"`<hr />` present in rendered documentation text. It should be a header, not text followed by a horizontal rule."
|
||||||
|
)
|
||||||
|
assert_equal("<h2>Comment 1</h2>\n", r.sections[0][0])
|
||||||
|
end
|
||||||
|
end
|
64
test/test_skippable_lines.rb
Normal file
64
test/test_skippable_lines.rb
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoSkippableLines < Test::Unit::TestCase
|
||||||
|
def test_shebang_first_line
|
||||||
|
r = Rocco.new( 'filename.sh' ) { "" }
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "#!/usr/bin/env bash\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
|
||||||
|
"Shebang should be stripped when it appears as the first line."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_shebang_in_content
|
||||||
|
r = Rocco.new( 'filename.sh' ) { "" }
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
# @TODO: `#!/` shouldn't be recognized as a comment.
|
||||||
|
[ [ "Comment 1", "!/usr/bin/env bash" ], [ "def codeblock" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "# Comment 1\n#!/usr/bin/env bash\ndef codeblock\n# Comment 2\nend\n" ),
|
||||||
|
"Shebang shouldn't be stripped anywhere other than as the first line."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_encoding_in_ruby
|
||||||
|
r = Rocco.new( 'filename.rb' ) { "" }
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "#!/usr/bin/env bash\n# encoding: utf-8\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
|
||||||
|
"Strings matching the PEP 263 encoding definition regex should be stripped when they appear at the top of a python document."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_encoding_in_python
|
||||||
|
r = Rocco.new( 'filename.py' ) { "" }
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "Comment 1" ], [ "def codeblock" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "#!/usr/bin/env bash\n# encoding: utf-8\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
|
||||||
|
"Strings matching the PEP 263 encoding definition regex should be stripped when they appear at the top of a python document."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_encoding_in_notpython
|
||||||
|
r = Rocco.new( 'filename.sh' ) { "" }
|
||||||
|
assert_equal(
|
||||||
|
[
|
||||||
|
[ [ "encoding: utf-8", "Comment 1" ], [ "def codeblock" ] ],
|
||||||
|
[ [ "Comment 2" ], [ "end" ] ]
|
||||||
|
],
|
||||||
|
r.parse( "#!/usr/bin/env bash\n# encoding: utf-8\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
|
||||||
|
"Strings matching the PEP 263 encoding definition regex should be stripped when they appear at the top of a python document."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
29
test/test_source_list.rb
Normal file
29
test/test_source_list.rb
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
|
class RoccoSourceListTests < Test::Unit::TestCase
|
||||||
|
def test_flat_sourcelist
|
||||||
|
r = Rocco.new( 'issue26.sh', [ 'issue26a.sh', 'issue26b.sh', 'issue26c.sh' ] ) {
|
||||||
|
"# Comment 1\n# Comment 1\nprint 'omg!'"
|
||||||
|
}
|
||||||
|
html = r.to_html
|
||||||
|
assert(
|
||||||
|
html.include?( '<a class="source" href="issue26a.html">issue26a.sh</a>' ) &&
|
||||||
|
html.include?( '<a class="source" href="issue26b.html">issue26b.sh</a>' ) &&
|
||||||
|
html.include?( '<a class="source" href="issue26c.html">issue26c.sh</a>' ),
|
||||||
|
"URLs correctly generated for files in a flat directory structure"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_heiarachical_sourcelist
|
||||||
|
r = Rocco.new( 'a/issue26.sh', [ 'a/issue26a.sh', 'b/issue26b.sh', 'c/issue26c.sh' ] ) {
|
||||||
|
"# Comment 1\n# Comment 1\nprint 'omg!'"
|
||||||
|
}
|
||||||
|
html = r.to_html
|
||||||
|
assert(
|
||||||
|
html.include?( '<a class="source" href="issue26a.html">issue26a.sh</a>' ) &&
|
||||||
|
html.include?( '<a class="source" href="../b/issue26b.html">issue26b.sh</a>' ) &&
|
||||||
|
html.include?( '<a class="source" href="../c/issue26c.html">issue26c.sh</a>' ),
|
||||||
|
"URLs correctly generated for files in a flat directory structure"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user