X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=bin%2Fstem2pod;fp=bin%2Fstem2pod;h=6e070af2686a3e733e3421bc16d98ad5c8ad7b22;hb=3104a1f12a49e8a1ca779c32ce0dd91eb3752915;hp=97143d58961dc4d37109517713838ad1529f74c0;hpb=41984f7ec1e2807b47ec3bda65e0c6e41fa36c4d;p=urisagit%2FStem.git diff --git a/bin/stem2pod b/bin/stem2pod index 97143d5..6e070af 100755 --- a/bin/stem2pod +++ b/bin/stem2pod @@ -1,410 +1,532 @@ -#!/usr/local/bin/perl -w +#!/usr/bin/perl -w # -# stem2pod +# stem2pod # -# takes filename (a stem module) arguments and it updates their -# pod from their attribute descriptions. it also will insert pod -# templates for methods, subs and standard pod sections. +# scans a file containing the code for a stem cell +# and updates it with pod generated from the cell's +# attribute variables. +# +# also generates pod for detected methods, subs, and +# any missing standard stuff (author, license, etc.) # -# if a file is changed, it is written out over itself. unchanged -# files are not touched. use strict; - -use Carp qw( carp cluck ) ; +use warnings; use Data::Dumper; +use File::Slurp; +use Carp; + -#use Test::More tests => 1 ; -#$SIG{__WARN__} = sub { cluck } ; +# These globals are initalized in the BEGIN block at the bottom of this file +# Please treat as constants. +our ( %PRE_POD, %POST_POD ); -my $changed ; -my $package ; +# Another global/"constant" +our %IS_ATTR_PART = map { $_ => 1 } qw( + name + type + help + default + required + class + class_args +); -my %is_attr_part = map { $_ => 1 } qw( - name - type - help - default - required - class - class_args -) ; -foreach my $file_name ( @ARGV ) { - process_source_file( $file_name ) ; +for my $file_name (@ARGV) { + stem2pod($file_name); } +exit; -exit ; -sub process_source_file { - my ( $file_name ) = @_ ; - my $code_text = read_file( $file_name ) ; +sub stem2pod { + my ($file_name) = @_; - my $new_code_text = process_code_text( $file_name, $code_text ) ; + my $file_text = read_file($file_name); -#print $new_code_text ; + my $attr_spec = find_attr_spec( $file_text, $file_name ); - if ( $new_code_text eq $code_text ) { + my @meth_names = find_method_names( $file_text ); - print "$file_name SAME\n" ; - return ; - } + # extract_pod _literally_ extracts it - + # removing any pod found from the text. + my $new_text = $file_text; + my $pod_sections = extract_pod( \$new_text, \@meth_names ); - print "$file_name CHANGED\n" ; + my ($class_name) = ($file_text =~ /^package\s+([\w:]+)/m); - write_file( "$file_name.new, $new_code_text ) ; + my $attr_pod = generate_attr_pod( $attr_spec, $file_name, $class_name ); + + generate_boilerplate( $file_text, $class_name ); -# write_file( "$file_name.bak, $code_text ) ; -# write_file( $file_name, $new_code_text ) ; + my $new_pod = generate_pod( $attr_pod, \@meth_names, $pod_sections ); -} + my $new_source = build_source( $new_pod, $new_text ); + + if ( $file_text eq $new_source ) { + warn "No changes to make to $file_name\n"; + return; + } -sub process_code_text { - - my ( $file_name, $text ) = @_ ; - - $text =~ s{ - ( - ^package # start at package line - .+? # the middle stuff - ^sub # start of constructor - ) - } - { - update_attr_spec( $1, $file_name ) - }mgsex ; - - $text =~ s{ - (.{0,20}?) - ^sub - \s+ - (\w+) - \s* - } - { update_sub_pod( $1, $2 ) }mgsex ; - - unless( $text =~ /^=cut\s*^\s*1\s*;\s*/m ) { - - $text =~ s{^\s*1\s*;\s*$}{ update_trailing_pod() }mex ; - } - - return $text ; + write_file( "$file_name.new", $new_source ); } -sub update_attr_spec { - my( $attr_text, $file_name ) = @_ ; -#print "U1 <$attr_text>\n" ; - ( $package ) = $attr_text =~ /^package\s+([\w:]+)/ ; - $attr_text =~ s/\n*^\#{5,}\n.+?^\#{5,}\n*//ms ; -# and print "DELETED OLD POD\n" ; -#print "U3 <$attr_text>\n" ; - $attr_text =~ s{ (^my\s+\$attr_spec.+?^]\s*;\s*) } - { attr_spec_to_pod( $1, $file_name ) }gmsex ; -#dump_attr( 'ATTR', $attr_text ) ; -#print "ATTR [", substr( $attr_text, -40 ), "]\n" ; -#print "U2 [$attr_text]\n" ; - return $attr_text ; -} -sub attr_spec_to_pod { - my ( $attr_text, $file_name ) = @_ ; - my $pod ; -#print "ATTR [$attr_text]\n" ; -#print "ATTR END1 [", substr( $attr_text, -30), "]\n" ; +# Finds and returns a list of the names of +# methods/subroutines found in the file +sub find_method_names { + my ( $file_text ) = @_; - $attr_text =~ s/\s*\z// ; + my @meth_names = $file_text =~ /^sub\s+([^\W_]\w*)/gm; - my( $attr_list_text ) = - $attr_text =~ /^my\s+\$attr_spec.+?=(.+?^\])/ms ; - $attr_list_text or die - "can't parse out attr list from file $file_name class $package" ; + return @meth_names; +} -#print "ATTR2 [$attr_list_text]\n" ; - my $attr_list = eval $attr_list_text ; - $pod .= <{name} ; - if ( $name ) { - $pod .= < + my $new_pod; -=over 4 + $new_pod .= generate_pod_sections( $pod_sections, \%PRE_POD ); -POD - } - else { + # attr_pod is always regenerated... + # TODO: perhaps it's better to call generate_attr_pod() here? + if ($attr_pod) { + $new_pod .= "=head1 ATTRIBUTES\n\n"; + $new_pod .= strip_blank_lines( $attr_pod ) . "\n\n"; + } - warn <{methods}{$meth_name} ) { + $new_pod .= strip_blank_lines( $meth_pod->{body} ) . "\n\n"; + next; + } - my $help = $attr_ref->{help} ; + if ( $meth_name eq 'new' ) { + $new_pod .= strip_blank_lines( < object. This method is called if no +specific message handler method is found in this cell. This is missing +specific documentation to be filled in. - warn <{class} ) { + if ( $meth_name =~ /^(.+)_in$/ ) { + my $msg_type = $1; + $new_pod .= strip_blank_lines( <{class_args} || []} ) - . '>' ; +This method is a Stem message handler for messages of type $msg_type. +It is passed a single argument which is a L object and +its return value is ignored. This is missing specific documentation +to be filled in. - $pod .= < object. +Any return value will be sent back as a 'response' type message to the sender. +This is missing specific documentation to be filled in. +END_POD + next; + } - exists( $attr_ref->{type} ) and $pod .= <{type} -POD + return $new_pod; +} - if ( exists( $attr_ref->{default} ) ) { - my $default = $attr_ref->{default} ; +sub generate_pod_sections { + my ($existing_pod, $bp_pod) = @_; + + my $new_pod; - if( ref($default) eq "ARRAY" ) { + foreach my $section ( @{ $bp_pod->{names} } ) { - $default = - '(' . join( ', ', @{$default} ) . ')' ; - } + (my $printable_section = $section) =~ tr/_/ /; + $new_pod .= "=head1 $printable_section\n\n"; - $pod .= <{other}{ lc $section }; + + my $next_part = $found_sec ? + $found_sec->{body} : $bp_pod->{boilerplate}{$section} ; -=item B value: + $new_pod .= strip_blank_lines( $next_part ) . "\n\n"; + } -$default -POD - } + return $new_pod; +} - exists( $attr_ref->{required} ) and $pod .= <. -POD +sub generate_boilerplate { + my ($file_text, $class_name) = @_; - foreach my $attr ( sort keys %{ $attr_ref } ) { - next if $is_attr_part{ $attr } ; - $pod .= "Unknown attribute $attr\n" ; - } + $PRE_POD{boilerplate}{NAME} = < +END_POD + } -=back +return; +} -POD - } +# strip out leading and trailing blank lines +sub strip_blank_lines { + my ($text) = @_; + $text =~ s/\A[\n]?\s*[\n]|[\n]\s*[\n]?\z//msg; + return $text; +} - $pod .= < to parse the arguments +passed to the class constructor new(). + +=over 4 -sub $name POD - chomp $pod ; + #print "POD [$pod]\n" ; -#print "SUB2 [$pod]\n" if $name eq 'new' ; + foreach my $attr_ref ( @{$attr_list} ) { - return $pod ; -} + my $name = $attr_ref->{name}; + + unless ($name) { -sub get_sub_pod { + warn < +=item * B<$name> -The B method creates an object of the class B<$package>. +=over 4 POD - return < + my $help = $attr_ref->{help}; -The B method is effectively a default method for message -delivery. If any message to this cell can't be delivered to another -method, then it will be delivered to the B method. If a -command message is delivered and a value is returned by B, a -response message is sent back to the originating cell with that value. -POD + unless ( defined($help) ) { + warn < type messages are delivered to this method. Its return value is -ignored by the message delivery system. +=item - $help POD +# suppress line break in POD output after description + chomp $pod; + + if ( my $attr_class = $attr_ref->{class} ) { - return <{class_args} || [] } ) . '>'; -B<$1> command messages are delivered to this method. If any value is -returned, the message delivery system will create a response type -message and dispatch it back to the sending cell. + $pod .= <{type} ) and $pod .= <{type} POD -} + if ( exists( $attr_ref->{default} ) ) { -sub update_trailing_pod { + my $default = $attr_ref->{default}; - my( $tail_text ) = @_ ; + if ( ref($default) eq "ARRAY" ) { -# return $tail_text if $tail_text =~ /=cut/ ; + $default = '(' . join( ', ', @{$default} ) . ')'; + } -#print "1 [$tail_text]\n" ; + $pod .= < value: $default +POD + } -=head1 Bugs + exists( $attr_ref->{required} ) and $pod .= <. +POD -=head1 See Also + foreach my $attr ( sort keys %{$attr_ref} ) { + next if $IS_ATTR_PART{$attr}; + $pod .= "Unknown attribute $attr\n"; + } -=head1 Author + $pod .= <uri\@stemsystems.comE + $pod .= < if wantarray ; - my $buf ; - sysread( FH, $buf, -s FH ) ; - return $buf ; -} -sub write_file { - my( $file_name ) = shift ; - local( *FH ) ; - open( FH, ">$file_name" ) || carp "can't create $file_name $!" ; - print FH @_ ; -} +# Globals declared at the top of this script are initalized here +# Keeping at the bottom of this file to (try to) reduce clutter. +BEGIN { + + # Pod sections that go before attribute and method sections + my @PRE_POD_NAMES = qw( NAME SYNOPSIS DESCRIPTION ); + + # sections that go after + my @POST_POD_NAMES = qw( BUGS TODO SEE_ALSO AUTHOR LICENSE COPYRIGHT ); + + + # Boilerplate for each "pre" section + my %PRE_POD_BP = ( + + NAME => <<'END_POD', +Name of module +END_POD + + SYNOPSIS => <<'END_POD', +Code/config example +END_POD + + DESCRIPTION => <<'END_POD', +What is this module good for? +END_POD + + ); + + + # Boilerplate for each "post" section + my %POST_POD_BP = ( + + BUGS => <<'END_POD', +None +END_POD + + TODO => <<'END_POD', +END_POD + + SEE_ALSO => <<'END_POD', +END_POD + + AUTHOR => <<'END_POD', +Uri Guttman uri@cpan.org +END_POD + + LICENSE => <<'END_POD', +Same as Perl +END_POD + + COPYRIGHT => <<'END_POD', +(C) Uri Guttman, 2009 +END_POD + + ); -sub dump_attr { - my( $key, $text ) = @_ ; + # globals used in other parts of the program + %PRE_POD = ( + names => \@PRE_POD_NAMES, + boilerplate => \%PRE_POD_BP, + ); - $text =~ /(;\s+#{3,})/s or return ; + %POST_POD = ( + names => \@POST_POD_NAMES, + boilerplate => \%POST_POD_BP, + ); - print "$key [$1]\n" ; } -__END__