Permalink
Browse files

Improved documentation

Added Net::AMQP::Protocol->full_docs_to_dir()
Changed the documentation a bit.
  • Loading branch information...
1 parent e26b960 commit fb648a51bc3391e34d0855294f68fc6433c9dfee @ewaters committed Aug 21, 2009
Showing with 212 additions and 65 deletions.
  1. +7 −5 lib/Net/AMQP.pm
  2. +8 −30 lib/Net/AMQP/Frame.pm
  3. +0 −4 lib/Net/AMQP/Frame/Header.pm
  4. +68 −14 lib/Net/AMQP/Protocol.pm
  5. +103 −12 lib/Net/AMQP/Protocol/Base.pm
  6. +26 −0 t/04_autodocs.t
View
12 lib/Net/AMQP.pm
@@ -36,7 +36,7 @@ Net::AMQP - Advanced Message Queue Protocol (de)serialization and representation
=head1 DESCRIPTION
-This module implements the frame (de)serialization and representation of the Advanced Message Queue Protocol (http://www.amqp.org/). It is to be used in conjunction with client or server software that does the actual TCP/IP communication. While it's being written with AMQP version 0-8 in mind, as the spec is defined by an external xml file, support for 0-9, 0-9-1 and eventually 0-10 is hoped for.
+This module implements the frame (de)serialization and representation of the Advanced Message Queue Protocol (http://www.amqp.org/). It is to be used in conjunction with client or server software that does the actual TCP/IP communication.
=cut
@@ -50,14 +50,12 @@ our $VERSION = '0.01.1';
=head1 CLASS METHODS
-=head2 parse_raw_frames ($string_ref)
+=head2 parse_raw_frames
-=over 4
+ Net::AMQP->parse_raw_frames(\$binary_payload)
Given a scalar reference to a binary string, return a list of L<Net::AMQP::Frame> objects, consuming the data in the string. Croaks on invalid input.
-=back
-
=cut
sub parse_raw_frames {
@@ -91,6 +89,10 @@ sub parse_raw_frames {
L<POE::Component::Client::AMQP>
+=head1 TODO
+
+At the moment, only AMQP v0-8 is supported. Support for v0-10 and later v1-0 is hoped for.
+
=head1 COPYRIGHT
Copyright (c) 2009 Eric Waters and XMission LLC (http://www.xmission.com/). All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
View
38 lib/Net/AMQP/Frame.pm
@@ -31,28 +31,26 @@ our $VERSION = 0.01;
=head1 CLASS METHODS
-=head2 new (...)
-
-=over 4
+=head2 new
Takes an arbitrary list of key/value pairs and casts it into this class. Nothing special here.
-=back
-
=cut
sub new {
my ($class, %self) = @_;
return bless \%self, $class;
}
-=head2 factory (...)
+=head2 factory
-=over 4
-
-Pass in 'type_id', 'channel' and 'payload'. Will attempt to identify a L<Net::AMQP::Frame> subclass for further parsing, and will croak on failure. Returns a L<Net::AMQP::Frame> subclass object.
+ Net::AMQP::Frame->factory(
+ type_id => 1,
+ channel => 1,
+ payload => '',
+ );
-=back
+Will attempt to identify a L<Net::AMQP::Frame> subclass for further parsing, and will croak on failure. Returns a L<Net::AMQP::Frame> subclass object.
=cut
@@ -88,8 +86,6 @@ sub factory {
=head2 Field accessors
-=over 4
-
Each subclass extends these accessors, but they share in common the following:
=over 4
@@ -104,32 +100,18 @@ Each subclass extends these accessors, but they share in common the following:
=back
-=back
-
=head2 parse_payload
-=over 4
-
Performs the parsing of the 'payload' binary data.
-=back
-
=head2 to_raw_payload
-=over 4
-
Returns the binary data the represents this frame's payload.
-=back
-
=head2 to_raw_frame
-=over 4
-
Returns a raw binary string representing this frame on the wire.
-=back
-
=cut
sub to_raw_frame {
@@ -147,12 +129,8 @@ sub to_raw_frame {
=head2 type_string
-=over 4
-
Returns a string that uniquely represents this frame type, such as 'Method Basic.Consume', 'Header Basic' or 'Body'
-=back
-
=cut
sub type_string {
View
4 lib/Net/AMQP/Frame/Header.pm
@@ -31,8 +31,6 @@ our $VERSION = 0.01;
=head1 OBJECT METHODS
-=over 4
-
Provides the following field accessors
=over 4
@@ -49,8 +47,6 @@ Exposes the L<Net::AMQP::Protocol::Base> object that this frame wraps
=back
-=back
-
=cut
sub register_header_class {
View
82 lib/Net/AMQP/Protocol.pm
@@ -15,6 +15,7 @@ use warnings;
use Net::AMQP::Common qw(:all);
use Net::AMQP::Protocol::Base;
use XML::LibXML;
+use File::Path;
our $VERSION = 0.01;
our ($VERSION_MAJOR, $VERSION_MINOR, %spec);
@@ -23,23 +24,17 @@ our ($VERSION_MAJOR, $VERSION_MINOR, %spec);
=head2 header
-=over 4
-
Returns a binary string representing the header of any AMQP communications
-=back
-
=cut
sub header {
'AMQP' . pack 'C*', 1, 1, $VERSION_MAJOR, $VERSION_MINOR;
}
-=head2 load_xml_spec ($xml_fn)
-
-=over 4
+=head2 load_xml_spec
-Reads in the AMQP XML specifications file, XML document node <amqp>, and generates subclasses of L<Net::AMQP::Protocol::Base> for each frame type.
+Pass in the XML filename. Reads in the AMQP XML specifications file, XML document node <amqp>, and generates subclasses of L<Net::AMQP::Protocol::Base> for each frame type.
Names are normalized, as demonstrated by this example:
@@ -49,7 +44,7 @@ Names are normalized, as demonstrated by this example:
</method>
</class>
-creates the class L<Net::AMQP::Protocol::Basic::ConsumeOk> with the field accessor L<consumer_tag()>, allowing you to create a new object as such:
+creates the class L<Net::AMQP::Protocol::Basic::ConsumeOk> with the field accessor C<consumer_tag()>, allowing you to create a new object as such:
my $method = Net::AMQP::Protocol::Basic::ConsumeOk->new(
consumer_tag => 'blah'
@@ -60,15 +55,13 @@ creates the class L<Net::AMQP::Protocol::Basic::ConsumeOk> with the field access
# do something
}
-=back
-
=cut
sub load_xml_spec {
- my ($class, $xml_fn) = @_;
+ my ($class, $xml_fn, $xml_str_ref) = @_;
my $parser = XML::LibXML->new();
- my $doc = $parser->parse_file($xml_fn);
+ my $doc = defined $xml_fn ? $parser->parse_file($xml_fn) : $parser->parse_string($$xml_str_ref);
my $root = $doc->documentElement;
# Header
@@ -106,11 +99,28 @@ sub load_xml_spec {
);
foreach my $child_field ($child_method->getChildrenByTagName('field')) {
- push @{ $method{fields} }, {
+ my $field = {
map { $_->name => $_->getValue }
grep { defined $_ }
$child_field->attributes
};
+
+ my @doc;
+ if ($child_field->firstChild && $child_field->firstChild->nodeType == 3) {
+ @doc = ( $child_field->firstChild->textContent );
+ }
+ foreach my $doc ($child_field->getChildrenByTagName('doc')) {
+ next if $doc->hasAttribute('name');
+ push @doc, $doc->textContent;
+ }
+ foreach my $i (0 .. $#doc) {
+ $doc[$i] =~ s{[\n\t]}{ }g;
+ $doc[$i] =~ s{\s{2,}}{ }g;
+ $doc[$i] =~ s{^\s*}{};
+ }
+ $field->{doc} = join "\n\n", @doc;
+
+ push @{ $method{fields} }, $field;
}
foreach my $child_response ($child_method->getChildrenByTagName('response')) {
@@ -214,6 +224,50 @@ EOF
}
}
+=head2 full_docs_to_dir
+
+ Net::AMQP::Protocol->full_docs_to_dir($dir, $format);
+
+Using the dynamically generated classes, this will create 'pod' or 'pm' files in the target directory in the following format:
+
+ $dir/Net::AMQP::Protocol::Basic::Publish.pod
+ (or with format 'pm')
+ $dir/Net/AMQP/Protocol/Basic/Publish.pm
+
+The directory will be created if it doesn't exist.
+
+=cut
+
+sub full_docs_to_dir {
+ my ($class, $dir, $format) = @_;
+ $class = ref $class if ref $class;
+ $format ||= 'pod';
+
+ foreach my $service_name (sort keys %{ $spec{class} }) {
+ foreach my $method (sort { $a->{name} cmp $b->{name} } @{ $spec{class}{$service_name}{methods} }) {
+ my $method_class = 'Net::AMQP::Protocol::' . $service_name . '::' . $method->{name};
+
+ my $pod = $method_class->docs_as_pod;
+ my $filename;
+
+ if ($format eq 'pod') {
+ $filename = $dir . '/' . $method_class . '.pod';
+ }
+ elsif ($format eq 'pm') {
+ $filename = $dir . '/' . $method_class . '.pm';
+ $filename =~ s{::}{/}g;
+ }
+
+ my ($base_path) = $filename =~ m{^(.+)/[^/]+$};
+ -d $base_path || mkpath($base_path) || die "Can't mkpath $base_path: $!";
+
+ open my $podfn, '>', $filename or die "Can't open '$filename' for writing: $!";
+ print $podfn $pod;
+ close $podfn;
+ }
+ }
+}
+
=head1 SEE ALSO
L<Net::AMQP>
View
115 lib/Net/AMQP/Protocol/Base.pm
@@ -6,7 +6,7 @@ Net::AMQP::Protocol::Base - Base class of auto-generated protocol classes
=head1 DESCRIPTION
-See L<Net::AMQP::Protocol::load_xml_spec()> for how subclasses to this class are auto-generated.
+See L<Net::AMQP::Protocol/load_xml_spec> for how subclasses to this class are auto-generated.
=cut
@@ -28,21 +28,21 @@ our $VERSION = 0.01;
=head1 CLASS METHODS
-=over 4
+=head2 class_id
-=item I<class_id>
+The class id from the specficiation.
-=item I<method_id>
+=head2 method_id
-In the case of a content <class> (such as Basic, File or Stream), method_id is 0 for the virtual ContentHeader method. This allows you to create a Header frame in much the same way you create a Method frame, but with the virtual method 'ContentHeader'. For example:
+The method id from the specification. In the case of a content <class> (such as Basic, File or Stream), method_id is 0 for the virtual ContentHeader method. This allows you to create a Header frame in much the same way you create a Method frame, but with the virtual method 'ContentHeader'. For example:
my $header_frame = Net::AMQP::Protocol::Basic::ContentHeader->new(
content_type => 'text/html'
);
print $header_frame->method_id(); # prints '0'
-=item I<frame_arguments>
+=head2 frame_arguments
Contains an ordered arrayref of the fields that comprise a frame for this method. For example:
@@ -52,11 +52,11 @@ Contains an ordered arrayref of the fields that comprise a frame for this method
This is used by the L<Net::AMQP::Frame> subclasses to (de)serialize raw binary data. Each of these fields are also an accessor for the class objects.
-=item I<class_spec>
+=head2 class_spec
Contains the hashref that the C<load_xml_spec()> call generated for this class.
-=item I<method_spec>
+=head2 method_spec
Same as above, but for this method.
@@ -95,12 +95,8 @@ sub register {
=head2 frame_wrap
-=over 4
-
Returns a L<Net::AMQP::Frame> subclass object that wraps the given object, if possible.
-=back
-
=cut
sub frame_wrap {
@@ -117,6 +113,101 @@ sub frame_wrap {
}
}
+sub docs_as_pod {
+ my $class = shift;
+ my $package = __PACKAGE__;
+
+ my $class_spec = $class->class_spec;
+ my $method_spec = $class->method_spec;
+ my $frame_arguments = $class->frame_arguments;
+
+ my $description = "This is an auto-generated subclass of L<$package>; see the docs for that module for inherited methods. Check the L</USAGE> below for details on the auto-generated methods within this class.\n";
+
+ if ($class->method_id == 0) {
+ my $base_class = 'Net::AMQP::Protocol::' . $class_spec->{name};
+ $description .= "\n" . <<EOF;
+This class is not a real class of the AMQP spec. Instead, it's a helper class that allows you to create L<Net::AMQP::Frame::Header> objects for L<$base_class> frames.
+EOF
+ }
+ else {
+ $description .= "\n" . "This class implements the class B<$$class_spec{name}> (id ".$class->class_id.") method B<$$method_spec{name}> (id ".$class->method_id."), which is ".($method_spec->{synchronous} ? 'a synchronous' : 'an asynchronous')." method\n";
+ }
+
+ my $synopsis_new_args = '';
+ my $usage = <<EOF;
+ =head2 Fields and Accessors
+
+Each of the following represents a field in the specification. These are the optional arguments to B<new()> and are also read/write accessors.
+
+ =over
+
+EOF
+
+ use Data::Dumper;
+ #$usage .= Dumper($method_spec);
+
+ foreach my $field_spec (@{ $method_spec->{fields} }) {
+ my $type = $field_spec->{type}; # may be 'undef'
+ if ($field_spec->{domain}) {
+ $type = $Net::AMQP::Protocol::spec{domain}{ $field_spec->{domain} }{type};
+ }
+
+ my $local_name = $field_spec->{name};
+ $local_name =~ s{ }{_}g;
+
+ $field_spec->{doc} ||= '';
+
+ $usage .= <<EOF;
+ =item I<$local_name> (type: $type)
+
+$$field_spec{doc}
+
+EOF
+
+ $synopsis_new_args .= <<EOF;
+ $local_name => \$$local_name,
+EOF
+ }
+
+ chomp $synopsis_new_args; # trailing \n
+
+ $usage .= "=back\n\n";
+
+
+ my $pod = <<EOF;
+ =pod
+
+ =head1 NAME
+
+$class - An auto-generated subclass of $package
+
+ =head1 SYNOPSIS
+
+ use $class;
+
+ my \$object = $class\->new(
+$synopsis_new_args
+ );
+
+ =head1 DESCRIPTION
+
+$description
+
+ =head1 USAGE
+
+$usage
+
+ =head1 SEE ALSO
+
+L<$package>
+
+EOF
+
+ $pod =~ s{^ =}{=}gms;
+
+ return $pod;
+}
+
=head1 SEE ALSO
L<Net::AMQP::Protocol>
View
26 t/04_autodocs.t
@@ -0,0 +1,26 @@
+use strict;
+use warnings;
+use FindBin;
+use Test::More;
+
+BEGIN {
+ use_ok('Net::AMQP');
+}
+
+Net::AMQP::Protocol->load_xml_spec($FindBin::Bin . '/../spec/amqp0-8.xml');
+
+SKIP: {
+
+ eval { require File::Temp };
+ skip "File::Temp is not installed", 1 if $@;
+
+ my $dir = File::Temp->newdir();
+ my $dirname = $dir->dirname;
+
+ Net::AMQP::Protocol->full_docs_to_dir($dirname);
+
+ #print "Written to $dirname\n";
+ #system "pod2man $dirname/Net::AMQP::Protocol::Basic::Publish.pod | man -l -";
+}
+
+done_testing();

0 comments on commit fb648a5

Please sign in to comment.