rocco 0.5 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.md +63 -0
- data/Rakefile +5 -5
- data/bin/rocco +4 -1
- data/lib/rocco.rb +284 -36
- data/lib/rocco/layout.mustache +4 -4
- data/lib/rocco/layout.rb +24 -8
- data/lib/rocco/tasks.rb +2 -1
- data/rocco.gemspec +18 -4
- data/test/fixtures/issue10.iso-8859-1.rb +1 -0
- data/test/fixtures/issue10.utf-8.rb +1 -0
- data/test/helper.rb +20 -0
- data/test/suite.rb +5 -0
- data/test/test_basics.rb +63 -0
- data/test/test_block_comments.rb +101 -0
- data/test/test_comment_normalization.rb +25 -0
- data/test/test_commentchar_detection.rb +28 -0
- data/test/test_descriptive_section_names.rb +30 -0
- data/test/test_language_detection.rb +27 -0
- data/test/test_reported_issues.rb +86 -0
- data/test/test_skippable_lines.rb +64 -0
- data/test/test_source_list.rb +29 -0
- metadata +26 -4
data/CHANGES.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
CHANGES
|
2
|
+
=======
|
3
|
+
|
4
|
+
0.6 (2011-03-05)
|
5
|
+
----------------
|
6
|
+
|
7
|
+
This release brought to you almost entirely
|
8
|
+
by [mikewest](http://github.com/mikewest).
|
9
|
+
|
10
|
+
### Features
|
11
|
+
|
12
|
+
* Added `-t`/`--template` CLI option that allows you to specify a Mustache
|
13
|
+
template that ought be used when rendering the final documentation.
|
14
|
+
(Issue #16)
|
15
|
+
|
16
|
+
* More variables in templates:
|
17
|
+
* `docs?`: True if `docs` contains text of any sort, False if it's empty.
|
18
|
+
* `code?`: True if `code` contains text of any sort, False if it's empty.
|
19
|
+
* `empty?`: True if both `code` and `docs` are empty. False otherwise.
|
20
|
+
* `header?`: True if `docs` contains _only_ a HTML header. False otherwise.
|
21
|
+
|
22
|
+
* Test suite! (Run `rake test`)
|
23
|
+
|
24
|
+
* Autodetect file's language if Pygments is installed locally (Issue #19)
|
25
|
+
|
26
|
+
* Autopopulate comment characters for known languages (Issue #20)
|
27
|
+
|
28
|
+
* Correctly parse block comments (Issue #22)
|
29
|
+
|
30
|
+
* Stripping encoding definitions from Ruby and Python files in the same
|
31
|
+
way we strip shebang lines (Issue #21)
|
32
|
+
|
33
|
+
* Adjusting section IDs to contain descriptive test from headers. A header
|
34
|
+
section's ID might be `section-Header_text_goes_here` for friendlier URLs.
|
35
|
+
Other section IDs will remain the same (`section-2` will stay
|
36
|
+
`section-2`). (Issue #28)
|
37
|
+
|
38
|
+
### Bugs Fixed
|
39
|
+
|
40
|
+
* Docco's CSS changed: we updated Rocco's HTML accordingly, and pinned
|
41
|
+
the CSS file to Docco's 0.3.0 tag. (Issues #12 and #23)
|
42
|
+
|
43
|
+
* Fixed code highlighting for shell scripts (among others) (Issue #13)
|
44
|
+
|
45
|
+
* Fixed buggy regex for comment char stripping (Issue #15)
|
46
|
+
|
47
|
+
* Specifying UTF-8 encoding for Pygments (Issue #10)
|
48
|
+
|
49
|
+
* Extensionless file support (thanks to [Vasily Polovnyov][vast] for the
|
50
|
+
fix!) (Issue #24)
|
51
|
+
|
52
|
+
* Fixing language support for Pygments webservice (Issue #11)
|
53
|
+
|
54
|
+
* The source jumplist now generates correctly relative URLs (Issue #26)
|
55
|
+
|
56
|
+
* Fixed an issue with using mustache's `template_path=` incorrectly.
|
57
|
+
|
58
|
+
[vast]: https://github.com/vast
|
59
|
+
|
60
|
+
0.5
|
61
|
+
---
|
62
|
+
|
63
|
+
Rocco 0.5 emerged from the hazy mists, complete and unfettered by history.
|
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift 'lib'
|
|
3
3
|
require 'rake/testtask'
|
4
4
|
require 'rake/clean'
|
5
5
|
|
6
|
-
task :default => [:sup, :docs]
|
6
|
+
task :default => [:sup, :docs, :test]
|
7
7
|
|
8
8
|
desc 'Holla'
|
9
9
|
task :sup do
|
@@ -16,7 +16,7 @@ end
|
|
16
16
|
|
17
17
|
desc 'Run tests (default)'
|
18
18
|
Rake::TestTask.new(:test) do |t|
|
19
|
-
t.test_files = FileList['test
|
19
|
+
t.test_files = FileList['test/suite.rb']
|
20
20
|
t.ruby_opts = ['-rubygems'] if defined? Gem
|
21
21
|
end
|
22
22
|
|
@@ -30,12 +30,12 @@ directory 'docs/'
|
|
30
30
|
|
31
31
|
desc 'Build docs and open in browser for the reading'
|
32
32
|
task :read => :docs do
|
33
|
-
sh 'open docs/rocco.html'
|
33
|
+
sh 'open docs/lib/rocco.html'
|
34
34
|
end
|
35
35
|
|
36
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
|
37
|
+
file 'docs/index.html' => 'docs/lib/rocco.html' do |f|
|
38
|
+
cp 'docs/lib/rocco.html', 'docs/index.html', :preserve => true
|
39
39
|
end
|
40
40
|
task :docs => 'docs/index.html'
|
41
41
|
CLEAN.include 'docs/index.html'
|
data/bin/rocco
CHANGED
@@ -7,6 +7,7 @@
|
|
7
7
|
#/ -c, --comment-chars=<chars>
|
8
8
|
#/ The string to recognize as a comment marker
|
9
9
|
#/ -o, --output=<dir> Directory where generated HTML files are written
|
10
|
+
#/ -t, --template=<path> The file to use as template when rendering HTML
|
10
11
|
#/ --help Show this help message
|
11
12
|
|
12
13
|
require 'optparse'
|
@@ -37,6 +38,7 @@ ARGV.options { |o|
|
|
37
38
|
o.on("-o", "--output=DIR") { |dir| output_dir = dir }
|
38
39
|
o.on("-l", "--language=LANG") { |lang| options[:language] = lang }
|
39
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 }
|
40
42
|
o.on_tail("-h", "--help") { usage($stdout, 0) }
|
41
43
|
o.parse!
|
42
44
|
} or abort_with_note
|
@@ -88,7 +90,8 @@ end
|
|
88
90
|
# Run each file through Rocco and write output.
|
89
91
|
sources.each do |filename|
|
90
92
|
rocco = Rocco.new(filename, sources, options)
|
91
|
-
dest =
|
93
|
+
dest = filename.sub(Regexp.new("#{File.extname(filename)}$"),".html")
|
94
|
+
dest = File.join(output_dir, dest) if output_dir != '.'
|
92
95
|
puts "rocco: #{filename} -> #{dest}"
|
93
96
|
FileUtils.mkdir_p File.dirname(dest)
|
94
97
|
File.open(dest, 'wb') { |fd| fd.write(rocco.to_html) }
|
data/lib/rocco.rb
CHANGED
@@ -61,37 +61,82 @@ end
|
|
61
61
|
|
62
62
|
# `Rocco.new` takes a source `filename`, an optional list of source filenames
|
63
63
|
# for other documentation sources, an `options` hash, and an optional `block`.
|
64
|
-
# The `options` hash respects
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
# to
|
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
|
+
#
|
71
76
|
class Rocco
|
72
|
-
VERSION = '0.
|
77
|
+
VERSION = '0.6'
|
73
78
|
|
74
79
|
def initialize(filename, sources=[], options={}, &block)
|
75
|
-
@file
|
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.
|
76
86
|
@data =
|
77
87
|
if block_given?
|
78
88
|
yield
|
79
89
|
else
|
80
90
|
File.read(filename)
|
81
91
|
end
|
92
|
+
|
82
93
|
defaults = {
|
83
94
|
:language => 'ruby',
|
84
95
|
:comment_chars => '#',
|
96
|
+
:template_file => nil
|
85
97
|
}
|
86
98
|
@options = defaults.merge(options)
|
87
|
-
|
88
|
-
|
99
|
+
|
100
|
+
# If we detect a language
|
101
|
+
if detect_language() != "text"
|
102
|
+
# then assign the detected language to `:language`, and look for
|
103
|
+
# comment characters based on that language
|
104
|
+
@options[:language] = detect_language()
|
105
|
+
@options[:comment_chars] = generate_comment_chars()
|
106
|
+
|
107
|
+
# If we didn't detect a language, but the user provided one, use it
|
108
|
+
# to look around for comment characters to override the default.
|
109
|
+
elsif @options[:language] != defaults[:language]
|
110
|
+
@options[:comment_chars] = generate_comment_chars()
|
111
|
+
|
112
|
+
# If neither is true, then convert the default comment character string
|
113
|
+
# into the comment_char syntax (we'll discuss that syntax in detail when
|
114
|
+
# we get to `generate_comment_chars()` in a moment.
|
115
|
+
else
|
116
|
+
@options[:comment_chars] = {
|
117
|
+
:single => @options[:comment_chars],
|
118
|
+
:multi => nil
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
# Turn `:comment_chars` into a regex matching a series of spaces, the
|
123
|
+
# `:comment_chars` string, and the an optional space. We'll use that
|
124
|
+
# to detect single-line comments.
|
125
|
+
@comment_pattern =
|
126
|
+
Regexp.new("^\\s*#{@options[:comment_chars][:single]}\s?")
|
127
|
+
|
128
|
+
# `parse()` the file contents stored in `@data`. Run the result through
|
129
|
+
# `split()` and that result through `highlight()` to generate the final
|
130
|
+
# section list.
|
89
131
|
@sections = highlight(split(parse(@data)))
|
90
132
|
end
|
91
133
|
|
92
134
|
# The filename as given to `Rocco.new`.
|
93
135
|
attr_reader :file
|
94
136
|
|
137
|
+
# The merged options array
|
138
|
+
attr_reader :options
|
139
|
+
|
95
140
|
# A list of two-tuples representing each *section* of the source file. Each
|
96
141
|
# item in the list has the form: `[docs_html, code_html]`, where both
|
97
142
|
# elements are strings containing the documentation and source code HTML,
|
@@ -105,34 +150,207 @@ class Rocco
|
|
105
150
|
# Generate HTML output for the entire document.
|
106
151
|
require 'rocco/layout'
|
107
152
|
def to_html
|
108
|
-
Rocco::Layout.new(self).render
|
153
|
+
Rocco::Layout.new(self, @options[:template_file]).render
|
154
|
+
end
|
155
|
+
|
156
|
+
# Helper Functions
|
157
|
+
# ----------------
|
158
|
+
|
159
|
+
# Returns `true` if `pygmentize` is available locally, `false` otherwise.
|
160
|
+
def pygmentize?
|
161
|
+
@_pygmentize ||= ENV['PATH'].split(':').
|
162
|
+
any? { |dir| executable?("#{dir}/pygmentize") }
|
163
|
+
end
|
164
|
+
|
165
|
+
# If `pygmentize` is available, we can use it to autodetect a file's
|
166
|
+
# language based on its filename. Filenames without extensions, or with
|
167
|
+
# extensions that `pygmentize` doesn't understand will return `text`.
|
168
|
+
# We'll also return `text` if `pygmentize` isn't available.
|
169
|
+
#
|
170
|
+
# We'll memoize the result, as we'll call this a few times.
|
171
|
+
def detect_language
|
172
|
+
@_language ||=
|
173
|
+
if pygmentize?
|
174
|
+
%x[pygmentize -N #{@file}].strip!
|
175
|
+
else
|
176
|
+
"text"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Given a file's language, we should be able to autopopulate the
|
181
|
+
# `comment_chars` variables for single-line comments. If we don't
|
182
|
+
# have comment characters on record for a given language, we'll
|
183
|
+
# use the user-provided `:comment_char` option (which defaults to
|
184
|
+
# `#`).
|
185
|
+
#
|
186
|
+
# Comment characters are listed as:
|
187
|
+
#
|
188
|
+
# { :single => "//",
|
189
|
+
# :multi_start => "/**",
|
190
|
+
# :multi_middle => "*",
|
191
|
+
# :multi_end => "*/" }
|
192
|
+
#
|
193
|
+
# `:single` denotes the leading character of a single-line comment.
|
194
|
+
# `:multi_start` denotes the string that should appear alone on a
|
195
|
+
# line of code to begin a block of documentation. `:multi_middle`
|
196
|
+
# denotes the leading character of block comment content, and
|
197
|
+
# `:multi_end` is the string that ought appear alone on a line to
|
198
|
+
# close a block of documentation. That is:
|
199
|
+
#
|
200
|
+
# /** [:multi][:start]
|
201
|
+
# * [:multi][:middle]
|
202
|
+
# ...
|
203
|
+
# * [:multi][:middle]
|
204
|
+
# */ [:multi][:end]
|
205
|
+
#
|
206
|
+
# If a language only has one type of comment, the missing type
|
207
|
+
# should be assigned `nil`.
|
208
|
+
#
|
209
|
+
# At the moment, we're only returning `:single`. Consider this
|
210
|
+
# groundwork for block comment parsing.
|
211
|
+
COMMENT_STYLES = {
|
212
|
+
"bash" => { :single => "#", :multi => nil },
|
213
|
+
"c" => {
|
214
|
+
:single => "//",
|
215
|
+
:multi => { :start => "/**", :middle => "*", :end => "*/" }
|
216
|
+
},
|
217
|
+
"coffee-script" => {
|
218
|
+
:single => "#",
|
219
|
+
:multi => { :start => "###", :middle => nil, :end => "###" }
|
220
|
+
},
|
221
|
+
"cpp" => {
|
222
|
+
:single => "//",
|
223
|
+
:multi => { :start => "/**", :middle => "*", :end => "*/" }
|
224
|
+
},
|
225
|
+
"css" => {
|
226
|
+
:single => nil,
|
227
|
+
:multi => { :start => "/**", :middle => "*", :end => "*/" }
|
228
|
+
},
|
229
|
+
"java" => {
|
230
|
+
:single => "//",
|
231
|
+
:multi => { :start => "/**", :middle => "*", :end => "*/" }
|
232
|
+
},
|
233
|
+
"js" => {
|
234
|
+
:single => "//",
|
235
|
+
:multi => { :start => "/**", :middle => "*", :end => "*/" }
|
236
|
+
},
|
237
|
+
"lua" => {
|
238
|
+
:single => "--",
|
239
|
+
:multi => nil
|
240
|
+
},
|
241
|
+
"python" => {
|
242
|
+
:single => "#",
|
243
|
+
:multi => { :start => '"""', :middle => nil, :end => '"""' }
|
244
|
+
},
|
245
|
+
"rb" => {
|
246
|
+
:single => "#",
|
247
|
+
:multi => { :start => '=begin', :middle => nil, :end => '=end' }
|
248
|
+
},
|
249
|
+
"scheme" => { :single => ";;", :multi => nil },
|
250
|
+
}
|
251
|
+
|
252
|
+
def generate_comment_chars
|
253
|
+
@_commentchar ||=
|
254
|
+
if COMMENT_STYLES[@options[:language]]
|
255
|
+
COMMENT_STYLES[@options[:language]]
|
256
|
+
else
|
257
|
+
{ :single => @options[:comment_chars], :multi => nil }
|
258
|
+
end
|
109
259
|
end
|
110
260
|
|
111
|
-
|
261
|
+
# Internal Parsing and Highlighting
|
262
|
+
# ---------------------------------
|
112
263
|
|
113
264
|
# Parse the raw file data into a list of two-tuples. Each tuple has the
|
114
265
|
# form `[docs, code]` where both elements are arrays containing the
|
115
|
-
# raw lines parsed from the input file
|
116
|
-
# is a shebang line.
|
266
|
+
# raw lines parsed from the input file, comment characters stripped.
|
117
267
|
def parse(data)
|
118
268
|
sections = []
|
119
269
|
docs, code = [], []
|
120
270
|
lines = data.split("\n")
|
271
|
+
|
272
|
+
# The first line is ignored if it is a shebang line. We also ignore the
|
273
|
+
# PEP 263 encoding information in python sourcefiles, and the similar ruby
|
274
|
+
# 1.9 syntax.
|
121
275
|
lines.shift if lines[0] =~ /^\#\!/
|
276
|
+
lines.shift if lines[0] =~ /coding[:=]\s*[-\w.]+/ &&
|
277
|
+
[ "python", "rb" ].include?(@options[:language])
|
278
|
+
|
279
|
+
# To detect both block comments and single-line comments, we'll set
|
280
|
+
# up a tiny state machine, and loop through each line of the file.
|
281
|
+
# This requires an `in_comment_block` boolean, and a few regular
|
282
|
+
# expressions for line tests.
|
283
|
+
in_comment_block = false
|
284
|
+
single_line_comment, block_comment_start, block_comment_mid, block_comment_end =
|
285
|
+
nil, nil, nil, nil
|
286
|
+
if not @options[:comment_chars][:single].nil?
|
287
|
+
single_line_comment = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:single])}\\s?")
|
288
|
+
end
|
289
|
+
if not @options[:comment_chars][:multi].nil?
|
290
|
+
block_comment_start = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*$")
|
291
|
+
block_comment_end = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
|
292
|
+
if @options[:comment_chars][:multi][:middle]
|
293
|
+
block_comment_mid = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:middle])}\\s?")
|
294
|
+
end
|
295
|
+
end
|
122
296
|
lines.each do |line|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
297
|
+
# If we're currently in a comment block, check whether the line matches
|
298
|
+
# the _end_ of a comment block.
|
299
|
+
if in_comment_block
|
300
|
+
if block_comment_end && line.match( block_comment_end )
|
301
|
+
in_comment_block = false
|
302
|
+
else
|
303
|
+
docs << line.sub( block_comment_mid || '', '' )
|
128
304
|
end
|
129
|
-
|
305
|
+
# Otherwise, check whether the line matches the beginning of a block, or
|
306
|
+
# a single-line comment all on it's lonesome. In either case, if there's
|
307
|
+
# code, start a new section
|
130
308
|
else
|
131
|
-
|
309
|
+
if block_comment_start && line.match( block_comment_start )
|
310
|
+
in_comment_block = true
|
311
|
+
if code.any?
|
312
|
+
sections << [docs, code]
|
313
|
+
docs, code = [], []
|
314
|
+
end
|
315
|
+
elsif single_line_comment && line.match( single_line_comment )
|
316
|
+
if code.any?
|
317
|
+
sections << [docs, code]
|
318
|
+
docs, code = [], []
|
319
|
+
end
|
320
|
+
docs << line.sub( single_line_comment || '', '' )
|
321
|
+
else
|
322
|
+
code << line
|
323
|
+
end
|
132
324
|
end
|
133
325
|
end
|
134
326
|
sections << [docs, code] if docs.any? || code.any?
|
135
|
-
sections
|
327
|
+
normalize_leading_spaces( sections )
|
328
|
+
end
|
329
|
+
|
330
|
+
# Normalizes documentation whitespace by checking for leading whitespace,
|
331
|
+
# removing it, and then removing the same amount of whitespace from each
|
332
|
+
# succeeding line. That is:
|
333
|
+
#
|
334
|
+
# def func():
|
335
|
+
# """
|
336
|
+
# Comment 1
|
337
|
+
# Comment 2
|
338
|
+
# """
|
339
|
+
# print "omg!"
|
340
|
+
#
|
341
|
+
# should yield a comment block of `Comment 1\nComment 2` and code of
|
342
|
+
# `def func():\n print "omg!"`
|
343
|
+
def normalize_leading_spaces( sections )
|
344
|
+
sections.map do |section|
|
345
|
+
if section.any? && section[0].any?
|
346
|
+
leading_space = section[0][0].match( "^\s+" )
|
347
|
+
if leading_space
|
348
|
+
section[0] =
|
349
|
+
section[0].map{ |line| line.sub( /^#{leading_space.to_s}/, '' ) }
|
350
|
+
end
|
351
|
+
end
|
352
|
+
section
|
353
|
+
end
|
136
354
|
end
|
137
355
|
|
138
356
|
# Take the list of paired *sections* two-tuples and split into two
|
@@ -141,7 +359,7 @@ class Rocco
|
|
141
359
|
def split(sections)
|
142
360
|
docs_blocks, code_blocks = [], []
|
143
361
|
sections.each do |docs,code|
|
144
|
-
docs_blocks << docs.
|
362
|
+
docs_blocks << docs.join("\n")
|
145
363
|
code_blocks << code.map do |line|
|
146
364
|
tabs = line.match(/^(\t+)/)
|
147
365
|
tabs ? line.sub(/^\t+/, ' ' * tabs.captures[0].length) : line
|
@@ -163,20 +381,50 @@ class Rocco
|
|
163
381
|
to_html.
|
164
382
|
split(/\n*<h5>DIVIDER<\/h5>\n*/m)
|
165
383
|
|
166
|
-
# Combine all code blocks into a single big stream
|
167
|
-
# `pygmentize(1)` or <http://pygments.appspot.com>
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
384
|
+
# Combine all code blocks into a single big stream with section dividers and
|
385
|
+
# run through either `pygmentize(1)` or <http://pygments.appspot.com>
|
386
|
+
span, espan = '<span class="c.?">', '</span>'
|
387
|
+
if @options[:comment_chars][:single]
|
388
|
+
front = @options[:comment_chars][:single]
|
389
|
+
divider_input = "\n\n#{front} DIVIDER\n\n"
|
390
|
+
divider_output = Regexp.new(
|
391
|
+
[ "\\n*",
|
392
|
+
span,
|
393
|
+
Regexp.escape(front),
|
394
|
+
' DIVIDER',
|
395
|
+
espan,
|
396
|
+
"\\n*"
|
397
|
+
].join, Regexp::MULTILINE
|
398
|
+
)
|
399
|
+
else
|
400
|
+
front = @options[:comment_chars][:multi][:start]
|
401
|
+
back = @options[:comment_chars][:multi][:end]
|
402
|
+
divider_input = "\n\n#{front}\nDIVIDER\n#{back}\n\n"
|
403
|
+
divider_output = Regexp.new(
|
404
|
+
[ "\\n*",
|
405
|
+
span, Regexp.escape(front), espan,
|
406
|
+
"\\n",
|
407
|
+
span, "DIVIDER", espan,
|
408
|
+
"\\n",
|
409
|
+
span, Regexp.escape(back), espan,
|
410
|
+
"\\n*"
|
411
|
+
].join, Regexp::MULTILINE
|
412
|
+
)
|
174
413
|
end
|
175
414
|
|
415
|
+
code_stream = code_blocks.join( divider_input )
|
416
|
+
|
417
|
+
code_html =
|
418
|
+
if pygmentize?
|
419
|
+
highlight_pygmentize(code_stream)
|
420
|
+
else
|
421
|
+
highlight_webservice(code_stream)
|
422
|
+
end
|
423
|
+
|
176
424
|
# Do some post-processing on the pygments output to split things back
|
177
425
|
# into sections and remove partial `<pre>` blocks.
|
178
426
|
code_html = code_html.
|
179
|
-
split(
|
427
|
+
split(divider_output).
|
180
428
|
map { |code| code.sub(/\n?<div class="highlight"><pre>/m, '') }.
|
181
429
|
map { |code| code.sub(/\n?<\/pre><\/div>\n/m, '') }
|
182
430
|
|
@@ -188,7 +436,7 @@ class Rocco
|
|
188
436
|
# then fork off a child process to write the input.
|
189
437
|
def highlight_pygmentize(code)
|
190
438
|
code_html = nil
|
191
|
-
open("|pygmentize -l #{@options[:language]} -f html", 'r+') do |fd|
|
439
|
+
open("|pygmentize -l #{@options[:language]} -O encoding=utf-8 -f html", 'r+') do |fd|
|
192
440
|
pid =
|
193
441
|
fork {
|
194
442
|
fd.close_read
|
@@ -204,13 +452,13 @@ class Rocco
|
|
204
452
|
|
205
453
|
code_html
|
206
454
|
end
|
207
|
-
|
455
|
+
|
208
456
|
# Pygments is not one of those things that's trivial for a ruby user to install,
|
209
457
|
# so we'll fall back on a webservice to highlight the code if it isn't available.
|
210
458
|
def highlight_webservice(code)
|
211
459
|
Net::HTTP.post_form(
|
212
460
|
URI.parse('http://pygments.appspot.com/'),
|
213
|
-
{'lang' => @options[
|
461
|
+
{'lang' => @options[:language], 'code' => code}
|
214
462
|
).body
|
215
463
|
end
|
216
464
|
end
|
data/lib/rocco/layout.mustache
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
<head>
|
4
4
|
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
5
5
|
<title>{{ title }}</title>
|
6
|
-
<link rel="stylesheet" href="http://
|
6
|
+
<link rel="stylesheet" href="http://github.com/jashkenas/docco/raw/0.3.0/resources/docco.css">
|
7
7
|
</head>
|
8
8
|
<body>
|
9
9
|
<div id='container'>
|
@@ -29,10 +29,10 @@
|
|
29
29
|
</thead>
|
30
30
|
<tbody>
|
31
31
|
{{#sections}}
|
32
|
-
<tr id='section-{{
|
32
|
+
<tr id='section-{{ section_id }}'>
|
33
33
|
<td class=docs>
|
34
|
-
<div class="
|
35
|
-
<a class="
|
34
|
+
<div class="pilwrap">
|
35
|
+
<a class="pilcrow" href="#section-{{ section_id }}">¶</a>
|
36
36
|
</div>
|
37
37
|
{{{ docs }}}
|
38
38
|
</td>
|
data/lib/rocco/layout.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
require 'mustache'
|
2
|
+
require 'pathname'
|
2
3
|
|
3
4
|
class Rocco::Layout < Mustache
|
4
|
-
self.template_path = File.dirname(__FILE__)
|
5
|
+
self.template_path = "#{File.dirname(__FILE__)}/.."
|
5
6
|
|
6
|
-
def initialize(doc)
|
7
|
+
def initialize(doc, file=nil)
|
7
8
|
@doc = doc
|
9
|
+
if not file.nil?
|
10
|
+
Rocco::Layout.template_file = file
|
11
|
+
end
|
8
12
|
end
|
9
13
|
|
10
14
|
def title
|
@@ -14,10 +18,19 @@ class Rocco::Layout < Mustache
|
|
14
18
|
def sections
|
15
19
|
num = 0
|
16
20
|
@doc.sections.map do |docs,code|
|
21
|
+
is_header = /^<h.>(.+)<\/h.>$/.match( docs )
|
22
|
+
header_text = is_header && is_header[1].split.join("_")
|
23
|
+
num += 1
|
17
24
|
{
|
18
|
-
:docs
|
19
|
-
:
|
20
|
-
:
|
25
|
+
:docs => docs,
|
26
|
+
:docs? => !docs.empty?,
|
27
|
+
:header? => is_header,
|
28
|
+
|
29
|
+
:code => code,
|
30
|
+
:code? => !code.empty?,
|
31
|
+
|
32
|
+
:empty? => ( code.empty? && docs.empty? ),
|
33
|
+
:section_id => is_header ? header_text : num
|
21
34
|
}
|
22
35
|
end
|
23
36
|
end
|
@@ -27,11 +40,14 @@ class Rocco::Layout < Mustache
|
|
27
40
|
end
|
28
41
|
|
29
42
|
def sources
|
43
|
+
currentpath = Pathname.new( File.dirname( @doc.file ) )
|
30
44
|
@doc.sources.sort.map do |source|
|
45
|
+
htmlpath = Pathname.new( source.sub( Regexp.new( "#{File.extname(source)}$"), ".html" ) )
|
46
|
+
|
31
47
|
{
|
32
|
-
:path
|
33
|
-
:basename
|
34
|
-
:url
|
48
|
+
:path => source,
|
49
|
+
:basename => File.basename(source),
|
50
|
+
:url => htmlpath.relative_path_from( currentpath )
|
35
51
|
}
|
36
52
|
end
|
37
53
|
end
|
data/lib/rocco/tasks.rb
CHANGED
@@ -70,7 +70,7 @@ class Rocco
|
|
70
70
|
# Run over the source file list, constructing destination filenames
|
71
71
|
# and defining file tasks.
|
72
72
|
@sources.each do |source_file|
|
73
|
-
dest_file =
|
73
|
+
dest_file = source_file.sub(Regexp.new("#{File.extname(source_file)}$"), ".html")
|
74
74
|
define_file_task source_file, "#{@dest}#{dest_file}"
|
75
75
|
|
76
76
|
# If `rake/clean` was required, add the generated files to the list.
|
@@ -103,6 +103,7 @@ class Rocco
|
|
103
103
|
file dest_file => prerequisites do |f|
|
104
104
|
verbose { puts "rocco: #{source_file} -> #{dest_file}" }
|
105
105
|
rocco = Rocco.new(source_file, @sources.to_a, @options)
|
106
|
+
FileUtils.mkdir_p(File.dirname(dest_file))
|
106
107
|
File.open(dest_file, 'wb') { |fd| fd.write(rocco.to_html) }
|
107
108
|
end
|
108
109
|
task @name => dest_file
|
data/rocco.gemspec
CHANGED
@@ -3,17 +3,18 @@ Gem::Specification.new do |s|
|
|
3
3
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
4
|
|
5
5
|
s.name = 'rocco'
|
6
|
-
s.version = '0.
|
7
|
-
s.date = '
|
6
|
+
s.version = '0.6'
|
7
|
+
s.date = '2011-03-05'
|
8
8
|
|
9
9
|
s.description = "Docco in Ruby"
|
10
10
|
s.summary = s.description
|
11
11
|
|
12
|
-
s.authors = ["Ryan Tomayko"]
|
13
|
-
s.email
|
12
|
+
s.authors = ["Ryan Tomayko", "Mike West"]
|
13
|
+
s.email = ["r@tomayko.com", "<mike@mikewest.org>"]
|
14
14
|
|
15
15
|
# = MANIFEST =
|
16
16
|
s.files = %w[
|
17
|
+
CHANGES.md
|
17
18
|
COPYING
|
18
19
|
README
|
19
20
|
Rakefile
|
@@ -23,6 +24,19 @@ Gem::Specification.new do |s|
|
|
23
24
|
lib/rocco/layout.rb
|
24
25
|
lib/rocco/tasks.rb
|
25
26
|
rocco.gemspec
|
27
|
+
test/fixtures/issue10.iso-8859-1.rb
|
28
|
+
test/fixtures/issue10.utf-8.rb
|
29
|
+
test/helper.rb
|
30
|
+
test/suite.rb
|
31
|
+
test/test_basics.rb
|
32
|
+
test/test_block_comments.rb
|
33
|
+
test/test_comment_normalization.rb
|
34
|
+
test/test_commentchar_detection.rb
|
35
|
+
test/test_descriptive_section_names.rb
|
36
|
+
test/test_language_detection.rb
|
37
|
+
test/test_reported_issues.rb
|
38
|
+
test/test_skippable_lines.rb
|
39
|
+
test/test_source_list.rb
|
26
40
|
]
|
27
41
|
# = MANIFEST =
|
28
42
|
|
@@ -0,0 +1 @@
|
|
1
|
+
# hello w�rld
|
@@ -0,0 +1 @@
|
|
1
|
+
# hello ąćęłńóśźż
|
data/test/helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
rootdir = File.expand_path('../../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift "#{rootdir}/lib"
|
3
|
+
|
4
|
+
require 'test/unit'
|
5
|
+
begin; require 'turn'; rescue LoadError; end
|
6
|
+
begin
|
7
|
+
require 'rdiscount'
|
8
|
+
rescue LoadError
|
9
|
+
if !defined?(Gem)
|
10
|
+
require 'rubygems'
|
11
|
+
retry
|
12
|
+
end
|
13
|
+
end
|
14
|
+
require 'rocco'
|
15
|
+
|
16
|
+
def roccoize( filename, contents, options = {} )
|
17
|
+
Rocco.new( filename, [ filename ], options ) {
|
18
|
+
contents
|
19
|
+
}
|
20
|
+
end
|
data/test/suite.rb
ADDED
data/test/test_basics.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class RoccoBasicTests < Test::Unit::TestCase
|
4
|
+
def test_rocco_exists_and_is_instancable
|
5
|
+
roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_filename
|
9
|
+
r = roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
|
10
|
+
assert_equal "filename.rb", r.file
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_sources
|
14
|
+
r = roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
|
15
|
+
assert_equal [ "filename.rb" ], r.sources
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_sections
|
19
|
+
r = roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
|
20
|
+
assert_equal 1, r.sections.length
|
21
|
+
assert_equal 2, r.sections[ 0 ].length
|
22
|
+
assert_equal "<p>Comment 1</p>\n", r.sections[ 0 ][ 0 ]
|
23
|
+
assert_equal "<span class=\"k\">def</span> <span class=\"nf\">codeblock</span>\n<span class=\"k\">end</span>", r.sections[ 0 ][ 1 ]
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_parsing
|
27
|
+
r = Rocco.new( 'test' ) { "" } # Generate throwaway instance so I can test `parse`
|
28
|
+
assert_equal(
|
29
|
+
[
|
30
|
+
[ [ "Comment 1" ], [ "def codeblock", "end" ] ]
|
31
|
+
],
|
32
|
+
r.parse( "# Comment 1\ndef codeblock\nend\n" )
|
33
|
+
)
|
34
|
+
assert_equal(
|
35
|
+
[
|
36
|
+
[ [ "Comment 1" ], [ "def codeblock" ] ],
|
37
|
+
[ [ "Comment 2" ], [ "end" ] ]
|
38
|
+
],
|
39
|
+
r.parse( "# Comment 1\ndef codeblock\n# Comment 2\nend\n" )
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_splitting
|
44
|
+
r = Rocco.new( 'test' ) { "" } # Generate throwaway instance so I can test `split`
|
45
|
+
assert_equal(
|
46
|
+
[
|
47
|
+
[ "Comment 1" ],
|
48
|
+
[ "def codeblock\nend" ]
|
49
|
+
],
|
50
|
+
r.split([ [ [ "Comment 1" ], [ "def codeblock", "end" ] ] ])
|
51
|
+
)
|
52
|
+
assert_equal(
|
53
|
+
[
|
54
|
+
[ "Comment 1", "Comment 2" ],
|
55
|
+
[ "def codeblock", "end" ]
|
56
|
+
],
|
57
|
+
r.split( [
|
58
|
+
[ [ "Comment 1" ], [ "def codeblock" ] ],
|
59
|
+
[ [ "Comment 2" ], [ "end" ] ]
|
60
|
+
] )
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class RoccoBlockCommentTest < Test::Unit::TestCase
|
4
|
+
def test_basics
|
5
|
+
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
|
6
|
+
assert_equal(
|
7
|
+
[
|
8
|
+
[ [ "Comment 1" ], [ "def codeblock", "end" ] ]
|
9
|
+
],
|
10
|
+
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n" )
|
11
|
+
)
|
12
|
+
assert_equal(
|
13
|
+
[
|
14
|
+
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
|
15
|
+
],
|
16
|
+
r.parse( "/**\n * Comment 1a\n * Comment 1b\n */\ndef codeblock\nend\n" )
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_multiple_blocks
|
21
|
+
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
|
22
|
+
assert_equal(
|
23
|
+
[
|
24
|
+
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
25
|
+
[ [ "Comment 2" ], [] ]
|
26
|
+
],
|
27
|
+
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n/**\n * Comment 2\n */\n" )
|
28
|
+
)
|
29
|
+
assert_equal(
|
30
|
+
[
|
31
|
+
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
32
|
+
[ [ "Comment 2" ], [ "if false", "end" ] ]
|
33
|
+
],
|
34
|
+
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n/**\n * Comment 2\n */\nif false\nend" )
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_block_without_middle_character
|
39
|
+
r = Rocco.new( 'test', '', { :language => "python" } ) { "" } # Generate throwaway instance so I can test `parse`
|
40
|
+
assert_equal(
|
41
|
+
[
|
42
|
+
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
43
|
+
[ [ "Comment 2" ], [] ]
|
44
|
+
],
|
45
|
+
r.parse( "\"\"\"\n Comment 1\n\"\"\"\ndef codeblock\nend\n\"\"\"\n Comment 2\n\"\"\"\n" )
|
46
|
+
)
|
47
|
+
assert_equal(
|
48
|
+
[
|
49
|
+
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
50
|
+
[ [ "Comment 2" ], [ "if false", "end" ] ]
|
51
|
+
],
|
52
|
+
r.parse( "\"\"\"\n Comment 1\n\"\"\"\ndef codeblock\nend\n\"\"\"\n Comment 2\n\"\"\"\nif false\nend" )
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_language_without_single_line_comments_parse
|
57
|
+
r = Rocco.new( 'test', '', { :language => "css" } ) { "" } # Generate throwaway instance so I can test `parse`
|
58
|
+
assert_equal(
|
59
|
+
[
|
60
|
+
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
61
|
+
[ [ "Comment 2" ], [ "if false", "end" ] ]
|
62
|
+
],
|
63
|
+
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n/**\n * Comment 2\n */\nif false\nend" )
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_language_without_single_line_comments_split
|
68
|
+
r = Rocco.new( 'test', '', { :language => "css" } ) { "" } # Generate throwaway instance so I can test `parse`
|
69
|
+
assert_equal(
|
70
|
+
[
|
71
|
+
[ "Comment 1", "Comment 2" ],
|
72
|
+
[ "def codeblock\nend", "if false\nend" ]
|
73
|
+
],
|
74
|
+
r.split( [
|
75
|
+
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
|
76
|
+
[ [ "Comment 2" ], [ "if false", "end" ] ]
|
77
|
+
] )
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_language_without_single_line_comments_highlight
|
82
|
+
r = Rocco.new( 'test', '', { :language => "css" } ) { "" } # Generate throwaway instance so I can test `parse`
|
83
|
+
|
84
|
+
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; }" ) ) )
|
85
|
+
assert_equal(
|
86
|
+
"<p>This is a comment!</p>",
|
87
|
+
highlighted[0][0]
|
88
|
+
)
|
89
|
+
assert_equal(
|
90
|
+
"<p>Comment 2</p>\n",
|
91
|
+
highlighted[1][0]
|
92
|
+
)
|
93
|
+
assert(
|
94
|
+
!highlighted[0][1].include?("DIVIDER") &&
|
95
|
+
!highlighted[1][1].include?("DIVIDER"),
|
96
|
+
"`DIVIDER` stripped successfully."
|
97
|
+
)
|
98
|
+
assert( true )
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class RoccoCommentNormalization < Test::Unit::TestCase
|
4
|
+
def test_normal_comments
|
5
|
+
r = Rocco.new( 'test', '', { :language => "python" } ) { "" } # Generate throwaway instance so I can test `parse`
|
6
|
+
assert_equal(
|
7
|
+
[
|
8
|
+
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ],
|
9
|
+
[ [ "Comment 2a", " Comment 2b" ], [] ]
|
10
|
+
],
|
11
|
+
r.parse( "\"\"\"\n Comment 1a\n Comment 1b\n\"\"\"\ndef codeblock\nend\n\"\"\"\n Comment 2a\n Comment 2b\n\"\"\"\n" )
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_single_line_comments
|
16
|
+
r = Rocco.new( 'test', '', { :language => "python" } ) { "" } # Generate throwaway instance so I can test `parse`
|
17
|
+
assert_equal(
|
18
|
+
[
|
19
|
+
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ],
|
20
|
+
[ [ "Comment 2a", " Comment 2b" ], [] ]
|
21
|
+
],
|
22
|
+
r.parse( "# Comment 1a\n# Comment 1b\ndef codeblock\nend\n# Comment 2a\n# Comment 2b\n" )
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class RoccoAutomaticCommentChars < Test::Unit::TestCase
|
4
|
+
def test_basic_detection
|
5
|
+
r = Rocco.new( 'filename.js' ) { "" }
|
6
|
+
assert_equal "//", r.options[:comment_chars][:single]
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_fallback_language
|
10
|
+
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :language => "js" } ) { "" }
|
11
|
+
assert_equal "//", r.options[:comment_chars][:single]
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_fallback_default
|
15
|
+
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever' ) { "" }
|
16
|
+
assert_equal "#", r.options[:comment_chars][:single], "`:comment_chars` should be `#` when falling back to defaults."
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_fallback_user
|
20
|
+
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :comment_chars => "user" } ) { "" }
|
21
|
+
assert_equal "user", r.options[:comment_chars][:single], "`:comment_chars` should be the user's default when falling back to user-provided settings."
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_fallback_user_with_unknown_language
|
25
|
+
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :language => "not-a-language", :comment_chars => "user" } ) { "" }
|
26
|
+
assert_equal "user", r.options[:comment_chars][:single], "`:comment_chars` should be the user's default when falling back to user-provided settings."
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class RoccoDescriptiveSectionNamesTests < Test::Unit::TestCase
|
4
|
+
def test_section_name
|
5
|
+
r = roccoize( "filename.rb", "# # Comment 1\ndef codeblock\nend\n" )
|
6
|
+
html = r.to_html
|
7
|
+
assert(
|
8
|
+
html.include?( "<tr id='section-Comment_1'>" ),
|
9
|
+
"The first section should be named"
|
10
|
+
)
|
11
|
+
assert(
|
12
|
+
html.include?( '<a class="pilcrow" href="#section-Comment_1">' ),
|
13
|
+
"The rendered HTML should link to a named section"
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_section_numbering
|
18
|
+
r = roccoize( "filename.rb", "# # Header 1\ndef codeblock\nend\n# Comment 1\ndef codeblock1\nend\n# # Header 2\ndef codeblock2\nend" )
|
19
|
+
html = r.to_html
|
20
|
+
assert(
|
21
|
+
html.include?( '<a class="pilcrow" href="#section-Header_1">' ) &&
|
22
|
+
html.include?( '<a class="pilcrow" href="#section-Header_2">' ),
|
23
|
+
"First and second headers should be named sections"
|
24
|
+
)
|
25
|
+
assert(
|
26
|
+
html.include?( '<a class="pilcrow" href="#section-2">' ),
|
27
|
+
"Sections should continue numbering as though headers were counted."
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class RoccoLanguageDetection < Test::Unit::TestCase
|
4
|
+
def test_basic_detection
|
5
|
+
r = Rocco.new( 'filename.py' ) { "" }
|
6
|
+
if r.pygmentize?
|
7
|
+
assert_equal "python", r.detect_language(), "`detect_language()` should return the correct language"
|
8
|
+
assert_equal "python", r.options[:language], "`@options[:language]` should be set to the correct language"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_fallback_default
|
13
|
+
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever' ) { "" }
|
14
|
+
if r.pygmentize?
|
15
|
+
assert_equal "text", r.detect_language(), "`detect_language()` should return `text` when nothing else is detected"
|
16
|
+
assert_equal "ruby", r.options[:language], "`@options[:language]` should be set to `ruby` when nothing else is detected"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_fallback_user
|
21
|
+
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :language => "c" } ) { "" }
|
22
|
+
if r.pygmentize?
|
23
|
+
assert_equal "text", r.detect_language(), "`detect_language()` should return `text` nothing else is detected"
|
24
|
+
assert_equal "c", r.options[:language], "`@options[:language]` should be set to the user's setting when nothing else is detected"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path('../helper', __FILE__)
|
3
|
+
|
4
|
+
class RoccoIssueTests < Test::Unit::TestCase
|
5
|
+
def test_issue07_incorrect_parsing_in_c_mode
|
6
|
+
# Precursor to issue #13 below, Rocco incorrectly parsed C/C++
|
7
|
+
# http://github.com/rtomayko/rocco/issues#issue/7
|
8
|
+
r = Rocco.new( 'issue7.c', [], { :language => 'c' } ) {
|
9
|
+
"// *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!"
|
10
|
+
}
|
11
|
+
r.sections.each do | section |
|
12
|
+
if not section[1].nil?
|
13
|
+
assert(
|
14
|
+
!section[1].include?("<span class=\"c\"># DIVIDER</span>"),
|
15
|
+
"`# DIVIDER` present in code text, which means the highligher screwed up somewhere."
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_issue10_utf8_processing
|
22
|
+
# Rocco has issues with strange UTF-8 characters: need to explicitly set the encoding for Pygments
|
23
|
+
# http://github.com/rtomayko/rocco/issues#issue/10
|
24
|
+
r = Rocco.new( File.dirname(__FILE__) + "/fixtures/issue10.utf-8.rb" )
|
25
|
+
assert_equal(
|
26
|
+
"<p>hello ąćęłńóśźż</p>\n",
|
27
|
+
r.sections[0][0],
|
28
|
+
"UTF-8 input files ought behave correctly."
|
29
|
+
)
|
30
|
+
# and, just for grins, ensure that iso-8859-1 works too.
|
31
|
+
# @TODO: Is this really the correct behavior? Converting text
|
32
|
+
# to UTF-8 on the way out is probably preferable.
|
33
|
+
r = Rocco.new( File.dirname(__FILE__) + "/fixtures/issue10.iso-8859-1.rb" )
|
34
|
+
assert_equal(
|
35
|
+
"<p>hello w\366rld</p>\n",
|
36
|
+
r.sections[0][0],
|
37
|
+
"ISO-8859-1 input should probably also behave correctly."
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_issue12_css_octothorpe_classname_change
|
42
|
+
# Docco changed some CSS classes. Rocco needs to update its default template.
|
43
|
+
# http://github.com/rtomayko/rocco/issues#issue/12
|
44
|
+
r = Rocco.new( 'issue12.sh' ) {
|
45
|
+
"# Comment 1\n# Comment 1\nprint 'omg!'"
|
46
|
+
}
|
47
|
+
html = r.to_html
|
48
|
+
assert(
|
49
|
+
!html.include?( "<div class=\"octowrap\">" ),
|
50
|
+
"`octowrap` wrapper is present in rendered HTML. This ought be replaced with `pilwrap`."
|
51
|
+
)
|
52
|
+
assert(
|
53
|
+
!html.include?( "<a class=\"octothorpe\" href=\"#section-1\">" ),
|
54
|
+
"`octothorpe` link is present in rendered HTML. This ought be replaced with `pilcrow`."
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_issue13_incorrect_code_divider_parsing
|
59
|
+
# In `bash` mode (among others), the comment class is `c`, not `c1`.
|
60
|
+
# http://github.com/rtomayko/rocco/issues#issue/13
|
61
|
+
r = Rocco.new( 'issue13.sh', [], { :language => 'bash' } ) {
|
62
|
+
"# Comment 1\necho 'code';\n# Comment 2\necho 'code';\n# Comment 3\necho 'code';\n"
|
63
|
+
}
|
64
|
+
r.sections.each do | section |
|
65
|
+
if not section[1].nil?
|
66
|
+
assert(
|
67
|
+
!section[1].include?("<span class=\"c\"># DIVIDER</span>"),
|
68
|
+
"`# DIVIDER` present in code text, which means the highligher screwed up somewhere."
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_issue15_extra_space_after_comment_character_remains
|
75
|
+
# After the comment character, a single space should be removed.
|
76
|
+
# http://github.com/rtomayko/rocco/issues#issue/15
|
77
|
+
r = Rocco.new( 'issue15.sh') {
|
78
|
+
"# Comment 1\n# ---------\necho 'code';"
|
79
|
+
}
|
80
|
+
assert(
|
81
|
+
!r.sections[0][0].include?( "<hr />" ),
|
82
|
+
"`<hr />` present in rendered documentation text. It should be a header, not text followed by a horizontal rule."
|
83
|
+
)
|
84
|
+
assert_equal("<h2>Comment 1</h2>\n", r.sections[0][0])
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class RoccoSkippableLines < Test::Unit::TestCase
|
4
|
+
def test_shebang_first_line
|
5
|
+
r = Rocco.new( 'filename.sh' ) { "" }
|
6
|
+
assert_equal(
|
7
|
+
[
|
8
|
+
[ [ "Comment 1" ], [ "def codeblock" ] ],
|
9
|
+
[ [ "Comment 2" ], [ "end" ] ]
|
10
|
+
],
|
11
|
+
r.parse( "#!/usr/bin/env bash\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
|
12
|
+
"Shebang should be stripped when it appears as the first line."
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_shebang_in_content
|
17
|
+
r = Rocco.new( 'filename.sh' ) { "" }
|
18
|
+
assert_equal(
|
19
|
+
[
|
20
|
+
# @TODO: `#!/` shouldn't be recognized as a comment.
|
21
|
+
[ [ "Comment 1", "!/usr/bin/env bash" ], [ "def codeblock" ] ],
|
22
|
+
[ [ "Comment 2" ], [ "end" ] ]
|
23
|
+
],
|
24
|
+
r.parse( "# Comment 1\n#!/usr/bin/env bash\ndef codeblock\n# Comment 2\nend\n" ),
|
25
|
+
"Shebang shouldn't be stripped anywhere other than as the first line."
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_encoding_in_ruby
|
30
|
+
r = Rocco.new( 'filename.rb' ) { "" }
|
31
|
+
assert_equal(
|
32
|
+
[
|
33
|
+
[ [ "Comment 1" ], [ "def codeblock" ] ],
|
34
|
+
[ [ "Comment 2" ], [ "end" ] ]
|
35
|
+
],
|
36
|
+
r.parse( "#!/usr/bin/env bash\n# encoding: utf-8\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
|
37
|
+
"Strings matching the PEP 263 encoding definition regex should be stripped when they appear at the top of a python document."
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_encoding_in_python
|
42
|
+
r = Rocco.new( 'filename.py' ) { "" }
|
43
|
+
assert_equal(
|
44
|
+
[
|
45
|
+
[ [ "Comment 1" ], [ "def codeblock" ] ],
|
46
|
+
[ [ "Comment 2" ], [ "end" ] ]
|
47
|
+
],
|
48
|
+
r.parse( "#!/usr/bin/env bash\n# encoding: utf-8\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
|
49
|
+
"Strings matching the PEP 263 encoding definition regex should be stripped when they appear at the top of a python document."
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_encoding_in_notpython
|
54
|
+
r = Rocco.new( 'filename.sh' ) { "" }
|
55
|
+
assert_equal(
|
56
|
+
[
|
57
|
+
[ [ "encoding: utf-8", "Comment 1" ], [ "def codeblock" ] ],
|
58
|
+
[ [ "Comment 2" ], [ "end" ] ]
|
59
|
+
],
|
60
|
+
r.parse( "#!/usr/bin/env bash\n# encoding: utf-8\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
|
61
|
+
"Strings matching the PEP 263 encoding definition regex should be stripped when they appear at the top of a python document."
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class RoccoSourceListTests < Test::Unit::TestCase
|
4
|
+
def test_flat_sourcelist
|
5
|
+
r = Rocco.new( 'issue26.sh', [ 'issue26a.sh', 'issue26b.sh', 'issue26c.sh' ] ) {
|
6
|
+
"# Comment 1\n# Comment 1\nprint 'omg!'"
|
7
|
+
}
|
8
|
+
html = r.to_html
|
9
|
+
assert(
|
10
|
+
html.include?( '<a class="source" href="issue26a.html">issue26a.sh</a>' ) &&
|
11
|
+
html.include?( '<a class="source" href="issue26b.html">issue26b.sh</a>' ) &&
|
12
|
+
html.include?( '<a class="source" href="issue26c.html">issue26c.sh</a>' ),
|
13
|
+
"URLs correctly generated for files in a flat directory structure"
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_heiarachical_sourcelist
|
18
|
+
r = Rocco.new( 'a/issue26.sh', [ 'a/issue26a.sh', 'b/issue26b.sh', 'c/issue26c.sh' ] ) {
|
19
|
+
"# Comment 1\n# Comment 1\nprint 'omg!'"
|
20
|
+
}
|
21
|
+
html = r.to_html
|
22
|
+
assert(
|
23
|
+
html.include?( '<a class="source" href="issue26a.html">issue26a.sh</a>' ) &&
|
24
|
+
html.include?( '<a class="source" href="../b/issue26b.html">issue26b.sh</a>' ) &&
|
25
|
+
html.include?( '<a class="source" href="../c/issue26c.html">issue26c.sh</a>' ),
|
26
|
+
"URLs correctly generated for files in a flat directory structure"
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,19 +1,21 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rocco
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 7
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
-
|
8
|
-
version: "0.
|
8
|
+
- 6
|
9
|
+
version: "0.6"
|
9
10
|
platform: ruby
|
10
11
|
authors:
|
11
12
|
- Ryan Tomayko
|
13
|
+
- Mike West
|
12
14
|
autorequire:
|
13
15
|
bindir: bin
|
14
16
|
cert_chain: []
|
15
17
|
|
16
|
-
date:
|
18
|
+
date: 2011-03-05 00:00:00 -08:00
|
17
19
|
default_executable:
|
18
20
|
dependencies:
|
19
21
|
- !ruby/object:Gem::Dependency
|
@@ -24,6 +26,7 @@ dependencies:
|
|
24
26
|
requirements:
|
25
27
|
- - ">="
|
26
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
27
30
|
segments:
|
28
31
|
- 0
|
29
32
|
version: "0"
|
@@ -37,13 +40,16 @@ dependencies:
|
|
37
40
|
requirements:
|
38
41
|
- - ">="
|
39
42
|
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
40
44
|
segments:
|
41
45
|
- 0
|
42
46
|
version: "0"
|
43
47
|
type: :runtime
|
44
48
|
version_requirements: *id002
|
45
49
|
description: Docco in Ruby
|
46
|
-
email:
|
50
|
+
email:
|
51
|
+
- r@tomayko.com
|
52
|
+
- <mike@mikewest.org>
|
47
53
|
executables:
|
48
54
|
- rocco
|
49
55
|
extensions: []
|
@@ -51,6 +57,7 @@ extensions: []
|
|
51
57
|
extra_rdoc_files: []
|
52
58
|
|
53
59
|
files:
|
60
|
+
- CHANGES.md
|
54
61
|
- COPYING
|
55
62
|
- README
|
56
63
|
- Rakefile
|
@@ -60,6 +67,19 @@ files:
|
|
60
67
|
- lib/rocco/layout.rb
|
61
68
|
- lib/rocco/tasks.rb
|
62
69
|
- rocco.gemspec
|
70
|
+
- test/fixtures/issue10.iso-8859-1.rb
|
71
|
+
- test/fixtures/issue10.utf-8.rb
|
72
|
+
- test/helper.rb
|
73
|
+
- test/suite.rb
|
74
|
+
- test/test_basics.rb
|
75
|
+
- test/test_block_comments.rb
|
76
|
+
- test/test_comment_normalization.rb
|
77
|
+
- test/test_commentchar_detection.rb
|
78
|
+
- test/test_descriptive_section_names.rb
|
79
|
+
- test/test_language_detection.rb
|
80
|
+
- test/test_reported_issues.rb
|
81
|
+
- test/test_skippable_lines.rb
|
82
|
+
- test/test_source_list.rb
|
63
83
|
has_rdoc: true
|
64
84
|
homepage: http://rtomayko.github.com/rocco/
|
65
85
|
licenses: []
|
@@ -74,6 +94,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
94
|
requirements:
|
75
95
|
- - ">="
|
76
96
|
- !ruby/object:Gem::Version
|
97
|
+
hash: 3
|
77
98
|
segments:
|
78
99
|
- 0
|
79
100
|
version: "0"
|
@@ -82,6 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
103
|
requirements:
|
83
104
|
- - ">="
|
84
105
|
- !ruby/object:Gem::Version
|
106
|
+
hash: 3
|
85
107
|
segments:
|
86
108
|
- 0
|
87
109
|
version: "0"
|