rocco 0.6 → 0.7

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