rocco 0.5 → 0.6

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