5 # scans a file containing the code for a stem cell
6 # and updates it with pod generated from the cell's
9 # also generates pod for detected methods, subs, and
10 # any missing standard stuff (author, license, etc.)
21 # These globals are initalized in the BEGIN block at the bottom of this file
22 # Please treat as constants.
23 our ( %PRE_POD, %POST_POD );
25 # Another global/"constant"
26 our %IS_ATTR_PART = map { $_ => 1 } qw(
38 for my $file_name (@ARGV) {
49 my $file_text = read_file($file_name);
51 my $attr_spec = find_attr_spec( $file_text, $file_name );
53 my @meth_names = find_method_names( $file_text );
55 # extract_pod _literally_ extracts it -
56 # removing any pod found from the text.
57 my $new_text = $file_text;
58 my $pod_sections = extract_pod( \$new_text, \@meth_names );
60 my ($class_name) = ($file_text =~ /^package\s+([\w:]+)/m);
62 my $attr_pod = generate_attr_pod( $attr_spec, $file_name, $class_name );
64 generate_boilerplate( $file_text, $class_name );
66 my $new_pod = generate_pod( $attr_pod, \@meth_names, $pod_sections );
68 my $new_source = build_source( $new_pod, $new_text );
70 if ( $file_text eq $new_source ) {
71 warn "No changes to make to $file_name\n";
75 write_file( "$file_name.new", $new_source );
91 # Finds and returns a list of the names of
92 # methods/subroutines found in the file
93 sub find_method_names {
94 my ( $file_text ) = @_;
96 my @meth_names = $file_text =~ /^sub\s+([^\W_]\w*)/gm;
105 # Find and _remove_ any POD in the file.
106 # Returns a HoH-like data-structure that you should probably inspect
107 # with DD to see what it looks like...
109 my ( $text_ref, $method_names ) = @_;
112 while ( ${$text_ref} =~
115 ( # grab pod head line as $1
116 =head\w* # begin pod section
118 (.+?)$ # grab section title as $2
121 .+? # grab pod body as $3
123 (?: # match until after =cut, before =head or at EOF
134 # # strip out leading and trailing blank lines
135 # $body =~ s/\A[\n]\s*[\n]|[\n]\s*[\n]\z//msg;
137 # Does the section title contain the name of a method?
138 my ($method_name) = grep { $title =~ /$_/i and $_ } @$method_names;
139 if ( $method_name ) {
141 warn "Duplicate pod section for method "
142 . "$method_name named [$title].\n"
143 if exists $pod_sections{methods}{$method_name};
145 $pod_sections{methods}{$method_name}{head} = $head;
146 $pod_sections{methods}{$method_name}{body} = $body;
151 warn "Found pod section with duplicate name [$title]\n"
152 if exists $pod_sections{other}{ lc $title };
154 $pod_sections{other}{ lc $title }{head} = $head;
155 $pod_sections{other}{ lc $title }{body} = $body;
157 # print Dumper \%pod_sections; exit;
158 return \%pod_sections;
166 # Generate all the POD text in one big chunk...
167 # Fill in most sections with boilerplate, but will
168 # merge in sections that were already present ( found by extract_pod() )
170 my ( $attr_pod, $meth_names, $pod_sections ) = @_;
174 $new_pod .= generate_pod_sections( $pod_sections, \%PRE_POD );
176 # attr_pod is always regenerated...
177 # TODO: perhaps it's better to call generate_attr_pod() here?
179 $new_pod .= "=head1 ATTRIBUTES\n\n";
180 $new_pod .= strip_blank_lines( $attr_pod ) . "\n\n";
183 $new_pod .= "=head1 METHODS\n\n";
184 foreach my $meth_name ( @$meth_names ) {
186 $new_pod .= "=head2 $meth_name\n\n";
187 if ( my $meth_pod = $pod_sections->{methods}{$meth_name} ) {
188 $new_pod .= strip_blank_lines( $meth_pod->{body} ) . "\n\n";
192 if ( $meth_name eq 'new' ) {
193 $new_pod .= strip_blank_lines( <<END_POD ) . "\n\n";
195 This is a class method that constructs the object. It uses
196 the standard Stem API of key/value attributes. These are described
197 in the \$attr_spec in the module, and it's POD is auto-generated.
203 if ( $meth_name eq 'msg_in' ) {
204 $new_pod .= strip_blank_lines( <<END_POD ) . "\n\n";
206 This method is the 'wildcard' Stem message handler and it is passed a single
207 argument which is a L<Stem::Msg> object. This method is called if no
208 specific message handler method is found in this cell. This is missing
209 specific documentation to be filled in.
215 if ( $meth_name =~ /^(.+)_in$/ ) {
217 $new_pod .= strip_blank_lines( <<END_POD ) . "\n\n";
219 This method is a Stem message handler for messages of type $msg_type.
220 It is passed a single argument which is a L<Stem::Msg> object and
221 its return value is ignored. This is missing specific documentation
228 if ( $meth_name =~ /^(.+)_cmd$/ ) {
230 $new_pod .= strip_blank_lines( <<END_POD ) . "\n\n";
232 This method is a Stem message handler for command messages of type $cmd_type.
233 It is passed a single argument which is a L<Stem::Msg> object.
234 Any return value will be sent back as a 'response' type message to the sender.
235 This is missing specific documentation to be filled in.
241 # All other methods just get this...
242 $new_pod .= "This method is missing documentation\n\n";
245 $new_pod .= generate_pod_sections( $pod_sections, \%POST_POD );
247 $new_pod .= "=cut\n\n";
253 sub generate_pod_sections {
254 my ($existing_pod, $bp_pod) = @_;
258 foreach my $section ( @{ $bp_pod->{names} } ) {
260 (my $printable_section = $section) =~ tr/_/ /;
261 $new_pod .= "=head1 $printable_section\n\n";
263 my $found_sec = $existing_pod->{other}{ lc $section };
265 my $next_part = $found_sec ?
266 $found_sec->{body} : $bp_pod->{boilerplate}{$section} ;
268 $new_pod .= strip_blank_lines( $next_part ) . "\n\n";
275 sub generate_boilerplate {
276 my ($file_text, $class_name) = @_;
278 $PRE_POD{boilerplate}{NAME} = <<END_POD;
282 if ($file_text =~ /\$attr_spec/) {
283 $POST_POD{boilerplate}{SEE_ALSO} = <<END_POD;
291 # strip out leading and trailing blank lines
292 sub strip_blank_lines {
294 $text =~ s/\A[\n]?\s*[\n]|[\n]\s*[\n]?\z//msg;
299 # tries to locate the $attr_spec array ref in the text
300 # and returns it as a real data structure (eval magick)
302 my ( $file_text, $file_name ) = @_;
304 # The regex below is naive but will be fine for now. I would
305 # prefer to use Text::Balanced or Regexp::Common though...
307 if ( $file_text =~ /\$attr_spec\s*=\s*(\[[^\]]*\])\s*;/ ) {
309 or croak "Error parsing \$attr_spec from $file_name:\n$@\n";
318 # blow away __END__ and everything after (if present)
319 # and replace with generated POD
321 my ( $new_pod, $file_text ) = @_;
323 ( my $new_text = $file_text ) =~ s/^__END__[\n].*//msg;
325 return $new_text . "__END__\n\n" . $new_pod;
335 # Stolen from uri's original script, but really,
336 # really needs to be cleaned up...
337 # TODO: clean up this sub, somehow...
338 sub generate_attr_pod {
339 my ( $attr_list, $file_name, $package ) = @_;
343 =head2 Class Attributes for $package
345 This pod is generated from the \$attr_spec in the module.
346 That data is also used by L<Stem::Class> to parse the arguments
347 passed to the class constructor new().
353 #print "POD [$pod]\n" ;
355 foreach my $attr_ref ( @{$attr_list} ) {
357 my $name = $attr_ref->{name};
362 Missing attribute name in Class $package in file $file_name
375 my $help = $attr_ref->{help};
377 unless ( defined($help) ) {
379 Missing help in attribute $name in Class $package in file $file_name
388 # suppress line break in POD output after description
391 if ( my $attr_class = $attr_ref->{class} ) {
394 '<' . join( ', ', @{ $attr_ref->{class_args} || [] } ) . '>';
397 =item - Class Attribute: '$name' is an object of class $attr_class and constructed with: $class_args
401 exists( $attr_ref->{type} ) and $pod .= <<POD ;
403 =item - The type of '$name' is: $attr_ref->{type}
406 if ( exists( $attr_ref->{default} ) ) {
408 my $default = $attr_ref->{default};
410 if ( ref($default) eq "ARRAY" ) {
412 $default = '(' . join( ', ', @{$default} ) . ')';
417 =item - B<Default> value: $default
421 exists( $attr_ref->{required} ) and $pod .= <<POD ;
423 =item - It is B<required>.
426 foreach my $attr ( sort keys %{$attr_ref} ) {
427 next if $IS_ATTR_PART{$attr};
428 $pod .= "Unknown attribute $attr\n";
448 #print "POD2 [", substr($pod, 0, 40), "]\n" ;
463 # Globals declared at the top of this script are initalized here
464 # Keeping at the bottom of this file to (try to) reduce clutter.
467 # Pod sections that go before attribute and method sections
468 my @PRE_POD_NAMES = qw( NAME SYNOPSIS DESCRIPTION );
470 # sections that go after
471 my @POST_POD_NAMES = qw( BUGS TODO SEE_ALSO AUTHOR LICENSE COPYRIGHT );
474 # Boilerplate for each "pre" section
481 SYNOPSIS => <<'END_POD',
485 DESCRIPTION => <<'END_POD',
486 What is this module good for?
492 # Boilerplate for each "post" section
502 SEE_ALSO => <<'END_POD',
505 AUTHOR => <<'END_POD',
506 Uri Guttman uri@cpan.org
509 LICENSE => <<'END_POD',
513 COPYRIGHT => <<'END_POD',
514 (C) Uri Guttman, 2009
520 # globals used in other parts of the program
522 names => \@PRE_POD_NAMES,
523 boilerplate => \%PRE_POD_BP,
527 names => \@POST_POD_NAMES,
528 boilerplate => \%POST_POD_BP,