rocco-obb 0.5

Sign up to get free protection for your applications and to get access to all the features.
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
+