shell bypass 403
=head1 NAME
PDL::Doc::Perldl - commands for accessing PDL doc database from 'perldl' shell
=head1 DESCRIPTION
This module provides a set of functions to
access the PDL documentation database, for use
from the I<perldl> or I<pdl2> shells as well as the
I<pdldoc> command-line program.
Autoload files are also matched, via a search of the PDLLIB autoloader
tree. That behavior can be switched off with the variable
C<$PERLDL::STRICT_DOCS> (true: don't search autoload tree; false: search
the autoload tree.)
In the interest of brevity, functions that print module names (at the moment
just L</apropos> and L</usage>) use some shorthand notation for module names.
Currently-implemented shorthands are
=over 3
=item * P:: (short for PDL::)
=item * P::G:: (short for PDL::Graphics::)
=back
To turn this feature off, set the variable $PERLDL::long_mod_names to a true value.
The feature is assumed to be on for the purposes of this documentation.
=head1 SYNOPSIS
use PDL::Doc::Perldl; # Load all documentation functions
=head1 FUNCTIONS
=cut
package PDL::Doc::Perldl;
use Exporter;
use strict;
use vars qw(@ISA @EXPORT);
@ISA = qw(Exporter);
@EXPORT = qw( apropos aproposover usage help sig badinfo whatis );
use PDL::Doc;
use Pod::Select;
use IO::File;
use Pod::PlainText;
use Term::ReadKey; #for GetTerminalSize
$PDL::onlinedoc = undef;
$PDL::onlinedoc = PDL::Doc->new(FindStdFile());
use PDL::Config;
my $bvalflag = $PDL::Config{WITH_BADVAL} || 0;
# Find std file
sub FindStdFile {
my ($d,$f);
for $d (@INC) {
$f = $d."/PDL/pdldoc.db";
if (-f $f) {
print "Found docs database $f\n" if $PDL::verbose;
print "Type 'help' for online help\n" if $PDL::verbose;
return $f;
}
}
warn "Unable to find PDL/pdldoc.db in ".join(":",@INC)."\n";
}
# used to find out how wide the screen should be
# for printmatch() - really should check for a
# sensible lower limit (for printmatch >~ 40
# would be my guess)
#
# taken from Pod::Text (v1.0203), then hacked to get it
# to work (at least on my solaris and linux
# machines)
#
sub screen_width() {
return ( ( GetTerminalSize(\*STDOUT) )[0] // 72);
}
sub printmatch {
my @match = @_;
if (@match) {
foreach my $t ( format_ref( @_ ) ) { print $t; }
} else {
print "no match\n\n";
}
} # sub: print_match()
# given a long module name, return the (perhaps shortened) module name.
sub shortmod {
my $module = shift;
$module =~ s/::$//;
unless ($PERLDL::long_mod_names){
$module =~ s/^PDL::/P::/;
$module =~ s/^P::Graphics::/P::G::/;
#additional abbreviation substitutions go here
}
return $module;
}
# return a string containing a formated version of the Ref string
# for the given matches
#
sub format_ref {
my @match = @_;
my @text = ();
#finding the max width before doing the printing means looping through @match an extra time; so be it.
my @module_shorthands = map { shortmod($_->[1]) } @match;
my $max_mod_length = -1;
map {$max_mod_length = length if (length>$max_mod_length) } @module_shorthands;
my $width = screen_width()-17-1-$max_mod_length;
my $parser = new Pod::PlainText( width => $width, indent => 0, sentence => 0 );
for my $m (@match) {
my $ref = $m->[2]{Ref} ||
( (defined $m->[2]{CustomFile})
? "[No ref avail. for `".$m->[2]{CustomFile}."']"
: "[No reference available]"
);
my $name = $m->[0];
my $module = shortmod($m->[1]);
$ref = $parser->interpolate( $ref );
$ref = $parser->reformat( $ref );
# remove last new lines (so substitution doesn't append spaces at end of text)
$ref =~ s/\n*$//;
$ref =~ s/\n/"\n ".' 'x($max_mod_length+2)/eg;
$ref =~ s/^\s*//;
if ( length($name) > 15 ) {
push @text, sprintf "%s ...\n " . ' 'x15 . "%-*s %s\n", $name, $max_mod_length, $module, $ref;
} else {
push @text, sprintf "%-15s %-*s %s\n", $name, $max_mod_length, $module, $ref;
}
}
return wantarray ? @text : $text[0];
} # sub: format_ref()
=head2 apropos
=for ref
Regex search PDL documentation database
=for usage
apropos 'text'
=for example
pdl> apropos 'pic'
PDL::IO::Pic P::IO::Pic Module: image I/O for PDL
grabpic3d P::G::TriD Grab a 3D image from the screen.
rim P::IO::Pic Read images in most formats, with improved RGB handling.
rpic P::IO::Pic Read images in many formats with automatic format detection.
rpiccan P::IO::Pic Test which image formats can be read/written
wim P::IO::Pic Write a pdl to an image file with selected type (or using filename extensions)
wmpeg P::IO::Pic Write an image sequence (a (3,x,y,n) byte pdl) as an animation.
wpic P::IO::Pic Write images in many formats with automatic format selection.
wpiccan P::IO::Pic Test which image formats can be read/written
To find all the manuals that come with PDL, try
apropos 'manual:'
and to get quick info about PDL modules say
apropos 'module:'
You get more detailed info about a PDL function/module/manual
with the C<help> function
=cut
sub aproposover {
die "Usage: aproposover \$funcname\n" unless $#_>-1;
die "no online doc database" unless defined $PDL::onlinedoc;
my $func = shift;
$func =~ s:\/:\\\/:g;
search_docs("m/$func/",['Name','Ref','Module'],1);
}
sub apropos {
die "Usage: apropos \$funcname\n" unless $#_>-1;
die "no online doc database" unless defined $PDL::onlinedoc;
my $func = shift;
printmatch aproposover $func;
}
=head2 PDL::Doc::Perldl::search_docs
=for ref
Internal routine to search docs database and autoload files
=cut
sub search_docs {
my ($func,$types,$sortflag,$exact) = @_;
my @match;
@match = $PDL::onlinedoc->search($func,$types,$sortflag);
push(@match,find_autodoc( $func, $exact ) );
@match;
}
=head2 PDL::Doc::Perldl::finddoc
=for ref
Internal interface to the PDL documentation searcher
=cut
sub finddoc {
local $SIG{PIPE}= sub {}; # Prevent crashing if user exits the pager
die 'Usage: doc $topic' unless $#_>-1;
die "no online doc database" unless defined $PDL::onlinedoc;
my $topic = shift;
# See if it matches a PDL function name
my $subfield = $1
if( $topic =~ s/\[(\d*)\]$// ); #does it end with a number in square brackets?
(my $t2 = $topic) =~ s/([^a-zA-Z0-9_])/\\$1/g; #$t2 is a copy of $topic with escaped non-word characters
my @match = search_docs("m/^(PDL::)?".$t2."\$/",['Name'],0) ; #matches: ^PDL::topic$ or ^topic$
unless(@match) {
print "No PDL docs for '$topic'. Using 'whatis'. (Try 'apropos $topic'?)\n\n";
whatis($topic);
return;
}
# print out the matches
my $out = IO::File->new( "| pod2text | $PDL::Doc::pager" );
if($subfield) {
if($subfield <= @match) {
@match = ($match[$subfield-1]);
$subfield = 0;
} else {
print $out "\n\n=head1 PDL HELP: Ignoring out-of-range selector $subfield\n\n=head1\n\n=head1 --------------------------------\n\n";
$subfield = undef;
}
}
my $num_pdl_pod_matches = scalar @match;
my $pdl_pod_matchnum = 0;
while (@match) {
$pdl_pod_matchnum++;
if ( @match > 1 and !$subfield and $pdl_pod_matchnum==1 ) {
print $out "\n\n=head1 MULTIPLE MATCHES FOR HELP TOPIC '$topic':\n\n=head1\n\n=over 3\n\n";
my $i=0;
for my $m ( @match ) {
printf $out "\n=item [%d]\t%-30s %s%s\n\n", ++$i, $m->[0], $m->[2]{Module} && "in ", $m->[2]{CustomFile} || $m->[2]{Module};
}
print $out "\n=back\n\n=head1\n\n To see item number \$n, use 'help ${topic}\[\$n\]'. \n\n=cut\n\n";
}
if (@match > 0 and $num_pdl_pod_matches > 1) {
print $out "\n=head1 Displaying item $pdl_pod_matchnum:\n\n=head1 --------------------------------------\n\n=cut\n\n";
}
my $m = shift @match;
my $Ref = $m->[2]{Ref};
if ( $Ref =~ /^(Module|Manual|Script): / ) {
# We've got a file name and we have to open it. With the relocatable db, we have to reconstitute the absolute pathname.
my $relfile = $m->[2]{File};
my $absfile = undef;
my @scnd = @{$PDL::onlinedoc->{Scanned}};
for my $dbf(@scnd){
$dbf =~ s:\/[^\/]*$::; # Trim file name off the end of the database file to get just the directory
$dbf .= "/$relfile";
$absfile = $dbf if( -e $dbf );
}
unless ($absfile) {
die "Documentation error: couldn't find absolute path to $relfile\n";
}
my $in = IO::File->new("<$absfile");
print $out join("",<$in>);
} else {
if(defined $m->[2]{CustomFile}) {
my $parser= Pod::Select->new;
print $out "=head1 Autoload file \"".$m->[2]{CustomFile}."\"\n\n";
$parser->parse_from_file($m->[2]{CustomFile},$out);
print $out "\n\n=head2 Docs from\n\n".$m->[2]{CustomFile}."\n\n";
} else {
print $out "=head1 Module ",$m->[2]{Module}, "\n\n";
# print STDERR "calling funcdocs(" . $m->[0] . ", " . $m->[1] . ")\n";
$PDL::onlinedoc->funcdocs($m->[0],$m->[1],$out);
}
}
}
}
=head2 find_autodoc
=for ref
Internal routine that finds and returns documentation in the
PDL::AutoLoader path, if it exists.
You feed in a topic and it searches for the file "${topic}.pdl". If
that exists, then the filename gets returned in a match structure
appropriate for the rest of finddoc.
=cut
# Yuck. Sorry. At least it works. -CED
sub find_autodoc {
my $topic = shift;
my $exact = shift;
my $matcher;
# Fix up regexps and exact matches for the special case of
# searching the autoload dirs...
if($exact) {
$topic =~ s/\(\)$//; # "func()" -> "func"
$topic .= ".pdl" unless $topic =~ m/\.pdl$/;
} else {
$topic =~ s:([^\$])(.)$:$1\.\*\$$2:; # Include explicit ".*$" at end of
# vague matches -- so that we can
# make it a ".*\.pdl$" below.
$topic =~ s:\$(.)$:\.pdl\$$1:; # Force ".pdl" at end of file match
$matcher = eval "sub { ${topic}i && \$\_ };"; # Avoid multiple compiles
}
my @out;
return unless(@main::PDLLIB);
@main::PDLLIB_EXPANDED = PDL::AutoLoader::expand_path(@main::PDLLIB)
unless(@main::PDLLIB_EXPANDED);
for my $dir(@main::PDLLIB_EXPANDED) {
if($exact) {
my $file = $dir . "/" . "$topic";
push(@out,
[$file, undef, {CustomFile => "$file", Module => "file '$file'"}]
)
if(-e $file);
} else {
opendir(FOO,$dir) || next;
my @dir = readdir(FOO);
closedir(FOO);
for my $file( grep( &$matcher, @dir ) ) {
push(@out,
[$file, undef, {CustomFile => "$dir/$file", Module => "file '$dir/$file'"}]
);
}
}
}
@out;
}
=head2 usage
=for ref
Prints usage information for a PDL function
=for usage
Usage: usage 'func'
=for example
pdl> usage 'inner'
inner P::Primitive Inner product over one dimension
Signature: inner(a(n); b(n); [o]c())
=cut
sub usage {
die 'Usage: usage $funcname' unless $#_>-1;
die "no online doc database" unless defined $PDL::onlinedoc;
print usage_string(@_);
}
sub usage_string{
my $func = shift;
my $str = "";
my @match = search_docs("m/^(PDL::)?$func\$|\:\:$func\$/",['Name']);
my $count = @match;
unless ($count) { $str = "\n no match\n" }
else {
#this sorts by namespace depth by counting colons in the name.
#PDL::Ufunc::max comes before PDL::GSL::RNG::max, for example.
foreach my $m(sort { scalar(()=$a->[1]=~/\:/g) <=> scalar(()=$b->[1]=~/\:/g) } @match){
$str .= "\n" . format_ref( $m );
my ($name,$module,$hash) = @{$m};
#$str .= sprintf ( (' 'x16)."(Module %s)\n\n", $hash->{Module} );
$str.="\n";
die "No usage info found for $func\n" if (!defined $hash->{Example} && !defined $hash->{Sig} &&
!defined $hash->{Usage});
$str .= " Signature: $name($hash->{Sig})\n\n" if defined $hash->{Sig};
for (['Usage','Usage'],['Opt','Options'],['Example','Example']) {
$str .= " $_->[1]:\n".&allindent($hash->{$_->[0]},10)."\n" if defined $hash->{$_->[0]};
}
$str .= '='x20 unless 1==$count--;
}
}
return $str;
}
=head2 sig
=for ref
prints signature of PDL function
=for usage
sig 'func'
The signature is the normal dimensionality of the
function's arguments. Calling with different dimensions
doesn't break -- it causes threading. See L<PDL::PP> for details.
=for example
pdl> sig 'outer'
Signature: outer(a(n); b(m); [o]c(n,m))
=cut
sub sig {
die "Usage: sig \$funcname\n" unless $#_>-1;
die "no online doc database" unless defined $PDL::onlinedoc;
my $func = shift;
my @match = search_docs("m/^(PDL::)?$func\$|\:\:$func\$/",['Name']);
my $count = @match;
unless (@match) { print "\n no match\n" } else {
foreach my $m(sort { scalar(()=$a->[1]=~/\:/g) <=> scalar(()=$b->[1]=~/\:/g) } @match){
my ($name,$module,$hash) = @{$m};
die "No signature info found for $func\n" if !defined $hash->{Sig};
print " Signature: $name($hash->{Sig})\n" if defined $hash->{Sig};
print '='x20 unless 1==$count--;
}
}
}
sub allindent {
my ($txt,$n) = @_;
my ($ntxt,$tspc) = ($txt,' 'x8);
$ntxt =~ s/^\s*$//mg;
$ntxt =~ s/\t/$tspc/g;
my $minspc = length $txt;
for (split '\n', $txt) { if (/^(\s*)/)
{ $minspc = length $1 if length $1 < $minspc } }
$n -= $minspc;
$tspc = ' 'x abs($n);
$ntxt =~ s/^/$tspc/mg if $n > 0;
return $ntxt;
}
=head2 whatis
=for ref
Describe a perl and/or PDL variable or expression. Useful for
determining the type of an expression, identifying the keys in a hash
or a data structure, or examining WTF an unknown object is.
=for usage
Usage: whatis $var
whatis <expression>
=cut
sub whatis {
my $topic;
if(@_ > 1) {
whatis_r('',0,[@_]);
} else {
whatis_r('',0,shift);
}
}
$PDL::Doc::Perldl::max_strlen = 55;
$PDL::Doc::Perldl::max_arraylen = 1;
$PDL::Doc::Perldl::max_keylen = 8;
$PDL::Doc::Perldl::array_indent=5;
$PDL::Doc::Perldl::hash_indent=3;
sub whatis_r {
my $prefix = shift;
my $indent = shift;
my $x = shift;
unless(defined $x) {
print $prefix,"<undef>\n";
return;
}
unless(ref $x) {
print "${prefix}'".
substr($x,0,$PDL::Doc::Perldl::max_strlen).
"'".((length $x > $PDL::Doc::Perldl::max_strlen) && '...').
"\n";
return;
}
if(ref $x eq 'ARRAY') {
print "${prefix}Array (".scalar(@$x)." elements):\n";
my($el);
for $el(0..$#$x) {
my $pre = sprintf("%s %2d: "," "x$indent,$el);
whatis_r($pre,$indent + $PDL::Doc::Perldl::array_indent, $x->[$el]);
last if($el == $PDL::Doc::Perldl::max_arraylen);
}
printf "%s ... \n"," " x $indent
if($#$x > $PDL::Doc::Perldl::max_arraylen);
return;
}
if(ref $x eq 'HASH') {
print "${prefix}Hash (".scalar(keys %$x)." elements)\n";
my $key;
for $key(sort keys %$x) {
my $pre = " " x $indent .
" $key: " .
(" "x($PDL::Doc::Perldl::max_keylen - length($key))) ;
whatis_r($pre,$indent + $PDL::Doc::Perldl::hash_indent, $x->{$key});
}
return;
}
if(ref $x eq 'CODE') {
print "${prefix}Perl CODE ref\n";
return;
}
if(ref $x eq 'SCALAR' | ref $x eq 'REF') {
whatis_r($prefix." Ref -> ",$indent+8,$$x);
return;
}
if(UNIVERSAL::can($x,'px')) {
my $y;
local $PDL::debug = 1;
$y = ( (UNIVERSAL::isa($x,'PDL') && $x->nelem < 5 && $x->ndims < 2)
?
": $x" :
": *****"
);
$x->px($prefix.(ref $x)." %7T (%D) ".$y);
} else {
print "${prefix}Object: ".ref($x)."\n";
}
}
=head2 help
=for ref
print documentation about a PDL function or module or show a PDL manual
In the case of multiple matches, the first command found is printed out,
and the remaining commands listed, along with the names of their modules.
=for usage
Usage: help 'func'
=for example
pdl> help 'PDL::Tutorials' # show the guide to PDL tutorials
pdl> help 'PDL::Slices' # show the docs in the PDL::Slices module
pdl> help 'slice' # show docs on the 'slice' function
=cut
sub help_url {
local $_;
foreach(@INC) {
my $x = "$_/PDL/HtmlDocs/PDL/Index.html";
if(-e $x) {
return "file://$x";
}
}
}
sub help {
if ($#_>-1) {
require PDL::Dbg;
my $topic = shift;
if (PDL::Core::blessed($topic) && $topic->can('px')) {
local $PDL::debug = 1;
$topic->px('This variable is');
} else {
$topic = 'PDL::Doc::Perldl' if $topic =~ /^\s*help\s*$/i;
if ($topic =~ /^\s*vars\s*$/i) {
PDL->px((caller)[0]);
} elsif($topic =~ /^\s*url\s*/i) {
my $x = help_url();
if($x) {
print $x;
} else {
print "Hmmm. Curious: I couldn't find the HTML docs anywhere in \@INC...\n";
}
} elsif($topic =~ /^\s*www(:([^\s]+))?\s*/i) {
my $browser;
my $url = help_url();
if($2) {
$browser = $2;
} elsif($ENV{PERLDL_WWW}) {
$browser = $ENV{PERLDL_WWW};
} else {
$browser = 'mozilla';
}
chomp($browser = `which $browser`);
if(-e $browser && -x $browser) {
print "Spawning \"$browser $url\"...\n";
`$browser $url`;
}
} else {
finddoc($topic);
}
}
} else {
print <<'EOH';
The following commands support online help in the perldl shell:
help 'thing' -- print docs on 'thing' (func, module, manual, autoload-file)
help vars -- print information about all current piddles
help url -- locate the HTML version of the documentation
help www -- View docs with default web browser (set by env: PERLDL_WWW)
whatis <expr> -- Describe the type and structure of an expression or piddle.
apropos 'word' -- search for keywords/function names
usage -- print usage information for a given PDL function
sig -- print signature of PDL function
('?' is an alias for 'help'; '??' is an alias for 'apropos'.)
EOH
print " badinfo -- information on the support for bad values\n"
if $bvalflag;
print <<'EOH';
Quick start:
apropos 'manual:' -- Find all the manual documents
apropos 'module:' -- Quick summary of all PDL modules
help 'help' -- details about PDL help system
help 'perldl' -- help about this shell
EOH
}
}
=head2 badinfo
=for ref
provides information on the bad-value support of a function
And has a horrible name.
=for usage
badinfo 'func'
=for example
pdl> badinfo 'inner'
Bad value support for inner (in module PDL::Primitive)
If "a() * b()" contains only bad data, "c()" is set bad. Otherwise "c()"
will have its bad flag cleared, as it will not contain any bad values.
=cut
# need to get this to format the output - want a format_bad()
# subroutine that's like - but much simpler - than format_ref()
#
sub badinfo {
my $func = shift;
die "Usage: badinfo \$funcname\n" unless defined $func;
die "PDL has not been compiled with support for bad values.\n" .
"Recompile with WITH_BADVAL set to 1 in config file!.\n"
unless $bvalflag;
die "no online doc database" unless defined $PDL::onlinedoc;
local $SIG{PIPE}= sub {}; # Prevent crashing if user exits the pager
my @match = search_docs("m/^(PDL::)?$func\$|\:\:$func\$/",['Name']);
my $count = @match;
if ( $count ) {
my ($pagerstr, $noinfostr);
foreach my $m(@match) {
my ($name,$module,$hash) = @{$m};
my $info = $hash->{Bad};
if ( defined $info ) {
$name=~s/^(.*)\:\:(\w*)$/$2/;
$pagerstr .= "=head1 Bad value support for $name (in module $module)\n\n$info\n";
} else {
$noinfostr .= "\n No information on bad-value support found for $func (in module $module)\n";
}
}
if ($pagerstr){
my $out = new IO::File "| pod2text | $PDL::Doc::pager";
print $out $pagerstr, $noinfostr;
} else {
print $noinfostr;
}
} else {
print "\n no match\n";
}
} # sub: badinfo()
1; # OK