#! perl
# Copyright (C) 2001-2003, The Perl Foundation.
# $Id: extract_file_descriptions.pl 22479 2007-10-25 16:01:30Z paultcochrane $
=head1 NAME
tools/dev/extract_file_descriptions.pl - Extract descriptions from files
=head1 SYNOPSIS
% perl tools/dev/extract_file_descriptions.pl [--plus-misses] [files ...]
=head1 DESCRIPTION
This script extracts descriptions from the Parrot source files. If no
files are specified on the command-line then the files worth describing
will be found recursively from the current directory.
=head2 Options
=over 4
=item C<--plus-misses>
This creates a second section, with the heads of any files which had
familiar types, but from which descriptions were not obtained.
=back
=head1 TODO (RT#46903)
=over
=item * Given a directory argument, should recursively descend.
=item * Should create descriptive hashes earlier, before files are
filtered. So one can skip, say a binary file, but still have it listed
in the output. For instance, it is nice to see where the .pbc's land.
=item * Allow indescribable heads to be mixed in with rest, for when
exploring a location is more important than big-picture browsing.
=item * Is absense of "[...]"s in the indescribable listing a ui
consistency violation?
=item * Fragment describe_file() - it shouldn't both `cat` and dispatch
on file suffix.
=item * Finish making this usable as a library.
=item * It would be nice to have a ParrotSourceFile class of course. ;)
(a jest - sort of.)
=item * Misc: rationalize indent; clarify desc emptiness contract;
review readability of intra-comment blank line elimination; pod
handling;
=back
=cut
use strict;
use warnings;
use Regexp::Common qw/comment/;
use Getopt::Long;
if (1) {
my $show_full = 0;
GetOptions( 'plus-misses' => \$show_full )
|| die( "Usage: $0 [--plus-misses] [files...]\n\n"
. "FILES defaults to a recursive \" find . \".\n\n"
. "--plus-misses creates a second section, with the heads of any\n"
. "files which had familiar types, but from which descriptions\n"
. "were not obtained.\n\n" );
my @files = @ARGV ? @ARGV : files_worth_describing();
print "This file was generated by $0\non " . scalar(localtime) . ".\n\n";
print "Files in ( parenthesis ) did not have extractable descriptions.\n";
if ($show_full) {
print "Their heads are included in a second section below, " . "marked with \"#=#=#=\".\n";
}
else {
print "Run this script with --plus-misses, and a second section will be "
. "included,\nwith the heads of these indescribable files.\n";
}
print "\n";
my @no_descriptions;
foreach (@files) {
my $info = describe_file($_);
if ( $info->{desc} ) {
print "\n* $info->{path}\n\n$info->{desc}\n";
}
else {
print "( $info->{path} )\n";
push( @no_descriptions, $info );
}
}
print "\n\n";
if ($show_full) {
print "\n" . ( "#=" x 35 ) . "\n\nFiles from which descriptions were not obtained:\n\n";
foreach (@no_descriptions) {
my $top = $_->{top};
$top =~ s/^/> /mg;
print "- $_->{path}\n\n$top\n";
}
}
exit(0);
}
sub describe_file {
my ($path) = @_;
my $info = {};
$info->{path} = $path;
my $text = `cat $info->{path}`;
my ($top) = $text =~ /^(([^\n]*\n){1,15})/;
$info->{top} = $top;
return describe_c_file( $info, $text )
if $path =~ /\.([chlyC]|cpp|cola|xs|pmc)$|[_\.][ch]\.in$/;
return describe_perl_file( $info, $text )
if $path =~ /\.(pl|pm|t|PL|pod|pasm)$|_pm\.in$/
or $text =~ /^\#/;
return describe_misc_file( $info, $text );
}
sub describe_misc_file {
my ( $info, $text ) = @_;
my $top = $text;
clip_excess_lines( $top, 15 );
$top =~ s/^ {0,1}(\S)/ $1/mg; # minimum indent
$info->{desc} = $top;
return $info;
}
sub describe_perl_file {
my ( $info, $text ) = @_;
my $desc;
if ( $text =~ /^\#/ ) { # perl file (or sh)
my ($comment) = $text =~ /^(\#[^\n]*\n( *\n)?(\#[^\n]*\n)*)/s;
die "internal bug" if !$comment;
local $_ = $comment;
s/^\#\* /\# /mg; # #*
s/^\#(\#|\*)+\s*$/\#/mg; # line of "*"s or "#"s
s/^\#\!.+//m; # #!
s/^\# *[a-z0-9]+\.(p[ml]|pasm)\s*\n//mi; # own file name - kludgy
s/\#\s*Copyright[^\n]+\n(\# *\S[^\n]*\n)*/\#\n/s;
s/^\#\s*(\$I[d]: .+)\n//m;
$info->{Id} = $1;
s/^\#\s*Author:.+//m;
s/^\s*\n//mg; # truly blank lines, between the # comment lines
s/\n(\# *\n){2,}/\n\#\n/sg; # crush down double blank lines
s/^\s*(\# *\n)+//s; # remove leading
s/\n(\# *\n)+\s*$/\n/s; # remove trailing
s/^\#//mg; # get rid of #
#s/^( *\n)+//s;
s/^\s*$//s; # normalize emptiness
$comment = $_;
$desc = $info->{perl_comment} = $comment;
}
if ( !$desc && $text =~ /^=head1/m ) { # try an embedded pod
my ($doc) = $text =~ /(?:^|\n)=head1(.+)/s;
die "internal bug" if !$doc;
local $_ = $doc;
s/^\s*(NAME|TITLE)\s*//;
s/\n=.*//s;
# It would be nice to get the beginning of any DESCRIPTION. RT#46903
$doc = $_;
$desc = $info->{pod_doc} = $doc;
}
do {
$desc =~ s/^ {0,1}(\S)/ $1/mg; # minimum indent
clip_excess_lines($desc);
} if $desc;
$info->{desc} = $desc;
return $info;
}
sub describe_c_file {
my ( $info, $text ) = @_;
my $comment_is_at_beginning = $text =~ /^\/\*/;
my ($first_comment) = $text =~ /($RE{comment}{C})/;
($first_comment) = $text =~ /(($RE{comment}{'C++'}\s*)+)/
if !$first_comment;
return $info if !$first_comment;
local $_ = $first_comment;
s/^\/\*//;
s/\*\/$//; # /* */
s/^ *\/\///mg; # //
s/^ ?\*\*//mg; # |**
s/^ {0,2}\*//mg; # | *
s/^ *(\$I[d]: .+)\n//m;
$info->{Id} = $1;
my $desc;
if (/Overview:/) { # normal parrot code files
my $label = qr/ *[A-Z][a-zA-Z ]+:/;
($desc) = /(?:^|\n) *Overview: *\n(((?!$label) *[^\n]+\n)+)/s;
$info->{warning} .= "There was an Overview:, but it wasn't used.";
}
if ( $info->{path} =~ /\Wicu\Wsource/ ) {
s/Copyright \(C\) [^\n]+\n *Corporation [^\n]+\n//;
s/^ *(file name|encoding|tab size|indentation|created (on|by)):.*//mg;
$desc = $_;
}
if ( !$desc ) {
s/^ *[a-z0-9_]+\.[chly]\s*\n//mi; # own filename - kludgy.
$desc = $_;
$desc = "" # it's only emacs variables at the end of the file
if ( !$comment_is_at_beginning && /c-indentation-style/ );
}
$_ = $desc;
s/^ *\*+\s*$//mg; # line of "*"s.
s/\n( *\n){2,}/\n\n/sg; # excess blank lines
s/^\s*//s;
s/\s*$/\n/s; # trim (and ends with a newline)
s/^ {0,1}(\S)/ $1/mg; # minimum indent
s/^\s*$//s; # normalize emptiness
clip_excess_lines($_);
$info->{desc} = $_;
return $info;
}
sub files_worth_describing {
use File::Find;
my @files;
find(
sub {
my $name = $File::Find::name;
return if $name =~ /\.svn|core|\.[oa]|\.(so|brk|dsp|tmp)$/;
return if $name =~ /locales\W[a-z_]+\.txt$/i;
return if $name =~ /icu\Wsource/; # icu cleanup code above needs work
return if -d $_;
return if -B $_;
push( @files, $name );
},
"."
);
return @files;
}
sub clip_excess_lines {
my $cnt = defined $_[1] ? $_[1] : 20;
$_[0] =~ s/(([^\n]*\n){0,$cnt}).*/$1 [...]\n/s
if $_[0] =~ tr/\n/\n/ > $cnt;
return;
}
# Local Variables:
# mode: cperl
# cperl-indent-level: 4
# fill-column: 100
# End:
# vim: expandtab shiftwidth=4:
syntax highlighted by Code2HTML, v. 0.9.1