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.
@@ -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/*_test.rb']
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 = File.join(output_dir, (filename.split('.')[0..-2].empty? ? filename : filename.split('.')[0..-2].join('.')) + '.html')
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) }
@@ -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 two members: `:language`, which specifies which
65
- # Pygments lexer to use; and `:comment_chars`, which specifies the comment
66
- # characters of the target language. The options default to `'ruby'` and `'#'`,
67
- # respectively.
68
- # When `block` is given, it must read the contents of the file using whatever
69
- # means necessary and return it as a string. With no `block`, the file is read
70
- # to retrieve data.
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.5'
77
+ VERSION = '0.6'
73
78
 
74
79
  def initialize(filename, sources=[], options={}, &block)
75
- @file = filename
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
- @sources = sources
88
- @comment_pattern = Regexp.new("^\\s*#{@options[:comment_chars]}")
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
- #### Internal Parsing and Highlighting
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. The first line is ignored if it
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
- case line
124
- when @comment_pattern
125
- if code.any?
126
- sections << [docs, code]
127
- docs, code = [], []
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
- docs << line
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
- code << line
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.map { |line| line.sub(@comment_pattern, '') }.join("\n")
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 and run through either
167
- # `pygmentize(1)` or <http://pygments.appspot.com>
168
- code_stream = code_blocks.join("\n\n#{@options[:comment_chars]} DIVIDER\n\n")
169
-
170
- if ENV['PATH'].split(':').any? { |dir| executable?("#{dir}/pygmentize") }
171
- code_html = highlight_pygmentize(code_stream)
172
- else
173
- code_html = highlight_webservice(code_stream)
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(/\n*<span class="c.">#{@options[:comment_chars]} DIVIDER<\/span>\n*/m).
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['language'], 'code' => code}
461
+ {'lang' => @options[:language], 'code' => code}
214
462
  ).body
215
463
  end
216
464
  end
@@ -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://jashkenas.github.com/docco/resources/docco.css">
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-{{ num }}'>
32
+ <tr id='section-{{ section_id }}'>
33
33
  <td class=docs>
34
- <div class="octowrap">
35
- <a class="octothorpe" href="#section-{{ num }}">#</a>
34
+ <div class="pilwrap">
35
+ <a class="pilcrow" href="#section-{{ section_id }}">&#182;</a>
36
36
  </div>
37
37
  {{{ docs }}}
38
38
  </td>
@@ -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 => docs,
19
- :code => code,
20
- :num => (num += 1)
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 => source,
33
- :basename => File.basename(source),
34
- :url => File.basename(source).split('.')[0..-2].join('.') + '.html'
48
+ :path => source,
49
+ :basename => File.basename(source),
50
+ :url => htmlpath.relative_path_from( currentpath )
35
51
  }
36
52
  end
37
53
  end
@@ -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 = File.basename(source_file).split('.')[0..-2].join('.') + '.html'
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
@@ -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.5'
7
- s.date = '2010-09-10'
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 = "r@tomayko.com"
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 ąćęłńóśźż
@@ -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
@@ -0,0 +1,5 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+ require 'test/unit'
3
+
4
+ Dir[File.expand_path('../test_*.rb', __FILE__)].
5
+ each { |file| require file }
@@ -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
- - 5
8
- version: "0.5"
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: 2010-09-10 00:00:00 -07:00
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: r@tomayko.com
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"