rocco 0.6 → 0.7

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/bin/rocco CHANGED
@@ -8,6 +8,7 @@
8
8
  #/ The string to recognize as a comment marker
9
9
  #/ -o, --output=<dir> Directory where generated HTML files are written
10
10
  #/ -t, --template=<path> The file to use as template when rendering HTML
11
+ #/ -d, --docblocks Parse Docblock @annotations in comments
11
12
  #/ --help Show this help message
12
13
 
13
14
  require 'optparse'
@@ -39,6 +40,7 @@ ARGV.options { |o|
39
40
  o.on("-l", "--language=LANG") { |lang| options[:language] = lang }
40
41
  o.on("-c", "--comment-chars=CHARS") { |chars| options[:comment_chars] = Regexp.escape(chars) }
41
42
  o.on("-t", "--template=TEMPLATE") { |template| options[:template_file] = template }
43
+ o.on("-d", "--docblocks") { options[:docblocks] = true }
42
44
  o.on_tail("-h", "--help") { usage($stdout, 0) }
43
45
  o.parse!
44
46
  } or abort_with_note
@@ -52,8 +52,7 @@ require 'net/http'
52
52
 
53
53
  # Code is run through [Pygments](http://pygments.org/) for syntax
54
54
  # highlighting. If it's not installed, locally, use a webservice.
55
- include FileTest
56
- if !ENV['PATH'].split(':').any? { |dir| executable?("#{dir}/pygmentize") }
55
+ if !ENV['PATH'].split(':').any? { |dir| File.executable?("#{dir}/pygmentize") }
57
56
  warn "WARNING: Pygments not found. Using webservice."
58
57
  end
59
58
 
@@ -74,7 +73,7 @@ end
74
73
  # to `nil` (that is, Mustache will use `./lib/rocco/layout.mustache`)_.
75
74
  #
76
75
  class Rocco
77
- VERSION = '0.6'
76
+ VERSION = '0.7'
78
77
 
79
78
  def initialize(filename, sources=[], options={}, &block)
80
79
  @file = filename
@@ -159,7 +158,7 @@ class Rocco
159
158
  # Returns `true` if `pygmentize` is available locally, `false` otherwise.
160
159
  def pygmentize?
161
160
  @_pygmentize ||= ENV['PATH'].split(':').
162
- any? { |dir| executable?("#{dir}/pygmentize") }
161
+ any? { |dir| File.executable?("#{dir}/pygmentize") }
163
162
  end
164
163
 
165
164
  # If `pygmentize` is available, we can use it to autodetect a file's
@@ -171,7 +170,7 @@ class Rocco
171
170
  def detect_language
172
171
  @_language ||=
173
172
  if pygmentize?
174
- %x[pygmentize -N #{@file}].strip!
173
+ %x[pygmentize -N #{@file}].strip.split('+').first
175
174
  else
176
175
  "text"
177
176
  end
@@ -208,45 +207,55 @@ class Rocco
208
207
  #
209
208
  # At the moment, we're only returning `:single`. Consider this
210
209
  # groundwork for block comment parsing.
210
+ C_STYLE_COMMENTS = {
211
+ :single => "//",
212
+ :multi => { :start => "/**", :middle => "*", :end => "*/" },
213
+ :heredoc => nil
214
+ }
211
215
  COMMENT_STYLES = {
212
216
  "bash" => { :single => "#", :multi => nil },
213
- "c" => {
214
- :single => "//",
215
- :multi => { :start => "/**", :middle => "*", :end => "*/" }
216
- },
217
+ "c" => C_STYLE_COMMENTS,
217
218
  "coffee-script" => {
218
219
  :single => "#",
219
- :multi => { :start => "###", :middle => nil, :end => "###" }
220
- },
221
- "cpp" => {
222
- :single => "//",
223
- :multi => { :start => "/**", :middle => "*", :end => "*/" }
220
+ :multi => { :start => "###", :middle => nil, :end => "###" },
221
+ :heredoc => nil
224
222
  },
223
+ "cpp" => C_STYLE_COMMENTS,
224
+ "csharp" => C_STYLE_COMMENTS,
225
225
  "css" => {
226
226
  :single => nil,
227
- :multi => { :start => "/**", :middle => "*", :end => "*/" }
228
- },
229
- "java" => {
230
- :single => "//",
231
- :multi => { :start => "/**", :middle => "*", :end => "*/" }
227
+ :multi => { :start => "/**", :middle => "*", :end => "*/" },
228
+ :heredoc => nil
232
229
  },
233
- "js" => {
234
- :single => "//",
235
- :multi => { :start => "/**", :middle => "*", :end => "*/" }
230
+ "html" => {
231
+ :single => nil,
232
+ :multi => { :start => '<!--', :middle => nil, :end => '-->' },
233
+ :heredoc => nil
236
234
  },
235
+ "java" => C_STYLE_COMMENTS,
236
+ "js" => C_STYLE_COMMENTS,
237
237
  "lua" => {
238
238
  :single => "--",
239
- :multi => nil
239
+ :multi => nil,
240
+ :heredoc => nil
240
241
  },
242
+ "php" => C_STYLE_COMMENTS,
241
243
  "python" => {
242
244
  :single => "#",
243
- :multi => { :start => '"""', :middle => nil, :end => '"""' }
245
+ :multi => { :start => '"""', :middle => nil, :end => '"""' },
246
+ :heredoc => nil
244
247
  },
245
248
  "rb" => {
246
249
  :single => "#",
247
- :multi => { :start => '=begin', :middle => nil, :end => '=end' }
250
+ :multi => { :start => '=begin', :middle => nil, :end => '=end' },
251
+ :heredoc => "<<-"
252
+ },
253
+ "scheme" => { :single => ";;", :multi => nil, :heredoc => nil },
254
+ "xml" => {
255
+ :single => nil,
256
+ :multi => { :start => '<!--', :middle => nil, :end => '-->' },
257
+ :heredoc => nil
248
258
  },
249
- "scheme" => { :single => ";;", :multi => nil },
250
259
  }
251
260
 
252
261
  def generate_comment_chars
@@ -254,7 +263,7 @@ class Rocco
254
263
  if COMMENT_STYLES[@options[:language]]
255
264
  COMMENT_STYLES[@options[:language]]
256
265
  else
257
- { :single => @options[:comment_chars], :multi => nil }
266
+ { :single => @options[:comment_chars], :multi => nil, :heredoc => nil }
258
267
  end
259
268
  end
260
269
 
@@ -279,8 +288,9 @@ class Rocco
279
288
  # To detect both block comments and single-line comments, we'll set
280
289
  # up a tiny state machine, and loop through each line of the file.
281
290
  # This requires an `in_comment_block` boolean, and a few regular
282
- # expressions for line tests.
291
+ # expressions for line tests. We'll do the same for fake heredoc parsing.
283
292
  in_comment_block = false
293
+ in_heredoc = false
284
294
  single_line_comment, block_comment_start, block_comment_mid, block_comment_end =
285
295
  nil, nil, nil, nil
286
296
  if not @options[:comment_chars][:single].nil?
@@ -289,42 +299,77 @@ class Rocco
289
299
  if not @options[:comment_chars][:multi].nil?
290
300
  block_comment_start = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*$")
291
301
  block_comment_end = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
302
+ block_comment_one_liner = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
303
+ block_comment_start_with = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)$")
304
+ block_comment_end_with = Regexp.new("\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
292
305
  if @options[:comment_chars][:multi][:middle]
293
306
  block_comment_mid = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:middle])}\\s?")
294
307
  end
295
308
  end
309
+ if not @options[:comment_chars][:heredoc].nil?
310
+ heredoc_start = Regexp.new("#{Regexp.escape(@options[:comment_chars][:heredoc])}(\\S+)$")
311
+ end
296
312
  lines.each do |line|
297
313
  # If we're currently in a comment block, check whether the line matches
298
- # the _end_ of a comment block.
314
+ # the _end_ of a comment block or the _end_ of a comment block with a
315
+ # comment.
299
316
  if in_comment_block
300
- if block_comment_end && line.match( block_comment_end )
317
+ if block_comment_end && line.match(block_comment_end)
318
+ in_comment_block = false
319
+ elsif block_comment_end_with && line.match(block_comment_end_with)
301
320
  in_comment_block = false
321
+ docs << line.match(block_comment_end_with).captures.first.
322
+ sub(block_comment_mid || '', '')
302
323
  else
303
- docs << line.sub( block_comment_mid || '', '' )
324
+ docs << line.sub(block_comment_mid || '', '')
325
+ end
326
+ # If we're currently in a heredoc, we're looking for the end of the
327
+ # heredoc, and everything it contains is code.
328
+ elsif in_heredoc
329
+ if line.match(Regexp.new("^#{Regexp.escape(in_heredoc)}$"))
330
+ in_heredoc = false
304
331
  end
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
332
+ code << line
333
+ # Otherwise, check whether the line starts a heredoc. If so, note the end
334
+ # pattern, and the line is code. Otherwise check whether the line matches
335
+ # the beginning of a block, or a single-line comment all on it's lonesome.
336
+ # In either case, if there's code, start a new section.
308
337
  else
309
- if block_comment_start && line.match( block_comment_start )
338
+ if heredoc_start && line.match(heredoc_start)
339
+ in_heredoc = $1
340
+ code << line
341
+ elsif block_comment_one_liner && line.match(block_comment_one_liner)
342
+ if code.any?
343
+ sections << [docs, code]
344
+ docs, code = [], []
345
+ end
346
+ docs << line.match(block_comment_one_liner).captures.first
347
+ elsif block_comment_start && line.match(block_comment_start)
348
+ in_comment_block = true
349
+ if code.any?
350
+ sections << [docs, code]
351
+ docs, code = [], []
352
+ end
353
+ elsif block_comment_start_with && line.match(block_comment_start_with)
310
354
  in_comment_block = true
311
355
  if code.any?
312
356
  sections << [docs, code]
313
357
  docs, code = [], []
314
358
  end
315
- elsif single_line_comment && line.match( single_line_comment )
359
+ docs << line.match(block_comment_start_with).captures.first
360
+ elsif single_line_comment && line.match(single_line_comment)
316
361
  if code.any?
317
362
  sections << [docs, code]
318
363
  docs, code = [], []
319
364
  end
320
- docs << line.sub( single_line_comment || '', '' )
365
+ docs << line.sub(single_line_comment || '', '')
321
366
  else
322
367
  code << line
323
368
  end
324
369
  end
325
370
  end
326
371
  sections << [docs, code] if docs.any? || code.any?
327
- normalize_leading_spaces( sections )
372
+ normalize_leading_spaces(sections)
328
373
  end
329
374
 
330
375
  # Normalizes documentation whitespace by checking for leading whitespace,
@@ -340,13 +385,13 @@ class Rocco
340
385
  #
341
386
  # should yield a comment block of `Comment 1\nComment 2` and code of
342
387
  # `def func():\n print "omg!"`
343
- def normalize_leading_spaces( sections )
388
+ def normalize_leading_spaces(sections)
344
389
  sections.map do |section|
345
390
  if section.any? && section[0].any?
346
- leading_space = section[0][0].match( "^\s+" )
391
+ leading_space = section[0][0].match("^\s+")
347
392
  if leading_space
348
393
  section[0] =
349
- section[0].map{ |line| line.sub( /^#{leading_space.to_s}/, '' ) }
394
+ section[0].map{ |line| line.sub(/^#{leading_space.to_s}/, '') }
350
395
  end
351
396
  end
352
397
  section
@@ -368,11 +413,26 @@ class Rocco
368
413
  [docs_blocks, code_blocks]
369
414
  end
370
415
 
416
+ # Take a list of block comments and convert Docblock @annotations to
417
+ # Markdown syntax.
418
+ def docblock(docs)
419
+ docs.map do |doc|
420
+ doc.split("\n").map do |line|
421
+ line.match(/^@\w+/) ? line.sub(/^@(\w+)\s+/, '> **\1** ')+" " : line
422
+ end.join("\n")
423
+ end
424
+ end
425
+
371
426
  # Take the result of `split` and apply Markdown formatting to comments and
372
427
  # syntax highlighting to source code.
373
428
  def highlight(blocks)
374
429
  docs_blocks, code_blocks = blocks
375
430
 
431
+ # Pre-process Docblock @annotations.
432
+ if @options[:docblocks]
433
+ docs_blocks = docblock(docs_blocks)
434
+ end
435
+
376
436
  # Combine all docs blocks into a single big markdown document with section
377
437
  # dividers and run through the Markdown processor. Then split it back out
378
438
  # into separate sections.
@@ -390,7 +450,7 @@ class Rocco
390
450
  divider_output = Regexp.new(
391
451
  [ "\\n*",
392
452
  span,
393
- Regexp.escape(front),
453
+ Regexp.escape(CGI.escapeHTML(front)),
394
454
  ' DIVIDER',
395
455
  espan,
396
456
  "\\n*"
@@ -402,17 +462,17 @@ class Rocco
402
462
  divider_input = "\n\n#{front}\nDIVIDER\n#{back}\n\n"
403
463
  divider_output = Regexp.new(
404
464
  [ "\\n*",
405
- span, Regexp.escape(front), espan,
465
+ span, Regexp.escape(CGI.escapeHTML(front)), espan,
406
466
  "\\n",
407
467
  span, "DIVIDER", espan,
408
468
  "\\n",
409
- span, Regexp.escape(back), espan,
469
+ span, Regexp.escape(CGI.escapeHTML(back)), espan,
410
470
  "\\n*"
411
471
  ].join, Regexp::MULTILINE
412
472
  )
413
473
  end
414
474
 
415
- code_stream = code_blocks.join( divider_input )
475
+ code_stream = code_blocks.join(divider_input)
416
476
 
417
477
  code_html =
418
478
  if pygmentize?
@@ -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://github.com/jashkenas/docco/raw/0.3.0/resources/docco.css">
6
+ <link rel="stylesheet" href="http://jashkenas.github.com/docco/resources/docco.css">
7
7
  </head>
8
8
  <body>
9
9
  <div id='container'>
@@ -3,8 +3,8 @@ 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.6'
7
- s.date = '2011-03-05'
6
+ s.version = '0.7'
7
+ s.date = '2011-05-22'
8
8
 
9
9
  s.description = "Docco in Ruby"
10
10
  s.summary = s.description
@@ -29,10 +29,13 @@ Gem::Specification.new do |s|
29
29
  test/helper.rb
30
30
  test/suite.rb
31
31
  test/test_basics.rb
32
+ test/test_block_comment_styles.rb
32
33
  test/test_block_comments.rb
33
34
  test/test_comment_normalization.rb
34
35
  test/test_commentchar_detection.rb
35
36
  test/test_descriptive_section_names.rb
37
+ test/test_docblock_annotations.rb
38
+ test/test_heredoc.rb
36
39
  test/test_language_detection.rb
37
40
  test/test_reported_issues.rb
38
41
  test/test_skippable_lines.rb
@@ -0,0 +1,64 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class RoccoBlockCommentTest < Test::Unit::TestCase
4
+ def test_one_liner
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( "/** Comment 1 */\ndef codeblock\nend\n" )
11
+ )
12
+ end
13
+
14
+ def test_block_start_with_comment
15
+ r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
16
+ assert_equal(
17
+ [
18
+ [ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
19
+ ],
20
+ r.parse( "/** Comment 1a\n * Comment 1b\n */\ndef codeblock\nend\n" )
21
+ )
22
+ end
23
+
24
+ def test_block_end_with_comment
25
+ r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
26
+ assert_equal(
27
+ [
28
+ [ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
29
+ ],
30
+ r.parse( "/**\n * Comment 1a\n Comment 1b */\ndef codeblock\nend\n" )
31
+ )
32
+ end
33
+
34
+ def test_block_end_with_comment_and_middle
35
+ r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
36
+ assert_equal(
37
+ [
38
+ [ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
39
+ ],
40
+ r.parse( "/**\n * Comment 1a\n * Comment 1b */\ndef codeblock\nend\n" )
41
+ )
42
+ end
43
+
44
+ def test_block_start_with_comment_and_end_with_comment
45
+ r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
46
+ assert_equal(
47
+ [
48
+ [ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
49
+ ],
50
+ r.parse( "/** Comment 1a\n Comment 1b */\ndef codeblock\nend\n" )
51
+ )
52
+ end
53
+
54
+ def test_block_start_with_comment_and_end_with_comment_and_middle
55
+ r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
56
+ assert_equal(
57
+ [
58
+ [ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
59
+ ],
60
+ r.parse( "/** Comment 1a\n * Comment 1b */\ndef codeblock\nend\n" )
61
+ )
62
+ end
63
+
64
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class RoccoDocblockAnnotationsTest < Test::Unit::TestCase
4
+ def test_basics
5
+ r = Rocco.new( 'test', '', { :language => "c", :docblocks => true } ) { "" } # Generate throwaway instance so I can test `parse`
6
+ assert_equal(
7
+ [
8
+ "Comment\n\n> **param** mixed foo \n> **return** void "
9
+ ],
10
+ r.docblock( ["Comment\n\n@param mixed foo\n@return void"] )
11
+ )
12
+ end
13
+ def test_highlighted_in_blocks
14
+ r = Rocco.new( 'test', '', { :language => "c", :docblocks => true } ) { "" } # Generate throwaway instance so I can test `parse`
15
+ highlighted = r.highlight( r.split( r.parse( "/**\n * Comment\n * @param type name\n */\ndef codeblock\nend\n" ) ) )
16
+
17
+ assert_equal(
18
+ "<p>Comment</p>\n\n<blockquote><p><strong>param</strong> type name</p></blockquote>\n",
19
+ highlighted[0][0]
20
+ )
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class RoccoHeredocTest < Test::Unit::TestCase
4
+ def test_basics
5
+ r = Rocco.new( 'test', '', { :language => "rb" } ) { "" } # Generate throwaway instance so I can test `parse`
6
+ assert_equal(
7
+ [
8
+ [ [ "Comment 1" ], [ "heredoc <<-EOH", "#comment", "code", "EOH" ] ]
9
+ ],
10
+ r.parse( "# Comment 1\nheredoc <<-EOH\n#comment\ncode\nEOH" )
11
+ )
12
+ end
13
+ end
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rocco
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 5
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 6
9
- version: "0.6"
8
+ - 7
9
+ version: "0.7"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ryan Tomayko
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-05 00:00:00 -08:00
18
+ date: 2011-05-22 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -72,10 +72,13 @@ files:
72
72
  - test/helper.rb
73
73
  - test/suite.rb
74
74
  - test/test_basics.rb
75
+ - test/test_block_comment_styles.rb
75
76
  - test/test_block_comments.rb
76
77
  - test/test_comment_normalization.rb
77
78
  - test/test_commentchar_detection.rb
78
79
  - test/test_descriptive_section_names.rb
80
+ - test/test_docblock_annotations.rb
81
+ - test/test_heredoc.rb
79
82
  - test/test_language_detection.rb
80
83
  - test/test_reported_issues.rb
81
84
  - test/test_skippable_lines.rb