Full Code of rtomayko/rocco for AI

master 2586dc3bd4b0 cached
31 files
59.4 KB
17.7k tokens
92 symbols
1 requests
Download .txt
Repository: rtomayko/rocco
Branch: master
Commit: 2586dc3bd4b0
Files: 31
Total size: 59.4 KB

Directory structure:
gitextract_bjr7abbr/

├── .gitignore
├── CHANGES.md
├── COPYING
├── Gemfile
├── LICENSE
├── README
├── Rakefile
├── bin/
│   └── rocco
├── lib/
│   ├── rocco/
│   │   ├── comment_styles.rb
│   │   ├── layout.mustache
│   │   ├── layout.rb
│   │   └── tasks.rb
│   └── rocco.rb
├── rocco.gemspec
└── test/
    ├── fixtures/
    │   ├── issue10.iso-8859-1.rb
    │   └── issue10.utf-8.rb
    ├── helper.rb
    ├── suite.rb
    ├── test_basics.rb
    ├── test_block_comment_styles.rb
    ├── test_block_comments.rb
    ├── test_comment_normalization.rb
    ├── test_commentchar_detection.rb
    ├── test_descriptive_section_names.rb
    ├── test_docblock_annotations.rb
    ├── test_heredoc.rb
    ├── test_language_detection.rb
    ├── test_reported_issues.rb
    ├── test_skippable_lines.rb
    ├── test_source_list.rb
    └── test_stylesheet.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp


================================================
FILE: CHANGES.md
================================================
CHANGES
=======

0.8 (2011-06-19)
----------------

https://github.com/rtomayko/rocco/compare/0.7...0.8

0.7 (2011-05-22)
----------------

https://github.com/rtomayko/rocco/compare/0.6...0.7

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.


================================================
FILE: COPYING
================================================
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.


================================================
FILE: Gemfile
================================================
source :rubygems

gemspec


================================================
FILE: LICENSE
================================================
Copyright (c) 2010-2014 Ryan Tomayko <http://tomayko.com>

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 OR COPYRIGHT
HOLDERS 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.


================================================
FILE: README
================================================


                  ___       ___       ___       ___       ___
                 /\  \     /\  \     /\  \     /\  \     /\  \
                /::\  \   /::\  \   /::\  \   /::\  \   /::\  \
               /::\:\__\ /:/\:\__\ /:/\:\__\ /:/\:\__\ /:/\:\__\
               \;:::/  / \:\/:/  / \:\ \/__/ \:\ \/__/ \:\/:/  /
                |:\/__/   \::/  /   \:\__\    \:\__\    \::/  /
                 \|__|     \/__/     \/__/     \/__/     \/__/



    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/>


                   N O   L O N G E R   M A I N T A I N E D

    This repository  is  archived and  no longer  actively maintained by
    @rtomayko  as of 2017-11-08.  Issues  and  PRs  documenting  current
    issues  have been intentionally left open for informational purposes.


================================================
FILE: Rakefile
================================================
$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


================================================
FILE: bin/rocco
================================================
#!/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'
require 'rocco'

# 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("-s", "--stylesheet=STYLESHEET") { |stylesheet| options[:stylesheet] = stylesheet }
  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.
unless 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

# 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


================================================
FILE: lib/rocco/comment_styles.rb
================================================
class Rocco
  module CommentStyles
    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
      },
    }
  end
end


================================================
FILE: lib/rocco/layout.mustache
================================================
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=utf-8">
  <title>{{ title }}</title>
  <link rel="stylesheet" href="{{ stylesheet }}">
</head>
<body>
<div id='container'>
  <div id="background"></div>
  {{#sources?}}
  <div id="jump_to">
    Jump To &hellip;
    <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 }}">&#182;</a>
        </div>
        {{{ docs }}}
      </td>
      <td class=code>
        <div class='highlight'><pre>{{{ code }}}</pre></div>
      </td>
    </tr>
    {{/sections}}
  </table>
</div>
</body>


================================================
FILE: lib/rocco/layout.rb
================================================
require 'mustache'
require 'pathname'

class Rocco::Layout < Mustache
  self.template_path = "#{File.dirname(__FILE__)}/.."

  def initialize(doc, stylesheet, file=nil)
    @doc = doc
    @stylesheet = stylesheet
    if not file.nil?
      Rocco::Layout.template_file = file
    end
  end

  def title
    File.basename(@doc.file)
  end

  def stylesheet
    @stylesheet
  end

  def file
    @doc.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.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


================================================
FILE: lib/rocco/tasks.rb
================================================
#### Rocco Rake Tasks
#
# To use the Rocco Rake tasks, require `rocco/tasks` in your `Rakefile`
# and define a Rake task with `rocco_task`. In its simplest form, `rocco_task`
# takes the path to a destination directory where HTML docs should be built:
#
#     require 'rocco/tasks'
#
#     desc "Build Rocco Docs"
#     Rocco::make 'docs/'
#
# This creates a `:rocco` rake task, which can then be run with:
#
#     rake rocco
#
# It's a good idea to guard against Rocco not being available, since your
# Rakefile will fail to load otherwise. Consider doing something like this,
# so that your Rakefile will still work
#
#     begin
#       require 'rocco/tasks'
#       Rocco::make 'docs/'
#     rescue LoadError
#       warn "#$! -- rocco tasks not loaded."
#       task :rocco
#     end
#
# It's also possible to pass a glob pattern:
#
#     Rocco::make 'html/', 'lib/thing/**/*.rb'
#
# Or a list of glob patterns:
#
#     Rocco::make 'html/', ['lib/thing.rb', 'lib/thing/*.rb']
#
# 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


================================================
FILE: lib/rocco.rb
================================================
# **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. Try to load one if not already established.
unless defined?(Markdown)
  markdown_libraries = %w[redcarpet rdiscount bluecloth]
  begin
    require markdown_libraries.shift
  rescue LoadError => boom
    retry if markdown_libraries.any?
    raise
  end
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.
unless 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`)_.
#
# * `:stylesheet`, which specifies the css stylesheet to use for each
#   rendered template.  _Defaults to `http://jashkenas.github.com/docco/resources/docco.css`
#   (the original docco stylesheet)
class Rocco
  VERSION = '0.8.2'

  def initialize(filename, sources=[], options={})
    @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? then yield else File.read(filename) end

    @options =  {
      :language      => 'ruby',
      :comment_chars => '#',
      :template_file => nil,
      :stylesheet    => 'http://jashkenas.github.io/docco/resources/linear/docco.css'
    }.merge(options)

    # If we detect a language
    if "text" != detect_language
      # 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]
      @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[:stylesheet], @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.
  require 'rocco/comment_styles'
  include CommentStyles

  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.
    docs_blocks = docblock(docs_blocks) if @options[:docblocks]

    # 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)
    url = URI.parse 'http://pygments.appspot.com/'
    options = { 'lang' => @options[:language], 'code' => code}
    Net::HTTP.post_form(url, options).body
  end
end

# And that's it.


================================================
FILE: rocco.gemspec
================================================
# rocco.gemspec
#
# To update this file's version, date, and file list, change the VERSION
# constant in lib/rocco.rb and run `rake rocco.gemspec`.

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 = '0.8.2'
  s.date = '2011-08-27'

  s.description = "Docco in Ruby"
  s.summary     = s.description

  s.authors = ["Ryan Tomayko", "Mike West"]
  s.email   = ["r@tomayko.com", "<mike@mikewest.org>"]

  s.licenses = ['MIT']

  # = MANIFEST =
  s.files = %w[
    CHANGES.md
    COPYING
    README
    Rakefile
    bin/rocco
    lib/rocco.rb
    lib/rocco/comment_styles.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_comment_styles.rb
    test/test_block_comments.rb
    test/test_comment_normalization.rb
    test/test_commentchar_detection.rb
    test/test_descriptive_section_names.rb
    test/test_docblock_annotations.rb
    test/test_heredoc.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 'redcarpet', '~> 1.17'
  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


================================================
FILE: test/fixtures/issue10.iso-8859-1.rb
================================================
# hello wrld


================================================
FILE: test/fixtures/issue10.utf-8.rb
================================================
# hello ąćęłńóśźż


================================================
FILE: test/helper.rb
================================================
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


================================================
FILE: test/suite.rb
================================================
require File.expand_path('../helper', __FILE__)
require 'test/unit'

Dir[File.expand_path('../test_*.rb', __FILE__)].
each { |file| require file }


================================================
FILE: test/test_basics.rb
================================================
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


================================================
FILE: test/test_block_comment_styles.rb
================================================
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


================================================
FILE: test/test_block_comments.rb
================================================
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


================================================
FILE: test/test_comment_normalization.rb
================================================
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


================================================
FILE: test/test_commentchar_detection.rb
================================================
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


================================================
FILE: test/test_descriptive_section_names.rb
================================================
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


================================================
FILE: test/test_docblock_annotations.rb
================================================
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


================================================
FILE: test/test_heredoc.rb
================================================
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


================================================
FILE: test/test_language_detection.rb
================================================
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


================================================
FILE: test/test_reported_issues.rb
================================================
# 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


================================================
FILE: test/test_skippable_lines.rb
================================================
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


================================================
FILE: test/test_source_list.rb
================================================
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


================================================
FILE: test/test_stylesheet.rb
================================================
require File.expand_path('../helper', __FILE__)

class RoccoStylesheetTests < Test::Unit::TestCase
  def test_default_stylesheet
    r = Rocco.new( 'file.rb', [ 'file.rb'] ) {
      "# Content"
    }
    html = r.to_html
    assert(
      html.include?('<link rel="stylesheet" href="http://jashkenas.github.com/docco/resources/docco.css">')
    )
  end

  def test_custom_stylesheet
    r = Rocco.new( 'file.rb', [ 'file.rb'], :stylesheet => 'http://example.com/custom.css' ) {
      "# Content"
    }
    html = r.to_html
    assert(
      html.include?('<link rel="stylesheet" href="http://example.com/custom.css">')
    )
  end
end
Download .txt
gitextract_bjr7abbr/

├── .gitignore
├── CHANGES.md
├── COPYING
├── Gemfile
├── LICENSE
├── README
├── Rakefile
├── bin/
│   └── rocco
├── lib/
│   ├── rocco/
│   │   ├── comment_styles.rb
│   │   ├── layout.mustache
│   │   ├── layout.rb
│   │   └── tasks.rb
│   └── rocco.rb
├── rocco.gemspec
└── test/
    ├── fixtures/
    │   ├── issue10.iso-8859-1.rb
    │   └── issue10.utf-8.rb
    ├── helper.rb
    ├── suite.rb
    ├── test_basics.rb
    ├── test_block_comment_styles.rb
    ├── test_block_comments.rb
    ├── test_comment_normalization.rb
    ├── test_commentchar_detection.rb
    ├── test_descriptive_section_names.rb
    ├── test_docblock_annotations.rb
    ├── test_heredoc.rb
    ├── test_language_detection.rb
    ├── test_reported_issues.rb
    ├── test_skippable_lines.rb
    ├── test_source_list.rb
    └── test_stylesheet.rb
Download .txt
SYMBOL INDEX (92 symbols across 18 files)

FILE: lib/rocco.rb
  class Rocco (line 77) | class Rocco
    method initialize (line 80) | def initialize(filename, sources=[], options={})
    method to_html (line 144) | def to_html
    method pygmentize? (line 152) | def pygmentize?
    method detect_language (line 163) | def detect_language
    method generate_comment_chars (line 206) | def generate_comment_chars
    method parse (line 221) | def parse(data)
    method normalize_leading_spaces (line 332) | def normalize_leading_spaces(sections)
    method split (line 348) | def split(sections)
    method docblock (line 362) | def docblock(docs)
    method highlight (line 372) | def highlight(blocks)
    method process_markdown (line 436) | def process_markdown(text)
    method highlight_pygmentize (line 442) | def highlight_pygmentize(code)
    method highlight_webservice (line 463) | def highlight_webservice(code)

FILE: lib/rocco/comment_styles.rb
  class Rocco (line 1) | class Rocco
    type CommentStyles (line 2) | module CommentStyles

FILE: lib/rocco/layout.rb
  class Rocco::Layout (line 4) | class Rocco::Layout < Mustache
    method initialize (line 7) | def initialize(doc, stylesheet, file=nil)
    method title (line 15) | def title
    method stylesheet (line 19) | def stylesheet
    method file (line 23) | def file
    method sections (line 27) | def sections
    method sources? (line 48) | def sources?
    method sources (line 52) | def sources

FILE: lib/rocco/tasks.rb
  class Rocco (line 53) | class Rocco
    method make (line 54) | def self.make(dest='docs/', source_files='lib/**/*.rb', options={})
    class Task (line 60) | class Task
      method initialize (line 63) | def initialize(task_name, dest='docs/', sources='lib/**/*.rb', optio...
      method define_directory_task (line 87) | def define_directory_task(path)
      method define_file_task (line 103) | def define_file_task(source_file, dest_file)
      method rocco_source_files (line 117) | def rocco_source_files

FILE: test/helper.rb
  function roccoize (line 16) | def roccoize( filename, contents, options = {} )

FILE: test/test_basics.rb
  class RoccoBasicTests (line 3) | class RoccoBasicTests < Test::Unit::TestCase
    method test_rocco_exists_and_is_instancable (line 4) | def test_rocco_exists_and_is_instancable
    method test_filename (line 8) | def test_filename
    method test_sources (line 13) | def test_sources
    method test_sections (line 18) | def test_sections
    method test_parsing (line 26) | def test_parsing
    method test_splitting (line 43) | def test_splitting

FILE: test/test_block_comment_styles.rb
  class RoccoBlockCommentTest (line 3) | class RoccoBlockCommentTest < Test::Unit::TestCase
    method test_one_liner (line 4) | def test_one_liner
    method test_block_start_with_comment (line 14) | def test_block_start_with_comment
    method test_block_end_with_comment (line 24) | def test_block_end_with_comment
    method test_block_end_with_comment_and_middle (line 34) | def test_block_end_with_comment_and_middle
    method test_block_start_with_comment_and_end_with_comment (line 44) | def test_block_start_with_comment_and_end_with_comment
    method test_block_start_with_comment_and_end_with_comment_and_middle (line 54) | def test_block_start_with_comment_and_end_with_comment_and_middle

FILE: test/test_block_comments.rb
  class RoccoBlockCommentTest (line 3) | class RoccoBlockCommentTest < Test::Unit::TestCase
    method test_basics (line 4) | def test_basics
    method test_multiple_blocks (line 20) | def test_multiple_blocks
    method test_block_without_middle_character (line 38) | def test_block_without_middle_character
    method test_language_without_single_line_comments_parse (line 56) | def test_language_without_single_line_comments_parse
    method test_language_without_single_line_comments_split (line 67) | def test_language_without_single_line_comments_split
    method test_language_without_single_line_comments_highlight (line 81) | def test_language_without_single_line_comments_highlight

FILE: test/test_comment_normalization.rb
  class RoccoCommentNormalization (line 3) | class RoccoCommentNormalization < Test::Unit::TestCase
    method test_normal_comments (line 4) | def test_normal_comments
    method test_single_line_comments (line 15) | def test_single_line_comments

FILE: test/test_commentchar_detection.rb
  class RoccoAutomaticCommentChars (line 3) | class RoccoAutomaticCommentChars < Test::Unit::TestCase
    method test_basic_detection (line 4) | def test_basic_detection
    method test_fallback_language (line 9) | def test_fallback_language
    method test_fallback_default (line 14) | def test_fallback_default
    method test_fallback_user (line 19) | def test_fallback_user
    method test_fallback_user_with_unknown_language (line 24) | def test_fallback_user_with_unknown_language

FILE: test/test_descriptive_section_names.rb
  class RoccoDescriptiveSectionNamesTests (line 3) | class RoccoDescriptiveSectionNamesTests < Test::Unit::TestCase
    method test_section_name (line 4) | def test_section_name
    method test_section_numbering (line 17) | def test_section_numbering

FILE: test/test_docblock_annotations.rb
  class RoccoDocblockAnnotationsTest (line 3) | class RoccoDocblockAnnotationsTest < Test::Unit::TestCase
    method test_basics (line 4) | def test_basics
    method test_highlighted_in_blocks (line 13) | def test_highlighted_in_blocks

FILE: test/test_heredoc.rb
  class RoccoHeredocTest (line 3) | class RoccoHeredocTest < Test::Unit::TestCase
    method test_basics (line 4) | def test_basics

FILE: test/test_language_detection.rb
  class RoccoLanguageDetection (line 3) | class RoccoLanguageDetection < Test::Unit::TestCase
    method test_basic_detection (line 4) | def test_basic_detection
    method test_fallback_default (line 12) | def test_fallback_default
    method test_fallback_user (line 20) | def test_fallback_user

FILE: test/test_reported_issues.rb
  class RoccoIssueTests (line 4) | class RoccoIssueTests < Test::Unit::TestCase
    method test_issue07_incorrect_parsing_in_c_mode (line 5) | def test_issue07_incorrect_parsing_in_c_mode
    method test_issue10_utf8_processing (line 21) | def test_issue10_utf8_processing
    method test_issue12_css_octothorpe_classname_change (line 41) | def test_issue12_css_octothorpe_classname_change
    method test_issue13_incorrect_code_divider_parsing (line 58) | def test_issue13_incorrect_code_divider_parsing
    method test_issue15_extra_space_after_comment_character_remains (line 74) | def test_issue15_extra_space_after_comment_character_remains

FILE: test/test_skippable_lines.rb
  class RoccoSkippableLines (line 3) | class RoccoSkippableLines < Test::Unit::TestCase
    method test_shebang_first_line (line 4) | def test_shebang_first_line
    method test_shebang_in_content (line 16) | def test_shebang_in_content
    method test_encoding_in_ruby (line 29) | def test_encoding_in_ruby
    method test_encoding_in_python (line 41) | def test_encoding_in_python
    method test_encoding_in_notpython (line 53) | def test_encoding_in_notpython

FILE: test/test_source_list.rb
  class RoccoSourceListTests (line 3) | class RoccoSourceListTests < Test::Unit::TestCase
    method test_flat_sourcelist (line 4) | def test_flat_sourcelist
    method test_heiarachical_sourcelist (line 17) | def test_heiarachical_sourcelist

FILE: test/test_stylesheet.rb
  class RoccoStylesheetTests (line 3) | class RoccoStylesheetTests < Test::Unit::TestCase
    method test_default_stylesheet (line 4) | def test_default_stylesheet
    method test_custom_stylesheet (line 14) | def test_custom_stylesheet
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (66K chars).
[
  {
    "path": ".gitignore",
    "chars": 154,
    "preview": "*.gem\n*.rbc\n.bundle\n.config\n.yardoc\nGemfile.lock\nInstalledFiles\n_yardoc\ncoverage\ndoc/\nlib/bundler/man\npkg\nrdoc\nspec/repo"
  },
  {
    "path": "CHANGES.md",
    "chars": 2168,
    "preview": "CHANGES\n=======\n\n0.8 (2011-06-19)\n----------------\n\nhttps://github.com/rtomayko/rocco/compare/0.7...0.8\n\n0.7 (2011-05-22"
  },
  {
    "path": "COPYING",
    "chars": 1063,
    "preview": "Copyright (c) 2010 Ryan Tomayko <http://tomayko.com/about>\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "Gemfile",
    "chars": 26,
    "preview": "source :rubygems\n\ngemspec\n"
  },
  {
    "path": "LICENSE",
    "chars": 1082,
    "preview": "Copyright (c) 2010-2014 Ryan Tomayko <http://tomayko.com>\n\nPermission is hereby granted, free of charge, to any person\no"
  },
  {
    "path": "README",
    "chars": 1190,
    "preview": "\n\n                  ___       ___       ___       ___       ___\n                 /\\  \\     /\\  \\     /\\  \\     /\\  \\    "
  },
  {
    "path": "Rakefile",
    "chars": 3118,
    "preview": "$LOAD_PATH.unshift 'lib'\n\nrequire 'rake/testtask'\nrequire 'rake/clean'\n\ntask :default => [:sup, :docs, :test]\n\ndesc 'Hol"
  },
  {
    "path": "bin/rocco",
    "chars": 2718,
    "preview": "#!/usr/bin/env ruby\n#/ Usage: rocco [-l <lang>] [-c <chars>] [-o <dir>] <file>...\n#/ Generate literate-programming-style"
  },
  {
    "path": "lib/rocco/comment_styles.rb",
    "chars": 1691,
    "preview": "class Rocco\n  module CommentStyles\n    C_STYLE_COMMENTS = {\n      :single => \"//\",\n      :multi  => { :start => \"/**\", :"
  },
  {
    "path": "lib/rocco/layout.mustache",
    "chars": 1053,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n  <title>{{ title }}<"
  },
  {
    "path": "lib/rocco/layout.rb",
    "chars": 1370,
    "preview": "require 'mustache'\nrequire 'pathname'\n\nclass Rocco::Layout < Mustache\n  self.template_path = \"#{File.dirname(__FILE__)}/"
  },
  {
    "path": "lib/rocco/tasks.rb",
    "chars": 4386,
    "preview": "#### Rocco Rake Tasks\n#\n# To use the Rocco Rake tasks, require `rocco/tasks` in your `Rakefile`\n# and define a Rake task"
  },
  {
    "path": "lib/rocco.rb",
    "chars": 17583,
    "preview": "# **Rocco** is a Ruby port of [Docco][do], the quick-and-dirty,\n# hundred-line-long, literate-programming-style document"
  },
  {
    "path": "rocco.gemspec",
    "chars": 1705,
    "preview": "# rocco.gemspec\n#\n# To update this file's version, date, and file list, change the VERSION\n# constant in lib/rocco.rb an"
  },
  {
    "path": "test/fixtures/issue10.iso-8859-1.rb",
    "chars": 13,
    "preview": "# hello wrld\n"
  },
  {
    "path": "test/fixtures/issue10.utf-8.rb",
    "chars": 18,
    "preview": "# hello ąćęłńóśźż\n"
  },
  {
    "path": "test/helper.rb",
    "chars": 396,
    "preview": "rootdir = File.expand_path('../../lib', __FILE__)\n$LOAD_PATH.unshift \"#{rootdir}/lib\"\n\nrequire 'test/unit'\nbegin; requir"
  },
  {
    "path": "test/suite.rb",
    "chars": 147,
    "preview": "require File.expand_path('../helper', __FILE__)\nrequire 'test/unit'\n\nDir[File.expand_path('../test_*.rb', __FILE__)].\nea"
  },
  {
    "path": "test/test_basics.rb",
    "chars": 1839,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoBasicTests < Test::Unit::TestCase\n  def test_rocco_exists_an"
  },
  {
    "path": "test/test_block_comment_styles.rb",
    "chars": 2176,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoBlockCommentTest < Test::Unit::TestCase\n  def test_one_liner"
  },
  {
    "path": "test/test_block_comments.rb",
    "chars": 3406,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoBlockCommentTest < Test::Unit::TestCase\n  def test_basics\n  "
  },
  {
    "path": "test/test_comment_normalization.rb",
    "chars": 984,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoCommentNormalization < Test::Unit::TestCase\n  def test_norma"
  },
  {
    "path": "test/test_commentchar_detection.rb",
    "chars": 1329,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoAutomaticCommentChars < Test::Unit::TestCase\n  def test_basi"
  },
  {
    "path": "test/test_descriptive_section_names.rb",
    "chars": 1074,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoDescriptiveSectionNamesTests < Test::Unit::TestCase\n  def te"
  },
  {
    "path": "test/test_docblock_annotations.rb",
    "chars": 877,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoDocblockAnnotationsTest < Test::Unit::TestCase\n  def test_ba"
  },
  {
    "path": "test/test_heredoc.rb",
    "chars": 423,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoHeredocTest < Test::Unit::TestCase\n  def test_basics\n    r ="
  },
  {
    "path": "test/test_language_detection.rb",
    "chars": 1238,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoLanguageDetection < Test::Unit::TestCase\n  def test_basic_de"
  },
  {
    "path": "test/test_reported_issues.rb",
    "chars": 3489,
    "preview": "# encoding: utf-8\nrequire File.expand_path('../helper', __FILE__)\n\nclass RoccoIssueTests < Test::Unit::TestCase\n  def te"
  },
  {
    "path": "test/test_skippable_lines.rb",
    "chars": 2289,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoSkippableLines < Test::Unit::TestCase\n  def test_shebang_fir"
  },
  {
    "path": "test/test_source_list.rb",
    "chars": 1194,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoSourceListTests < Test::Unit::TestCase\n  def test_flat_sourc"
  },
  {
    "path": "test/test_stylesheet.rb",
    "chars": 635,
    "preview": "require File.expand_path('../helper', __FILE__)\n\nclass RoccoStylesheetTests < Test::Unit::TestCase\n  def test_default_st"
  }
]

About this extraction

This page contains the full source code of the rtomayko/rocco GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (59.4 KB), approximately 17.7k tokens, and a symbol index with 92 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!