#!/usr/bin/perl # This script parses LJ function info from all the library files # that make up the site. See cgi-bin/ljlib.pl for an example # of the necessary syntax. use strict; use Getopt::Long; use Data::Dumper; my $opt_warn = 0; my $opt_file; my $opt_stubs = 0; # generate stubs of undoced funcs my $opt_class = 0; # group by class my ($opt_include, $opt_exclude); # which packages to inc/excl my @do_dirs; my $basedir; my $opt_conf; die unless GetOptions( 'warn' => \$opt_warn, 'file=s' => \$opt_file, 'stubs' => \$opt_stubs, 'class' => \$opt_class, 'include=s' => \$opt_include, 'exclude=s' => \$opt_exclude, 'conf=s' => \$opt_conf, ); die "Unknown arguments.\n" if @ARGV; die "Can't exclude and include at same time!\n" if $opt_include && $opt_exclude; my (@classes, %classname, %common_args); if ($opt_conf) { open (C, $opt_conf) or die "Can't open conf file: $opt_conf\n"; while () { chomp; if (/^basedir\s+(\S+)$/) { $basedir = $1; $basedir =~ s/\$(\w+)/$ENV{$1} or die "Undefined ENV: $1"/eg; } elsif (/^dodir\s+(\S+)$/) { push @do_dirs, $1; } elsif (/^class\s+(\w+)\s+(.+)/) { push @classes, $1; $classname{$1} = $2; } elsif (/^arg\s+(\S+)\s+(.+)/) { $common_args{$1} = $2; } elsif (/\S/) { die "Unknown line in conf file:\n$_\n"; } } close C; } my %funcs; if ($opt_file) { check_file($opt_file); } else { unless ($basedir) { die "No base directory specified.\n"; } chdir $basedir or die "Can't cd to base: $basedir\n"; foreach (@do_dirs) { find($_); } } exit if $opt_warn; if ($opt_class) { my %by_class; foreach my $n (sort keys %funcs) { my $f = $funcs{$n}; push @{$by_class{$f->{'class'}}}, $f; } my $ret = []; foreach my $cn (@classes) { push @$ret, [ $classname{$cn}, $by_class{$cn} ]; } print Dumper($ret); exit; } print Dumper(\%funcs); exit; sub find { my @dirs = @_; while (@dirs) { my $dir = shift @dirs; opendir (D, $dir) or die "Can't open dir: $dir\n"; my @files = sort { $a cmp $b } readdir(D); close D; foreach my $f (@files) { next if ($f eq "." || $f eq ".."); my $full = "$dir/$f"; if (-d $full) { find($full); } elsif (-f $full) { check_file($full); } } } } sub check_file { $_ = shift; return unless (-f); return if (/\.(gif|jpg|png|class|jar|zip|exe|orig|rej)$/); return if (/~$/); my $curpackage = ""; my $file = $_; my $infunc = 0; my $f; # the current function info we're loading my $prefix; my $curkey; my $contlen; open (F, $file) or die "Can't open file: $file\n"; while (my $l = ) { if ($l =~ /^package\s*(.+);/) { $curpackage = $1; } if ($opt_warn && $curpackage && $l =~ /^sub\s+([a-zA-Z0-9]\S+)/) { my $s = $1; my $total = $curpackage . "::" . $s; unless ($funcs{$total}) { print STDERR "Undocumented: $total\n"; if ($opt_stubs) { print "# \n"; print "# name: $total\n"; print "# class: \n"; print "# des: \n"; print "# info: \n"; print "# args: \n"; print "# des-: \n"; print "# returns: \n"; print "# \n"; } } } print $l if $opt_stubs; if (! $infunc) { if ($l =~ //) { $infunc = 1; $f = {}; } next; } if ($l =~ /<\/LJFUNC>/) { $infunc = 0; $prefix = ""; $curkey = ""; $contlen = 0; my $include = 0; if ($opt_exclude) { $include = 1; $include = 0 if $f->{'name'} =~ /^$opt_exclude/; } elsif ($opt_include) { $include = 1 if $f->{'name'} =~ /^$opt_include/; } elsif (! $opt_include && ! $opt_exclude) { $include = 1; } if ($f->{'name'} && $include) { $f->{'source'} = $file; $f->{'class'} ||= "general"; unless ($classname{$f->{'class'}}) { print STDERR "Unknown class: $f->{'class'} ($f->{'name'})\n"; } $funcs{$f->{'name'}} = $f; treeify($f); } next; } # continuing a line from line before... must have # same indenting. if ($prefix && $contlen) { my $cont = $prefix . " "x$contlen; if ($l =~ /^\Q$cont\E(.+)/) { my $v = $1; $v =~ s/^\s+//; $v =~ s/\s+$//; $f->{$curkey} .= " " . $v; next; } } if ($l =~ /^(\W*)([\w\-]+)(:\s*)(.+)/) { $prefix = $1; my $k = $2; my $v = $4; $v =~ s/^\s+//; $v =~ s/\s+$//; $f->{$k} = $v; $curkey = $k; $contlen = length($2) + length($3); } } close (F); } sub treeify { my $f = shift; my $args = $f->{'args'}; $f->{'args'} = []; $args =~ s/\s+//g; foreach my $arg (split(/\,/, $args)) { my $opt = 0; if ($arg =~ s/\?$//) { $opt = 1; } my $list = 0; if ($arg =~ s/\*$//) { $list = 1; } my $a = { 'name' => $arg }; if ($opt) { $a->{'optional'} = 1; } if ($list) { $a->{'list'} = 1; } $a->{'des'} = $f->{"des-$arg"} || $common_args{$arg}; delete $f->{"des-$arg"}; unless ($a->{'des'}) { if ($opt_warn) { print "Warning: undescribed argument '$arg' in $a->{'name'}\n"; } } push @{$f->{'args'}}, $a; } }