rocco 0.5 → 0.6
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/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"
|