rocco-obb 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2010 Ryan Tomayko <http://tomayko.com/about>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,23 @@
1
+
2
+
3
+ ___ ___ ___ ___ ___
4
+ /\ \ /\ \ /\ \ /\ \ /\ \
5
+ /::\ \ /::\ \ /::\ \ /::\ \ /::\ \
6
+ /::\:\__\ /:/\:\__\ /:/\:\__\ /:/\:\__\ /:/\:\__\
7
+ \;:::/ / \:\/:/ / \:\ \/__/ \:\ \/__/ \:\/:/ /
8
+ |:\/__/ \::/ / \:\__\ \:\__\ \::/ /
9
+ \|__| \/__/ \/__/ \/__/ \/__/
10
+
11
+
12
+
13
+ Rocco is a quick-and-dirty, literate-programming-style documentation
14
+ generator for Ruby. See the Rocco generated docs for more information:
15
+
16
+ <http://rtomayko.github.com/rocco/>
17
+
18
+
19
+ Rocco is a port of, and borrows heavily from, Docco -- the original
20
+ quick-and-dirty, hundred-line-long, literate-programming-style
21
+ documentation generator in CoffeeScript:
22
+
23
+ <http://jashkenas.github.com/docco/>
data/Rakefile ADDED
@@ -0,0 +1,115 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+
3
+ require 'rake/testtask'
4
+ require 'rake/clean'
5
+
6
+ task :default => [:sup, :docs, :test]
7
+
8
+ desc 'Holla'
9
+ task :sup do
10
+ verbose do
11
+ lines = File.read('README').split("\n")[0,12]
12
+ lines.map! { |line| line[15..-1] }
13
+ puts lines.join("\n")
14
+ end
15
+ end
16
+
17
+ desc 'Run tests (default)'
18
+ Rake::TestTask.new(:test) do |t|
19
+ t.test_files = FileList['test/*_test.rb']
20
+ t.ruby_opts = ['-rubygems'] if defined? Gem
21
+ end
22
+
23
+ # Bring in Rocco tasks
24
+ require 'rocco/tasks'
25
+ Rocco::make 'docs/'
26
+
27
+ desc 'Build rocco docs'
28
+ task :docs => :rocco
29
+ directory 'docs/'
30
+
31
+ desc 'Build docs and open in browser for the reading'
32
+ task :read => :docs do
33
+ sh 'open docs/rocco.html'
34
+ end
35
+
36
+ # Make index.html a copy of rocco.html
37
+ file 'docs/index.html' => 'docs/rocco.html' do |f|
38
+ cp 'docs/rocco.html', 'docs/index.html', :preserve => true
39
+ end
40
+ task :docs => 'docs/index.html'
41
+ CLEAN.include 'docs/index.html'
42
+
43
+ # Alias for docs task
44
+ task :doc => :docs
45
+
46
+ # GITHUB PAGES ===============================================================
47
+
48
+ desc 'Update gh-pages branch'
49
+ task :pages => ['docs/.git', :docs] do
50
+ rev = `git rev-parse --short HEAD`.strip
51
+ Dir.chdir 'docs' do
52
+ sh "git add *.html"
53
+ sh "git commit -m 'rebuild pages from #{rev}'" do |ok,res|
54
+ if ok
55
+ verbose { puts "gh-pages updated" }
56
+ sh "git push -q o HEAD:gh-pages"
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ # Update the pages/ directory clone
63
+ file 'docs/.git' => ['docs/', '.git/refs/heads/gh-pages'] do |f|
64
+ sh "cd docs && git init -q && git remote add o ../.git" if !File.exist?(f.name)
65
+ sh "cd docs && git fetch -q o && git reset -q --hard o/gh-pages && touch ."
66
+ end
67
+ CLOBBER.include 'docs/.git'
68
+
69
+ # PACKAGING =================================================================
70
+
71
+ if defined?(Gem)
72
+ SPEC = eval(File.read('rocco.gemspec'))
73
+
74
+ def package(ext='')
75
+ "pkg/rocco-#{SPEC.version}" + ext
76
+ end
77
+
78
+ desc 'Build packages'
79
+ task :package => %w[.gem .tar.gz].map {|e| package(e)}
80
+
81
+ desc 'Build and install as local gem'
82
+ task :install => package('.gem') do
83
+ sh "gem install #{package('.gem')}"
84
+ end
85
+
86
+ directory 'pkg/'
87
+
88
+ file package('.gem') => %w[pkg/ rocco.gemspec] + SPEC.files do |f|
89
+ sh "gem build rocco.gemspec"
90
+ mv File.basename(f.name), f.name
91
+ end
92
+
93
+ file package('.tar.gz') => %w[pkg/] + SPEC.files do |f|
94
+ sh "git archive --format=tar HEAD | gzip > #{f.name}"
95
+ end
96
+ end
97
+
98
+ # GEMSPEC ===================================================================
99
+
100
+ file 'rocco.gemspec' => FileList['{lib,test,bin}/**','Rakefile'] do |f|
101
+ version = File.read('lib/rocco.rb')[/VERSION = '(.*)'/] && $1
102
+ date = Time.now.strftime("%Y-%m-%d")
103
+ spec = File.
104
+ read(f.name).
105
+ sub(/s\.version\s*=\s*'.*'/, "s.version = '#{version}'")
106
+ parts = spec.split(" # = MANIFEST =\n")
107
+ files = `git ls-files`.
108
+ split("\n").sort.reject{ |file| file =~ /^\./ }.
109
+ map{ |file| " #{file}" }.join("\n")
110
+ parts[1] = " s.files = %w[\n#{files}\n ]\n"
111
+ spec = parts.join(" # = MANIFEST =\n")
112
+ spec.sub!(/s.date = '.*'/, "s.date = '#{date}'")
113
+ File.open(f.name, 'w') { |io| io.write(spec) }
114
+ puts "#{f.name} #{version} (#{date})"
115
+ end
data/bin/rocco ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env ruby
2
+ #/ Usage: rocco [-l <lang>] [-c <chars>] [-o <dir>] <file>...
3
+ #/ Generate literate-programming-style documentation for Ruby source <file>s.
4
+ #/
5
+ #/ Options:
6
+ #/ -l, --language=<lang> The Pygments lexer to use to highlight code
7
+ #/ -c, --comment-chars=<chars>
8
+ #/ The string to recognize as a comment marker
9
+ #/ -o, --output=<dir> Directory where generated HTML files are written
10
+ #/ -t, --template=<path> The file to use as template when rendering HTML
11
+ #/ --help Show this help message
12
+
13
+ require 'optparse'
14
+ require 'fileutils'
15
+
16
+ # Write usage message to stdout and exit.
17
+ def usage(stream=$stderr, status=1)
18
+ stream.puts File.readlines(__FILE__).
19
+ grep(/^#\//).
20
+ map { |line| line.sub(/^#. ?/, '') }.
21
+ join
22
+ exit status
23
+ end
24
+
25
+ # Like `Kernel#abort` but writes a note encouraging the user to consult
26
+ # `rocco --help` for more information.
27
+ def abort_with_note(message=nil)
28
+ $stderr.puts message if message
29
+ abort "See `rocco --help' for usage information."
30
+ end
31
+
32
+ # Parse command line options, aborting if anything goes wrong.
33
+ output_dir = '.'
34
+ sources = []
35
+ options = {}
36
+ ARGV.options { |o|
37
+ o.program_name = File.basename($0)
38
+ o.on("-o", "--output=DIR") { |dir| output_dir = dir }
39
+ o.on("-l", "--language=LANG") { |lang| options[:language] = lang }
40
+ o.on("-c", "--comment-chars=CHARS") { |chars| options[:comment_chars] = Regexp.escape(chars) }
41
+ o.on("-t", "--template=TEMPLATE") { |template| options[:template_file] = template }
42
+ o.on_tail("-h", "--help") { usage($stdout, 0) }
43
+ o.parse!
44
+ } or abort_with_note
45
+
46
+ # Use http://pygments.appspot.com in case `pygmentize(1)` isn't available.
47
+ if ! ENV['PATH'].split(':').any? { |dir| File.exist?("#{dir}/pygmentize") }
48
+ unless options[:webservice]
49
+ $stderr.puts "pygmentize not in PATH; using pygments.appspot.com instead"
50
+ options[:webservice] = true
51
+ end
52
+ end
53
+
54
+ # Eat sources from ARGV.
55
+ sources << ARGV.shift while ARGV.any?
56
+
57
+ # Make sure we have some files to work with.
58
+ if sources.empty?
59
+ abort_with_note "#{File.basename($0)}: no input <file>s given"
60
+ end
61
+
62
+ # What a fucking mess. Most of this is duplicated in rocco.rb too.
63
+ libdir = File.expand_path('../../lib', __FILE__).sub(/^#{Dir.pwd}\//, '')
64
+ begin
65
+ require 'rdiscount'
66
+ require 'rocco'
67
+ rescue LoadError
68
+ case $!.to_s
69
+ when /rdiscount/
70
+ if !defined?(Gem)
71
+ warn "warn: #$!. trying again with rubygems"
72
+ require 'rubygems'
73
+ retry
74
+ else
75
+ require 'bluecloth'
76
+ Markdown = BlueCloth
77
+ $LOADED_FEATURES << 'rdiscount.rb'
78
+ retry
79
+ end
80
+ when /rocco/
81
+ if !$:.include?(libdir)
82
+ warn "warn: #$!. trying again with #{libdir} on load path"
83
+ $:.unshift(libdir)
84
+ retry
85
+ end
86
+ end
87
+ raise
88
+ end
89
+
90
+ # Run each file through Rocco and write output.
91
+ sources.each do |filename|
92
+ rocco = Rocco.new(filename, sources, options)
93
+ dest = File.join(output_dir, (filename.split('.')[0..-2].empty? ? filename : filename.split('.')[0..-2].join('.')) + '.html')
94
+ puts "rocco: #{filename} -> #{dest}"
95
+ FileUtils.mkdir_p File.dirname(dest)
96
+ File.open(dest, 'wb') { |fd| fd.write(rocco.to_html) }
97
+ end
@@ -0,0 +1,46 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
5
+ <title>{{ title }}</title>
6
+ <link rel="stylesheet" href="http://jashkenas.github.com/docco/resources/docco.css">
7
+ </head>
8
+ <body>
9
+ <div id='container'>
10
+ <div id="background"></div>
11
+ {{#sources?}}
12
+ <div id="jump_to">
13
+ Jump To &hellip;
14
+ <div id="jump_wrapper">
15
+ <div id="jump_page">
16
+ {{#sources}}
17
+ <a class="source" href="{{ url }}">{{ basename }}</a>
18
+ {{/sources}}
19
+ </div>
20
+ </div>
21
+ </div>
22
+ {{/sources?}}
23
+ <table cellspacing=0 cellpadding=0>
24
+ <thead>
25
+ <tr>
26
+ <th class=docs><h1>{{ title }}</h1></th>
27
+ <th class=code></th>
28
+ </tr>
29
+ </thead>
30
+ <tbody>
31
+ {{#sections}}
32
+ <tr id='section-{{ num }}'>
33
+ <td class=docs>
34
+ <div class="pilwrap">
35
+ <a class="pilcrow" href="#section-{{ num }}">&#182;</a>
36
+ </div>
37
+ {{{ docs }}}
38
+ </td>
39
+ <td class=code>
40
+ <div class='highlight'><pre>{{{ code }}}</pre></div>
41
+ </td>
42
+ </tr>
43
+ {{/sections}}
44
+ </table>
45
+ </div>
46
+ </body>
@@ -0,0 +1,47 @@
1
+ require 'mustache'
2
+
3
+ class Rocco::Layout < Mustache
4
+ self.template_path = File.dirname(__FILE__)
5
+
6
+ def initialize(doc, file=nil)
7
+ @doc = doc
8
+ if not file.nil?
9
+ Rocco::Layout.template_file = file
10
+ end
11
+ end
12
+
13
+ def title
14
+ File.basename(@doc.file)
15
+ end
16
+
17
+ def sections
18
+ num = 0
19
+ @doc.sections.map do |docs,code|
20
+ {
21
+ :docs => docs,
22
+ :docs? => !docs.empty?,
23
+ :header? => /^<h.>.+<\/h.>$/.match( docs ),
24
+
25
+ :code => code,
26
+ :code? => !code.empty?,
27
+
28
+ :empty? => ( code.empty? && docs.empty? ),
29
+ :num => (num += 1)
30
+ }
31
+ end
32
+ end
33
+
34
+ def sources?
35
+ @doc.sources.length > 1
36
+ end
37
+
38
+ def sources
39
+ @doc.sources.sort.map do |source|
40
+ {
41
+ :path => source,
42
+ :basename => File.basename(source),
43
+ :url => File.basename(source).split('.')[0..-2].join('.') + '.html'
44
+ }
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,120 @@
1
+ #### Rocco Rake Tasks
2
+ #
3
+ # To use the Rocco Rake tasks, require `rocco/tasks` in your `Rakefile`
4
+ # and define a Rake task with `rocco_task`. In its simplest form, `rocco_task`
5
+ # takes the path to a destination directory where HTML docs should be built:
6
+ #
7
+ # require 'rocco/tasks'
8
+ #
9
+ # desc "Build Rocco Docs"
10
+ # Rocco::make 'docs/'
11
+ #
12
+ # This creates a `:rocco` rake task, which can then be run with:
13
+ #
14
+ # rake rocco
15
+ #
16
+ # It's a good idea to guard against Rocco not being available, since your
17
+ # Rakefile will fail to load otherwise. Consider doing something like this,
18
+ # so that your Rakefile will still work
19
+ #
20
+ # begin
21
+ # require 'rocco/tasks'
22
+ # Rocco::make 'docs/'
23
+ # rescue LoadError
24
+ # warn "#$! -- rocco tasks not loaded."
25
+ # task :rocco
26
+ # end
27
+ #
28
+ # It's also possible to pass a glob pattern:
29
+ #
30
+ # Rocco::make 'html/', 'lib/thing/**/*.rb'
31
+ #
32
+ # Or a list of glob patterns:
33
+ #
34
+ # Rocco::make 'html/', ['lib/thing.rb', 'lib/thing/*.rb']
35
+ #
36
+ # Finally, it is also possible to specify which Pygments language you would
37
+ # like to use to highlight the code, as well as the comment characters for the
38
+ # language in the `options` hash:
39
+ #
40
+ # Rocco::make 'html/', 'lib/thing/**/*.rb', {
41
+ # :language => 'io',
42
+ # :comment_chars => '#'
43
+ # }
44
+ #
45
+
46
+ # Might be nice to defer this until we actually need to build docs but this
47
+ # will have to do for now.
48
+ require 'rocco'
49
+
50
+ # Reopen the Rocco class and add a `make` class method. This is a simple bit
51
+ # of sugar over `Rocco::Task.new`. If you want your Rake task to be named
52
+ # something other than `:rocco`, you can use `Rocco::Task` directly.
53
+ class Rocco
54
+ def self.make(dest='docs/', source_files='lib/**/*.rb', options={})
55
+ Task.new(:rocco, dest, source_files, options)
56
+ end
57
+
58
+ # `Rocco::Task.new` takes a task name, the destination directory docs
59
+ # should be built under, and a source file pattern or file list.
60
+ class Task
61
+ def initialize(task_name, dest='docs/', sources='lib/**/*.rb', options={})
62
+ @name = task_name
63
+ @dest = dest[-1] == ?/ ? dest : "#{dest}/"
64
+ @sources = FileList[sources]
65
+ @options = options
66
+
67
+ # Make sure there's a `directory` task defined for our destination.
68
+ define_directory_task @dest
69
+
70
+ # Run over the source file list, constructing destination filenames
71
+ # and defining file tasks.
72
+ @sources.each do |source_file|
73
+ dest_file = File.basename(source_file).split('.')[0..-2].join('.') + '.html'
74
+ define_file_task source_file, "#{@dest}#{dest_file}"
75
+
76
+ # If `rake/clean` was required, add the generated files to the list.
77
+ # That way all Rocco generated are removed when running `rake clean`.
78
+ CLEAN.include "#{@dest}#{dest_file}" if defined? CLEAN
79
+ end
80
+ end
81
+
82
+ # Define the destination directory task and make the `:rocco` task depend
83
+ # on it. This causes the destination directory to be created if it doesn't
84
+ # already exist.
85
+ def define_directory_task(path)
86
+ directory path
87
+ task @name => path
88
+ end
89
+
90
+ # Setup a `file` task for a single Rocco output file (`dest_file`). It
91
+ # depends on the source file, the destination directory, and all of Rocco's
92
+ # internal source code, so that the destination file is rebuilt when any of
93
+ # those changes.
94
+ #
95
+ # You can run these tasks directly with Rake:
96
+ #
97
+ # rake docs/foo.html docs/bar.html
98
+ #
99
+ # ... would generate the `foo.html` and `bar.html` files but only if they
100
+ # don't already exist or one of their dependencies was changed.
101
+ def define_file_task(source_file, dest_file)
102
+ prerequisites = [@dest, source_file] + rocco_source_files
103
+ file dest_file => prerequisites do |f|
104
+ verbose { puts "rocco: #{source_file} -> #{dest_file}" }
105
+ rocco = Rocco.new(source_file, @sources.to_a, @options)
106
+ File.open(dest_file, 'wb') { |fd| fd.write(rocco.to_html) }
107
+ end
108
+ task @name => dest_file
109
+ end
110
+
111
+ # Return a `FileList` that includes all of Roccos source files. This causes
112
+ # output files to be regenerated properly when someone upgrades the Rocco
113
+ # library.
114
+ def rocco_source_files
115
+ libdir = File.expand_path('../..', __FILE__)
116
+ FileList["#{libdir}/rocco.rb", "#{libdir}/rocco/**"]
117
+ end
118
+
119
+ end
120
+ end
data/lib/rocco.rb ADDED
@@ -0,0 +1,324 @@
1
+ # **Rocco** is a Ruby port of [Docco][do], the quick-and-dirty,
2
+ # hundred-line-long, literate-programming-style documentation generator.
3
+ #
4
+ # Rocco reads Ruby source files and produces annotated source documentation
5
+ # in HTML format. Comments are formatted with [Markdown][md] and presented
6
+ # alongside syntax highlighted code so as to give an annotation effect.
7
+ # This page is the result of running Rocco against [its own source file][so].
8
+ #
9
+ # Most of this was written while waiting for [node.js][no] to build (so I
10
+ # could use Docco!). Docco's gorgeous HTML and CSS are taken verbatim.
11
+ # The main difference is that Rocco is written in Ruby instead of
12
+ # [CoffeeScript][co] and may be a bit easier to obtain and install in
13
+ # existing Ruby environments or where node doesn't run yet.
14
+ #
15
+ # Install Rocco with Rubygems:
16
+ #
17
+ # gem install rocco
18
+ #
19
+ # Once installed, the `rocco` command can be used to generate documentation
20
+ # for a set of Ruby source files:
21
+ #
22
+ # rocco lib/*.rb
23
+ #
24
+ # The HTML files are written to the current working directory.
25
+ #
26
+ # [no]: http://nodejs.org/
27
+ # [do]: http://jashkenas.github.com/docco/
28
+ # [co]: http://coffeescript.org/
29
+ # [md]: http://daringfireball.net/projects/markdown/
30
+ # [so]: http://github.com/rtomayko/rocco/blob/master/lib/rocco.rb#commit
31
+
32
+ #### Prerequisites
33
+
34
+ # We'll need a Markdown library. [RDiscount][rd], if we're lucky. Otherwise,
35
+ # issue a warning and fall back on using BlueCloth.
36
+ #
37
+ # [rd]: http://github.com/rtomayko/rdiscount
38
+ begin
39
+ require 'rdiscount'
40
+ rescue LoadError => boom
41
+ warn "WARNING: #{boom}. Trying bluecloth."
42
+ require 'bluecloth'
43
+ Markdown = BlueCloth
44
+ end
45
+
46
+ # We use [{{ mustache }}](http://defunkt.github.com/mustache/) for
47
+ # HTML templating.
48
+ require 'mustache'
49
+
50
+ # We use `Net::HTTP` to highlight code via <http://pygments.appspot.com>
51
+ require 'net/http'
52
+
53
+ # Code is run through [Pygments](http://pygments.org/) for syntax
54
+ # highlighting. If it's not installed, locally, use a webservice.
55
+ include FileTest
56
+ if !ENV['PATH'].split(':').any? { |dir| executable?("#{dir}/pygmentize") }
57
+ warn "WARNING: Pygments not found. Using webservice."
58
+ end
59
+
60
+ #### Public Interface
61
+
62
+ # `Rocco.new` takes a source `filename`, an optional list of source filenames
63
+ # for other documentation sources, an `options` hash, and an optional `block`.
64
+ # The `options` hash respects three members:
65
+ #
66
+ # * `:language`: specifies which Pygments lexer to use if one can't be
67
+ # auto-detected from the filename. _Defaults to `ruby`_.
68
+ #
69
+ # * `:comment_chars`, which specifies the comment characters of the
70
+ # target language. _Defaults to `#`_.
71
+ #
72
+ # * `:template_file`, which specifies a external template file to use
73
+ # when rendering the final, highlighted file via Mustache. _Defaults
74
+ # to `nil` (that is, Mustache will use `./lib/rocco/layout.mustache`)_.
75
+ #
76
+ class Rocco
77
+ VERSION = '0.5'
78
+
79
+ def initialize(filename, sources=[], options={}, &block)
80
+ @file = filename
81
+ @sources = sources
82
+
83
+ # When `block` is given, it must read the contents of the file using
84
+ # whatever means necessary and return it as a string. With no `block`,
85
+ # the file is read to retrieve data.
86
+ @data =
87
+ if block_given?
88
+ yield
89
+ else
90
+ File.read(filename)
91
+ end
92
+ defaults = {
93
+ :language => 'ruby',
94
+ :comment_chars => '#',
95
+ :template_file => nil
96
+ }
97
+ @options = defaults.merge(options)
98
+ @template_file = @options[:template_file]
99
+
100
+ # If we detect a language
101
+ if detect_language() != "text"
102
+ # then assign the detected language to `:language`
103
+ @options[:language] = detect_language()
104
+ # and look for some comment characters
105
+ @options[:comment_chars] = generate_comment_chars()
106
+ # If we didn't detect a language, but the user provided one, use it
107
+ # to look around for comment characters to override the default.
108
+ elsif @options[:language] != defaults[:language]
109
+ @options[:comment_chars] = generate_comment_chars()
110
+ end
111
+ @comment_pattern = Regexp.new("^\\s*#{@options[:comment_chars]}\s?")
112
+
113
+ @sections = highlight(split(parse(@data)))
114
+ end
115
+
116
+ # Returns `true` if `pygmentize` is available locally, `false` otherwise.
117
+ def pygmentize?
118
+ # Memoize the result, we'll call this a few times
119
+ @_pygmentize ||= ENV['PATH'].split(':').any? { |dir| executable?("#{dir}/pygmentize") }
120
+ end
121
+
122
+ # If `pygmentize` is available, we can use it to autodetect a file's
123
+ # language based on its filename. Filenames without extensions, or with
124
+ # extensions that `pygmentize` doesn't understand will return `text`.
125
+ # We'll also return `text` if `pygmentize` isn't available.
126
+ #
127
+ # We'll memoize the result, as we'll call this a few times.
128
+ def detect_language
129
+ @_language ||= begin
130
+ if pygmentize?
131
+ lang = %x[pygmentize -N #{@file}].strip!
132
+ else
133
+ "text"
134
+ end
135
+ end
136
+ end
137
+
138
+ # Given a file's language, we should be able to autopopulate the
139
+ # `comment_chars` variables for single-line comments. If we don't
140
+ # have comment characters on record for a given language, we'll
141
+ # use the user-provided `:comment_char` option (which defaults to
142
+ # `#`).
143
+ #
144
+ # Comment characters are listed as:
145
+ #
146
+ # { :single => "//", :multi_start => "/**", :multi_middle => "*", :multi_end => "*/" }
147
+ #
148
+ # `:single` denotes the leading character of a single-line comment.
149
+ # `:multi_start` denotes the string that should appear alone on a
150
+ # line of code to begin a block of documentation. `:multi_middle`
151
+ # denotes the leading character of block comment content, and
152
+ # `:multi_end` is the string that ought appear alone on a line to
153
+ # close a block of documentation. That is:
154
+ #
155
+ # /** [:multi][:start]
156
+ # * [:multi][:middle]
157
+ # * [:multi][:middle]
158
+ # * [:multi][:middle]
159
+ # */ [:multi][:end]
160
+ #
161
+ # If a language only has one type of comment, the missing type
162
+ # should be assigned `nil`.
163
+ #
164
+ # At the moment, we're only returning `:single`. Consider this
165
+ # groundwork for block comment parsing.
166
+ def generate_comment_chars
167
+ @_commentchar ||= begin
168
+ language = @options[:language]
169
+ comment_styles = {
170
+ "bash" => { :single => "#", :multi => nil },
171
+ "c" => { :single => "//", :multi => { :start => "/**", :middle => "*", :end => "*/" } },
172
+ "coffee-script" => { :single => "#", :multi => { :start => "###", :middle => nil, :end => "###" } },
173
+ "cpp" => { :single => "//", :multi => { :start => "/**", :middle => "*", :end => "*/" } },
174
+ "java" => { :single => "//", :multi => { :start => "/**", :middle => "*", :end => "*/" } },
175
+ "js" => { :single => "//", :multi => { :start => "/**", :middle => "*", :end => "*/" } },
176
+ "php" => { :single => "//", :multi => { :start => "/**", :middle => "*", :end => "*/" } },
177
+ "lua" => { :single => "--", :multi => nil },
178
+ "python" => { :single => "#", :multi => { :start => '"""', :middle => nil, :end => '"""' } },
179
+ "ruby" => { :single => "#", :multi => nil },
180
+ "scheme" => { :single => ";;", :multi => nil },
181
+ }
182
+
183
+ if comment_styles[language]
184
+ comment_styles[language][:single]
185
+ else
186
+ @options[:comment_chars]
187
+ end
188
+ end
189
+ end
190
+
191
+ # The filename as given to `Rocco.new`.
192
+ attr_reader :file
193
+
194
+ # The merged options array
195
+ attr_reader :options
196
+
197
+ # A list of two-tuples representing each *section* of the source file. Each
198
+ # item in the list has the form: `[docs_html, code_html]`, where both
199
+ # elements are strings containing the documentation and source code HTML,
200
+ # respectively.
201
+ attr_reader :sections
202
+
203
+ # A list of all source filenames included in the documentation set. Useful
204
+ # for building an index of other files.
205
+ attr_reader :sources
206
+
207
+ # An absolute path to a file that ought be used as a template for the
208
+ # HTML-rendered documentation.
209
+ attr_reader :template_file
210
+
211
+ # Generate HTML output for the entire document.
212
+ require 'rocco/layout'
213
+ def to_html
214
+ Rocco::Layout.new(self, @template_file).render
215
+ end
216
+
217
+ #### Internal Parsing and Highlighting
218
+
219
+ # Parse the raw file data into a list of two-tuples. Each tuple has the
220
+ # form `[docs, code]` where both elements are arrays containing the
221
+ # raw lines parsed from the input file. The first line is ignored if it
222
+ # is a shebang line.
223
+ def parse(data)
224
+ sections = []
225
+ docs, code = [], []
226
+ lines = data.split("\n")
227
+ lines.shift if lines[0] =~ /^\#\!/
228
+ lines.each do |line|
229
+ case line
230
+ when @comment_pattern
231
+ if code.any?
232
+ sections << [docs, code]
233
+ docs, code = [], []
234
+ end
235
+ docs << line
236
+ else
237
+ code << line
238
+ end
239
+ end
240
+ sections << [docs, code] if docs.any? || code.any?
241
+ sections
242
+ end
243
+
244
+ # Take the list of paired *sections* two-tuples and split into two
245
+ # separate lists: one holding the comments with leaders removed and
246
+ # one with the code blocks.
247
+ def split(sections)
248
+ docs_blocks, code_blocks = [], []
249
+ sections.each do |docs,code|
250
+ docs_blocks << docs.map { |line| line.sub(@comment_pattern, '') }.join("\n")
251
+ code_blocks << code.map do |line|
252
+ tabs = line.match(/^(\t+)/)
253
+ tabs ? line.sub(/^\t+/, ' ' * tabs.captures[0].length) : line
254
+ end.join("\n")
255
+ end
256
+ [docs_blocks, code_blocks]
257
+ end
258
+
259
+ # Take the result of `split` and apply Markdown formatting to comments and
260
+ # syntax highlighting to source code.
261
+ def highlight(blocks)
262
+ docs_blocks, code_blocks = blocks
263
+
264
+ # Combine all docs blocks into a single big markdown document with section
265
+ # dividers and run through the Markdown processor. Then split it back out
266
+ # into separate sections.
267
+ markdown = docs_blocks.join("\n\n##### DIVIDER\n\n")
268
+ docs_html = Markdown.new(markdown, :smart).
269
+ to_html.
270
+ split(/\n*<h5>DIVIDER<\/h5>\n*/m)
271
+
272
+ # Combine all code blocks into a single big stream and run through either
273
+ # `pygmentize(1)` or <http://pygments.appspot.com>
274
+ code_stream = code_blocks.join("\n\n#{@options[:comment_chars]} DIVIDER\n\n")
275
+
276
+ if pygmentize?
277
+ code_html = highlight_pygmentize(code_stream)
278
+ else
279
+ code_html = highlight_webservice(code_stream)
280
+ end
281
+
282
+ # Do some post-processing on the pygments output to split things back
283
+ # into sections and remove partial `<pre>` blocks.
284
+ code_html = code_html.
285
+ split(/\n*<span class="c.?">#{@options[:comment_chars]} DIVIDER<\/span>\n*/m).
286
+ map { |code| code.sub(/\n?<div class="highlight"><pre>/m, '') }.
287
+ map { |code| code.sub(/\n?<\/pre><\/div>\n/m, '') }
288
+
289
+ # Lastly, combine the docs and code lists back into a list of two-tuples.
290
+ docs_html.zip(code_html)
291
+ end
292
+
293
+ # We `popen` a read/write pygmentize process in the parent and
294
+ # then fork off a child process to write the input.
295
+ def highlight_pygmentize(code)
296
+ code_html = nil
297
+ open("|pygmentize -l #{@options[:language]} -O encoding=utf-8 -f html", 'r+') do |fd|
298
+ pid =
299
+ fork {
300
+ fd.close_read
301
+ fd.write code
302
+ fd.close_write
303
+ exit!
304
+ }
305
+ fd.close_write
306
+ code_html = fd.read
307
+ fd.close_read
308
+ Process.wait(pid)
309
+ end
310
+
311
+ code_html
312
+ end
313
+
314
+ # Pygments is not one of those things that's trivial for a ruby user to install,
315
+ # so we'll fall back on a webservice to highlight the code if it isn't available.
316
+ def highlight_webservice(code)
317
+ Net::HTTP.post_form(
318
+ URI.parse('http://pygments.appspot.com/'),
319
+ {'lang' => @options[:language], 'code' => code}
320
+ ).body
321
+ end
322
+ end
323
+
324
+ # And that's it.
data/rocco.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+
5
+ s.name = 'rocco-obb'
6
+ s.version = '0.5'
7
+ s.date = '2010-09-10'
8
+
9
+ s.description = "Docco in Ruby"
10
+ s.summary = s.description
11
+
12
+ s.authors = ["Ryan Tomayko"]
13
+ s.email = "r@tomayko.com"
14
+
15
+ # = MANIFEST =
16
+ s.files = %w[
17
+ COPYING
18
+ README
19
+ Rakefile
20
+ bin/rocco
21
+ lib/rocco.rb
22
+ lib/rocco/layout.mustache
23
+ lib/rocco/layout.rb
24
+ lib/rocco/tasks.rb
25
+ rocco.gemspec
26
+ ]
27
+ # = MANIFEST =
28
+
29
+ s.executables = ["rocco"]
30
+
31
+ s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
32
+ s.add_dependency 'rdiscount'
33
+ s.add_dependency 'mustache'
34
+
35
+ s.has_rdoc = false
36
+ s.homepage = "http://github.com/oneblackbear/rocco/"
37
+ s.require_paths = %w[lib]
38
+ s.rubygems_version = '1.1.1'
39
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rocco-obb
3
+ version: !ruby/object:Gem::Version
4
+ hash: 1
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 5
9
+ version: "0.5"
10
+ platform: ruby
11
+ authors:
12
+ - Ryan Tomayko
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-10 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rdiscount
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: mustache
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: Docco in Ruby
49
+ email: r@tomayko.com
50
+ executables:
51
+ - rocco
52
+ extensions: []
53
+
54
+ extra_rdoc_files: []
55
+
56
+ files:
57
+ - COPYING
58
+ - README
59
+ - Rakefile
60
+ - bin/rocco
61
+ - lib/rocco.rb
62
+ - lib/rocco/layout.mustache
63
+ - lib/rocco/layout.rb
64
+ - lib/rocco/tasks.rb
65
+ - rocco.gemspec
66
+ has_rdoc: true
67
+ homepage: http://github.com/oneblackbear/rocco/
68
+ licenses: []
69
+
70
+ post_install_message:
71
+ rdoc_options: []
72
+
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ requirements: []
94
+
95
+ rubyforge_project:
96
+ rubygems_version: 1.3.7
97
+ signing_key:
98
+ specification_version: 2
99
+ summary: Docco in Ruby
100
+ test_files: []
101
+