Permalink
Browse files

Hugh Sasse's patch adding simple Perl/POD support.

  • Loading branch information...
1 parent 8529a08 commit efe0d34c080731384c2c8cc19a169c467d219778 drbrain committed Sep 23, 2008
Showing with 239 additions and 0 deletions.
  1. +1 −0 History.txt
  2. +165 −0 lib/rdoc/parser/perl.rb
  3. +1 −0 lib/rdoc/rdoc.rb
  4. +72 −0 test/test_rdoc_parser_perl.rb
View
1 History.txt
@@ -7,6 +7,7 @@ to polish RDoc for Ruby 1.9.1.
* Y Minor Enhancements
* Support for parsing RDoc from SWIG. Ruby patch #10742 by Gonzalo
Garramuno, #13993 by Steven Jenkins.
+ * Simple support for Perl POD documentation. Patch by Hugh Sasse.
* Z Bug Fixes
* Explicitly set the html template's text color, so that the generated
View
165 lib/rdoc/parser/perl.rb
@@ -0,0 +1,165 @@
+require 'rdoc/parser'
+
+##
+#
+# This is an attamept to write a basic parser for Perl's
+# POD (Plain old Documentation) format. Ruby code must
+# co-exist with Perl, and some tasks are easier in Perl
+# than Ruby because of existing libraries.
+#
+# One difficult is that Perl POD has no means of identifying
+# the classes (packages) and methods (subs) with which it
+# is associated, it is more like literate programming in so
+# far as it just happens to be in the same place as the code,
+# but need not be.
+#
+# We would like to support all the markup the POD provides
+# so that it will convert happily to HTML. At the moment
+# I don't think I can do that: time constraints.
+#
+
+class RDoc::Parser::PerlPOD < RDoc::Parser
+
+ parse_files_matching(/.p[lm]$/)
+
+ ##
+ # Prepare to parse a perl file
+
+ def initialize(top_level, file_name, content, options, stats)
+ super
+
+ preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include
+
+ preprocess.handle @content do |directive, param|
+ warn "Unrecognized directive '#{directive}' in #{@file_name}"
+ end
+ end
+
+ ##
+ # Extract the Pod(-like) comments from the code.
+ # At its most basic there will ne no need to distinguish
+ # between the different types of header, etc.
+ #
+ # This uses a simple finite state machine, in a very
+ # procedural pattern. I could "replace case with polymorphism"
+ # but I think it would obscure the intent, scatter the
+ # code all over tha place. This machine is necessary
+ # because POD requires that directives be preceded by
+ # blank lines, so reading line by line is necessary,
+ # and preserving state about what is seen is necesary.
+
+ def scan
+
+ @top_level.comment ||= ""
+ state=:code_blank
+ line_number = 0
+ line = nil
+
+ # This started out as a really long nested case statement,
+ # which also led to repetitive code. I'd like to avoid that
+ # so I'm using a "table" instead.
+
+ # Firstly we need some procs to do the transition and processing
+ # work. Because these are procs they are closures, and they can
+ # use variables in the local scope.
+ #
+ # First, the "nothing to see here" stuff.
+ code_noop = lambda do
+ if line =~ /^\s+$/
+ state = :code_blank
+ end
+ end
+
+ pod_noop = lambda do
+ if line =~ /^\s+$/
+ state = :pod_blank
+ end
+ @top_level.comment += filter(line)
+ end
+
+ begin_noop = lambda do
+ if line =~ /^\s+$/
+ state = :begin_blank
+ end
+ @top_level.comment += filter(line)
+ end
+
+ # Now for the blocks that process code and comments...
+
+ transit_to_pod = lambda do
+ case line
+ when /^=(?:pod|head\d+)/
+ state = :pod_no_blank
+ @top_level.comment += filter(line)
+ when /^=over/
+ state = :over_no_blank
+ @top_level.comment += filter(line)
+ when /^=(?:begin|for)/
+ state = :begin_no_blank
+ end
+ end
+
+ process_pod = lambda do
+ case line
+ when /^\s*$/
+ state = :pod_blank
+ @top_level.comment += filter(line)
+ when /^=cut/
+ state = :code_no_blank
+ when /^=end/
+ $stderr.puts "'=end' unexpected at #{line_number} in #{@file_name}"
+ else
+ @top_level.comment += filter(line)
+ end
+ end
+
+
+ process_begin = lambda do
+ case line
+ when /^\s*$/
+ state = :begin_blank
+ @top_level.comment += filter(line)
+ when /^=end/
+ state = :code_no_blank
+ when /^=cut/
+ $stderr.puts "'=cut' unexpected at #{line_number} in #{@file_name}"
+ else
+ @top_level.comment += filter(line)
+ end
+
+ end
+
+
+ transitions = { :code_no_blank => code_noop,
+ :code_blank => transit_to_pod,
+ :pod_no_blank => pod_noop,
+ :pod_blank => process_pod,
+ :begin_no_blank => begin_noop,
+ :begin_blank => process_begin}
+ @content.each_line do |l|
+ line = l
+ line_number += 1
+ transitions[state].call
+ end # each line
+
+ @top_level
+ end
+
+ # Filter the perl markup that does the same as the rdoc
+ # filtering. Only basic for now. Will probably need a
+ # proper parser to cope with C<<...>> etc
+ def filter(comment)
+ return '' if comment =~ /^=pod\s*$/
+ comment.gsub!(/^=pod/, '==')
+ comment.gsub!(/^=head(\d+)/) do
+ "=" * $1.to_i
+ end
+ comment.gsub!(/=item/, '');
+ comment.gsub!(/C<(.*?)>/, '<tt>\1</tt>');
+ comment.gsub!(/I<(.*?)>/, '<i>\1</i>');
+ comment.gsub!(/B<(.*?)>/, '<b>\1</b>');
+ comment
+ end
+
+end
+
View
1 lib/rdoc/rdoc.rb
@@ -7,6 +7,7 @@
require 'rdoc/parser/ruby'
require 'rdoc/parser/c'
require 'rdoc/parser/f95'
+require 'rdoc/parser/perl'
require 'rdoc/stats'
require 'rdoc/options'
View
72 test/test_rdoc_parser_perl.rb
@@ -0,0 +1,72 @@
+require 'stringio'
+require 'tempfile'
+require 'test/unit'
+require 'rdoc/options'
+require 'rdoc/parser/perl'
+
+class TestRdocParserPerlPOD < Test::Unit::TestCase
+
+ def setup
+ @tempfile = Tempfile.new self.class.name
+ filename = @tempfile.path
+
+ @top_level = RDoc::TopLevel.new filename
+ @fn = filename
+ @options = RDoc::Options.new
+ @stats = RDoc::Stats.new 0
+ end
+
+ def teardown
+ @tempfile.close
+ end
+
+ def test_uncommented_perl
+ content = <<-EOF
+while (<>) {
+ tr/a-z/A-Z;
+ print
+}
+ EOF
+
+ comment = util_get_comment content
+ assert_equal "", comment
+ end
+
+ def test_perl_without_pod
+ content = <<-EOF
+#!/usr/local/bin/perl
+#
+#This is a pointless perl program because it does -p.
+#
+while(<>) {print;}:
+ EOF
+
+ comment = util_get_comment content
+ assert_equal "", comment
+ end
+
+ def test_simple_pod_no_structure
+ content = <<-EOF
+=begin pod
+
+This just contains plain old documentation
+
+=end
+ EOF
+ comment = util_get_comment content
+ assert_equal "\nThis just contains plain old documentation\n\n", comment
+ end
+
+ # Get the comment of the @top_level when it has processed the input.
+ def util_get_comment(content)
+ parser = util_parser content
+ parser.scan.comment
+ end
+
+ # create a new parser with the supplied content.
+ def util_parser(content)
+ RDoc::Parser::PerlPOD.new @top_level, @fn, content, @options, @stats
+ end
+
+end
+

0 comments on commit efe0d34

Please sign in to comment.